From 59315c3a8d367eeebcc00feae9a2d179156ea5cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?=
 <roznawski.przemyslaw@gmail.com>
Date: Tue, 12 Mar 2024 11:37:32 +0100
Subject: [PATCH 1/2] Add analytics

---
 templates/doc/README.md               |  43 +++
 templates/doc/build-search-index.js   |  30 ++
 templates/doc/content.css             | 438 ++++++++++++++++++++++++++
 templates/doc/custom.css              |   4 +
 templates/doc/error.html.jinja2       |  38 +++
 templates/doc/frame.html.jinja2       |  54 ++++
 templates/doc/index.html.jinja2       |  74 +++++
 templates/doc/layout.css              | 230 ++++++++++++++
 templates/doc/livereload.html.jinja2  |  15 +
 templates/doc/math.html.jinja2        |  28 ++
 templates/doc/mermaid.html.jinja2     |  18 ++
 templates/doc/module.html.jinja2      |  28 +-
 templates/doc/search.html.jinja2      | 190 +++++++++++
 templates/doc/search.js.jinja2        |  51 +++
 templates/doc/syntax-highlighting.css |  72 +++++
 templates/frame.html.jinja2           |  85 +++++
 templates/module.html.jinja2          | 298 ++++++++++++++++++
 17 files changed, 1691 insertions(+), 5 deletions(-)
 create mode 100644 templates/doc/README.md
 create mode 100644 templates/doc/build-search-index.js
 create mode 100644 templates/doc/content.css
 create mode 100644 templates/doc/custom.css
 create mode 100644 templates/doc/error.html.jinja2
 create mode 100644 templates/doc/frame.html.jinja2
 create mode 100644 templates/doc/index.html.jinja2
 create mode 100644 templates/doc/layout.css
 create mode 100644 templates/doc/livereload.html.jinja2
 create mode 100644 templates/doc/math.html.jinja2
 create mode 100755 templates/doc/mermaid.html.jinja2
 create mode 100644 templates/doc/search.html.jinja2
 create mode 100644 templates/doc/search.js.jinja2
 create mode 100644 templates/doc/syntax-highlighting.css
 create mode 100644 templates/frame.html.jinja2
 create mode 100644 templates/module.html.jinja2

