diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..0c26029f6
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,43 @@
+name: Build documentation
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+# Cancel ongoing builds on new changes
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Run tasks
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+ with:
+ python-version: "3.14"
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
+ with:
+ enable-cache: true
+
+ - name: Install dependencies
+ run: uv sync --frozen --group docs
+
+ - name: Build documentation
+ run: uv run poe build-docs
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ path: doc/_build
+ include-hidden-files: true
diff --git a/.gitignore b/.gitignore
index 7e40b86ad..36026debd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ htmlcov/
.dir-locals.el
.venv/
junit.xml
+doc/_build/
diff --git a/doc/_static/css/custom.css b/doc/_static/css/custom.css
new file mode 100644
index 000000000..2253c4f22
--- /dev/null
+++ b/doc/_static/css/custom.css
@@ -0,0 +1,1117 @@
+/**
+ * Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community
+ * Copyright (c) 2021, Teslabs Engineering S.L.
+ * Copyright (c) 2023-2025, The Linux Foundation.
+ * SPDX-License-Identifier: CC-BY-3.0
+ *
+ * Various tweaks to the Read the Docs theme to better conform with Zephyr's
+ * visual identity. Many colors are also overridden to use CSS variables.
+ */
+
+ :root {
+ /* Use system font stacks for better performance (no Web fonts required) */
+ --system-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --header-font-family: Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
+ --monospace-font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', 'Droid Sans Mono', 'Source Code Pro', monospace;
+ }
+
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+input[type="text"],
+input[type="button"],
+input[type="reset"],
+input[type="submit"],
+textarea,
+legend,
+.btn,
+.rst-content .toctree-wrapper p.caption,
+.rst-versions {
+ font-family: var(--system-font-family);
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+legend,
+.rst-content .toctree-wrapper p.caption {
+ /* Use a lighter font for headers (Semi-Bold instead of Bold) */
+ font-weight: 600;
+ font-family: var(--header-font-family);
+}
+
+.rst-content div.figure p.caption {
+ /* Tweak caption styling to be closer to typical captions */
+ text-align: center;
+ margin-top: 8px;
+ opacity: 0.75;
+}
+
+.rst-content div.figure.figure-w480 {
+ max-width: 480px;
+}
+
+p,
+article ul,
+article ol,
+.wy-plain-list-disc,
+.wy-plain-list-decimal,
+.rst-content ol.arabic,
+.rst-content .section ul,
+.rst-content .toctree-wrapper ul,
+.rst-content .section ol {
+ /* Increase the line height slightly to account for the different font */
+ line-height: 25px;
+}
+
+body,
+.rst-content table.docutils thead {
+ color: var(--body-color);
+}
+
+a {
+ color: var(--link-color);
+}
+
+a:hover {
+ color: var(--link-color-hover);
+ text-decoration: underline;
+}
+
+a:active {
+ /* Add visual feedback when clicking on a link */
+ color: var(--link-color-active);
+}
+
+a:visited {
+ color: var(--link-color-visited);
+}
+
+a.btn:hover {
+ text-decoration: none;
+}
+
+.sphinx-tabs .sphinx-menu a.item {
+ /* Original definition has `!important` */
+ color: var(--link-color) !important;
+}
+
+.rst-content .toc-backref {
+ color: var(--link-color);
+}
+
+/* Style external links differently to make them easier to distinguish from internal links. */
+.reference.external {
+ background-position: center right;
+ background-repeat: no-repeat;
+ background-image: var(--external-reference-icon);
+ padding-right: 13px;
+}
+
+hr,
+#search-results .search li:first-child,
+#search-results .search li {
+ border-color: var(--hr-color);
+}
+
+/* JavaScript documentation directives */
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list) > dt {
+ background-color: var(--admonition-note-background-color);
+ border-color: var(--admonition-note-title-background-color);
+ color: var(--admonition-note-color);
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl dt {
+ background-color: transparent;
+ border-color: transparent;
+ color: var(--footer-color);
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).class dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).function dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).method dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).attribute dt {
+ font-weight: 600;
+ padding: 0 8px;
+ margin-bottom: 1px;
+ width: 100%;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).class > dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).function > dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).method > dt,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).attribute > dt {
+ font-family: var(--monospace-font-family);
+ font-variant-ligatures: none;
+ font-size: 90%;
+ font-weight: normal;
+ margin-bottom: 16px;
+ padding: 6px 8px;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-prename.descclassname {
+ color: var(--highlight-type2-color);
+ font-weight: normal;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name.descname {
+ color: var(--highlight-function-color);
+ font-weight: 700;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-paren,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional {
+ color: var(--highlight-operator-color) !important;
+ font-weight: normal;
+ padding: 0 2px;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional {
+ font-style: italic;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-param,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).class dt > em,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).function dt > em,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).method dt > em {
+ color: var(--code-literal-color);
+ font-style: normal;
+ padding: 0 4px;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k {
+ font-style: normal;
+}
+html.writer-html5 .rst-content dl:not(.docutils) > dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt {
+ border-top-color: var(--highlight-background-emph-color);
+ background: var(--highlight-background-color);
+}
+html.writer-html5 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt {
+ border-left-color: var(--highlight-background-emph-color);
+ background: var(--highlight-background-color);
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-param,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).class dt > .optional ~ em,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).function dt > .optional ~ em,
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).method dt > .optional ~ em {
+ color: var(--highlight-number-color);
+ font-style: italic;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple).class dt > em.property {
+ color: var(--highlight-keyword-color);
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dt a.headerlink {
+ color: var(--link-color) !important;
+}
+html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dt a.headerlink:visited {
+ color: var(--link-color-visited);
+}
+html.writer-html5 .rst-content dl.field-list > dd strong {
+ font-family: var(--monospace-font-family);
+ font-variant-ligatures: none;
+}
+
+footer,
+#search-results .context {
+ color: var(--footer-color);
+}
+
+/* Icon tweaks */
+a.icon-home,
+a.icon-home:visited {
+ color: var(--navbar-level-1-color);
+}
+
+/* Main sections */
+
+.wy-nav-content-wrap {
+ background-color: var(--content-wrap-background-color);
+}
+
+.wy-nav-content {
+ background-color: var(--content-background-color);
+ min-height: 100vh;
+ min-height: 100dvh;
+ display: flex;
+}
+
+.wy-body-for-nav {
+ background-color: var(--content-wrap-background-color);
+}
+
+@media only screen and (min-width: 769px) {
+ .wy-nav-content {
+ max-width: 915px;
+ }
+}
+
+/* Table display tweaks */
+
+.rst-content table.docutils,
+.wy-table-bordered-all td,
+.rst-content table.docutils td,
+.wy-table thead th,
+.rst-content table.docutils thead th,
+.rst-content table.field-list thead th {
+ border-color: var(--code-border-color);
+}
+
+.rst-content table.docutils caption, .rst-content table.field-list caption, .wy-table caption {
+ color: var(--body-color);
+}
+.wy-table-odd td,
+.wy-table-striped tr:nth-child(2n-1) td,
+.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
+ background-color: var(--table-row-odd-background-color);
+}
+
+/* Override table no-wrap */
+/* The first column cells are not verbose, no need to wrap them */
+.wy-table-responsive table td:not(:nth-child(1)),
+.wy-table-responsive table th:not(:nth-child(1)) {
+ white-space: normal;
+}
+
+/* Allow to control wrapping behavior per table */
+.wy-table-responsive table.wrap-normal td,
+.wy-table-responsive table.wrap-normal th {
+ white-space: normal;
+}
+
+/* Make sure not to wrap keyboard shortcuts */
+.wy-table-responsive table td kbd {
+ white-space: nowrap;
+}
+
+/* Force table content font-size in responsive tables to be 100%
+ * fixing larger font size for paragraphs in the kconfig tables */
+ .wy-table-responsive td p {
+ font-size: 100%;
+}
+
+/* Code display tweaks */
+
+code,
+.rst-content tt,
+.rst-content code {
+ font-size: 14px;
+ background-color: var(--code-background-color);
+ border: 1px solid var(--code-border-color);
+}
+
+.rst-content tt.literal,
+.rst-content code.literal {
+ color: var(--code-literal-color);
+}
+
+.rst-content div[class^="highlight"] {
+ border-color: none;
+ border: none;
+}
+
+.rst-content pre.literal-block,
+.rst-content div[class^="highlight"] pre,
+.rst-content .linenodiv pre {
+ /* Increase the font size and line height in code blocks */
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.rst-content pre.literal-block {
+ border: none;
+ border-radius: 0.25rem;
+ background-color: var(--code-background-color);
+}
+
+/* Code tab display tweaks */
+
+.ui.tabular.menu .active.item,
+.ui.segment,
+.sphinx-tabs-panel {
+ background-color: var(--code-background-color) !important;
+}
+
+.sphinx-tabs-tab {
+ color: var(--link-color) !important;
+}
+
+.sphinx-tabs-tab[aria-selected="true"] {
+ background-color: var(--code-background-color) !important;
+ border-bottom: 1px solid var(--code-background-color) !important;
+}
+
+/* Code literals */
+a.internal code.literal {
+ color: var(--link-color);
+}
+
+a.internal:visited code.literal {
+ color: var(--link-color-visited);
+}
+
+/* Syntax highlighting */
+
+.tab div[class^='highlight']:last-child {
+ margin-bottom: 1em;
+}
+
+.rst-content tt.literal, .rst-content code.literal, .highlight {
+ border-radius: 0.25rem;
+}
+
+.highlight {
+ background-color: var(--highlight-background-color);
+}
+
+/* Emphasized lines */
+.highlight .hll {
+ background-color: var(--highlight-background-emph-color);
+}
+
+.highlight .gh /* Generic.Heading */,
+.highlight .gu /* Generic.Subheading */,
+.highlight .go /* Generic.Output */,
+.highlight .gt /* Generic.Traceback */ {
+ color: var(--highlight-default-color);
+}
+
+.highlight .c /* Comment */,
+.highlight .c1 /* Comment.Single */,
+.highlight .cm /* Comment.Multiline */,
+.highlight .cs /* Comment.Special */ {
+ color: var(--highlight-comment-color);
+}
+
+.highlight .bp /* Name.Builtin.Pseudo */,
+.highlight .k /* Keyword */,
+.highlight .kc /* Keyword.Constant */,
+.highlight .kd /* Keyword.Declaration */,
+.highlight .kn /* Keyword.Namespace */,
+.highlight .kp /* Keyword.Pseudo */,
+.highlight .kr /* Keyword.Reserved */,
+.highlight .kt /* Keyword.Type */,
+.highlight .ow /* Operator.Word */ {
+ color: var(--highlight-keyword-color);
+}
+
+.highlight .ch /* Comment.Hashbang */,
+.highlight .cp /* Comment.Preproc */ {
+ color: var(--highlight-keyword2-color);
+}
+
+.highlight .m /* Literal.Number */,
+.highlight .mf /* Literal.Number.Float */,
+.highlight .mi /* Literal.Number.Integer */,
+.highlight .il /* Literal.Number.Integer.Long */,
+.highlight .mb /* Literal.Number.Bin */,
+.highlight .mh /* Literal.Number.Hex */,
+.highlight .mo /* Literal.Number.Oct */ {
+ color: var(--highlight-number-color);
+}
+
+.highlight .na /* Name.Attribute */,
+.highlight .nd /* Name.Decorator */,
+.highlight .ni /* Name.Entity */,
+.highlight .nl /* Name.Label */ {
+ color: var(--highlight-decorator-color);
+}
+
+.highlight .nb /* Name.Builtin */,
+.highlight .ne /* Name.Exception */ {
+ color: var(--highlight-type-color);
+}
+
+.highlight .nc /* Name.Class */,
+.highlight .nn /* Name.Namespace */,
+.highlight .no /* Name.Constant */,
+.highlight .nv /* Name.Variable */,
+.highlight .vc /* Name.Variable.Class */,
+.highlight .vg /* Name.Variable.Global */,
+.highlight .vi /* Name.Variable.Instance */,
+.highlight .vm /* Name.Variable.Magic */ {
+ color: var(--highlight-type2-color);
+}
+
+.highlight .nf /* Name.Function */,
+.highlight .fm /* Name.Function.Magic */,
+.highlight .nt /* Name.Tag */ {
+ color: var(--highlight-function-color);
+}
+
+.highlight .o /* Operator */,
+.highlight .si /* Literal.String.Interpol */,
+.highlight .sx /* Literal.String.Other */,
+.highlight .sr /* Literal.String.Regex */,
+.highlight .ss /* Literal.String.Symbol */ {
+ color: var(--highlight-operator-color);
+}
+
+.highlight .cpf/* Comment.PreprocFile */,
+.highlight .s /* Literal.String */,
+.highlight .s1 /* Literal.String.Single */,
+.highlight .s2 /* Literal.String.Double */,
+.highlight .sc /* Literal.String.Char */,
+.highlight .se /* Literal.String.Escape */,
+.highlight .sa /* Literal.String.Affix */,
+.highlight .sb /* Literal.String.Backtick */,
+.highlight .dl /* Literal.String.Delimiter */,
+.highlight .sd /* Literal.String.Doc */,
+.highlight .sh /* Literal.String.Heredoc */ {
+ color: var(--highlight-string-color);
+}
+
+/* Admonition tweaks */
+
+.rst-content .admonition,
+.rst-content .admonition.note,
+.rst-content .admonition.seealso {
+ background-color: var(--admonition-note-background-color);
+ color: var(--admonition-note-color);
+ overflow: auto;
+}
+
+.rst-content .admonition .admonition-title,
+.rst-content .admonition.note .admonition-title,
+.rst-content .admonition.seealso .admonition-title {
+ background-color: var(--admonition-note-title-background-color);
+ color: var(--admonition-note-title-color);
+}
+
+.rst-content .admonition.attention,
+.rst-content .admonition.caution,
+.rst-content .admonition.warning {
+ background-color: var(--admonition-attention-background-color);
+ color: var(--admonition-attention-color);
+ overflow: auto;
+}
+
+.rst-content .admonition.attention .admonition-title,
+.rst-content .admonition.caution .admonition-title,
+.rst-content .admonition.warning .admonition-title {
+ background-color: var(--admonition-attention-title-background-color);
+ color: var(--admonition-attention-title-color);
+}
+
+.rst-content .admonition.danger {
+ background-color: var(--admonition-danger-background-color);
+ color: var(--admonition-danger-color);
+ overflow: auto;
+}
+
+.rst-content .admonition.danger .admonition-title {
+ background-color: var(--admonition-danger-title-background-color);
+ color: var(--admonition-danger-title-color);
+}
+
+.rst-content .admonition.tip,
+.rst-content .admonition.important {
+ background-color: var(--admonition-tip-background-color);
+ color: var(--admonition-tip-color);
+ overflow: auto;
+}
+
+.rst-content .admonition.tip .admonition-title,
+.rst-content .admonition.important .admonition-title {
+ background-color: var(--admonition-tip-title-background-color);
+ color: var(--admonition-tip-title-color);
+}
+
+/* Admonition tweaks - sphinx_togglebutton */
+
+.rst-content .admonition.toggle {
+ overflow: visible;
+}
+
+.rst-content .admonition.toggle button {
+ display: inline-flex;
+ color: var(--admonition-note-title-color);
+}
+
+.rst-content .admonition.toggle .tb-icon {
+ height: 1em;
+ width: 1em;
+}
+
+/* Keyboard shortcuts tweaks */
+kbd, .kbd,
+.rst-content :not(dl.option-list) > :not(dt):not(kbd):not(.kbd) > kbd,
+.rst-content :not(dl.option-list) > :not(dt):not(kbd):not(.kbd) > .kbd {
+ background-color: var(--kbd-background-color);
+ border: 1px solid var(--kbd-outline-color);
+ border-radius: 3px;
+ box-shadow: inset 0 -1px 0 var(--kbd-shadow-color);
+ color: var(--kbd-text-color);
+ display: inline-block;
+ font-size: 12px;
+ line-height: 11px;
+ padding: 4px 5px;
+ vertical-align: middle;
+}
+
+/* guilabel and menuselection tweaks */
+.rst-content .guilabel,
+.rst-content .menuselection {
+ color: var(--body-color);
+ background-color: var(--guiitems-background-color);
+ border-color: var(--guiitems-border-color);
+}
+
+/* heading tweaks to make document hierarchy easier to grasp */
+
+.rst-content section > h1 {
+ font-weight: 700;
+ margin-bottom: 2.5rem;
+ position: relative;
+ line-height: 1;
+ z-index: 1;
+}
+
+.rst-content section > h1::before {
+ content: '';
+ position: absolute;
+ z-index:-1;
+ left: 0;
+ right: 0;
+ height: 4px;
+ bottom: -1px;
+ background: linear-gradient(to right, var(--admonition-note-title-background-color), var(--admonition-note-title-background-color) 50%, var(--admonition-note-background-color) 80%, transparent); /* Example gradient */
+ opacity:50%;
+}
+
+.rst-content section > h2,
+.rst-content section > h3,
+.rst-content section > h4,
+.rst-content section > h5 {
+ font-weight: 500;
+ padding-inline-start: 8px;
+ margin-inline-start: 0px;
+ border-inline-start: 8px solid;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+}
+
+.rst-content section > h2 {
+ border-color: var(--admonition-note-title-background-color);
+}
+
+.rst-content section > h3 {
+ border-color: var(--admonition-note-background-color);
+}
+
+.rst-content section > h4 {
+ border-color: transparent;
+ font-weight: 400;
+}
+
+.rst-content section > h5 {
+ border-color: transparent;
+ font-weight: 100;
+}
+
+/* Buttons */
+
+.btn-neutral {
+ background-color: var(--btn-neutral-background-color) !important;
+ color: var(--body-color) !important;
+}
+
+.btn-neutral:hover {
+ background-color: var(--btn-neutral-hover-background-color) !important;
+}
+
+.btn-neutral:visited {
+ color: var(--body-color) !important;
+}
+
+/* Navigation bar logo and search */
+
+.logo {
+ opacity: var(--logo-opacity);
+}
+
+.wy-side-nav-search > a img.logo {
+ /* Fixed size to prevent reflows and support hiDPI displays */
+ height: 105px;
+}
+
+.wy-side-nav-search {
+ background-color: var(--navbar-background-color);
+}
+
+.wy-side-nav-search.fixed {
+ position: fixed;
+}
+
+@media only screen and (min-width: 769px) {
+ /* Simulate a drop shadow that only affects the bottom edge */
+ /* This is used to indicate the search bar is fixed */
+ .wy-side-nav-search.fixed-and-scrolled::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: -8px;
+ width: 300px;
+ height: 8px;
+ pointer-events: none;
+ background: linear-gradient(hsla(0, 0%, 0%, 0.2), transparent);
+ }
+}
+
+.wy-side-nav-search > a:hover,
+.wy-side-nav-search .wy-dropdown > a:hover {
+ background-color: var(--navbar-background-color-hover);
+}
+
+.wy-side-nav-search > a:active,
+.wy-side-nav-search .wy-dropdown > a:active {
+ background-color: var(--navbar-background-color-active);
+}
+
+.wy-side-nav-search input[type=search] {
+ width: 100%;
+ border-radius: 50px;
+ padding: 6px 12px;
+ background-color: var(--input-background-color);
+ color: var(--body-color);
+ /* Avoid reflowing when toggling the focus state */
+ border: 2px solid transparent;
+ box-shadow: none;
+ /* Make visual feedback instant */
+ transition: none;
+ font-size: 14px;
+}
+
+.wy-side-nav-search input[type="search"]:focus {
+ border: 2px solid var(--input-focus-border-color);
+}
+
+.wy-side-nav-search input[type="search"]::placeholder {
+ color: var(--body-color);
+ opacity: 0.55;
+}
+
+/* Navigation bar */
+
+.wy-nav-side {
+ background-color: var(--navbar-background-color);
+}
+
+.wy-menu-vertical header,
+.wy-menu-vertical p.caption {
+ color: var(--navbar-heading-color);
+
+ /* Improves the appearance of uppercase text */
+ letter-spacing: 0.75px;
+}
+
+/* Mobile navigation */
+
+.wy-nav-top,
+.wy-nav-top a {
+ background-color: var(--navbar-background-color);
+ color: var(--navbar-level-1-color);
+}
+
+/* Version branch label below the logo */
+.wy-side-nav-search > div.version {
+ color: var(--navbar-level-3-color);
+ opacity: 0.9;
+}
+
+/* First level of navigation items */
+
+.wy-menu-vertical a {
+ color: var(--navbar-level-1-color);
+}
+
+.wy-menu-vertical a:hover {
+ background-color: var(--navbar-background-color-hover);
+ color: var(--navbar-level-1-color);
+}
+
+.wy-menu-vertical a:active {
+ background-color: var(--navbar-background-color-active);
+}
+
+.wy-menu-vertical li.toctree-l1.current > a {
+ border: none;
+}
+
+.wy-menu-vertical a button.toctree-expand,
+.wy-menu-vertical li.toctree-l2 a button.toctree-expand {
+ color: var(--navbar-level-3-color);
+ opacity: 0.9;
+ margin-right: 6px;
+}
+
+.wy-menu-vertical a:hover button.toctree-expand,
+.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand {
+ color: var(--navbar-level-2-color);
+ opacity: 1;
+}
+
+.wy-menu-vertical a:active button.toctree-expand,
+.wy-menu-vertical li.toctree-l2 a:active button.toctree-expand {
+ color: var(--navbar-level-1-color);
+ opacity: 1;
+}
+
+/* Second (and higher) levels of navigation items */
+
+.wy-menu-vertical li.current a {
+ /* Make long words always display on a single line, keep wrapping for multiple words */
+ /* This fixes the class reference titles' display with very long class names */
+ display: flex;
+}
+
+.wy-menu-vertical li.current a,
+.wy-menu-vertical li.toctree-l2.current > a,
+.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a,
+.wy-menu-vertical li.toctree-l2.current li.toctree-l4 > a {
+ background-color: var(--navbar-current-background-color);
+ color: var(--navbar-level-2-color);
+ border-color: var(--navbar-current-background-color);
+}
+
+.wy-menu-vertical li.current a:hover,
+.wy-menu-vertical li.toctree-l2.current > a:hover,
+.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a:hover,
+.wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a:hover {
+ background-color: var(--navbar-current-background-color-hover);
+}
+
+.wy-menu-vertical li.current a:active,
+.wy-menu-vertical li.toctree-l2.current > a:active,
+.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a:active,
+.wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a:active {
+ background-color: var(--navbar-current-background-color-active);
+}
+
+.wy-menu-vertical a {
+ /* This overrides 8px margin added in other multi-selector rules */
+ margin-right: 0;
+}
+
+/* Banner panel in sidebar */
+.wy-nav-side .ethical-rtd.fixed {
+ position: fixed;
+}
+
+/* Version selector (only visible on Read the Docs) */
+
+.rst-versions {
+ background-color: var(--navbar-current-background-color);
+}
+
+.rst-versions a,
+.rst-versions .rst-current-version,
+.rst-versions .rst-current-version .fa,
+.rst-versions .rst-other-versions dd a {
+ color: var(--navbar-level-1-color);
+}
+
+.rst-versions .rst-other-versions small {
+ color: var(--navbar-level-3-color);
+}
+
+.rst-versions .rst-other-versions dd a:hover {
+ text-decoration: underline;
+}
+
+.rst-versions .rst-other-versions {
+ color: var(--navbar-heading-color);
+}
+
+.rst-versions .rst-current-version {
+ background-color: var(--navbar-current-background-color);
+}
+
+.rst-versions .rst-current-version:hover {
+ background-color: var(--navbar-current-background-color-hover);
+}
+
+.rst-versions .rst-current-version:active {
+ background-color: var(--navbar-current-background-color-active);
+}
+
+.rst-versions.shift-up {
+ overflow-y: auto;
+}
+
+/* Hide the obnoxious automatic highlight in search results */
+.rst-content .highlighted {
+ background-color: transparent;
+ font-weight: inherit;
+ padding: 0;
+}
+
+/* Allows the scrollbar to be shown in the sidebar */
+@media only screen and (min-width: 769px) {
+ .wy-side-scroll {
+ overflow: hidden;
+ }
+
+ .wy-nav-side .wy-side-scroll .ethical-rtd {
+ width: calc(300px - 1.25em);
+ padding: 0 0 0 1em;
+ }
+}
+.wy-menu.wy-menu-vertical {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: calc(100% - 243px);
+}
+@media screen and (max-width: 768px) {
+ .wy-nav-side {
+ padding-bottom: 44px;
+ }
+ .wy-menu.wy-menu-vertical {
+ overflow-y: initial;
+ max-height: initial;
+ }
+ .wy-nav-content {
+ min-height: calc(100vh - 64px);
+ min-height: calc(100dvh - 64px);
+ }
+}
+
+/* Scrollbar styling */
+.wy-menu.wy-menu-vertical {
+ scrollbar-color: var(--navbar-scrollbar-color) var(--navbar-scrollbar-background);
+}
+.wy-menu.wy-menu-vertical::-webkit-scrollbar {
+ width: .75rem;
+}
+.wy-menu.wy-menu-vertical::-webkit-scrollbar-track {
+ background-color: var(--navbar-scrollbar-background);
+}
+.wy-menu.wy-menu-vertical::-webkit-scrollbar-thumb {
+ background-color: var(--navbar-scrollbar-color);
+}
+/* Firefox does the dimming on hover automatically. We emulate it for Webkit-based browsers. */
+.wy-menu.wy-menu-vertical::-webkit-scrollbar-thumb:hover {
+ background-color: var(--navbar-scrollbar-hover-color);
+}
+.wy-menu.wy-menu-vertical::-webkit-scrollbar-thumb:active {
+ background-color: var(--navbar-scrollbar-active-color);
+}
+
+/* Misc tweaks */
+
+.rst-columns {
+ column-width: 18em;
+}
+
+.rst-content div.figure, .rst-content figure {
+ text-align: center;
+}
+
+.wy-alert.wy-alert-danger {
+ background-color: var(--admonition-danger-background-color);
+}
+
+
+dark-mode-toggle::part(fieldset) {
+ padding-inline: 0.75rem;
+ padding-block: 0;
+}
+
+dark-mode-toggle::part(darkLabel),
+dark-mode-toggle::part(lightLabel),
+dark-mode-toggle::part(toggleLabel){
+ font-size: unset;
+}
+
+div.graphviz > object {
+ filter: var(--graphviz-filter);
+}
+
+/* Home page grid display */
+.grid {
+ list-style-type: none !important;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ margin: 1rem auto;
+ max-width: calc((160px + 2rem) * 4);
+}
+
+.grid-item {
+ list-style-type: none !important;
+ -webkit-box-flex: 0;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ width: 150px;
+ text-align: center;
+ margin: 1rem;
+}
+
+.grid-item a {
+ display: block;
+ width: 150px;
+ height: 150px;
+ padding: 20px;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ border-radius: 1rem;
+ background: linear-gradient(135deg, #0070c5 0%, #5c13a5 100%);
+ color: white;
+}
+
+.grid-item h2 {
+ font-size: 1rem;
+}
+
+.grid-item img {
+ margin-bottom: 1rem;
+ max-width: 75%;
+}
+
+.grid-item a:hover {
+ text-decoration: none;
+}
+
+.grid-item p {
+ font-size: 0.9rem;
+ margin-top: 0.5rem;
+ color: var(--body-color);
+ font-weight: 200;
+ margin-left: -0.9em;
+ margin-right: -0.9em;
+ line-height: 1.4rem;
+}
+
+.grid-icon {
+ line-height: 1.5;
+ font-size: 3rem;
+ color: white;
+}
+
+.lastupdated {
+ font-weight: 200;
+ font-size: 0.9rem;
+}
+
+/* Make actual document take all vertical space available so that footer is always at the bottom */
+
+.rst-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.document {
+ flex-grow: 1;
+}
+
+/* Custom search box, including search engine selection */
+
+.search-container {
+ position: relative;
+}
+
+#search-se-settings-icon {
+ position: absolute;
+ color: var(--body-color);
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ cursor: pointer;
+ opacity: 0.8;
+}
+
+#search-se-menu {
+ display: none;
+ position: absolute;
+ font-size: 11px;
+ background-color: var(--input-background-color);
+ color: var(--body-color);
+ right: 0px;
+ top: 36px;
+ border: solid 1px var(--body-color);
+ border-radius: 10px;
+ z-index: 1000;
+}
+
+#search-se-menu ul {
+ list-style: none;
+ margin: 0;
+ padding: 2px;
+}
+
+#search-se-menu ul li {
+ padding: 8px 12px;
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1em;
+}
+
+#search-se-menu [role="menuitemradio"]:focus {
+ background-color: var(--navbar-current-background-color-hover);
+ color: var(--navbar-level-1-color);
+ border-radius: 10px;
+}
+
+#search-se-menu ul li .fa-check {
+ display: none;
+ }
+
+ #search-se-menu ul li.selected .fa-check {
+ display: inline;
+ }
+
+.doxygroup::after {
+ content: 'Doxygen';
+ display: inline-block;
+ background-color: var(--admonition-note-title-background-color);
+ color: var(--admonition-note-title-color);
+ padding: 2px 8px;
+ border-radius: 12px;
+ margin-left: 8px;
+ font-size: 0.875em;
+ font-weight: bold;
+}
+
+.code-sample-list li {
+ margin-bottom: 0.25em;
+}
+.code-sample-name {
+ font-weight: bold;
+ padding-right: 0.5em;
+}
+
+.code-sample-description {
+ font-weight: 300;
+}
+
+.code-sample-description::before {
+ content: '\F0A9'; /* arrow-circle-right */
+ font-family: 'FontAwesome';
+ padding-right: 0.5em;
+}
+
+li>a.code-sample-link.reference.internal {
+ font-weight: 100;
+}
+
+li>a.code-sample-link.reference.internal.current {
+ text-decoration: underline;
+}
diff --git a/doc/_static/css/dark.css b/doc/_static/css/dark.css
new file mode 100644
index 000000000..bb1c20c0b
--- /dev/null
+++ b/doc/_static/css/dark.css
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community
+ * Copyright (c) 2021, Teslabs Engineering S.L.
+ * SPDX-License-Identifier: CC-BY-3.0
+ *
+ * Dark theme colors
+ */
+
+:root {
+ --body-color: rgba(255, 255, 255, 0.85);
+ --content-wrap-background-color: #202326;
+ --content-background-color: #2e3236;
+ /* Decrease the logo opacity when using the dark theme to be less distracting */
+ --logo-opacity: 0.85;
+ --navbar-background-color: #25282b;
+ --navbar-background-color-hover: #333639;
+ --navbar-background-color-active: #111417;
+ --navbar-current-background-color: #333639;
+ --navbar-current-background-color-hover: #44474a;
+ --navbar-current-background-color-active: #222528;
+ --navbar-level-1-color: #ddd;
+ --navbar-level-2-color: #ccc;
+ --navbar-level-3-color: #bbb;
+ --navbar-heading-color: #af7fe4;
+ --navbar-scrollbar-color: #af7fe4;
+ --navbar-scrollbar-hover-color: #9454db;
+ --navbar-scrollbar-active-color: #7929d2;
+ --navbar-scrollbar-background: #1c1e21;
+
+ --link-color: #8cf;
+ --link-color-hover: #9df;
+ --link-color-active: #6ad;
+ --link-color-visited: #cb99f6;
+ --external-reference-icon: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOGNmIj48cGF0aCBkPSJtNy41IDcuMXYzLjRoLTZ2LTZoMy40Ii8+PHBhdGggZD0ibTUuNzY1IDFoNS4yMzV2NS4zOWwtMS41NzMgMS41NDctMS4zMS0xLjMxLTIuNzI0IDIuNzIzLTIuNjktMi42ODggMi44MS0yLjgwOC0xLjMxMy0xLjMxeiIvPjwvZz48L3N2Zz4K");
+ --classref-badge-text-color: hsl(0, 0%, 70%);
+
+ --hr-color: #555;
+ --table-row-odd-background-color: #3b3e41;
+ --code-background-color: #434649;
+ --code-border-color: #505356;
+ --code-literal-color: #faa;
+ --input-background-color: #333537;
+ --input-focus-border-color: #5f8cff;
+
+ --search-input-background-color: #43464a; /* derived from --input-background-color */
+ --search-match-color: #52b4ff; /* derived from --link-color */
+ --search-match-background-color: #414c56; /* derived from --link-color */
+ --search-active-color: #202326;
+ --search-credits-background-color: #202123; /* derived from --navbar-background-color */
+ --search-credits-color: #6b6b6b; /* derived from --footer-color */
+ --search-credits-link-color: #628fb1; /* derived from --link-color */
+
+ /* Colors taken from the Godot script editor with the Adaptive theme */
+ --highlight-background-color: #202531;
+ --highlight-background-emph-color: #2d3444;
+ --highlight-default-color: rgba(255, 255, 255, 0.85);
+ --highlight-comment-color: rgba(204, 206, 211, 0.5);
+ --highlight-keyword-color: #ff7085;
+ --highlight-keyword2-color: #42ffc2;
+ --highlight-number-color: #a1ffe0;
+ --highlight-decorator-color: #abc8ff;
+ --highlight-type-color: #8effda;
+ --highlight-type2-color: #c6ffed;
+ --highlight-function-color: #57b3ff;
+ --highlight-operator-color: #abc8ff;
+ --highlight-string-color: #ffeca1;
+
+ --admonition-note-background-color: #303d4f;
+ --admonition-note-color: #bfeeff;
+ --admonition-note-title-background-color: #305070;
+ --admonition-note-title-color: #bfefff;
+ --admonition-attention-background-color: #444033;
+ --admonition-attention-color: #ffeeaf;
+ --admonition-attention-title-background-color: #665022;
+ --admonition-attention-title-color: #ffeeaf;
+ --admonition-danger-background-color: #433;
+ --admonition-danger-color: #fcc;
+ --admonition-danger-title-background-color: #633;
+ --admonition-danger-title-color: #fcc;
+ --admonition-tip-background-color: #28382d;
+ --admonition-tip-color: #dfd;
+ --admonition-tip-title-background-color: #336648;
+ --admonition-tip-title-color: #dfd;
+
+ --kbd-background-color: #595b5d;
+ --kbd-outline-color: #3d4144;
+ --kbd-shadow-color: #1e2023;
+ --kbd-text-color: #e2f2ff;
+
+ --guiitems-background-color: #303d4f;
+ --guiitems-border-color: #7fbbe3;
+
+ --btn-neutral-background-color: #404040;
+ --btn-neutral-hover-background-color: #505050;
+ --footer-color: #aaa;
+
+ --graphviz-filter: invert(0.9) brightness(1.2);
+}
diff --git a/doc/_static/css/gcs.css b/doc/_static/css/gcs.css
new file mode 100644
index 000000000..165011088
--- /dev/null
+++ b/doc/_static/css/gcs.css
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2023, Benjamin Cabé .
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Custom stylesheet for Google Programmable Search Engine results.
+ */
+
+.gs-webResult .gs-snippet,
+.gs-fileFormatType,
+.gs-spelling,
+.gsc-refinementHeader,
+.gsc-result-info,
+.gsc-orderby-label {
+ color: var(--body-color) !important;
+}
+
+.gsc-control-cse {
+ width: 100%;
+ padding: 0 !important;
+ font-family: inherit !important;
+ font-size: inherit !important;
+ background-color: inherit !important;
+ border: none !important;
+ font-size: inherit !important;
+}
+
+.gsc-completion-container {
+ font-family: inherit !important;
+}
+
+.gs-result .gs-title, .gs-result .gs-title * {
+ color: var(--link-color) !important;
+ background-color: var(--content-background-color) !important;
+}
+
+.gs-result .gs-title:visited, .gs-result .gs-title:visited * {
+ color: var(--link-color-visited) !important;
+}
+
+.gsc-results .gsc-cursor-box .gsc-cursor-page,
+.gsc-results .gsc-cursor-box .gsc-cursor-current-page {
+ background-color: var(--content-background-color) !important;
+ color: var(--link-color) !important;
+}
+
+.gs-result .gs-image, .gs-result .gs-promotion-image {
+ border: none !important;
+ padding-right: .5em;
+}
+
+.gsc-table-result {
+ font-family: inherit !important;
+ padding-top: .5em !important;
+ font-size: inherit !important;
+}
+
+.gsc-tabHeader.gsc-tabhActive, .gsc-refinementHeader.gsc-refinementhActive,
+.gsc-tabHeader.gsc-tabhInactive, .gsc-refinementHeader.gsc-refinementhInactive {
+ background-color: inherit !important;
+}
+
+.gs-no-results-result .gs-snippet, .gs-error-result .gs-snippet {
+ color: var(--admonition-attention-title-color) !important;
+ background-color: var(--admonition-attention-title-background-color) !important;
+}
+
+.gsc-webResult .gsc-result {
+ background-color: inherit !important;
+ margin: 0;
+ padding: .5em 0;
+ border: none !important;
+ border-bottom: 1px solid var(--hr-color) !important;
+}
+
+.gs-webResult div.gs-per-result-labels {
+ margin-top: 1.3em;
+ margin-bottom: 0.3em;
+ font-size:0.8em;
+}
+
+.gs-webResult div.gs-per-result-labels span {
+ display: none;
+}
+
+.gs-webResult div.gs-per-result-labels a.gs-label {
+ text-decoration: none !important;
+ cursor: pointer;
+ padding: 0.4em 0.6em;
+ margin-left: .5em;
+ color: var(--admonition-tip-title-color) !important;
+ background-color: var(--admonition-tip-title-background-color) !important;
+ border-radius: 50px;
+}
+
+.gcsc-find-more-on-google {
+ color: var(--link-color) !important;
+}
+
+.gcsc-find-more-on-google-magnifier {
+ fill: var(--link-color) !important;
+}
diff --git a/doc/_static/css/light.css b/doc/_static/css/light.css
new file mode 100644
index 000000000..eb019863c
--- /dev/null
+++ b/doc/_static/css/light.css
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community
+ * Copyright (c) 2021, Teslabs Engineering S.L.
+ * SPDX-License-Identifier: CC-BY-3.0
+ *
+ * Light theme colors
+ */
+
+:root {
+ --body-color: #404040;
+ --content-wrap-background-color: #efefef;
+ --content-background-color: #fcfcfc;
+ --logo-opacity: 1.0;
+ --navbar-background-color: #333f67;
+ --navbar-background-color-hover: #29355c;
+ --navbar-background-color-active: #212d51;
+ --navbar-current-background-color: #212d51;
+ --navbar-current-background-color-hover: #182343;
+ --navbar-current-background-color-active: #131e3b;
+ --navbar-level-1-color: #c3e3ff;
+ --navbar-level-2-color: #b8d6f0;
+ --navbar-level-3-color: #a3c4e1;
+ --navbar-heading-color: #af7fe4;
+ --navbar-scrollbar-color: #af7fe4;
+ --navbar-scrollbar-hover-color: #9454db;
+ --navbar-scrollbar-active-color: #7929d2;
+ --navbar-scrollbar-background: #131e2b;
+
+ --link-color: #237ab3;
+ --link-color-hover: #3091d1;
+ --link-color-active: #105078;
+ --link-color-visited: #9b59b6;
+ --external-reference-icon: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMjk4MGI5Ij48cGF0aCBkPSJtNy41IDcuMXYzLjRoLTZ2LTZoMy40Ii8+PHBhdGggZD0ibTUuNzY1IDFoNS4yMzV2NS4zOWwtMS41NzMgMS41NDctMS4zMS0xLjMxLTIuNzI0IDIuNzIzLTIuNjktMi42ODggMi44MS0yLjgwOC0xLjMxMy0xLjMxeiIvPjwvZz48L3N2Zz4K");
+ --classref-badge-text-color: hsl(0, 0%, 45%);
+
+ --hr-color: #e1e4e5;
+ --table-row-odd-background-color: #f3f6f6;
+ --code-background-color: #fff;
+ --code-border-color: #e1e4e5;
+ --code-literal-color: #d04c60;
+ --input-background-color: #fcfcfc;
+ --input-focus-border-color: #5f8cff;
+
+ --search-input-background-color: #e6eef3; /* derived from --input-background-color */
+ --search-match-color: #2c6b96; /* derived from --link-color */
+ --search-match-background-color: #e3f2fd; /* derived from --link-color */
+ --search-active-color: #efefef;
+ --search-credits-background-color: #333f67; /* derived from --navbar-background-color */
+ --search-credits-color: #b3b3b3; /* derived from --footer-color */
+ --search-credits-link-color: #4392c5; /* derived from --link-color */
+
+ --highlight-background-color: #f0f2f4;
+ --highlight-background-emph-color: #dbe6c3;
+ --highlight-default-color: #404040;
+ --highlight-comment-color: #408090;
+ --highlight-keyword-color: #007020;
+ --highlight-keyword2-color: #902000;
+ --highlight-number-color: #208050;
+ --highlight-decorator-color: #4070a0;
+ --highlight-type-color: #007020;
+ --highlight-type2-color: #0e84b5;
+ --highlight-function-color: #06287e;
+ --highlight-operator-color: #666666;
+ --highlight-string-color: #4070a0;
+
+ --admonition-note-background-color: #e7f2fa;
+ --admonition-note-color: #404040;
+ --admonition-note-title-background-color: #6ab0de;
+ --admonition-note-title-color: #fff;
+ --admonition-attention-background-color: #ffedcc;
+ --admonition-attention-color: #404040;
+ --admonition-attention-title-background-color: #f0b37e;
+ --admonition-attention-title-color: #fff;
+ --admonition-danger-background-color: #fcf3f2;
+ --admonition-danger-color: #404040;
+ --admonition-danger-title-background-color: #e9a499;
+ --admonition-danger-title-color: #fff;
+ --admonition-tip-background-color: #dbfaf4;
+ --admonition-tip-color: #404040;
+ --admonition-tip-title-background-color: #1abc9c;
+ --admonition-tip-title-color: #fff;
+
+ --kbd-background-color: #fafbfc;
+ --kbd-outline-color: #d1d5da;
+ --kbd-shadow-color: #b0b7bf;
+ --kbd-text-color: #444d56;
+
+ --guiitems-background-color: #e7f2fa;
+ --guiitems-border-color: #7fbbe3;
+
+ --btn-neutral-background-color: #f3f6f6;
+ --btn-neutral-hover-background-color: #e5ebeb;
+ --footer-color: #747474;
+
+ --graphviz-filter: none;
+}
diff --git a/doc/_static/images/logo.png b/doc/_static/images/logo.png
new file mode 100644
index 000000000..861db7d9f
Binary files /dev/null and b/doc/_static/images/logo.png differ
diff --git a/doc/_static/images/west-mr-model.png b/doc/_static/images/west-mr-model.png
new file mode 100644
index 000000000..ddbfdb62a
Binary files /dev/null and b/doc/_static/images/west-mr-model.png differ
diff --git a/doc/_static/js/custom.js b/doc/_static/js/custom.js
new file mode 100644
index 000000000..049d327c8
--- /dev/null
+++ b/doc/_static/js/custom.js
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2020-2023, The Godot community
+ * Copyright (c) 2023, Benjamin Cabé
+ * SPDX-License-Identifier: CC-BY-3.0
+ */
+
+
+// Handle page scroll and adjust sidebar accordingly.
+
+// Each page has two scrolls: the main scroll, which is moving the content of the page;
+// and the sidebar scroll, which is moving the navigation in the sidebar.
+// We want the logo to gradually disappear as the main content is scrolled, giving
+// more room to the navigation on the left. This means adjusting the height
+// available to the navigation on the fly.
+const registerOnScrollEvent = (function(){
+ // Configuration.
+
+ // The number of pixels the user must scroll by before the logo is completely hidden.
+ const scrollTopPixels = 137;
+ // The target margin to be applied to the navigation bar when the logo is hidden.
+ const menuTopMargin = 54;
+ // The max-height offset when the logo is completely visible.
+ const menuHeightOffset_default = 210;
+ // The max-height offset when the logo is completely hidden.
+ const menuHeightOffset_fixed = 63;
+ // The distance between the two max-height offset values above; used for intermediate values.
+ const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed);
+
+ // Media query handler.
+ return function(mediaQuery) {
+ // We only apply this logic to the "desktop" resolution (defined by a media query at the bottom).
+ // This handler is executed when the result of the query evaluation changes, which means that
+ // the page has moved between "desktop" and "mobile" states.
+
+ // When entering the "desktop" state, we register scroll events and adjust elements on the page.
+ // When entering the "mobile" state, we clean up any registered events and restore elements on the page
+ // to their initial state.
+
+ const $window = $(window);
+ const $sidebar = $('.wy-side-scroll');
+ const $search = $sidebar.children('.wy-side-nav-search');
+ const $menu = $sidebar.children('.wy-menu-vertical');
+
+ if (mediaQuery.matches) {
+ // Entering the "desktop" state.
+
+ // The main scroll event handler.
+ // Executed as the page is scrolled and once immediately as the page enters this state.
+ const handleMainScroll = (currentScroll) => {
+ if (currentScroll >= scrollTopPixels) {
+ // After the page is scrolled below the threshold, we fix everything in place.
+ $search.css('margin-top', `-${scrollTopPixels}px`);
+ $menu.css('margin-top', `${menuTopMargin}px`);
+ $menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`);
+ }
+ else {
+ // Between the top of the page and the threshold we calculate intermediate values
+ // to guarantee a smooth transition.
+ $search.css('margin-top', `-${currentScroll}px`);
+ $menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`);
+
+ if (currentScroll > 0) {
+ const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels;
+ const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent;
+ $menu.css('max-height', `calc(100% - ${offsetValue}px)`);
+ } else {
+ $menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`);
+ }
+ }
+ };
+
+ // The sidebar scroll event handler.
+ // Executed as the sidebar is scrolled as well as after the main scroll. This is needed
+ // because the main scroll can affect the scrollable area of the sidebar.
+ const handleSidebarScroll = () => {
+ const menuElement = $menu.get(0);
+ const menuScrollTop = $menu.scrollTop();
+ const menuScrollBottom = menuElement.scrollHeight - (menuScrollTop + menuElement.offsetHeight);
+
+ // As the navigation is scrolled we add a shadow to the top bar hanging over it.
+ if (menuScrollTop > 0) {
+ $search.addClass('fixed-and-scrolled');
+ } else {
+ $search.removeClass('fixed-and-scrolled');
+ }
+ };
+
+ $search.addClass('fixed');
+
+ $window.scroll(function() {
+ handleMainScroll(window.scrollY);
+ handleSidebarScroll();
+ });
+
+ $menu.scroll(function() {
+ handleSidebarScroll();
+ });
+
+ handleMainScroll(window.scrollY);
+ handleSidebarScroll();
+ } else {
+ // Entering the "mobile" state.
+
+ $window.unbind('scroll');
+ $menu.unbind('scroll');
+
+ $search.removeClass('fixed');
+
+ $search.css('margin-top', `0px`);
+ $menu.css('margin-top', `0px`);
+ $menu.css('max-height', 'initial');
+ }
+ };
+ })();
+
+ $(document).ready(() => {
+ // Initialize handlers for page scrolling and our custom sidebar.
+ const mediaQuery = window.matchMedia('only screen and (min-width: 769px)');
+
+ registerOnScrollEvent(mediaQuery);
+ mediaQuery.addListener(registerOnScrollEvent);
+ });
diff --git a/doc/_static/js/dark-mode-toggle-stylesheets-loader.min.js b/doc/_static/js/dark-mode-toggle-stylesheets-loader.min.js
new file mode 100644
index 000000000..4b708b543
--- /dev/null
+++ b/doc/_static/js/dark-mode-toggle-stylesheets-loader.min.js
@@ -0,0 +1,2 @@
+// @license © 2024 Google LLC. Licensed under the Apache License, Version 2.0.
+(()=>{const e="dark-mode-toggle-stylesheets";const s="dark-mode-toggle";const t="light";const l="dark";let o=document.getElementById(e).textContent;let c=null;try{c=localStorage.getItem(s)}catch(e){}const a=/\(\s*prefers-color-scheme\s*:\s*light\s*\)/gi;const r=/\(\s*prefers-color-scheme\s*:\s*dark\s*\)/gi;switch(c){case t:o=o.replace(a,"$&, all").replace(r,"$& and not all");break;case l:o=o.replace(r,"$&, all").replace(a,"$& and not all");break}document.write(o)})();
\ No newline at end of file
diff --git a/doc/_static/js/dark-mode-toggle.min.mjs b/doc/_static/js/dark-mode-toggle.min.mjs
new file mode 100644
index 000000000..9c78635c4
--- /dev/null
+++ b/doc/_static/js/dark-mode-toggle.min.mjs
@@ -0,0 +1,2 @@
+// @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0.
+const e=document;let t={};try{t=localStorage}catch(e){}const i="prefers-color-scheme";const a="media";const s="light";const r="dark";const h="system";const o=`(${i}:${r})`;const l=`(${i}:${s})`;const n="link[rel=stylesheet]";const c="remember";const d="legend";const p="toggle";const b="switch";const g="three-way";const m="appearance";const u="permanent";const f="mode";const k="colorschemechange";const y="permanentcolorscheme";const v="all";const $="not all";const L="dark-mode-toggle";const T="https://googlechromelabs.github.io/dark-mode-toggle/demo/";const W=(e,t,i=t)=>{Object.defineProperty(e,i,{enumerable:true,get(){const e=this.getAttribute(t);return e===null?"":e},set(e){this.setAttribute(t,e)}})};const w=(e,t,i=t)=>{Object.defineProperty(e,i,{enumerable:true,get(){return this.hasAttribute(t)},set(e){if(e){this.setAttribute(t,"")}else{this.removeAttribute(t)}}})};const x=e.createElement("template");x.innerHTML=``;export class DarkModeToggle extends HTMLElement{static get observedAttributes(){return[f,m,u,d,s,r,c]}constructor(){super();W(this,f);W(this,m);W(this,d);W(this,s);W(this,r);W(this,h);W(this,c);w(this,u);this.t=null;this.i=null;e.addEventListener(k,(e=>{this.mode=e.detail.colorScheme;this.h();this.o();this.l()}));e.addEventListener(y,(e=>{this.permanent=e.detail.permanent;this.p.checked=this.permanent;this.l()}));this.m()}m(){const t=this.attachShadow({mode:"open"});t.append(x.content.cloneNode(true));this.t=e.querySelectorAll(`${n}[${a}*=${i}][${a}*="${r}"]`);this.i=e.querySelectorAll(`${n}[${a}*=${i}][${a}*="${s}"]`);this.u=t.querySelector("[part=lightRadio]");this.k=t.querySelector("[part=lightLabel]");this.v=t.querySelector("[part=darkRadio]");this.$=t.querySelector("[part=darkLabel]");this.L=t.querySelector("[part=toggleCheckbox]");this.T=t.querySelector("[part=toggleLabel]");this.W=t.querySelector("[part=lightThreeWayRadio]");this.R=t.querySelector("[part=lightThreeWayLabel]");this.C=t.querySelector("[part=systemThreeWayRadio]");this.M=t.querySelector("[part=systemThreeWayLabel]");this.S=t.querySelector("[part=darkThreeWayRadio]");this._=t.querySelector("[part=darkThreeWayLabel]");this.A=t.querySelector("legend");this.D=t.querySelector("aside");this.p=t.querySelector("[part=permanentCheckbox]");this.O=t.querySelector("[part=permanentLabel]")}connectedCallback(){const e=matchMedia(o).media!==$;if(e){matchMedia(o).addListener((({matches:e})=>{if(this.permanent){return}this.mode=e?r:s;this.j(k,{colorScheme:this.mode})}))}let i=false;try{i=t.getItem(L)}catch(e){}if(i&&[r,s].includes(i)){this.mode=i;this.p.checked=true;this.permanent=true}else if(e){this.mode=matchMedia(l).matches?s:r}if(!this.mode){this.mode=s}if(this.permanent&&!i){try{t.setItem(L,this.mode)}catch(e){}}if(!this.appearance){this.appearance=p}this.P();this.h();this.o();this.l();[this.u,this.v].forEach((e=>{e.addEventListener("change",(()=>{this.mode=this.u.checked?s:r;this.o();this.l();this.j(k,{colorScheme:this.mode})}))}));this.L.addEventListener("change",(()=>{this.mode=this.L.checked?r:s;this.h();this.l();this.j(k,{colorScheme:this.mode})}));this.W.addEventListener("change",(()=>{this.mode=s;this.permanent=true;this.o();this.h();this.l();this.j(k,{colorScheme:this.mode});this.j(y,{permanent:this.permanent})}));this.S.addEventListener("change",(()=>{this.mode=r;this.permanent=true;this.o();this.h();this.l();this.j(k,{colorScheme:this.mode});this.j(y,{permanent:this.permanent})}));this.C.addEventListener("change",(()=>{this.mode=this.H();this.permanent=false;this.o();this.h();this.l();this.j(k,{colorScheme:this.mode});this.j(y,{permanent:this.permanent})}));this.p.addEventListener("change",(()=>{this.permanent=this.p.checked;this.l();this.j(y,{permanent:this.permanent})}));this.q();this.j(k,{colorScheme:this.mode});this.j(y,{permanent:this.permanent})}attributeChangedCallback(e,i,a){if(e===f){const e=[s,h,r];if(!e.includes(a)){throw new RangeError(`Allowed values are: "${e.join(`", "`)}".`)}if(matchMedia("(hover:none)").matches&&this.remember){this.B()}if(this.permanent){try{t.setItem(L,this.mode)}catch(e){}}this.h();this.o();this.l();this.q()}else if(e===m){const e=[p,b,g];if(!e.includes(a)){throw new RangeError(`Allowed values are: "${e.join(`", "`)}".`)}this.P()}else if(e===u){if(this.permanent){if(this.mode){try{t.setItem(L,this.mode)}catch(e){}}}else{try{t.removeItem(L)}catch(e){}}this.p.checked=this.permanent}else if(e===d){this.A.textContent=a}else if(e===c){this.O.textContent=a}else if(e===s){this.k.textContent=a;if(this.mode===s){this.T.textContent=a}}else if(e===r){this.$.textContent=a;if(this.mode===r){this.T.textContent=a}}}H(){return matchMedia(l).matches?s:r}j(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:true,composed:true,detail:t}))}P(){this.u.hidden=this.k.hidden=this.v.hidden=this.$.hidden=this.L.hidden=this.T.hidden=this.W.hidden=this.R.hidden=this.C.hidden=this.M.hidden=this.S.hidden=this._.hidden=true;switch(this.appearance){case b:this.u.hidden=this.k.hidden=this.v.hidden=this.$.hidden=false;break;case g:this.W.hidden=this.R.hidden=this.C.hidden=this.M.hidden=this.S.hidden=this._.hidden=false;break;case p:default:this.L.hidden=this.T.hidden=false;break}}h(){if(this.mode===s){this.u.checked=true}else{this.v.checked=true}}o(){if(this.mode===s){this.T.style.setProperty(`--${L}-checkbox-icon`,`var(--${L}-light-icon,url("${T}moon.png"))`);this.T.textContent=this.light;if(!this.light){this.T.ariaLabel=r}this.L.checked=false}else{this.T.style.setProperty(`--${L}-checkbox-icon`,`var(--${L}-dark-icon,url("${T}sun.png"))`);this.T.textContent=this.dark;if(!this.dark){this.T.ariaLabel=s}this.L.checked=true}}l(){this.R.ariaLabel=s;this.M.ariaLabel=h;this.R.ariaLabel=r;this.R.textContent=this.light;this.M.textContent=this.system;this._.textContent=this.dark;if(this.permanent){if(this.mode===s){this.W.checked=true}else{this.S.checked=true}}else{this.C.checked=true}}q(){if(this.mode===s){this.i.forEach((e=>{e.media=v;e.disabled=false}));this.t.forEach((e=>{e.media=$;e.disabled=true}))}else{this.t.forEach((e=>{e.media=v;e.disabled=false}));this.i.forEach((e=>{e.media=$;e.disabled=true}))}}B(){this.D.style.visibility="visible";setTimeout((()=>{this.D.style.visibility="hidden"}),3e3)}}customElements.define(L,DarkModeToggle);
\ No newline at end of file
diff --git a/doc/_templates/breadcrumbs.html b/doc/_templates/breadcrumbs.html
new file mode 100644
index 000000000..3852afcfa
--- /dev/null
+++ b/doc/_templates/breadcrumbs.html
@@ -0,0 +1,34 @@
+{% extends "!breadcrumbs.html" %}
+{% block breadcrumbs %}
+
+ {# parameterize default name "Docs" in breadcrumb via docs_title in conf.py #}
+ {% if not docs_title %}
+ {% set docs_title = "Docs" %}
+ {% endif %}
+
+ {{ docs_title }} »
+ {% for doc in parents %}
+ {{ doc.title }} »
+ {% endfor %}
+ {{ title }}
+
+{% endblock %}
+{%- block breadcrumbs_aside %}
+
+
+
+
+ {%- if display_gh_links %}
+ {% set gh_blob_url = pagename | gh_link_get_blob_url %}
+ {% if gh_blob_url %}
+ {{ _('Open on GitHub') }}
+ {% endif %}
+ {%- set git_last_updated, sha1 = pagename | git_info | default((None, None), true) %}
+ {%- if sha1 %}
+
+ {{ _('Report an issue with this page')}}
+
+ {% endif %}
+ {% endif %}
+
+{%- endblock %}
diff --git a/doc/_templates/footer.html b/doc/_templates/footer.html
new file mode 100644
index 000000000..9dacea711
--- /dev/null
+++ b/doc/_templates/footer.html
@@ -0,0 +1,18 @@
+{% extends "!footer.html" %}
+{% block contentinfo %}
+
+ {%- if show_copyright %}
+ © Copyright {{ copyright }}.
+ {%- endif %}
+
+ {%- if last_updated %}
+
+ Last generated: {{ last_updated }}.
+ {%- set git_last_updated, sha1 = pagename | git_info | default((None, None), true) %}
+ {%- if git_last_updated %}
+ Last source update: {{ git_last_updated }}.
+ {%- endif %}
+
+ {%- endif -%}
+
+{% endblock %}
diff --git a/doc/_templates/gsearch.html b/doc/_templates/gsearch.html
new file mode 100644
index 000000000..195346635
--- /dev/null
+++ b/doc/_templates/gsearch.html
@@ -0,0 +1,17 @@
+{%- extends "!search.html" %}
+
+{%- block scripts %}
+ {{ super.super() }}
+
+{%- endblock %}
+
+{% block footer %}
+ {{ super.super() }}
+{% endblock %}
+
+{% block body %}
+ {{ _('Search Results') }}
+
+
+{% endblock %}
diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html
new file mode 100644
index 000000000..c46987e9c
--- /dev/null
+++ b/doc/_templates/layout.html
@@ -0,0 +1,40 @@
+{% extends "!layout.html" %}
+{% block document %}
+ {% if is_release %}
+
+ {% endif %}
+ {{ super() }}
+{% endblock %}
+{% block menu %}
+
+ {{ super() }}
+ {% if reference_links %}
+
+
Reference
+
+ {% for title, url in reference_links.items() %}
+ -
+ {{ title }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+{% endblock %}
+{% block extrahead %}
+
+ {# Use dark mode loader script to prevent "flashing" of the page on load.
+ As we need a