diff --git a/templates/doc/README.md b/templates/doc/README.md
new file mode 100644
index 0000000..5bd1994
--- /dev/null
+++ b/templates/doc/README.md
@@ -0,0 +1,43 @@
+# 📑 pdoc templates
+
+This directory contains pdoc's default templates, which you can extend in your own template directory. See
+[the documentation](https://pdoc.dev/docs/pdoc.html#edit-pdocs-html-template) for an example.
+
+For customization, the most important files are:
+
+**Main HTML Templates**
+
+ - `default/module.html.jinja2`: Template for documentation pages.
+ - `default/index.html.jinja2`: Template for the top-level `index.html`.
+ - `default/frame.html.jinja2`: The common page layout for `module.html.jinja2` and `index.html.jinja2`.
+
+**CSS Stylesheets**
+
+ - `custom.css`: Empty be default, add custom additional rules here.
+ - `content.css`: All style definitions for documentation contents.
+ - `layout.css`: All style definitions for the page layout (navigation, sidebar, ...).
+ - `theme.css`: Color definitions (see [`examples/dark-mode`](../../examples/dark-mode)).
+ - `syntax-highlighting.css`: Code snippet style, see below.
+
+## Extending templates
+
+pdoc will first check for `$template.jinja2` before checking `default/$template.jinja2`. This allows you to reuse the
+macros from the main templates in `default/`. For example, you can create a `module.html.jinja2` file in your custom 
+template directory that extends the default template as follows:
+
+```html
+{% extends "default/module.html.jinja2" %}
+{% block title %}new page title{% endblock %}
+```
+
+## Syntax Highlighting
+
+The `syntax-highlighting.css` file contains the CSS styles used for syntax highlighting.
+It is generated as follows:
+
+```
+pygmentize -f html -a .pdoc-code -S <theme> > default/syntax-highlighting.css
+```
+
+The default theme is `default`, with extended padding added to the `.linenos` class.
+Alternative color schemes can be tested on [the Pygments website](https://pygments.org/demo/).
diff --git a/templates/doc/build-search-index.js b/templates/doc/build-search-index.js
new file mode 100644
index 0000000..7661d27
--- /dev/null
+++ b/templates/doc/build-search-index.js
@@ -0,0 +1,30 @@
+/**
+ * This script is invoked by pdoc to precompile the search index.
+ * Precompiling the search index increases file size, but skips the CPU-heavy index building in the browser.
+ */
+let elasticlunr = require("./resources/elasticlunr.min");
+
+let fs = require("fs");
+let docs = JSON.parse(fs.readFileSync(0, "utf-8"));
+
+/* mirrored in search.js.jinja2  (part 1) */
+elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/);
+
+/* mirrored in search.js.jinja2  (part 2) */
+searchIndex = elasticlunr(function () {
+    this.pipeline.remove(elasticlunr.stemmer);
+    this.pipeline.remove(elasticlunr.stopWordFilter);
+    this.addField("qualname");
+    this.addField("fullname");
+    this.addField("annotation");
+    this.addField("default_value");
+    this.addField("signature");
+    this.addField("bases");
+    this.addField("doc");
+    this.setRef("fullname");
+});
+for (let doc of docs) {
+    searchIndex.addDoc(doc);
+}
+
+process.stdout.write(JSON.stringify(searchIndex.toJSON()));
diff --git a/templates/doc/content.css b/templates/doc/content.css
new file mode 100644
index 0000000..39b7417
--- /dev/null
+++ b/templates/doc/content.css
@@ -0,0 +1,438 @@
+/*
+This CSS file contains all style definitions for documentation content.
+
+All selectors are scoped with ".pdoc".
+This makes sure that the pdoc styling doesn't leak to the rest of the page when pdoc is embedded.
+*/
+
+.pdoc {
+    color: var(--text);
+    /* enforce some styling even if bootstrap reboot is not included */
+    box-sizing: border-box;
+    line-height: 1.5;
+    /* override background from pygments */
+    /*unnecessary since pdoc 10, only left here to keep old custom templates working. */
+    background: none;
+}
+
+.pdoc .pdoc-button {
+    cursor: pointer;
+    display: inline-block;
+    border: solid black 1px;
+    border-radius: 2px;
+    font-size: .75rem;
+    padding: calc(0.5em - 1px) 1em;
+    transition: 100ms all;
+}
+
+
+/* Admonitions */
+.pdoc .pdoc-alert {
+    padding: 1rem 1rem 1rem calc(1.5rem + 24px);
+    border: 1px solid transparent;
+    border-radius: .25rem;
+    background-repeat: no-repeat;
+    background-position: 1rem center;
+    margin-bottom: 1rem;
+}
+
+.pdoc .pdoc-alert > *:last-child {
+    margin-bottom: 0;
+}
+
+/* Admonitions are currently not stylable via theme.css */
+.pdoc .pdoc-alert-note  {
+    color: #084298;
+    background-color: #cfe2ff;
+    border-color: #b6d4fe;
+    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/info-circle-fill.svg' %}{% endfilter %}");
+}
+
+.pdoc .pdoc-alert-warning {
+    color: #664d03;
+    background-color: #fff3cd;
+    border-color: #ffecb5;
+    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/exclamation-triangle-fill.svg' %}{% endfilter %}");
+}
+
+.pdoc .pdoc-alert-danger {
+    color: #842029;
+    background-color: #f8d7da;
+    border-color: #f5c2c7;
+    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/lightning-fill.svg' %}{% endfilter %}");
+}
+
+.pdoc .visually-hidden {
+    position: absolute !important;
+    width: 1px !important;
+    height: 1px !important;
+    padding: 0 !important;
+    margin: -1px !important;
+    overflow: hidden !important;
+    clip: rect(0, 0, 0, 0) !important;
+    white-space: nowrap !important;
+    border: 0 !important;
+}
+
+.pdoc h1, .pdoc h2, .pdoc h3 {
+    font-weight: 300;
+    margin: .3em 0;
+    padding: .2em 0;
+}
+
+.pdoc > section:not(.module-info) h1 {
+    font-size: 1.5rem;
+    font-weight: 500;
+}
+.pdoc > section:not(.module-info) h2 {
+    font-size: 1.4rem;
+    font-weight: 500;
+}
+.pdoc > section:not(.module-info) h3 {
+    font-size: 1.3rem;
+    font-weight: 500;
+}
+.pdoc > section:not(.module-info) h4 {
+    font-size: 1.2rem;
+}
+.pdoc > section:not(.module-info) h5 {
+    font-size: 1.1rem;
+}
+
+.pdoc a {
+    text-decoration: none;
+    color: var(--link);
+}
+
+.pdoc a:hover {
+    color: var(--link-hover);
+}
+
+.pdoc blockquote {
+    margin-left: 2rem;
+}
+
+.pdoc pre {
+    border-top: 1px solid var(--accent2);
+    border-bottom: 1px solid var(--accent2);
+    margin-top: 0;
+    margin-bottom: 1em;
+    padding: .5rem 0 .5rem .5rem;
+    overflow-x: auto;
+    /*unnecessary since pdoc 10, only left here to keep old custom templates working. */
+    background-color: var(--code);
+}
+
+.pdoc code {
+    color: var(--text);
+    padding: .2em .4em;
+    margin: 0;
+    font-size: 85%;
+    background-color: var(--accent);
+    border-radius: 6px;
+}
+
+.pdoc a > code {
+    color: inherit;
+}
+
+.pdoc pre > code {
+    display: inline-block;
+    font-size: inherit;
+    background: none;
+    border: none;
+    padding: 0;
+}
+
+.pdoc > section:not(.module-info) {
+    /* this margin should collapse with docstring margin,
+       but not for the module docstr which is followed by view_source. */
+    margin-bottom: 1.5rem;
+}
+
+/* Page Heading */
+.pdoc .modulename {
+    margin-top: 0;
+    font-weight: bold;
+}
+
+.pdoc .modulename a {
+    color: var(--link);
+    transition: 100ms all;
+}
+
+/* GitHub Button */
+.pdoc .git-button {
+    float: right;
+    border: solid var(--link) 1px;
+}
+
+.pdoc .git-button:hover {
+    background-color: var(--link);
+    color: var(--pdoc-background);
+}
+
+.view-source-toggle-state,
+.view-source-toggle-state ~ .pdoc-code {
+    display: none;
+}
+.view-source-toggle-state:checked ~ .pdoc-code {
+    display: block;
+}
+
+.view-source-button {
+    display: inline-block;
+    float: right;
+    font-size: .75rem;
+    line-height: 1.5rem;
+    color: var(--muted);
+    padding: 0 .4rem 0 1.3rem;
+    cursor: pointer;
+    /* odd hack to reduce space between "bullet" and text */
+    text-indent: -2px;
+}
+.view-source-button > span {
+    visibility: hidden;
+}
+.module-info .view-source-button {
+    float: none;
+    display: flex;
+    justify-content: flex-end;
+    margin: -1.2rem .4rem -.2rem 0;
+}
+.view-source-button::before {
+    /* somewhat awkward recreation of a <summary> element. ideally we'd just use `display: inline list-item`, but
+     that does not work in Chrome (yet), see https://crbug.com/995106. */
+    position: absolute;
+    content: "View Source";
+    display: list-item;
+    list-style-type: disclosure-closed;
+}
+.view-source-toggle-state:checked ~ .attr .view-source-button::before,
+.view-source-toggle-state:checked ~ .view-source-button::before {
+    list-style-type: disclosure-open;
+}
+
+/* Docstrings */
+.pdoc .docstring {
+    margin-bottom: 1.5rem;
+}
+
+.pdoc section:not(.module-info) .docstring {
+    margin-left: clamp(0rem, 5vw - 2rem, 1rem);
+}
+
+.pdoc .docstring .pdoc-code {
+    margin-left: 1em;
+    margin-right: 1em;
+}
+
+/* Highlight focused element */
+.pdoc h1:target,
+.pdoc h2:target,
+.pdoc h3:target,
+.pdoc h4:target,
+.pdoc h5:target,
+.pdoc h6:target,
+.pdoc .pdoc-code > pre > span:target {
+    background-color: var(--active);
+    box-shadow: -1rem 0 0 0 var(--active);
+}
+
+.pdoc .pdoc-code > pre > span:target {
+    /* make the highlighted line full width so that the background extends */
+    display: block;
+}
+
+.pdoc div:target > .attr,
+.pdoc section:target > .attr,
+.pdoc dd:target > a {
+    background-color: var(--active);
+}
+
+.pdoc * {
+    scroll-margin: 2rem;
+}
+
+.pdoc .pdoc-code .linenos {
+    user-select: none;
+}
+
+.pdoc .attr:hover {
+    filter: contrast(0.95);
+}
+
+/* Header link */
+.pdoc section, .pdoc .classattr {
+    position: relative;
+}
+
+.pdoc .headerlink {
+    --width: clamp(1rem, 3vw, 2rem);
+    position: absolute;
+    top: 0;
+    left: calc(0rem - var(--width));
+    transition: all 100ms ease-in-out;
+    opacity: 0;
+}
+.pdoc .headerlink::before {
+    content: "#";
+    display: block;
+    text-align: center;
+    width: var(--width);
+    height: 2.3rem;
+    line-height: 2.3rem;
+    font-size: 1.5rem;
+}
+
+.pdoc .attr:hover ~ .headerlink,
+.pdoc *:target > .headerlink,
+.pdoc .headerlink:hover {
+    opacity: 1;
+}
+
+/* Attributes */
+.pdoc .attr {
+    display: block;
+    margin: .5rem 0 .5rem;
+    padding: .4rem .4rem .4rem 1rem;
+    background-color: var(--accent);
+    overflow-x: auto;
+}
+
+.pdoc .classattr {
+    margin-left: 2rem;
+}
+
+.pdoc .name {
+    color: var(--name);
+    font-weight: bold;
+}
+
+.pdoc .def {
+    color: var(--def);
+    font-weight: bold;
+}
+
+.pdoc .signature {
+    /* override pygments background color */
+    background-color: transparent;
+}
+
+.pdoc .param, .pdoc .return-annotation {
+    white-space: pre;
+}
+.pdoc .signature.multiline .param {
+    display: block;
+}
+.pdoc .signature.condensed .param {
+    display:inline-block;
+}
+
+.pdoc .annotation {
+    color: var(--annotation);
+}
+
+/* Show/Hide buttons for long default values */
+.pdoc .view-value-toggle-state,
+.pdoc .view-value-toggle-state ~ .default_value {
+    display: none;
+}
+.pdoc .view-value-toggle-state:checked ~ .default_value {
+    display: inherit;
+}
+.pdoc .view-value-button {
+    font-size: .5rem;
+    vertical-align: middle;
+    border-style: dashed;
+    margin-top: -0.1rem;
+}
+.pdoc .view-value-button:hover {
+    background: white;
+}
+.pdoc .view-value-button::before {
+    content: "show";
+    text-align: center;
+    width: 2.2em;
+    display: inline-block;
+}
+.pdoc .view-value-toggle-state:checked ~ .view-value-button::before {
+    content: "hide";
+}
+
+/* Inherited Members */
+.pdoc .inherited {
+    margin-left: 2rem;
+}
+
+.pdoc .inherited dt {
+    font-weight: 700;
+}
+
+.pdoc .inherited dt, .pdoc .inherited dd {
+    display: inline;
+    margin-left: 0;
+    margin-bottom: .5rem;
+}
+
+.pdoc .inherited dd:not(:last-child):after {
+    content: ", ";
+}
+
+.pdoc .inherited .class:before {
+    content: "class ";
+}
+
+.pdoc .inherited .function a:after {
+    content: "()";
+}
+
+/* Search results */
+.pdoc .search-result .docstring {
+    overflow: auto;
+    max-height: 25vh;
+}
+
+.pdoc .search-result.focused > .attr {
+    background-color: var(--active);
+}
+
+/* "built with pdoc" attribution */
+.pdoc .attribution {
+    margin-top: 2rem;
+    display: block;
+    opacity: 0.5;
+    transition: all 200ms;
+    filter: grayscale(100%);
+}
+
+.pdoc .attribution:hover {
+    opacity: 1;
+    filter: grayscale(0%);
+}
+
+.pdoc .attribution img {
+    margin-left: 5px;
+    height: 35px;
+    vertical-align: middle;
+    width: 70px;
+    transition: all 200ms;
+}
+
+/* Tables */
+.pdoc table {
+    display: block;
+    width: max-content;
+    max-width: 100%;
+    overflow: auto;
+    margin-bottom: 1rem;
+}
+
+.pdoc table th {
+    font-weight: 600;
+}
+
+.pdoc table th, .pdoc table td {
+    padding: 6px 13px;
+    border: 1px solid var(--accent2);
+}
diff --git a/templates/doc/custom.css b/templates/doc/custom.css
new file mode 100644
index 0000000..48a444a
--- /dev/null
+++ b/templates/doc/custom.css
@@ -0,0 +1,4 @@
+/*
+This file is empty by default.
+Override it in your custom template to provide additional CSS rules.
+*/
diff --git a/templates/doc/error.html.jinja2 b/templates/doc/error.html.jinja2
new file mode 100644
index 0000000..6ffdb5d
--- /dev/null
+++ b/templates/doc/error.html.jinja2
@@ -0,0 +1,38 @@
+{# this template is used by pdoc's integrated web server to display runtime errors. #}
+{% extends "frame.html.jinja2" %}
+{% block title %}{{ error }}{% endblock %}
+{% block style %}
+    <style>{% include "resources/bootstrap-reboot.min.css" %}</style>
+    <style>
+        body {
+            padding: 2rem;
+            max-width: 54rem;
+        }
+
+        footer {
+            text-align: right;
+        }
+
+        h1 {
+            margin-bottom: 2rem;
+        }
+
+        footer svg {
+            max-width: 70px;
+            max-height: 35px;
+            transition: all 200ms;
+            filter: grayscale(100%);
+        }
+
+        footer svg:hover {
+            filter: grayscale(0%);
+        }
+    </style>
+{% endblock %}
+{% block body %}
+    <h1>{{ error }}</h1>
+    <pre>{{ details }}</pre>
+    <hr>
+    <footer><a title="built with pdoc {{ __version__ }}"
+               href="https://pdoc.dev">{% include 'resources/pdoc-logo.svg' %}</a></footer>
+{% endblock %}
diff --git a/templates/doc/frame.html.jinja2 b/templates/doc/frame.html.jinja2
new file mode 100644
index 0000000..33ae656
--- /dev/null
+++ b/templates/doc/frame.html.jinja2
@@ -0,0 +1,54 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="generator" content="pdoc {{ __version__ }}"/>
+    <title>{% block title %}{% endblock %}</title>
+    {% block favicon %}
+        {% if favicon %}
+            <link rel="icon" href="{{ favicon }}"/>
+        {% endif %}
+    {% endblock %}
+    {% block head %}{% endblock %}
+    {% filter minify_css | indent %}
+        {% block style %}
+            <style>{% include "resources/bootstrap-reboot.min.css" %}</style>
+            <style>/*! syntax-highlighting.css */{% include "syntax-highlighting.css" %}</style>
+            {#
+            The style_pdoc, style_theme, style_layout, and style_content Jinja2 blocks are deprecated and will be
+            removed in a future release. Custom templates should either provide alternatives for the specific CSS files,
+            or append their own styles by providing `custom.css` (see examples/custom-template/).
+            #}
+            {% block style_pdoc %}
+                {% block style_theme %}<style>/*! theme.css */{% include "theme.css" %}</style>{% endblock %}
+                {% block style_layout %}<style>/*! layout.css */{% include "layout.css" %}</style>{% endblock %}
+                {% block style_content %}<style>/*! content.css */{% include "content.css" %}</style>{% endblock %}
+                {# Use this file in your custom template directory to add additional CSS styling: #}
+                <style>/*! custom.css */{% include "custom.css" %}</style>
+            {% endblock %}
+        {% endblock %}
+    {% endfilter %}
+    {% if math %}{% include "math.html.jinja2" %}{% endif %}
+    {% if mermaid %}{% include "mermaid.html.jinja2" %}{% endif %}
+</head>
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-G3M2G8V4M7"></script>
+<script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag() { dataLayer.push(arguments); }
+    gtag('js', new Date());
+
+    gtag('config', 'G-G3M2G8V4M7');
+</script>
+<body>
+{% block body %}
+    <nav class="pdoc">
+        <label id="navtoggle" for="togglestate" class="pdoc-button">{% include 'resources/navtoggle.svg' %}</label>
+        <input id="togglestate" type="checkbox" aria-hidden="true" tabindex="-1">
+        <div>{% block nav %}{% endblock %}</div>
+    </nav>
+    {% block content %}{% endblock %}
+{% endblock body %}
+</body>
+</html>
diff --git a/templates/doc/index.html.jinja2 b/templates/doc/index.html.jinja2
new file mode 100644
index 0000000..6653876
--- /dev/null
+++ b/templates/doc/index.html.jinja2
@@ -0,0 +1,74 @@
+{# this template is used to render the top-level index.html. #}
+{% if root_module_name %}
+{#
+If there is one common parent module for all documented modules, redirect there immediately.
+This makes a package's `__init__.py` the main entrypoint by default.
+A custom template could override this by setting root_module_name to false before `{% extend ... %}`.
+#}
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="refresh" content="0; url=./{{ root_module_name.replace(".","/") }}.html"/>
+</head>
+</html>
+{% else %}
+{% extends "frame.html.jinja2" %}
+{% block title %}Module List &ndash; pdoc {{ __version__ }}{% endblock %}
+{% block style %}
+    {{ super() | safe }}
+    <style>
+        header.pdoc {
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+        }
+
+        header.pdoc img {
+            max-width: 200px;
+            max-height: 75px;
+            padding-right: 2rem;
+        }
+
+        header.pdoc input[type=search] {
+            outline-offset: 0;
+            font-size: 1.5rem;
+            min-width: 60%;
+            flex-grow: 1;
+            padding-left: .5rem;
+            margin: 1.75rem 0;
+        }
+    </style>
+{% endblock %}
+{% block nav %}
+    <h2>Available Modules</h2>
+    <ul>
+        {% for submodule in all_modules if "._" not in submodule and not submodule.startswith("_") %}
+            <li><a href="{{ submodule.replace(".","/") }}.html">{{ submodule }}</a></li>
+        {% endfor %}
+    </ul>
+{% endblock %}
+{% block content %}
+    <header class="pdoc">
+        {% block logo %}
+            {% if logo %}
+                {% if logo_link %}<a href="{{ logo_link }}">{% endif %}
+                <img src="{{ logo }}" alt="project logo"/>
+                {% if logo_link %}</a>{% endif %}
+            {% else %}
+                <a href="https://pdoc.dev">
+                    <img src="data:image/svg+xml,{% filter urlencode %}{% include "resources/pdoc-logo.svg" %}{% endfilter %}"
+                         alt="pdoc"/>
+                </a>
+            {% endif %}
+        {% endblock %}
+        {% if search %}
+            <input type="search" placeholder="Search API Documentation..." aria-label="search box">
+        {% endif %}
+    </header>
+    <main class="pdoc"></main>
+    {% if search %}
+        {% include "search.html.jinja2" %}
+    {% endif %}
+{% endblock %}
+{% endif %}
diff --git a/templates/doc/layout.css b/templates/doc/layout.css
new file mode 100644
index 0000000..73c7163
--- /dev/null
+++ b/templates/doc/layout.css
@@ -0,0 +1,230 @@
+/*
+This CSS file contains all style definitions for the global page layout.
+
+When pdoc is embedded into other systems, it may be left out (or overwritten with an empty file) entirely.
+ */
+
+/* Responsive Layout */
+html, body {
+    width: 100%;
+    height: 100%;
+}
+
+html, main {
+    scroll-behavior: smooth;
+}
+
+body {
+    background-color: var(--pdoc-background);
+}
+
+@media (max-width: 769px) {
+    #navtoggle {
+        cursor: pointer;
+        position: absolute;
+        width: 50px;
+        height: 40px;
+        top: 1rem;
+        right: 1rem;
+        border-color: var(--text);
+        color: var(--text);
+        display: flex;
+        opacity: 0.8;
+        z-index: 999;
+    }
+
+    #navtoggle:hover {
+        opacity: 1;
+    }
+
+    #togglestate + div {
+        display: none;
+    }
+
+    #togglestate:checked + div {
+        display: inherit;
+    }
+
+    main, header {
+        padding: 2rem 3vw;
+    }
+
+    header + main {
+        margin-top: -3rem;
+    }
+
+    .git-button {
+        display: none !important;
+    }
+
+    nav input[type="search"] {
+        /* don't overflow into menu button */
+        max-width: 77%;
+    }
+
+    nav input[type="search"]:first-child {
+        /* align vertically with the hamburger menu */
+        margin-top: -6px;
+    }
+
+    nav input[type="search"]:valid ~ * {
+        /* hide rest of the menu when search has contents */
+        display: none !important;
+    }
+}
+
+@media (min-width: 770px) {
+    :root {
+        --sidebar-width: clamp(12.5rem, 28vw, 22rem);
+    }
+
+    nav {
+        position: fixed;
+        overflow: auto;
+        height: 100vh;
+        width: var(--sidebar-width);
+    }
+
+    main, header {
+        padding: 3rem 2rem 3rem calc(var(--sidebar-width) + 3rem);
+        width: calc(54rem + var(--sidebar-width));
+        max-width: 100%;
+    }
+
+    header + main {
+        margin-top: -4rem;
+    }
+
+    #navtoggle {
+        display: none;
+    }
+}
+
+#togglestate {
+    /*
+    Don't do `display: none` here.
+    When a mobile browser is not scrolled all the way to the top,
+    clicking the label would insert the menu above the scrolling position
+    and it would stay out of view. By making the checkbox technically
+    visible it jumps up first and we always get the menu into view when clicked.
+    */
+    position: absolute;
+    height: 0;
+    /* height:0 isn't enough in Firefox, so we hide it extra well */
+    opacity: 0;
+}
+
+/* Nav */
+nav.pdoc {
+    --pad: clamp(0.5rem, 2vw, 1.75rem);
+    --indent: 1.5rem;
+    background-color: var(--accent);
+    border-right: 1px solid var(--accent2);
+    box-shadow: 0 0 20px rgba(50, 50, 50, .2) inset;
+    padding: 0 0 0 var(--pad);
+    overflow-wrap: anywhere;
+    scrollbar-width: thin; /* Scrollbar width on Firefox */
+    scrollbar-color: var(--accent2) transparent; /* Scrollbar color on Firefox */
+    z-index: 1
+}
+
+nav.pdoc::-webkit-scrollbar {
+    width: .4rem; /* Scrollbar width on Chromium-based browsers */
+}
+
+nav.pdoc::-webkit-scrollbar-thumb {
+    background-color: var(--accent2); /* Scrollbar color on Chromium-based browsers */
+}
+
+nav.pdoc > div {
+    padding: var(--pad) 0;
+}
+
+nav.pdoc .module-list-button {
+    display: inline-flex;
+    align-items: center;
+    color: var(--text);
+    border-color: var(--muted);
+    margin-bottom: 1rem;
+}
+
+nav.pdoc .module-list-button:hover {
+    border-color: var(--text);
+}
+
+nav.pdoc input[type=search] {
+    display: block;
+    outline-offset: 0;
+    width: calc(100% - var(--pad));
+}
+
+nav.pdoc .logo {
+    max-width: calc(100% - var(--pad));
+    max-height: 35vh;
+    display: block;
+    margin: 0 auto 1rem;
+    transform: translate(calc(-.5 * var(--pad)), 0);
+}
+
+nav.pdoc ul {
+    list-style: none;
+    padding-left: 0;
+}
+
+nav.pdoc > div > ul {
+    /* undo padding here so that links span entire width */
+    margin-left: calc(0px - var(--pad));
+}
+
+nav.pdoc li a {
+    /* re-add padding (+indent) here */
+    padding: .2rem 0 .2rem calc(var(--pad) + var(--indent));
+}
+
+nav.pdoc > div > ul > li > a {
+    /* no padding for top-level */
+    padding-left: var(--pad);
+}
+
+nav.pdoc li {
+    transition: all 100ms;
+}
+
+nav.pdoc li:hover {
+    background-color: var(--nav-hover);
+}
+
+nav.pdoc a, nav.pdoc a:hover {
+    color: var(--text);
+}
+
+nav.pdoc a {
+    display: block;
+}
+
+nav.pdoc > h2:first-of-type {
+    margin-top: 1.5rem;
+}
+
+nav.pdoc .class:before {
+    content: "class ";
+    color: var(--muted);
+}
+
+nav.pdoc .function:after {
+    content: "()";
+    color: var(--muted);
+}
+
+nav.pdoc footer:before {
+    content: "";
+    display: block;
+    width: calc(100% - var(--pad));
+    border-top: solid var(--accent2) 1px;
+    margin-top: 1.5rem;
+    padding-top: .5rem;
+}
+
+nav.pdoc footer {
+    font-size: small;
+}
diff --git a/templates/doc/livereload.html.jinja2 b/templates/doc/livereload.html.jinja2
new file mode 100644
index 0000000..ab5c1db
--- /dev/null
+++ b/templates/doc/livereload.html.jinja2
@@ -0,0 +1,15 @@
+{# This templates implements live-reloading for pdoc's integrated webserver. #}
+<script>
+    /* Periodically check with the pdoc server if there have been any changes. */
+    let mtime_generated = {{ mtime | tojson }},
+        url = new URL(window.location);
+    url.searchParams.set("mtime", 1);
+    window.setInterval(function () {
+        fetch(url.toString())
+            .then(response => response.text())
+            .then(mtime => {
+                if (mtime_generated !== mtime)
+                    location.reload();
+            });
+    }, 1000);
+</script>
diff --git a/templates/doc/math.html.jinja2 b/templates/doc/math.html.jinja2
new file mode 100644
index 0000000..e181630
--- /dev/null
+++ b/templates/doc/math.html.jinja2
@@ -0,0 +1,28 @@
+{# This template is included in math mode and loads MathJax for formula rendering. #}
+<script>
+    {#
+    MathJax by default does not define $ as a math delimiter because it's commonly used in non-mathematical settings.
+    We add it here. If you are unhappy with this default, you can override math.html.jinja2 with a custom template.
+     #}
+    window.MathJax = {
+        tex: {
+            inlineMath: [['$', '$'], ['\\(', '\\)']]
+        }
+    };
+</script>
+<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
+<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
+<script>
+    /* Re-invoke MathJax when DOM content changes, for example during search. */
+    document.addEventListener("DOMContentLoaded", () => {
+        new MutationObserver(() => MathJax.typeset()).observe(
+            document.querySelector("main.pdoc").parentNode,
+            {childList: true}
+        );
+    })
+</script>
+<style>
+    mjx-container {
+        overflow-x: auto;
+    }
+</style>
diff --git a/templates/doc/mermaid.html.jinja2 b/templates/doc/mermaid.html.jinja2
new file mode 100755
index 0000000..dc61c3c
--- /dev/null
+++ b/templates/doc/mermaid.html.jinja2
@@ -0,0 +1,18 @@
+{# This template is included in mermaid mode and loads Mermaid.js for formula rendering. #}
+<style>
+    .pdoc .mermaid-pre {
+        border: none;
+        background: none;
+    }
+</style>
+<script type="module" defer>
+    import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
+
+    /* Re-invoke Mermaid when DOM content changes, for example during search. */
+    document.addEventListener("DOMContentLoaded", () => {
+        new MutationObserver(() => mermaid.run()).observe(
+            document.querySelector("main.pdoc").parentNode,
+            {childList: true}
+        );
+    })
+</script>
\ No newline at end of file
diff --git a/templates/doc/module.html.jinja2 b/templates/doc/module.html.jinja2
index fbe9e10..4c4ed43 100644
--- a/templates/doc/module.html.jinja2
+++ b/templates/doc/module.html.jinja2
@@ -1,5 +1,5 @@
 {% extends "frame.html.jinja2" %}
-{% block title %}Jellyfish Python Server SDK{% endblock %}
+{% block title %}{{ module.modulename }} API documentation{% endblock %}
 {% block nav %}
     {% block module_list_link %}
         {% set parentmodule = ".".join(module.modulename.split(".")[:-1]) %}
@@ -177,6 +177,7 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro variable(var) -%}
+    {%- if var.is_type_alias_type %}<span class="def">type</span> {% endif -%}
     <span class="name">{{ var.name }}</span>{{ annotation(var) }}{{ default_value(var) }}
 {% enddefaultmacro %}
 {% defaultmacro submodule(mod) -%}
@@ -208,7 +209,7 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
 {% enddefaultmacro %}
 {% defaultmacro docstring(var) %}
     {% if var.docstring %}
-        <div class="docstring">{{ var.docstring | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
+        <div class="docstring">{{ var.docstring | replace("@public", "") | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro nav_members(members) %}
@@ -238,10 +239,13 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     Implementing this as a macro makes it very easy to override with a custom template, see
     https://github.com/mitmproxy/pdoc/tree/main/examples/custom-template.
     #}
-    {% if not include_undocumented and not doc.docstring %}
-        {# hide members that are undocumented if include_undocumented has been toggled off. #}
-    {% elif doc.docstring and "@private" in doc.docstring %}
+    {% if "@private" in doc.docstring %}
         {# hide members explicitly marked as @private #}
+    {% elif "@public" in doc.docstring %}
+        {# show members explicitly marked as @public #}
+        true
+    {% elif not include_undocumented and not doc.docstring %}
+        {# hide members that are undocumented if include_undocumented has been toggled off. #}
     {% elif doc.name == "__init__" and (doc.docstring or (doc.kind == "function" and doc.signature_without_self.parameters)) %}
         {# show constructors that have a docstring or at least one extra argument #}
         true
@@ -295,4 +299,18 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro module_name() %}
+    <h1 class="modulename">
+        {% set parts = module.modulename.split(".") %}
+        {% for part in parts %}
+            {%- set fullname = ".".join(parts[:loop.index]) -%}
+            {%- if fullname in all_modules and fullname != module.modulename -%}
+                <a href="./{{ "../" * loop.revindex0 }}{{ part }}.html">{{ part }}</a>
+            {%- else -%}
+                {{ part }}
+            {%- endif -%}
+            {%- if loop.nextitem -%}
+                <wbr>.
+            {%- endif -%}
+        {% endfor %}
+    </h1>
 {% enddefaultmacro %}
diff --git a/templates/doc/search.html.jinja2 b/templates/doc/search.html.jinja2
new file mode 100644
index 0000000..ff2e892
--- /dev/null
+++ b/templates/doc/search.html.jinja2
@@ -0,0 +1,190 @@
+{# This template implements pdoc's search functionality. It is also responsible for lazy-loading the search index.  #}
+{% set rootprefix = "../" * module.modulename.count(".") if module else "" %}
+<script>
+    function escapeHTML(html) {
+        return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML;
+    }
+
+    const originalContent = document.querySelector("main.pdoc");
+    let currentContent = originalContent;
+
+    function setContent(innerHTML) {
+        {# Replace the entire page contents. Calling this with an empty argument restores the original page. #}
+        let elem;
+        if (innerHTML) {
+            elem = document.createElement("main");
+            elem.classList.add("pdoc");
+            elem.innerHTML = innerHTML;
+        } else {
+            elem = originalContent;
+        }
+        if (currentContent !== elem) {
+            currentContent.replaceWith(elem);
+            currentContent = elem;
+        }
+    }
+
+    function getSearchTerm() {
+        return (new URL(window.location)).searchParams.get("search");
+    }
+
+    {# the control flow here is: search input -> update location -> onInput #}
+    const searchBox = document.querySelector(".pdoc input[type=search]");
+    searchBox.addEventListener("input", function () {
+        let url = new URL(window.location);
+        if (searchBox.value.trim()) {
+            url.hash = "";
+            url.searchParams.set("search", searchBox.value);
+        } else {
+            url.searchParams.delete("search");
+        }
+        history.replaceState("", "", url.toString());
+        onInput();
+    });
+    window.addEventListener("popstate", onInput);
+
+
+    let search, searchErr;
+
+    async function initialize() {
+        {# Get the search index and compile it if necessary.
+           This function will only be called once. #}
+        try {
+            search = await new Promise((resolve, reject) => {
+                const script = document.createElement("script");
+                script.type = "text/javascript";
+                script.async = true;
+                script.onload = () => resolve(window.pdocSearch);
+                script.onerror = (e) => reject(e);
+                script.src = "{{ rootprefix }}search.js";
+                document.getElementsByTagName("head")[0].appendChild(script);
+            });
+        } catch (e) {
+            console.error("Cannot fetch pdoc search index");
+            searchErr = "Cannot fetch search index.";
+        }
+        onInput();
+
+        {# if the user clicks an anchor link in the navigation, we need to restore the original contents. #}
+        document.querySelector("nav.pdoc").addEventListener("click", e => {
+            if (e.target.hash) {
+                searchBox.value = "";
+                searchBox.dispatchEvent(new Event("input"));
+            }
+        });
+    }
+
+    function onInput() {
+        setContent((() => {
+            const term = getSearchTerm();
+            if (!term) {
+                return null
+            }
+            if (searchErr) {
+                return `<h3>Error: ${searchErr}</h3>`
+            }
+            if (!search) {
+                return "<h3>Searching...</h3>"
+            }
+
+            window.scrollTo({top: 0, left: 0, behavior: 'auto'});
+
+            const results = search(term);
+
+            let html;
+            if (results.length === 0) {
+                html = `No search results for '${escapeHTML(term)}'.`
+            } else {
+                html = `<h4>${results.length} search result${results.length > 1 ? "s" : ""} for '${escapeHTML(term)}'.</h4>`;
+            }
+            for (let result of results.slice(0, 10)) {
+                let doc = result.doc;
+                let url = `{{ rootprefix }}${doc.modulename.replaceAll(".", "/")}.html`;
+                if (doc.qualname) {
+                    url += `#${doc.qualname}`;
+                }
+
+                let heading;
+                switch (result.doc.kind) {
+                    case "function":
+                        if (doc.fullname.endsWith(".__init__")) {
+                            heading = `<span class="name">${doc.fullname.replace(/\.__init__$/, "")}</span>${doc.signature}`;
+                        } else {
+                            heading = `<span class="def">${doc.funcdef}</span> <span class="name">${doc.fullname}</span>${doc.signature}`;
+                        }
+                        break;
+                    case "class":
+                        heading = `<span class="def">class</span> <span class="name">${doc.fullname}</span>`;
+                        if (doc.bases)
+                            heading += `<wbr>(<span class="base">${doc.bases}</span>)`;
+                        heading += `:`;
+                        break;
+                    case "variable":
+                        heading = `<span class="name">${doc.fullname}</span>`;
+                        if (doc.annotation)
+                            heading += `<span class="annotation">${doc.annotation}</span>`;
+                        if (doc.default_value)
+                            heading += `<span class="default_value"> = ${doc.default_value}</span>`;
+                        break;
+                    default:
+                        heading = `<span class="name">${doc.fullname}</span>`;
+                        break;
+                }
+                html += `
+                        <section class="search-result">
+                        <a href="${url}" class="attr ${doc.kind}">${heading}</a>
+                        <div class="docstring">${doc.doc}</div>
+                        </section>
+                    `;
+
+            }
+            return html;
+        })());
+    }
+
+    if (getSearchTerm()) {
+        initialize();
+        searchBox.value = getSearchTerm();
+        onInput();
+    } else {
+        searchBox.addEventListener("focus", initialize, {once: true});
+    }
+
+    {# keyboard navigation for results #}
+    searchBox.addEventListener("keydown", e => {
+        if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) {
+            let focused = currentContent.querySelector(".search-result.focused");
+            if (!focused) {
+                currentContent.querySelector(".search-result").classList.add("focused");
+            } else if (
+                e.key === "ArrowDown"
+                && focused.nextElementSibling
+                && focused.nextElementSibling.classList.contains("search-result")
+            ) {
+                focused.classList.remove("focused");
+                focused.nextElementSibling.classList.add("focused");
+                focused.nextElementSibling.scrollIntoView({
+                    behavior: "smooth",
+                    block: "nearest",
+                    inline: "nearest"
+                });
+            } else if (
+                e.key === "ArrowUp"
+                && focused.previousElementSibling
+                && focused.previousElementSibling.classList.contains("search-result")
+            ) {
+                focused.classList.remove("focused");
+                focused.previousElementSibling.classList.add("focused");
+                focused.previousElementSibling.scrollIntoView({
+                    behavior: "smooth",
+                    block: "nearest",
+                    inline: "nearest"
+                });
+            } else if (
+                e.key === "Enter"
+            ) {
+                focused.querySelector("a").click();
+            }
+        }
+    });
+</script>
diff --git a/templates/doc/search.js.jinja2 b/templates/doc/search.js.jinja2
new file mode 100644
index 0000000..6f1385d
--- /dev/null
+++ b/templates/doc/search.js.jinja2
@@ -0,0 +1,51 @@
+{#
+This file contains the search index and is only loaded on demand (when the user focuses the search field).
+We use a JS file instead of JSON as this works with `file://`.
+#}
+window.pdocSearch = (function(){
+    {% include "resources/elasticlunr.min.js" %}
+
+    /** pdoc search index */const docs = {{ search_index | safe }};
+
+    // mirrored in build-search-index.js (part 1)
+    // Also split on html tags. this is a cheap heuristic, but good enough.
+    elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/);
+
+    let searchIndex;
+    if (docs._isPrebuiltIndex) {
+        console.info("using precompiled search index");
+        searchIndex = elasticlunr.Index.load(docs);
+    } else {
+        console.time("building search index");
+        // mirrored in build-search-index.js (part 2)
+        searchIndex = elasticlunr(function () {
+            this.pipeline.remove(elasticlunr.stemmer);
+            this.pipeline.remove(elasticlunr.stopWordFilter);
+            this.addField("qualname");
+            this.addField("fullname");
+            this.addField("annotation");
+            this.addField("default_value");
+            this.addField("signature");
+            this.addField("bases");
+            this.addField("doc");
+            this.setRef("fullname");
+        });
+        for (let doc of docs) {
+            searchIndex.addDoc(doc);
+        }
+        console.timeEnd("building search index");
+    }
+
+    return (term) => searchIndex.search(term, {
+        fields: {
+            qualname: {boost: 4},
+            fullname: {boost: 2},
+            annotation: {boost: 2},
+            default_value: {boost: 2},
+            signature: {boost: 2},
+            bases: {boost: 2},
+            doc: {boost: 1},
+        },
+        expand: true
+    });
+})();
diff --git a/templates/doc/syntax-highlighting.css b/templates/doc/syntax-highlighting.css
new file mode 100644
index 0000000..4ed8035
--- /dev/null
+++ b/templates/doc/syntax-highlighting.css
@@ -0,0 +1,72 @@
+/* auto-generated, see templates/README.md */
+pre { line-height: 125%; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 20px; }
+.pdoc-code .hll { background-color: #ffffcc }
+.pdoc-code { background: #f8f8f8; }
+.pdoc-code .c { color: #3D7B7B; font-style: italic } /* Comment */
+.pdoc-code .err { border: 1px solid #FF0000 } /* Error */
+.pdoc-code .k { color: #008000; font-weight: bold } /* Keyword */
+.pdoc-code .o { color: #666666 } /* Operator */
+.pdoc-code .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.pdoc-code .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.pdoc-code .cp { color: #9C6500 } /* Comment.Preproc */
+.pdoc-code .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.pdoc-code .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.pdoc-code .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.pdoc-code .gd { color: #A00000 } /* Generic.Deleted */
+.pdoc-code .ge { font-style: italic } /* Generic.Emph */
+.pdoc-code .gr { color: #E40000 } /* Generic.Error */
+.pdoc-code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.pdoc-code .gi { color: #008400 } /* Generic.Inserted */
+.pdoc-code .go { color: #717171 } /* Generic.Output */
+.pdoc-code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.pdoc-code .gs { font-weight: bold } /* Generic.Strong */
+.pdoc-code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.pdoc-code .gt { color: #0044DD } /* Generic.Traceback */
+.pdoc-code .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.pdoc-code .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.pdoc-code .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.pdoc-code .kp { color: #008000 } /* Keyword.Pseudo */
+.pdoc-code .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.pdoc-code .kt { color: #B00040 } /* Keyword.Type */
+.pdoc-code .m { color: #666666 } /* Literal.Number */
+.pdoc-code .s { color: #BA2121 } /* Literal.String */
+.pdoc-code .na { color: #687822 } /* Name.Attribute */
+.pdoc-code .nb { color: #008000 } /* Name.Builtin */
+.pdoc-code .nc { color: #0000FF; font-weight: bold } /* Name.Class */
+.pdoc-code .no { color: #880000 } /* Name.Constant */
+.pdoc-code .nd { color: #AA22FF } /* Name.Decorator */
+.pdoc-code .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.pdoc-code .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.pdoc-code .nf { color: #0000FF } /* Name.Function */
+.pdoc-code .nl { color: #767600 } /* Name.Label */
+.pdoc-code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.pdoc-code .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.pdoc-code .nv { color: #19177C } /* Name.Variable */
+.pdoc-code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.pdoc-code .w { color: #bbbbbb } /* Text.Whitespace */
+.pdoc-code .mb { color: #666666 } /* Literal.Number.Bin */
+.pdoc-code .mf { color: #666666 } /* Literal.Number.Float */
+.pdoc-code .mh { color: #666666 } /* Literal.Number.Hex */
+.pdoc-code .mi { color: #666666 } /* Literal.Number.Integer */
+.pdoc-code .mo { color: #666666 } /* Literal.Number.Oct */
+.pdoc-code .sa { color: #BA2121 } /* Literal.String.Affix */
+.pdoc-code .sb { color: #BA2121 } /* Literal.String.Backtick */
+.pdoc-code .sc { color: #BA2121 } /* Literal.String.Char */
+.pdoc-code .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.pdoc-code .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.pdoc-code .s2 { color: #BA2121 } /* Literal.String.Double */
+.pdoc-code .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.pdoc-code .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.pdoc-code .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.pdoc-code .sx { color: #008000 } /* Literal.String.Other */
+.pdoc-code .sr { color: #A45A77 } /* Literal.String.Regex */
+.pdoc-code .s1 { color: #BA2121 } /* Literal.String.Single */
+.pdoc-code .ss { color: #19177C } /* Literal.String.Symbol */
+.pdoc-code .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.pdoc-code .fm { color: #0000FF } /* Name.Function.Magic */
+.pdoc-code .vc { color: #19177C } /* Name.Variable.Class */
+.pdoc-code .vg { color: #19177C } /* Name.Variable.Global */
+.pdoc-code .vi { color: #19177C } /* Name.Variable.Instance */
+.pdoc-code .vm { color: #19177C } /* Name.Variable.Magic */
+.pdoc-code .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/templates/frame.html.jinja2 b/templates/frame.html.jinja2
new file mode 100644
index 0000000..f5ac7b1
--- /dev/null
+++ b/templates/frame.html.jinja2
@@ -0,0 +1,85 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="generator" content="pdoc {{ __version__ }}" />
+    <title>{% block title %}{% endblock %}</title>
+    {% block favicon %}
+    {% if favicon %}
+    <link rel="icon" href="{{ favicon }}" />
+    {% endif %}
+    {% endblock %}
+    {% block head %}{% endblock %}
+    {% filter minify_css | indent %}
+    {% block style %}
+    <style>
+        {
+            % include "resources/bootstrap-reboot.min.css" %
+        }
+    </style>
+    <style>
+        /*! syntax-highlighting.css */
+            {
+            % include "syntax-highlighting.css" %
+        }
+    </style>
+    {#
+    The style_pdoc, style_theme, style_layout, and style_content Jinja2 blocks are deprecated and will be
+    removed in a future release. Custom templates should either provide alternatives for the specific CSS files,
+    or append their own styles by providing `custom.css` (see examples/custom-template/).
+    #}
+    {% block style_pdoc %}
+    {% block style_theme %}<style>
+        /*! theme.css */
+            {
+            % include "theme.css" %
+        }
+    </style>{% endblock %}
+    {% block style_layout %}<style>
+        /*! layout.css */
+            {
+            % include "layout.css" %
+        }
+    </style>{% endblock %}
+    {% block style_content %}<style>
+        /*! content.css */
+            {
+            % include "content.css" %
+        }
+    </style>{% endblock %}
+    {# Use this file in your custom template directory to add additional CSS styling: #}
+    <style>
+        /*! custom.css */
+            {
+            % include "custom.css" %
+        }
+    </style>
+    {% endblock %}
+    {% endblock %}
+    {% endfilter %}
+    {% if math %}{% include "math.html.jinja2" %}{% endif %}
+    {% if mermaid %}{% include "mermaid.html.jinja2" %}{% endif %}
+</head>
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-G3M2G8V4M7"></script>
+<script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag() { dataLayer.push(arguments); }
+    gtag('js', new Date());
+
+    gtag('config', 'G-G3M2G8V4M7');
+</script>
+<body>
+    {% block body %}
+    <nav class="pdoc">
+        <label id="navtoggle" for="togglestate" class="pdoc-button">{% include 'resources/navtoggle.svg' %}</label>
+        <input id="togglestate" type="checkbox" aria-hidden="true" tabindex="-1">
+        <div>{% block nav %}{% endblock %}</div>
+    </nav>
+    {% block content %}{% endblock %}
+    {% endblock body %}
+</body>
+
+</html>
\ No newline at end of file
diff --git a/templates/module.html.jinja2 b/templates/module.html.jinja2
new file mode 100644
index 0000000..fbe9e10
--- /dev/null
+++ b/templates/module.html.jinja2
@@ -0,0 +1,298 @@
+{% extends "frame.html.jinja2" %}
+{% block title %}Jellyfish Python Server SDK{% endblock %}
+{% block nav %}
+    {% block module_list_link %}
+        {% set parentmodule = ".".join(module.modulename.split(".")[:-1]) %}
+        {% if parentmodule and parentmodule in all_modules %}
+            <a class="pdoc-button module-list-button" href="../{{ parentmodule.split(".")[-1] }}.html">
+                {% include "resources/box-arrow-in-left.svg" %}
+                &nbsp;
+                {{- parentmodule -}}
+            </a>
+        {% elif not root_module_name %}
+            <a class="pdoc-button module-list-button" href="{{ "../" * module.modulename.count(".") }}index.html">
+                {% include "resources/box-arrow-in-left.svg" %}
+                &nbsp;
+                Module Index
+            </a>
+        {% endif %}
+    {% endblock %}
+
+    {% block nav_title %}
+        {% if logo %}
+            {% if logo_link %}<a href="{{ logo_link }}">{% endif %}
+            <img src="{{ logo }}" class="logo" alt="project logo"/>
+            {% if logo_link %}</a>{% endif %}
+        {% endif %}
+    {% endblock %}
+
+    {% block search_box %}
+        {% if search and all_modules|length > 1 %}
+            {# we set a pattern here so that we can use the :valid CSS selector #}
+            <input type="search" placeholder="Search..." role="searchbox" aria-label="search"
+                   pattern=".+" required>
+        {% endif %}
+    {% endblock %}
+
+    {% block nav_index %}
+        {% set index = module.docstring | to_markdown | to_html | attr("toc_html") %}
+        {% if index %}
+            <h2>Contents</h2>
+            {{ index | safe }}
+        {% endif %}
+    {% endblock %}
+
+    {% block nav_submodules %}
+        {% if module.submodules %}
+            <h2>Submodules</h2>
+            <ul>
+                {% for submodule in module.submodules if is_public(submodule) | trim %}
+                    <li>{{ submodule.taken_from | link(text=submodule.name) }}</li>
+                {% endfor %}
+            </ul>
+        {% endif %}
+    {% endblock %}
+
+    {% block nav_members %}
+        {% if module.members %}
+            <h2>API Documentation</h2>
+            {{ nav_members(module.members.values()) }}
+        {% endif %}
+    {% endblock %}
+
+    {% block nav_footer %}
+        {% if footer_text %}
+            <footer>{{ footer_text }}</footer>
+        {% endif %}
+    {% endblock %}
+
+    {% block attribution %}
+        <a class="attribution" title="pdoc: Python API documentation generator" href="https://pdoc.dev" target="_blank">
+            built with <span class="visually-hidden">pdoc</span><img
+                alt="pdoc logo"
+                src="data:image/svg+xml,
+                        {%- filter urlencode %}{% include "resources/pdoc-logo.svg" %}{% endfilter %}"/>
+        </a>
+    {% endblock %}
+{% endblock nav %}
+{% block content %}
+    <main class="pdoc">
+        {% block module_info %}
+            <section class="module-info">
+                {% block edit_button %}
+                    {% if edit_url %}
+                        {% if "github.com" in edit_url %}
+                            {% set edit_text = "Edit on GitHub" %}
+                        {% elif "gitlab" in edit_url %}
+                            {% set edit_text = "Edit on GitLab" %}
+                        {% else %}
+                            {% set edit_text = "Edit Source" %}
+                        {% endif %}
+                        <a class="pdoc-button git-button" href="{{ edit_url }}">{{ edit_text }}</a>
+                    {% endif %}
+                {% endblock %}
+                {{ module_name() }}
+                {{ docstring(module) }}
+                {{ view_source_state(module) }}
+                {{ view_source_button(module) }}
+                {{ view_source_code(module) }}
+            </section>
+        {% endblock %}
+        {% block module_contents %}
+            {% for m in module.flattened_own_members if is_public(m) | trim %}
+                <section id="{{ m.qualname or m.name }}">
+                    {{ member(m) }}
+                    {% if m.kind == "class" %}
+                        {% for m in m.own_members if m.kind != "class" and is_public(m) | trim %}
+                            <div id="{{ m.qualname }}" class="classattr">
+                                {{ member(m) }}
+                            </div>
+                        {% endfor %}
+                        {% set inherited_members = inherited(m) | trim %}
+                        {% if inherited_members %}
+                            <div class="inherited">
+                                <h5>Inherited Members</h5>
+                                <dl>
+                                    {{ inherited_members }}
+                                </dl>
+                            </div>
+                        {% endif %}
+                    {% endif %}
+                </section>
+            {% endfor %}
+        {% endblock %}
+    </main>
+    {% if mtime %}
+        {% include "livereload.html.jinja2" %}
+    {% endif %}
+    {% block search_js %}
+        {% if search and all_modules|length > 1 %}
+            {% include "search.html.jinja2" %}
+        {% endif %}
+    {% endblock %}
+{% endblock content %}
+{#
+End of content, beginning of helper macros.
+See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an explanation of defaultmacro.
+#}
+{% defaultmacro bases(cls) %}
+    {%- if cls.bases -%}
+        <wbr>(
+        {%- for base in cls.bases -%}
+            <span class="base">{{ base[:2] | link(text=base[2]) }}</span>
+            {%- if loop.nextitem %}, {% endif %}
+        {%- endfor -%}
+        )
+    {%- endif -%}
+{% enddefaultmacro %}
+{% defaultmacro default_value(var) -%}
+    {%- if var.default_value_str %}
+        =
+        {% if var.default_value_str | length > 100 -%}
+            <input id="{{ var.qualname }}-view-value" class="view-value-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
+            <label class="view-value-button pdoc-button" for="{{ var.qualname }}-view-value"></label>
+        {%- endif -%}
+        <span class="default_value">{{ var.default_value_str | escape | linkify }}</span>
+    {%- endif -%}
+{% enddefaultmacro %}
+{% defaultmacro annotation(var) %}
+    {%- if var.annotation_str -%}
+        <span class="annotation">{{ var.annotation_str | escape | linkify }}</span>
+    {%- endif -%}
+{% enddefaultmacro %}
+{% defaultmacro decorators(doc) %}
+    {% for d in doc.decorators if not d.startswith("@_") %}
+        <div class="decorator">{{ d }}</div>
+    {% endfor %}
+{% enddefaultmacro %}
+{% defaultmacro function(fn) -%}
+    {{ decorators(fn) }}
+    {% if fn.name == "__init__" %}
+        <span class="name">{{ ".".join(fn.qualname.split(".")[:-1]) }}</span>
+        {{- fn.signature_without_self | format_signature(colon=False) | linkify }}
+    {% else %}
+        <span class="def">{{ fn.funcdef }}</span>
+        <span class="name">{{ fn.name }}</span>
+        {{- fn.signature | format_signature(colon=True) | linkify }}
+    {% endif %}
+{% enddefaultmacro %}
+{% defaultmacro variable(var) -%}
+    <span class="name">{{ var.name }}</span>{{ annotation(var) }}{{ default_value(var) }}
+{% enddefaultmacro %}
+{% defaultmacro submodule(mod) -%}
+    <span class="name">{{ mod.taken_from | link }}</span>
+{% enddefaultmacro %}
+{% defaultmacro class(cls) -%}
+    {{ decorators(cls) }}
+    <span class="def">class</span>
+    <span class="name">{{ cls.qualname }}</span>
+    {{- bases(cls) -}}:
+{% enddefaultmacro %}
+{% defaultmacro member(doc) %}
+    {{- view_source_state(doc) -}}
+    <div class="attr {{ doc.kind }}">
+        {% if doc.kind == "class" %}
+            {{ class(doc) }}
+        {% elif doc.kind == "function" %}
+            {{ function(doc) }}
+        {% elif doc.kind == "module" %}
+            {{ submodule(doc) }}
+        {% else %}
+            {{ variable(doc) }}
+        {% endif %}
+        {{ view_source_button(doc) }}
+    </div>
+    <a class="headerlink" href="#{{ doc.qualname or doc.name }}"></a>
+    {{ view_source_code(doc) }}
+    {{ docstring(doc) }}
+{% enddefaultmacro %}
+{% defaultmacro docstring(var) %}
+    {% if var.docstring %}
+        <div class="docstring">{{ var.docstring | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
+    {% endif %}
+{% enddefaultmacro %}
+{% defaultmacro nav_members(members) %}
+    <ul class="memberlist">
+        {% for m in members if is_public(m) | trim %}
+            <li>
+                {% if m.kind == "class" %}
+                    <a class="class" href="#{{ m.qualname }}">{{ m.qualname }}</a>
+                    {% if m.own_members %}
+                        {{ nav_members(m.own_members) | indent(12) }}
+                    {% endif %}
+                {% elif m.kind == "module" %}
+                    <a class="module" href="#{{ m.name }}">{{ m.name }}</a>
+                {% elif m.name == "__init__" %}
+                    <a class="function" href="#{{ m.qualname }}">{{ m.qualname.split(".")[-2] }}</a>
+                {% else %}
+                    <a class="{{ m.kind }}" href="#{{ m.qualname }}">{{ m.name }}</a>
+                {% endif %}
+            </li>
+        {% endfor %}
+    </ul>
+{% enddefaultmacro %}
+{% defaultmacro is_public(doc) %}
+    {#
+    This macro is a bit unconventional in that its output is not rendered, but treated as a boolean:
+    Returning no text is interpreted as false, returning any other text is iterpreted as true.
+    Implementing this as a macro makes it very easy to override with a custom template, see
+    https://github.com/mitmproxy/pdoc/tree/main/examples/custom-template.
+    #}
+    {% if not include_undocumented and not doc.docstring %}
+        {# hide members that are undocumented if include_undocumented has been toggled off. #}
+    {% elif doc.docstring and "@private" in doc.docstring %}
+        {# hide members explicitly marked as @private #}
+    {% elif doc.name == "__init__" and (doc.docstring or (doc.kind == "function" and doc.signature_without_self.parameters)) %}
+        {# show constructors that have a docstring or at least one extra argument #}
+        true
+    {% elif doc.name == "__doc__" %}
+        {# We don't want to document __doc__ itself, https://github.com/mitmproxy/pdoc/issues/235 #}
+    {% elif doc.kind == "variable" and doc.is_typevar and not doc.docstring %}
+        {# do not document TypeVars, that only clutters the docs. #}
+    {% elif doc.kind == "module" and doc.fullname not in all_modules %}
+        {# Skip modules that were manually excluded, https://github.com/mitmproxy/pdoc/issues/334 #}
+    {% elif (doc.qualname or doc.name) is in(module.obj.__all__ or []) %}
+        {# members starting with an underscore are still public if mentioned in __all__ #}
+        true
+    {% elif not doc.name.startswith("_") %}
+        {# members not starting with an underscore are considered public by default #}
+        true
+    {% endif %}
+{% enddefaultmacro %}
+{# fmt: off #}
+{% defaultmacro inherited(cls) %}
+    {% for base, members in cls.inherited_members.items() %}
+        {% set m = None %}{# workaround for https://github.com/pallets/jinja/issues/1427 #}
+        {% set member_html %}
+            {% for m in members if is_public(m) | trim %}
+                <dd id="{{ m.qualname }}" class="{{ m.kind }}">
+                    {{- m.taken_from | link(text=m.name.replace("__init__",base[1])) -}}
+                </dd>
+            {% endfor %}
+        {% endset %}
+        {# we may not have any public members, in which case we don't want to print anything. #}
+        {% if member_html %}
+            <div><dt>{{ base | link }}</dt>
+                {{ member_html }}
+            </div>
+        {% endif %}
+    {% endfor %}
+{% enddefaultmacro %}
+{# fmt: on #}
+{% defaultmacro view_source_state(doc) %}
+    {% if show_source and doc.source %}
+        <input id="{{ doc.qualname or "mod-" + doc.name }}-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
+    {% endif %}
+{% enddefaultmacro %}
+{% defaultmacro view_source_button(doc) %}
+    {% if show_source and doc.source %}
+        <label class="view-source-button" for="{{ doc.qualname or "mod-" + doc.name }}-view-source"><span>View Source</span></label>
+    {% endif %}
+{% enddefaultmacro %}
+{% defaultmacro view_source_code(doc) %}
+    {% if show_source and doc.source %}
+        {{ doc | highlight }}
+    {% endif %}
+{% enddefaultmacro %}
+{% defaultmacro module_name() %}
+{% enddefaultmacro %}

From 369c0a06e362b200ddb1ab50093461bec15f477d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?=
 <roznawski.przemyslaw@gmail.com>
Date: Tue, 12 Mar 2024 11:40:11 +0100
Subject: [PATCH 2/2] Remove unused

---
 templates/doc/README.md               |  43 ---
 templates/doc/build-search-index.js   |  30 --
 templates/doc/content.css             | 438 --------------------------
 templates/doc/custom.css              |   4 -
 templates/doc/error.html.jinja2       |  38 ---
 templates/doc/index.html.jinja2       |  74 -----
 templates/doc/layout.css              | 230 --------------
 templates/doc/livereload.html.jinja2  |  15 -
 templates/doc/math.html.jinja2        |  28 --
 templates/doc/mermaid.html.jinja2     |  18 --
 templates/doc/module.html.jinja2      |  28 +-
 templates/doc/search.html.jinja2      | 190 -----------
 templates/doc/search.js.jinja2        |  51 ---
 templates/doc/syntax-highlighting.css |  72 -----
 templates/frame.html.jinja2           |  85 -----
 templates/module.html.jinja2          | 298 ------------------
 16 files changed, 5 insertions(+), 1637 deletions(-)
 delete mode 100644 templates/doc/README.md
 delete mode 100644 templates/doc/build-search-index.js
 delete mode 100644 templates/doc/content.css
 delete mode 100644 templates/doc/custom.css
 delete mode 100644 templates/doc/error.html.jinja2
 delete mode 100644 templates/doc/index.html.jinja2
 delete mode 100644 templates/doc/layout.css
 delete mode 100644 templates/doc/livereload.html.jinja2
 delete mode 100644 templates/doc/math.html.jinja2
 delete mode 100755 templates/doc/mermaid.html.jinja2
 delete mode 100644 templates/doc/search.html.jinja2
 delete mode 100644 templates/doc/search.js.jinja2
 delete mode 100644 templates/doc/syntax-highlighting.css
 delete mode 100644 templates/frame.html.jinja2
 delete mode 100644 templates/module.html.jinja2

diff --git a/templates/doc/README.md b/templates/doc/README.md
deleted file mode 100644
index 5bd1994..0000000
--- a/templates/doc/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# 📑 pdoc templates
-
-This directory contains pdoc's default templates, which you can extend in your own template directory. See
-[the documentation](https://pdoc.dev/docs/pdoc.html#edit-pdocs-html-template) for an example.
-
-For customization, the most important files are:
-
-**Main HTML Templates**
-
- - `default/module.html.jinja2`: Template for documentation pages.
- - `default/index.html.jinja2`: Template for the top-level `index.html`.
- - `default/frame.html.jinja2`: The common page layout for `module.html.jinja2` and `index.html.jinja2`.
-
-**CSS Stylesheets**
-
- - `custom.css`: Empty be default, add custom additional rules here.
- - `content.css`: All style definitions for documentation contents.
- - `layout.css`: All style definitions for the page layout (navigation, sidebar, ...).
- - `theme.css`: Color definitions (see [`examples/dark-mode`](../../examples/dark-mode)).
- - `syntax-highlighting.css`: Code snippet style, see below.
-
-## Extending templates
-
-pdoc will first check for `$template.jinja2` before checking `default/$template.jinja2`. This allows you to reuse the
-macros from the main templates in `default/`. For example, you can create a `module.html.jinja2` file in your custom 
-template directory that extends the default template as follows:
-
-```html
-{% extends "default/module.html.jinja2" %}
-{% block title %}new page title{% endblock %}
-```
-
-## Syntax Highlighting
-
-The `syntax-highlighting.css` file contains the CSS styles used for syntax highlighting.
-It is generated as follows:
-
-```
-pygmentize -f html -a .pdoc-code -S <theme> > default/syntax-highlighting.css
-```
-
-The default theme is `default`, with extended padding added to the `.linenos` class.
-Alternative color schemes can be tested on [the Pygments website](https://pygments.org/demo/).
diff --git a/templates/doc/build-search-index.js b/templates/doc/build-search-index.js
deleted file mode 100644
index 7661d27..0000000
--- a/templates/doc/build-search-index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * This script is invoked by pdoc to precompile the search index.
- * Precompiling the search index increases file size, but skips the CPU-heavy index building in the browser.
- */
-let elasticlunr = require("./resources/elasticlunr.min");
-
-let fs = require("fs");
-let docs = JSON.parse(fs.readFileSync(0, "utf-8"));
-
-/* mirrored in search.js.jinja2  (part 1) */
-elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/);
-
-/* mirrored in search.js.jinja2  (part 2) */
-searchIndex = elasticlunr(function () {
-    this.pipeline.remove(elasticlunr.stemmer);
-    this.pipeline.remove(elasticlunr.stopWordFilter);
-    this.addField("qualname");
-    this.addField("fullname");
-    this.addField("annotation");
-    this.addField("default_value");
-    this.addField("signature");
-    this.addField("bases");
-    this.addField("doc");
-    this.setRef("fullname");
-});
-for (let doc of docs) {
-    searchIndex.addDoc(doc);
-}
-
-process.stdout.write(JSON.stringify(searchIndex.toJSON()));
diff --git a/templates/doc/content.css b/templates/doc/content.css
deleted file mode 100644
index 39b7417..0000000
--- a/templates/doc/content.css
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
-This CSS file contains all style definitions for documentation content.
-
-All selectors are scoped with ".pdoc".
-This makes sure that the pdoc styling doesn't leak to the rest of the page when pdoc is embedded.
-*/
-
-.pdoc {
-    color: var(--text);
-    /* enforce some styling even if bootstrap reboot is not included */
-    box-sizing: border-box;
-    line-height: 1.5;
-    /* override background from pygments */
-    /*unnecessary since pdoc 10, only left here to keep old custom templates working. */
-    background: none;
-}
-
-.pdoc .pdoc-button {
-    cursor: pointer;
-    display: inline-block;
-    border: solid black 1px;
-    border-radius: 2px;
-    font-size: .75rem;
-    padding: calc(0.5em - 1px) 1em;
-    transition: 100ms all;
-}
-
-
-/* Admonitions */
-.pdoc .pdoc-alert {
-    padding: 1rem 1rem 1rem calc(1.5rem + 24px);
-    border: 1px solid transparent;
-    border-radius: .25rem;
-    background-repeat: no-repeat;
-    background-position: 1rem center;
-    margin-bottom: 1rem;
-}
-
-.pdoc .pdoc-alert > *:last-child {
-    margin-bottom: 0;
-}
-
-/* Admonitions are currently not stylable via theme.css */
-.pdoc .pdoc-alert-note  {
-    color: #084298;
-    background-color: #cfe2ff;
-    border-color: #b6d4fe;
-    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/info-circle-fill.svg' %}{% endfilter %}");
-}
-
-.pdoc .pdoc-alert-warning {
-    color: #664d03;
-    background-color: #fff3cd;
-    border-color: #ffecb5;
-    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/exclamation-triangle-fill.svg' %}{% endfilter %}");
-}
-
-.pdoc .pdoc-alert-danger {
-    color: #842029;
-    background-color: #f8d7da;
-    border-color: #f5c2c7;
-    background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/lightning-fill.svg' %}{% endfilter %}");
-}
-
-.pdoc .visually-hidden {
-    position: absolute !important;
-    width: 1px !important;
-    height: 1px !important;
-    padding: 0 !important;
-    margin: -1px !important;
-    overflow: hidden !important;
-    clip: rect(0, 0, 0, 0) !important;
-    white-space: nowrap !important;
-    border: 0 !important;
-}
-
-.pdoc h1, .pdoc h2, .pdoc h3 {
-    font-weight: 300;
-    margin: .3em 0;
-    padding: .2em 0;
-}
-
-.pdoc > section:not(.module-info) h1 {
-    font-size: 1.5rem;
-    font-weight: 500;
-}
-.pdoc > section:not(.module-info) h2 {
-    font-size: 1.4rem;
-    font-weight: 500;
-}
-.pdoc > section:not(.module-info) h3 {
-    font-size: 1.3rem;
-    font-weight: 500;
-}
-.pdoc > section:not(.module-info) h4 {
-    font-size: 1.2rem;
-}
-.pdoc > section:not(.module-info) h5 {
-    font-size: 1.1rem;
-}
-
-.pdoc a {
-    text-decoration: none;
-    color: var(--link);
-}
-
-.pdoc a:hover {
-    color: var(--link-hover);
-}
-
-.pdoc blockquote {
-    margin-left: 2rem;
-}
-
-.pdoc pre {
-    border-top: 1px solid var(--accent2);
-    border-bottom: 1px solid var(--accent2);
-    margin-top: 0;
-    margin-bottom: 1em;
-    padding: .5rem 0 .5rem .5rem;
-    overflow-x: auto;
-    /*unnecessary since pdoc 10, only left here to keep old custom templates working. */
-    background-color: var(--code);
-}
-
-.pdoc code {
-    color: var(--text);
-    padding: .2em .4em;
-    margin: 0;
-    font-size: 85%;
-    background-color: var(--accent);
-    border-radius: 6px;
-}
-
-.pdoc a > code {
-    color: inherit;
-}
-
-.pdoc pre > code {
-    display: inline-block;
-    font-size: inherit;
-    background: none;
-    border: none;
-    padding: 0;
-}
-
-.pdoc > section:not(.module-info) {
-    /* this margin should collapse with docstring margin,
-       but not for the module docstr which is followed by view_source. */
-    margin-bottom: 1.5rem;
-}
-
-/* Page Heading */
-.pdoc .modulename {
-    margin-top: 0;
-    font-weight: bold;
-}
-
-.pdoc .modulename a {
-    color: var(--link);
-    transition: 100ms all;
-}
-
-/* GitHub Button */
-.pdoc .git-button {
-    float: right;
-    border: solid var(--link) 1px;
-}
-
-.pdoc .git-button:hover {
-    background-color: var(--link);
-    color: var(--pdoc-background);
-}
-
-.view-source-toggle-state,
-.view-source-toggle-state ~ .pdoc-code {
-    display: none;
-}
-.view-source-toggle-state:checked ~ .pdoc-code {
-    display: block;
-}
-
-.view-source-button {
-    display: inline-block;
-    float: right;
-    font-size: .75rem;
-    line-height: 1.5rem;
-    color: var(--muted);
-    padding: 0 .4rem 0 1.3rem;
-    cursor: pointer;
-    /* odd hack to reduce space between "bullet" and text */
-    text-indent: -2px;
-}
-.view-source-button > span {
-    visibility: hidden;
-}
-.module-info .view-source-button {
-    float: none;
-    display: flex;
-    justify-content: flex-end;
-    margin: -1.2rem .4rem -.2rem 0;
-}
-.view-source-button::before {
-    /* somewhat awkward recreation of a <summary> element. ideally we'd just use `display: inline list-item`, but
-     that does not work in Chrome (yet), see https://crbug.com/995106. */
-    position: absolute;
-    content: "View Source";
-    display: list-item;
-    list-style-type: disclosure-closed;
-}
-.view-source-toggle-state:checked ~ .attr .view-source-button::before,
-.view-source-toggle-state:checked ~ .view-source-button::before {
-    list-style-type: disclosure-open;
-}
-
-/* Docstrings */
-.pdoc .docstring {
-    margin-bottom: 1.5rem;
-}
-
-.pdoc section:not(.module-info) .docstring {
-    margin-left: clamp(0rem, 5vw - 2rem, 1rem);
-}
-
-.pdoc .docstring .pdoc-code {
-    margin-left: 1em;
-    margin-right: 1em;
-}
-
-/* Highlight focused element */
-.pdoc h1:target,
-.pdoc h2:target,
-.pdoc h3:target,
-.pdoc h4:target,
-.pdoc h5:target,
-.pdoc h6:target,
-.pdoc .pdoc-code > pre > span:target {
-    background-color: var(--active);
-    box-shadow: -1rem 0 0 0 var(--active);
-}
-
-.pdoc .pdoc-code > pre > span:target {
-    /* make the highlighted line full width so that the background extends */
-    display: block;
-}
-
-.pdoc div:target > .attr,
-.pdoc section:target > .attr,
-.pdoc dd:target > a {
-    background-color: var(--active);
-}
-
-.pdoc * {
-    scroll-margin: 2rem;
-}
-
-.pdoc .pdoc-code .linenos {
-    user-select: none;
-}
-
-.pdoc .attr:hover {
-    filter: contrast(0.95);
-}
-
-/* Header link */
-.pdoc section, .pdoc .classattr {
-    position: relative;
-}
-
-.pdoc .headerlink {
-    --width: clamp(1rem, 3vw, 2rem);
-    position: absolute;
-    top: 0;
-    left: calc(0rem - var(--width));
-    transition: all 100ms ease-in-out;
-    opacity: 0;
-}
-.pdoc .headerlink::before {
-    content: "#";
-    display: block;
-    text-align: center;
-    width: var(--width);
-    height: 2.3rem;
-    line-height: 2.3rem;
-    font-size: 1.5rem;
-}
-
-.pdoc .attr:hover ~ .headerlink,
-.pdoc *:target > .headerlink,
-.pdoc .headerlink:hover {
-    opacity: 1;
-}
-
-/* Attributes */
-.pdoc .attr {
-    display: block;
-    margin: .5rem 0 .5rem;
-    padding: .4rem .4rem .4rem 1rem;
-    background-color: var(--accent);
-    overflow-x: auto;
-}
-
-.pdoc .classattr {
-    margin-left: 2rem;
-}
-
-.pdoc .name {
-    color: var(--name);
-    font-weight: bold;
-}
-
-.pdoc .def {
-    color: var(--def);
-    font-weight: bold;
-}
-
-.pdoc .signature {
-    /* override pygments background color */
-    background-color: transparent;
-}
-
-.pdoc .param, .pdoc .return-annotation {
-    white-space: pre;
-}
-.pdoc .signature.multiline .param {
-    display: block;
-}
-.pdoc .signature.condensed .param {
-    display:inline-block;
-}
-
-.pdoc .annotation {
-    color: var(--annotation);
-}
-
-/* Show/Hide buttons for long default values */
-.pdoc .view-value-toggle-state,
-.pdoc .view-value-toggle-state ~ .default_value {
-    display: none;
-}
-.pdoc .view-value-toggle-state:checked ~ .default_value {
-    display: inherit;
-}
-.pdoc .view-value-button {
-    font-size: .5rem;
-    vertical-align: middle;
-    border-style: dashed;
-    margin-top: -0.1rem;
-}
-.pdoc .view-value-button:hover {
-    background: white;
-}
-.pdoc .view-value-button::before {
-    content: "show";
-    text-align: center;
-    width: 2.2em;
-    display: inline-block;
-}
-.pdoc .view-value-toggle-state:checked ~ .view-value-button::before {
-    content: "hide";
-}
-
-/* Inherited Members */
-.pdoc .inherited {
-    margin-left: 2rem;
-}
-
-.pdoc .inherited dt {
-    font-weight: 700;
-}
-
-.pdoc .inherited dt, .pdoc .inherited dd {
-    display: inline;
-    margin-left: 0;
-    margin-bottom: .5rem;
-}
-
-.pdoc .inherited dd:not(:last-child):after {
-    content: ", ";
-}
-
-.pdoc .inherited .class:before {
-    content: "class ";
-}
-
-.pdoc .inherited .function a:after {
-    content: "()";
-}
-
-/* Search results */
-.pdoc .search-result .docstring {
-    overflow: auto;
-    max-height: 25vh;
-}
-
-.pdoc .search-result.focused > .attr {
-    background-color: var(--active);
-}
-
-/* "built with pdoc" attribution */
-.pdoc .attribution {
-    margin-top: 2rem;
-    display: block;
-    opacity: 0.5;
-    transition: all 200ms;
-    filter: grayscale(100%);
-}
-
-.pdoc .attribution:hover {
-    opacity: 1;
-    filter: grayscale(0%);
-}
-
-.pdoc .attribution img {
-    margin-left: 5px;
-    height: 35px;
-    vertical-align: middle;
-    width: 70px;
-    transition: all 200ms;
-}
-
-/* Tables */
-.pdoc table {
-    display: block;
-    width: max-content;
-    max-width: 100%;
-    overflow: auto;
-    margin-bottom: 1rem;
-}
-
-.pdoc table th {
-    font-weight: 600;
-}
-
-.pdoc table th, .pdoc table td {
-    padding: 6px 13px;
-    border: 1px solid var(--accent2);
-}
diff --git a/templates/doc/custom.css b/templates/doc/custom.css
deleted file mode 100644
index 48a444a..0000000
--- a/templates/doc/custom.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
-This file is empty by default.
-Override it in your custom template to provide additional CSS rules.
-*/
diff --git a/templates/doc/error.html.jinja2 b/templates/doc/error.html.jinja2
deleted file mode 100644
index 6ffdb5d..0000000
--- a/templates/doc/error.html.jinja2
+++ /dev/null
@@ -1,38 +0,0 @@
-{# this template is used by pdoc's integrated web server to display runtime errors. #}
-{% extends "frame.html.jinja2" %}
-{% block title %}{{ error }}{% endblock %}
-{% block style %}
-    <style>{% include "resources/bootstrap-reboot.min.css" %}</style>
-    <style>
-        body {
-            padding: 2rem;
-            max-width: 54rem;
-        }
-
-        footer {
-            text-align: right;
-        }
-
-        h1 {
-            margin-bottom: 2rem;
-        }
-
-        footer svg {
-            max-width: 70px;
-            max-height: 35px;
-            transition: all 200ms;
-            filter: grayscale(100%);
-        }
-
-        footer svg:hover {
-            filter: grayscale(0%);
-        }
-    </style>
-{% endblock %}
-{% block body %}
-    <h1>{{ error }}</h1>
-    <pre>{{ details }}</pre>
-    <hr>
-    <footer><a title="built with pdoc {{ __version__ }}"
-               href="https://pdoc.dev">{% include 'resources/pdoc-logo.svg' %}</a></footer>
-{% endblock %}
diff --git a/templates/doc/index.html.jinja2 b/templates/doc/index.html.jinja2
deleted file mode 100644
index 6653876..0000000
--- a/templates/doc/index.html.jinja2
+++ /dev/null
@@ -1,74 +0,0 @@
-{# this template is used to render the top-level index.html. #}
-{% if root_module_name %}
-{#
-If there is one common parent module for all documented modules, redirect there immediately.
-This makes a package's `__init__.py` the main entrypoint by default.
-A custom template could override this by setting root_module_name to false before `{% extend ... %}`.
-#}
-<!doctype html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <meta http-equiv="refresh" content="0; url=./{{ root_module_name.replace(".","/") }}.html"/>
-</head>
-</html>
-{% else %}
-{% extends "frame.html.jinja2" %}
-{% block title %}Module List &ndash; pdoc {{ __version__ }}{% endblock %}
-{% block style %}
-    {{ super() | safe }}
-    <style>
-        header.pdoc {
-            display: flex;
-            align-items: center;
-            flex-wrap: wrap;
-        }
-
-        header.pdoc img {
-            max-width: 200px;
-            max-height: 75px;
-            padding-right: 2rem;
-        }
-
-        header.pdoc input[type=search] {
-            outline-offset: 0;
-            font-size: 1.5rem;
-            min-width: 60%;
-            flex-grow: 1;
-            padding-left: .5rem;
-            margin: 1.75rem 0;
-        }
-    </style>
-{% endblock %}
-{% block nav %}
-    <h2>Available Modules</h2>
-    <ul>
-        {% for submodule in all_modules if "._" not in submodule and not submodule.startswith("_") %}
-            <li><a href="{{ submodule.replace(".","/") }}.html">{{ submodule }}</a></li>
-        {% endfor %}
-    </ul>
-{% endblock %}
-{% block content %}
-    <header class="pdoc">
-        {% block logo %}
-            {% if logo %}
-                {% if logo_link %}<a href="{{ logo_link }}">{% endif %}
-                <img src="{{ logo }}" alt="project logo"/>
-                {% if logo_link %}</a>{% endif %}
-            {% else %}
-                <a href="https://pdoc.dev">
-                    <img src="data:image/svg+xml,{% filter urlencode %}{% include "resources/pdoc-logo.svg" %}{% endfilter %}"
-                         alt="pdoc"/>
-                </a>
-            {% endif %}
-        {% endblock %}
-        {% if search %}
-            <input type="search" placeholder="Search API Documentation..." aria-label="search box">
-        {% endif %}
-    </header>
-    <main class="pdoc"></main>
-    {% if search %}
-        {% include "search.html.jinja2" %}
-    {% endif %}
-{% endblock %}
-{% endif %}
diff --git a/templates/doc/layout.css b/templates/doc/layout.css
deleted file mode 100644
index 73c7163..0000000
--- a/templates/doc/layout.css
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
-This CSS file contains all style definitions for the global page layout.
-
-When pdoc is embedded into other systems, it may be left out (or overwritten with an empty file) entirely.
- */
-
-/* Responsive Layout */
-html, body {
-    width: 100%;
-    height: 100%;
-}
-
-html, main {
-    scroll-behavior: smooth;
-}
-
-body {
-    background-color: var(--pdoc-background);
-}
-
-@media (max-width: 769px) {
-    #navtoggle {
-        cursor: pointer;
-        position: absolute;
-        width: 50px;
-        height: 40px;
-        top: 1rem;
-        right: 1rem;
-        border-color: var(--text);
-        color: var(--text);
-        display: flex;
-        opacity: 0.8;
-        z-index: 999;
-    }
-
-    #navtoggle:hover {
-        opacity: 1;
-    }
-
-    #togglestate + div {
-        display: none;
-    }
-
-    #togglestate:checked + div {
-        display: inherit;
-    }
-
-    main, header {
-        padding: 2rem 3vw;
-    }
-
-    header + main {
-        margin-top: -3rem;
-    }
-
-    .git-button {
-        display: none !important;
-    }
-
-    nav input[type="search"] {
-        /* don't overflow into menu button */
-        max-width: 77%;
-    }
-
-    nav input[type="search"]:first-child {
-        /* align vertically with the hamburger menu */
-        margin-top: -6px;
-    }
-
-    nav input[type="search"]:valid ~ * {
-        /* hide rest of the menu when search has contents */
-        display: none !important;
-    }
-}
-
-@media (min-width: 770px) {
-    :root {
-        --sidebar-width: clamp(12.5rem, 28vw, 22rem);
-    }
-
-    nav {
-        position: fixed;
-        overflow: auto;
-        height: 100vh;
-        width: var(--sidebar-width);
-    }
-
-    main, header {
-        padding: 3rem 2rem 3rem calc(var(--sidebar-width) + 3rem);
-        width: calc(54rem + var(--sidebar-width));
-        max-width: 100%;
-    }
-
-    header + main {
-        margin-top: -4rem;
-    }
-
-    #navtoggle {
-        display: none;
-    }
-}
-
-#togglestate {
-    /*
-    Don't do `display: none` here.
-    When a mobile browser is not scrolled all the way to the top,
-    clicking the label would insert the menu above the scrolling position
-    and it would stay out of view. By making the checkbox technically
-    visible it jumps up first and we always get the menu into view when clicked.
-    */
-    position: absolute;
-    height: 0;
-    /* height:0 isn't enough in Firefox, so we hide it extra well */
-    opacity: 0;
-}
-
-/* Nav */
-nav.pdoc {
-    --pad: clamp(0.5rem, 2vw, 1.75rem);
-    --indent: 1.5rem;
-    background-color: var(--accent);
-    border-right: 1px solid var(--accent2);
-    box-shadow: 0 0 20px rgba(50, 50, 50, .2) inset;
-    padding: 0 0 0 var(--pad);
-    overflow-wrap: anywhere;
-    scrollbar-width: thin; /* Scrollbar width on Firefox */
-    scrollbar-color: var(--accent2) transparent; /* Scrollbar color on Firefox */
-    z-index: 1
-}
-
-nav.pdoc::-webkit-scrollbar {
-    width: .4rem; /* Scrollbar width on Chromium-based browsers */
-}
-
-nav.pdoc::-webkit-scrollbar-thumb {
-    background-color: var(--accent2); /* Scrollbar color on Chromium-based browsers */
-}
-
-nav.pdoc > div {
-    padding: var(--pad) 0;
-}
-
-nav.pdoc .module-list-button {
-    display: inline-flex;
-    align-items: center;
-    color: var(--text);
-    border-color: var(--muted);
-    margin-bottom: 1rem;
-}
-
-nav.pdoc .module-list-button:hover {
-    border-color: var(--text);
-}
-
-nav.pdoc input[type=search] {
-    display: block;
-    outline-offset: 0;
-    width: calc(100% - var(--pad));
-}
-
-nav.pdoc .logo {
-    max-width: calc(100% - var(--pad));
-    max-height: 35vh;
-    display: block;
-    margin: 0 auto 1rem;
-    transform: translate(calc(-.5 * var(--pad)), 0);
-}
-
-nav.pdoc ul {
-    list-style: none;
-    padding-left: 0;
-}
-
-nav.pdoc > div > ul {
-    /* undo padding here so that links span entire width */
-    margin-left: calc(0px - var(--pad));
-}
-
-nav.pdoc li a {
-    /* re-add padding (+indent) here */
-    padding: .2rem 0 .2rem calc(var(--pad) + var(--indent));
-}
-
-nav.pdoc > div > ul > li > a {
-    /* no padding for top-level */
-    padding-left: var(--pad);
-}
-
-nav.pdoc li {
-    transition: all 100ms;
-}
-
-nav.pdoc li:hover {
-    background-color: var(--nav-hover);
-}
-
-nav.pdoc a, nav.pdoc a:hover {
-    color: var(--text);
-}
-
-nav.pdoc a {
-    display: block;
-}
-
-nav.pdoc > h2:first-of-type {
-    margin-top: 1.5rem;
-}
-
-nav.pdoc .class:before {
-    content: "class ";
-    color: var(--muted);
-}
-
-nav.pdoc .function:after {
-    content: "()";
-    color: var(--muted);
-}
-
-nav.pdoc footer:before {
-    content: "";
-    display: block;
-    width: calc(100% - var(--pad));
-    border-top: solid var(--accent2) 1px;
-    margin-top: 1.5rem;
-    padding-top: .5rem;
-}
-
-nav.pdoc footer {
-    font-size: small;
-}
diff --git a/templates/doc/livereload.html.jinja2 b/templates/doc/livereload.html.jinja2
deleted file mode 100644
index ab5c1db..0000000
--- a/templates/doc/livereload.html.jinja2
+++ /dev/null
@@ -1,15 +0,0 @@
-{# This templates implements live-reloading for pdoc's integrated webserver. #}
-<script>
-    /* Periodically check with the pdoc server if there have been any changes. */
-    let mtime_generated = {{ mtime | tojson }},
-        url = new URL(window.location);
-    url.searchParams.set("mtime", 1);
-    window.setInterval(function () {
-        fetch(url.toString())
-            .then(response => response.text())
-            .then(mtime => {
-                if (mtime_generated !== mtime)
-                    location.reload();
-            });
-    }, 1000);
-</script>
diff --git a/templates/doc/math.html.jinja2 b/templates/doc/math.html.jinja2
deleted file mode 100644
index e181630..0000000
--- a/templates/doc/math.html.jinja2
+++ /dev/null
@@ -1,28 +0,0 @@
-{# This template is included in math mode and loads MathJax for formula rendering. #}
-<script>
-    {#
-    MathJax by default does not define $ as a math delimiter because it's commonly used in non-mathematical settings.
-    We add it here. If you are unhappy with this default, you can override math.html.jinja2 with a custom template.
-     #}
-    window.MathJax = {
-        tex: {
-            inlineMath: [['$', '$'], ['\\(', '\\)']]
-        }
-    };
-</script>
-<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
-<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
-<script>
-    /* Re-invoke MathJax when DOM content changes, for example during search. */
-    document.addEventListener("DOMContentLoaded", () => {
-        new MutationObserver(() => MathJax.typeset()).observe(
-            document.querySelector("main.pdoc").parentNode,
-            {childList: true}
-        );
-    })
-</script>
-<style>
-    mjx-container {
-        overflow-x: auto;
-    }
-</style>
diff --git a/templates/doc/mermaid.html.jinja2 b/templates/doc/mermaid.html.jinja2
deleted file mode 100755
index dc61c3c..0000000
--- a/templates/doc/mermaid.html.jinja2
+++ /dev/null
@@ -1,18 +0,0 @@
-{# This template is included in mermaid mode and loads Mermaid.js for formula rendering. #}
-<style>
-    .pdoc .mermaid-pre {
-        border: none;
-        background: none;
-    }
-</style>
-<script type="module" defer>
-    import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
-
-    /* Re-invoke Mermaid when DOM content changes, for example during search. */
-    document.addEventListener("DOMContentLoaded", () => {
-        new MutationObserver(() => mermaid.run()).observe(
-            document.querySelector("main.pdoc").parentNode,
-            {childList: true}
-        );
-    })
-</script>
\ No newline at end of file
diff --git a/templates/doc/module.html.jinja2 b/templates/doc/module.html.jinja2
index 4c4ed43..fbe9e10 100644
--- a/templates/doc/module.html.jinja2
+++ b/templates/doc/module.html.jinja2
@@ -1,5 +1,5 @@
 {% extends "frame.html.jinja2" %}
-{% block title %}{{ module.modulename }} API documentation{% endblock %}
+{% block title %}Jellyfish Python Server SDK{% endblock %}
 {% block nav %}
     {% block module_list_link %}
         {% set parentmodule = ".".join(module.modulename.split(".")[:-1]) %}
@@ -177,7 +177,6 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro variable(var) -%}
-    {%- if var.is_type_alias_type %}<span class="def">type</span> {% endif -%}
     <span class="name">{{ var.name }}</span>{{ annotation(var) }}{{ default_value(var) }}
 {% enddefaultmacro %}
 {% defaultmacro submodule(mod) -%}
@@ -209,7 +208,7 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
 {% enddefaultmacro %}
 {% defaultmacro docstring(var) %}
     {% if var.docstring %}
-        <div class="docstring">{{ var.docstring | replace("@public", "") | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
+        <div class="docstring">{{ var.docstring | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro nav_members(members) %}
@@ -239,13 +238,10 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     Implementing this as a macro makes it very easy to override with a custom template, see
     https://github.com/mitmproxy/pdoc/tree/main/examples/custom-template.
     #}
-    {% if "@private" in doc.docstring %}
-        {# hide members explicitly marked as @private #}
-    {% elif "@public" in doc.docstring %}
-        {# show members explicitly marked as @public #}
-        true
-    {% elif not include_undocumented and not doc.docstring %}
+    {% if not include_undocumented and not doc.docstring %}
         {# hide members that are undocumented if include_undocumented has been toggled off. #}
+    {% elif doc.docstring and "@private" in doc.docstring %}
+        {# hide members explicitly marked as @private #}
     {% elif doc.name == "__init__" and (doc.docstring or (doc.kind == "function" and doc.signature_without_self.parameters)) %}
         {# show constructors that have a docstring or at least one extra argument #}
         true
@@ -299,18 +295,4 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
     {% endif %}
 {% enddefaultmacro %}
 {% defaultmacro module_name() %}
-    <h1 class="modulename">
-        {% set parts = module.modulename.split(".") %}
-        {% for part in parts %}
-            {%- set fullname = ".".join(parts[:loop.index]) -%}
-            {%- if fullname in all_modules and fullname != module.modulename -%}
-                <a href="./{{ "../" * loop.revindex0 }}{{ part }}.html">{{ part }}</a>
-            {%- else -%}
-                {{ part }}
-            {%- endif -%}
-            {%- if loop.nextitem -%}
-                <wbr>.
-            {%- endif -%}
-        {% endfor %}
-    </h1>
 {% enddefaultmacro %}
diff --git a/templates/doc/search.html.jinja2 b/templates/doc/search.html.jinja2
deleted file mode 100644
index ff2e892..0000000
--- a/templates/doc/search.html.jinja2
+++ /dev/null
@@ -1,190 +0,0 @@
-{# This template implements pdoc's search functionality. It is also responsible for lazy-loading the search index.  #}
-{% set rootprefix = "../" * module.modulename.count(".") if module else "" %}
-<script>
-    function escapeHTML(html) {
-        return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML;
-    }
-
-    const originalContent = document.querySelector("main.pdoc");
-    let currentContent = originalContent;
-
-    function setContent(innerHTML) {
-        {# Replace the entire page contents. Calling this with an empty argument restores the original page. #}
-        let elem;
-        if (innerHTML) {
-            elem = document.createElement("main");
-            elem.classList.add("pdoc");
-            elem.innerHTML = innerHTML;
-        } else {
-            elem = originalContent;
-        }
-        if (currentContent !== elem) {
-            currentContent.replaceWith(elem);
-            currentContent = elem;
-        }
-    }
-
-    function getSearchTerm() {
-        return (new URL(window.location)).searchParams.get("search");
-    }
-
-    {# the control flow here is: search input -> update location -> onInput #}
-    const searchBox = document.querySelector(".pdoc input[type=search]");
-    searchBox.addEventListener("input", function () {
-        let url = new URL(window.location);
-        if (searchBox.value.trim()) {
-            url.hash = "";
-            url.searchParams.set("search", searchBox.value);
-        } else {
-            url.searchParams.delete("search");
-        }
-        history.replaceState("", "", url.toString());
-        onInput();
-    });
-    window.addEventListener("popstate", onInput);
-
-
-    let search, searchErr;
-
-    async function initialize() {
-        {# Get the search index and compile it if necessary.
-           This function will only be called once. #}
-        try {
-            search = await new Promise((resolve, reject) => {
-                const script = document.createElement("script");
-                script.type = "text/javascript";
-                script.async = true;
-                script.onload = () => resolve(window.pdocSearch);
-                script.onerror = (e) => reject(e);
-                script.src = "{{ rootprefix }}search.js";
-                document.getElementsByTagName("head")[0].appendChild(script);
-            });
-        } catch (e) {
-            console.error("Cannot fetch pdoc search index");
-            searchErr = "Cannot fetch search index.";
-        }
-        onInput();
-
-        {# if the user clicks an anchor link in the navigation, we need to restore the original contents. #}
-        document.querySelector("nav.pdoc").addEventListener("click", e => {
-            if (e.target.hash) {
-                searchBox.value = "";
-                searchBox.dispatchEvent(new Event("input"));
-            }
-        });
-    }
-
-    function onInput() {
-        setContent((() => {
-            const term = getSearchTerm();
-            if (!term) {
-                return null
-            }
-            if (searchErr) {
-                return `<h3>Error: ${searchErr}</h3>`
-            }
-            if (!search) {
-                return "<h3>Searching...</h3>"
-            }
-
-            window.scrollTo({top: 0, left: 0, behavior: 'auto'});
-
-            const results = search(term);
-
-            let html;
-            if (results.length === 0) {
-                html = `No search results for '${escapeHTML(term)}'.`
-            } else {
-                html = `<h4>${results.length} search result${results.length > 1 ? "s" : ""} for '${escapeHTML(term)}'.</h4>`;
-            }
-            for (let result of results.slice(0, 10)) {
-                let doc = result.doc;
-                let url = `{{ rootprefix }}${doc.modulename.replaceAll(".", "/")}.html`;
-                if (doc.qualname) {
-                    url += `#${doc.qualname}`;
-                }
-
-                let heading;
-                switch (result.doc.kind) {
-                    case "function":
-                        if (doc.fullname.endsWith(".__init__")) {
-                            heading = `<span class="name">${doc.fullname.replace(/\.__init__$/, "")}</span>${doc.signature}`;
-                        } else {
-                            heading = `<span class="def">${doc.funcdef}</span> <span class="name">${doc.fullname}</span>${doc.signature}`;
-                        }
-                        break;
-                    case "class":
-                        heading = `<span class="def">class</span> <span class="name">${doc.fullname}</span>`;
-                        if (doc.bases)
-                            heading += `<wbr>(<span class="base">${doc.bases}</span>)`;
-                        heading += `:`;
-                        break;
-                    case "variable":
-                        heading = `<span class="name">${doc.fullname}</span>`;
-                        if (doc.annotation)
-                            heading += `<span class="annotation">${doc.annotation}</span>`;
-                        if (doc.default_value)
-                            heading += `<span class="default_value"> = ${doc.default_value}</span>`;
-                        break;
-                    default:
-                        heading = `<span class="name">${doc.fullname}</span>`;
-                        break;
-                }
-                html += `
-                        <section class="search-result">
-                        <a href="${url}" class="attr ${doc.kind}">${heading}</a>
-                        <div class="docstring">${doc.doc}</div>
-                        </section>
-                    `;
-
-            }
-            return html;
-        })());
-    }
-
-    if (getSearchTerm()) {
-        initialize();
-        searchBox.value = getSearchTerm();
-        onInput();
-    } else {
-        searchBox.addEventListener("focus", initialize, {once: true});
-    }
-
-    {# keyboard navigation for results #}
-    searchBox.addEventListener("keydown", e => {
-        if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) {
-            let focused = currentContent.querySelector(".search-result.focused");
-            if (!focused) {
-                currentContent.querySelector(".search-result").classList.add("focused");
-            } else if (
-                e.key === "ArrowDown"
-                && focused.nextElementSibling
-                && focused.nextElementSibling.classList.contains("search-result")
-            ) {
-                focused.classList.remove("focused");
-                focused.nextElementSibling.classList.add("focused");
-                focused.nextElementSibling.scrollIntoView({
-                    behavior: "smooth",
-                    block: "nearest",
-                    inline: "nearest"
-                });
-            } else if (
-                e.key === "ArrowUp"
-                && focused.previousElementSibling
-                && focused.previousElementSibling.classList.contains("search-result")
-            ) {
-                focused.classList.remove("focused");
-                focused.previousElementSibling.classList.add("focused");
-                focused.previousElementSibling.scrollIntoView({
-                    behavior: "smooth",
-                    block: "nearest",
-                    inline: "nearest"
-                });
-            } else if (
-                e.key === "Enter"
-            ) {
-                focused.querySelector("a").click();
-            }
-        }
-    });
-</script>
diff --git a/templates/doc/search.js.jinja2 b/templates/doc/search.js.jinja2
deleted file mode 100644
index 6f1385d..0000000
--- a/templates/doc/search.js.jinja2
+++ /dev/null
@@ -1,51 +0,0 @@
-{#
-This file contains the search index and is only loaded on demand (when the user focuses the search field).
-We use a JS file instead of JSON as this works with `file://`.
-#}
-window.pdocSearch = (function(){
-    {% include "resources/elasticlunr.min.js" %}
-
-    /** pdoc search index */const docs = {{ search_index | safe }};
-
-    // mirrored in build-search-index.js (part 1)
-    // Also split on html tags. this is a cheap heuristic, but good enough.
-    elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/);
-
-    let searchIndex;
-    if (docs._isPrebuiltIndex) {
-        console.info("using precompiled search index");
-        searchIndex = elasticlunr.Index.load(docs);
-    } else {
-        console.time("building search index");
-        // mirrored in build-search-index.js (part 2)
-        searchIndex = elasticlunr(function () {
-            this.pipeline.remove(elasticlunr.stemmer);
-            this.pipeline.remove(elasticlunr.stopWordFilter);
-            this.addField("qualname");
-            this.addField("fullname");
-            this.addField("annotation");
-            this.addField("default_value");
-            this.addField("signature");
-            this.addField("bases");
-            this.addField("doc");
-            this.setRef("fullname");
-        });
-        for (let doc of docs) {
-            searchIndex.addDoc(doc);
-        }
-        console.timeEnd("building search index");
-    }
-
-    return (term) => searchIndex.search(term, {
-        fields: {
-            qualname: {boost: 4},
-            fullname: {boost: 2},
-            annotation: {boost: 2},
-            default_value: {boost: 2},
-            signature: {boost: 2},
-            bases: {boost: 2},
-            doc: {boost: 1},
-        },
-        expand: true
-    });
-})();
diff --git a/templates/doc/syntax-highlighting.css b/templates/doc/syntax-highlighting.css
deleted file mode 100644
index 4ed8035..0000000
--- a/templates/doc/syntax-highlighting.css
+++ /dev/null
@@ -1,72 +0,0 @@
-/* auto-generated, see templates/README.md */
-pre { line-height: 125%; }
-span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 20px; }
-.pdoc-code .hll { background-color: #ffffcc }
-.pdoc-code { background: #f8f8f8; }
-.pdoc-code .c { color: #3D7B7B; font-style: italic } /* Comment */
-.pdoc-code .err { border: 1px solid #FF0000 } /* Error */
-.pdoc-code .k { color: #008000; font-weight: bold } /* Keyword */
-.pdoc-code .o { color: #666666 } /* Operator */
-.pdoc-code .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
-.pdoc-code .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
-.pdoc-code .cp { color: #9C6500 } /* Comment.Preproc */
-.pdoc-code .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
-.pdoc-code .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
-.pdoc-code .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
-.pdoc-code .gd { color: #A00000 } /* Generic.Deleted */
-.pdoc-code .ge { font-style: italic } /* Generic.Emph */
-.pdoc-code .gr { color: #E40000 } /* Generic.Error */
-.pdoc-code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.pdoc-code .gi { color: #008400 } /* Generic.Inserted */
-.pdoc-code .go { color: #717171 } /* Generic.Output */
-.pdoc-code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.pdoc-code .gs { font-weight: bold } /* Generic.Strong */
-.pdoc-code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.pdoc-code .gt { color: #0044DD } /* Generic.Traceback */
-.pdoc-code .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
-.pdoc-code .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
-.pdoc-code .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
-.pdoc-code .kp { color: #008000 } /* Keyword.Pseudo */
-.pdoc-code .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
-.pdoc-code .kt { color: #B00040 } /* Keyword.Type */
-.pdoc-code .m { color: #666666 } /* Literal.Number */
-.pdoc-code .s { color: #BA2121 } /* Literal.String */
-.pdoc-code .na { color: #687822 } /* Name.Attribute */
-.pdoc-code .nb { color: #008000 } /* Name.Builtin */
-.pdoc-code .nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.pdoc-code .no { color: #880000 } /* Name.Constant */
-.pdoc-code .nd { color: #AA22FF } /* Name.Decorator */
-.pdoc-code .ni { color: #717171; font-weight: bold } /* Name.Entity */
-.pdoc-code .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
-.pdoc-code .nf { color: #0000FF } /* Name.Function */
-.pdoc-code .nl { color: #767600 } /* Name.Label */
-.pdoc-code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.pdoc-code .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.pdoc-code .nv { color: #19177C } /* Name.Variable */
-.pdoc-code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.pdoc-code .w { color: #bbbbbb } /* Text.Whitespace */
-.pdoc-code .mb { color: #666666 } /* Literal.Number.Bin */
-.pdoc-code .mf { color: #666666 } /* Literal.Number.Float */
-.pdoc-code .mh { color: #666666 } /* Literal.Number.Hex */
-.pdoc-code .mi { color: #666666 } /* Literal.Number.Integer */
-.pdoc-code .mo { color: #666666 } /* Literal.Number.Oct */
-.pdoc-code .sa { color: #BA2121 } /* Literal.String.Affix */
-.pdoc-code .sb { color: #BA2121 } /* Literal.String.Backtick */
-.pdoc-code .sc { color: #BA2121 } /* Literal.String.Char */
-.pdoc-code .dl { color: #BA2121 } /* Literal.String.Delimiter */
-.pdoc-code .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
-.pdoc-code .s2 { color: #BA2121 } /* Literal.String.Double */
-.pdoc-code .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
-.pdoc-code .sh { color: #BA2121 } /* Literal.String.Heredoc */
-.pdoc-code .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
-.pdoc-code .sx { color: #008000 } /* Literal.String.Other */
-.pdoc-code .sr { color: #A45A77 } /* Literal.String.Regex */
-.pdoc-code .s1 { color: #BA2121 } /* Literal.String.Single */
-.pdoc-code .ss { color: #19177C } /* Literal.String.Symbol */
-.pdoc-code .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.pdoc-code .fm { color: #0000FF } /* Name.Function.Magic */
-.pdoc-code .vc { color: #19177C } /* Name.Variable.Class */
-.pdoc-code .vg { color: #19177C } /* Name.Variable.Global */
-.pdoc-code .vi { color: #19177C } /* Name.Variable.Instance */
-.pdoc-code .vm { color: #19177C } /* Name.Variable.Magic */
-.pdoc-code .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/templates/frame.html.jinja2 b/templates/frame.html.jinja2
deleted file mode 100644
index f5ac7b1..0000000
--- a/templates/frame.html.jinja2
+++ /dev/null
@@ -1,85 +0,0 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <meta name="generator" content="pdoc {{ __version__ }}" />
-    <title>{% block title %}{% endblock %}</title>
-    {% block favicon %}
-    {% if favicon %}
-    <link rel="icon" href="{{ favicon }}" />
-    {% endif %}
-    {% endblock %}
-    {% block head %}{% endblock %}
-    {% filter minify_css | indent %}
-    {% block style %}
-    <style>
-        {
-            % include "resources/bootstrap-reboot.min.css" %
-        }
-    </style>
-    <style>
-        /*! syntax-highlighting.css */
-            {
-            % include "syntax-highlighting.css" %
-        }
-    </style>
-    {#
-    The style_pdoc, style_theme, style_layout, and style_content Jinja2 blocks are deprecated and will be
-    removed in a future release. Custom templates should either provide alternatives for the specific CSS files,
-    or append their own styles by providing `custom.css` (see examples/custom-template/).
-    #}
-    {% block style_pdoc %}
-    {% block style_theme %}<style>
-        /*! theme.css */
-            {
-            % include "theme.css" %
-        }
-    </style>{% endblock %}
-    {% block style_layout %}<style>
-        /*! layout.css */
-            {
-            % include "layout.css" %
-        }
-    </style>{% endblock %}
-    {% block style_content %}<style>
-        /*! content.css */
-            {
-            % include "content.css" %
-        }
-    </style>{% endblock %}
-    {# Use this file in your custom template directory to add additional CSS styling: #}
-    <style>
-        /*! custom.css */
-            {
-            % include "custom.css" %
-        }
-    </style>
-    {% endblock %}
-    {% endblock %}
-    {% endfilter %}
-    {% if math %}{% include "math.html.jinja2" %}{% endif %}
-    {% if mermaid %}{% include "mermaid.html.jinja2" %}{% endif %}
-</head>
-<!-- Google tag (gtag.js) -->
-<script async src="https://www.googletagmanager.com/gtag/js?id=G-G3M2G8V4M7"></script>
-<script>
-    window.dataLayer = window.dataLayer || [];
-    function gtag() { dataLayer.push(arguments); }
-    gtag('js', new Date());
-
-    gtag('config', 'G-G3M2G8V4M7');
-</script>
-<body>
-    {% block body %}
-    <nav class="pdoc">
-        <label id="navtoggle" for="togglestate" class="pdoc-button">{% include 'resources/navtoggle.svg' %}</label>
-        <input id="togglestate" type="checkbox" aria-hidden="true" tabindex="-1">
-        <div>{% block nav %}{% endblock %}</div>
-    </nav>
-    {% block content %}{% endblock %}
-    {% endblock body %}
-</body>
-
-</html>
\ No newline at end of file
diff --git a/templates/module.html.jinja2 b/templates/module.html.jinja2
deleted file mode 100644
index fbe9e10..0000000
--- a/templates/module.html.jinja2
+++ /dev/null
@@ -1,298 +0,0 @@
-{% extends "frame.html.jinja2" %}
-{% block title %}Jellyfish Python Server SDK{% endblock %}
-{% block nav %}
-    {% block module_list_link %}
-        {% set parentmodule = ".".join(module.modulename.split(".")[:-1]) %}
-        {% if parentmodule and parentmodule in all_modules %}
-            <a class="pdoc-button module-list-button" href="../{{ parentmodule.split(".")[-1] }}.html">
-                {% include "resources/box-arrow-in-left.svg" %}
-                &nbsp;
-                {{- parentmodule -}}
-            </a>
-        {% elif not root_module_name %}
-            <a class="pdoc-button module-list-button" href="{{ "../" * module.modulename.count(".") }}index.html">
-                {% include "resources/box-arrow-in-left.svg" %}
-                &nbsp;
-                Module Index
-            </a>
-        {% endif %}
-    {% endblock %}
-
-    {% block nav_title %}
-        {% if logo %}
-            {% if logo_link %}<a href="{{ logo_link }}">{% endif %}
-            <img src="{{ logo }}" class="logo" alt="project logo"/>
-            {% if logo_link %}</a>{% endif %}
-        {% endif %}
-    {% endblock %}
-
-    {% block search_box %}
-        {% if search and all_modules|length > 1 %}
-            {# we set a pattern here so that we can use the :valid CSS selector #}
-            <input type="search" placeholder="Search..." role="searchbox" aria-label="search"
-                   pattern=".+" required>
-        {% endif %}
-    {% endblock %}
-
-    {% block nav_index %}
-        {% set index = module.docstring | to_markdown | to_html | attr("toc_html") %}
-        {% if index %}
-            <h2>Contents</h2>
-            {{ index | safe }}
-        {% endif %}
-    {% endblock %}
-
-    {% block nav_submodules %}
-        {% if module.submodules %}
-            <h2>Submodules</h2>
-            <ul>
-                {% for submodule in module.submodules if is_public(submodule) | trim %}
-                    <li>{{ submodule.taken_from | link(text=submodule.name) }}</li>
-                {% endfor %}
-            </ul>
-        {% endif %}
-    {% endblock %}
-
-    {% block nav_members %}
-        {% if module.members %}
-            <h2>API Documentation</h2>
-            {{ nav_members(module.members.values()) }}
-        {% endif %}
-    {% endblock %}
-
-    {% block nav_footer %}
-        {% if footer_text %}
-            <footer>{{ footer_text }}</footer>
-        {% endif %}
-    {% endblock %}
-
-    {% block attribution %}
-        <a class="attribution" title="pdoc: Python API documentation generator" href="https://pdoc.dev" target="_blank">
-            built with <span class="visually-hidden">pdoc</span><img
-                alt="pdoc logo"
-                src="data:image/svg+xml,
-                        {%- filter urlencode %}{% include "resources/pdoc-logo.svg" %}{% endfilter %}"/>
-        </a>
-    {% endblock %}
-{% endblock nav %}
-{% block content %}
-    <main class="pdoc">
-        {% block module_info %}
-            <section class="module-info">
-                {% block edit_button %}
-                    {% if edit_url %}
-                        {% if "github.com" in edit_url %}
-                            {% set edit_text = "Edit on GitHub" %}
-                        {% elif "gitlab" in edit_url %}
-                            {% set edit_text = "Edit on GitLab" %}
-                        {% else %}
-                            {% set edit_text = "Edit Source" %}
-                        {% endif %}
-                        <a class="pdoc-button git-button" href="{{ edit_url }}">{{ edit_text }}</a>
-                    {% endif %}
-                {% endblock %}
-                {{ module_name() }}
-                {{ docstring(module) }}
-                {{ view_source_state(module) }}
-                {{ view_source_button(module) }}
-                {{ view_source_code(module) }}
-            </section>
-        {% endblock %}
-        {% block module_contents %}
-            {% for m in module.flattened_own_members if is_public(m) | trim %}
-                <section id="{{ m.qualname or m.name }}">
-                    {{ member(m) }}
-                    {% if m.kind == "class" %}
-                        {% for m in m.own_members if m.kind != "class" and is_public(m) | trim %}
-                            <div id="{{ m.qualname }}" class="classattr">
-                                {{ member(m) }}
-                            </div>
-                        {% endfor %}
-                        {% set inherited_members = inherited(m) | trim %}
-                        {% if inherited_members %}
-                            <div class="inherited">
-                                <h5>Inherited Members</h5>
-                                <dl>
-                                    {{ inherited_members }}
-                                </dl>
-                            </div>
-                        {% endif %}
-                    {% endif %}
-                </section>
-            {% endfor %}
-        {% endblock %}
-    </main>
-    {% if mtime %}
-        {% include "livereload.html.jinja2" %}
-    {% endif %}
-    {% block search_js %}
-        {% if search and all_modules|length > 1 %}
-            {% include "search.html.jinja2" %}
-        {% endif %}
-    {% endblock %}
-{% endblock content %}
-{#
-End of content, beginning of helper macros.
-See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an explanation of defaultmacro.
-#}
-{% defaultmacro bases(cls) %}
-    {%- if cls.bases -%}
-        <wbr>(
-        {%- for base in cls.bases -%}
-            <span class="base">{{ base[:2] | link(text=base[2]) }}</span>
-            {%- if loop.nextitem %}, {% endif %}
-        {%- endfor -%}
-        )
-    {%- endif -%}
-{% enddefaultmacro %}
-{% defaultmacro default_value(var) -%}
-    {%- if var.default_value_str %}
-        =
-        {% if var.default_value_str | length > 100 -%}
-            <input id="{{ var.qualname }}-view-value" class="view-value-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
-            <label class="view-value-button pdoc-button" for="{{ var.qualname }}-view-value"></label>
-        {%- endif -%}
-        <span class="default_value">{{ var.default_value_str | escape | linkify }}</span>
-    {%- endif -%}
-{% enddefaultmacro %}
-{% defaultmacro annotation(var) %}
-    {%- if var.annotation_str -%}
-        <span class="annotation">{{ var.annotation_str | escape | linkify }}</span>
-    {%- endif -%}
-{% enddefaultmacro %}
-{% defaultmacro decorators(doc) %}
-    {% for d in doc.decorators if not d.startswith("@_") %}
-        <div class="decorator">{{ d }}</div>
-    {% endfor %}
-{% enddefaultmacro %}
-{% defaultmacro function(fn) -%}
-    {{ decorators(fn) }}
-    {% if fn.name == "__init__" %}
-        <span class="name">{{ ".".join(fn.qualname.split(".")[:-1]) }}</span>
-        {{- fn.signature_without_self | format_signature(colon=False) | linkify }}
-    {% else %}
-        <span class="def">{{ fn.funcdef }}</span>
-        <span class="name">{{ fn.name }}</span>
-        {{- fn.signature | format_signature(colon=True) | linkify }}
-    {% endif %}
-{% enddefaultmacro %}
-{% defaultmacro variable(var) -%}
-    <span class="name">{{ var.name }}</span>{{ annotation(var) }}{{ default_value(var) }}
-{% enddefaultmacro %}
-{% defaultmacro submodule(mod) -%}
-    <span class="name">{{ mod.taken_from | link }}</span>
-{% enddefaultmacro %}
-{% defaultmacro class(cls) -%}
-    {{ decorators(cls) }}
-    <span class="def">class</span>
-    <span class="name">{{ cls.qualname }}</span>
-    {{- bases(cls) -}}:
-{% enddefaultmacro %}
-{% defaultmacro member(doc) %}
-    {{- view_source_state(doc) -}}
-    <div class="attr {{ doc.kind }}">
-        {% if doc.kind == "class" %}
-            {{ class(doc) }}
-        {% elif doc.kind == "function" %}
-            {{ function(doc) }}
-        {% elif doc.kind == "module" %}
-            {{ submodule(doc) }}
-        {% else %}
-            {{ variable(doc) }}
-        {% endif %}
-        {{ view_source_button(doc) }}
-    </div>
-    <a class="headerlink" href="#{{ doc.qualname or doc.name }}"></a>
-    {{ view_source_code(doc) }}
-    {{ docstring(doc) }}
-{% enddefaultmacro %}
-{% defaultmacro docstring(var) %}
-    {% if var.docstring %}
-        <div class="docstring">{{ var.docstring | to_markdown | to_html | linkify(namespace=var.qualname) }}</div>
-    {% endif %}
-{% enddefaultmacro %}
-{% defaultmacro nav_members(members) %}
-    <ul class="memberlist">
-        {% for m in members if is_public(m) | trim %}
-            <li>
-                {% if m.kind == "class" %}
-                    <a class="class" href="#{{ m.qualname }}">{{ m.qualname }}</a>
-                    {% if m.own_members %}
-                        {{ nav_members(m.own_members) | indent(12) }}
-                    {% endif %}
-                {% elif m.kind == "module" %}
-                    <a class="module" href="#{{ m.name }}">{{ m.name }}</a>
-                {% elif m.name == "__init__" %}
-                    <a class="function" href="#{{ m.qualname }}">{{ m.qualname.split(".")[-2] }}</a>
-                {% else %}
-                    <a class="{{ m.kind }}" href="#{{ m.qualname }}">{{ m.name }}</a>
-                {% endif %}
-            </li>
-        {% endfor %}
-    </ul>
-{% enddefaultmacro %}
-{% defaultmacro is_public(doc) %}
-    {#
-    This macro is a bit unconventional in that its output is not rendered, but treated as a boolean:
-    Returning no text is interpreted as false, returning any other text is iterpreted as true.
-    Implementing this as a macro makes it very easy to override with a custom template, see
-    https://github.com/mitmproxy/pdoc/tree/main/examples/custom-template.
-    #}
-    {% if not include_undocumented and not doc.docstring %}
-        {# hide members that are undocumented if include_undocumented has been toggled off. #}
-    {% elif doc.docstring and "@private" in doc.docstring %}
-        {# hide members explicitly marked as @private #}
-    {% elif doc.name == "__init__" and (doc.docstring or (doc.kind == "function" and doc.signature_without_self.parameters)) %}
-        {# show constructors that have a docstring or at least one extra argument #}
-        true
-    {% elif doc.name == "__doc__" %}
-        {# We don't want to document __doc__ itself, https://github.com/mitmproxy/pdoc/issues/235 #}
-    {% elif doc.kind == "variable" and doc.is_typevar and not doc.docstring %}
-        {# do not document TypeVars, that only clutters the docs. #}
-    {% elif doc.kind == "module" and doc.fullname not in all_modules %}
-        {# Skip modules that were manually excluded, https://github.com/mitmproxy/pdoc/issues/334 #}
-    {% elif (doc.qualname or doc.name) is in(module.obj.__all__ or []) %}
-        {# members starting with an underscore are still public if mentioned in __all__ #}
-        true
-    {% elif not doc.name.startswith("_") %}
-        {# members not starting with an underscore are considered public by default #}
-        true
-    {% endif %}
-{% enddefaultmacro %}
-{# fmt: off #}
-{% defaultmacro inherited(cls) %}
-    {% for base, members in cls.inherited_members.items() %}
-        {% set m = None %}{# workaround for https://github.com/pallets/jinja/issues/1427 #}
-        {% set member_html %}
-            {% for m in members if is_public(m) | trim %}
-                <dd id="{{ m.qualname }}" class="{{ m.kind }}">
-                    {{- m.taken_from | link(text=m.name.replace("__init__",base[1])) -}}
-                </dd>
-            {% endfor %}
-        {% endset %}
-        {# we may not have any public members, in which case we don't want to print anything. #}
-        {% if member_html %}
-            <div><dt>{{ base | link }}</dt>
-                {{ member_html }}
-            </div>
-        {% endif %}
-    {% endfor %}
-{% enddefaultmacro %}
-{# fmt: on #}
-{% defaultmacro view_source_state(doc) %}
-    {% if show_source and doc.source %}
-        <input id="{{ doc.qualname or "mod-" + doc.name }}-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
-    {% endif %}
-{% enddefaultmacro %}
-{% defaultmacro view_source_button(doc) %}
-    {% if show_source and doc.source %}
-        <label class="view-source-button" for="{{ doc.qualname or "mod-" + doc.name }}-view-source"><span>View Source</span></label>
-    {% endif %}
-{% enddefaultmacro %}
-{% defaultmacro view_source_code(doc) %}
-    {% if show_source and doc.source %}
-        {{ doc | highlight }}
-    {% endif %}
-{% enddefaultmacro %}
-{% defaultmacro module_name() %}
-{% enddefaultmacro %}
