diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md
index d3df598ad..087b1a56f 100644
--- a/docs/mkdocs/docs/api/basic_json/basic_json.md
+++ b/docs/mkdocs/docs/api/basic_json/basic_json.md
@@ -113,15 +113,15 @@ basic_json(basic_json&& other) noexcept;
```mermaid
flowchart TD
- A(["initializer_list init"]) --> B{"empty, or every element is a 2-element array whose first element is a string?"}
- B -->|"yes"| C{"type_deduction"}
- B -->|"no"| D{"type_deduction"}
+ A(["initializer_list init"]) --> B{{"empty, or every element is a 2-element array whose first element is a string?"}}
+ B -->|"yes"| C{{"type_deduction"}}
+ B -->|"no"| D{{"type_deduction"}}
C -->|"true"| OBJ["create object"]
- C -->|"false"| E{"manual_type"}
+ C -->|"false"| E{{"manual_type"}}
E -->|"object"| OBJ
E -->|"array"| ARR["create array"]
D -->|"true"| ARR
- D -->|"false"| F{"manual_type"}
+ D -->|"false"| F{{"manual_type"}}
F -->|"array"| ARR
F -->|"object"| ERR["throw type_error.301"]
```
diff --git a/docs/mkdocs/docs/api/basic_json/begin.md b/docs/mkdocs/docs/api/basic_json/begin.md
index 72583ce57..850cf118a 100644
--- a/docs/mkdocs/docs/api/basic_json/begin.md
+++ b/docs/mkdocs/docs/api/basic_json/begin.md
@@ -7,7 +7,9 @@ const_iterator begin() const noexcept;
Returns an iterator to the first element.
-
+
+--8<-- "images/range-begin-end.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md
index b4c3e588f..2c200829a 100644
--- a/docs/mkdocs/docs/api/basic_json/cbegin.md
+++ b/docs/mkdocs/docs/api/basic_json/cbegin.md
@@ -6,7 +6,9 @@ const_iterator cbegin() const noexcept;
Returns an iterator to the first element.
-
+
+--8<-- "images/range-begin-end.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md
index fb568762a..7bc8adcfc 100644
--- a/docs/mkdocs/docs/api/basic_json/cend.md
+++ b/docs/mkdocs/docs/api/basic_json/cend.md
@@ -6,7 +6,9 @@ const_iterator cend() const noexcept;
Returns an iterator to one past the last element.
-
+
+--8<-- "images/range-begin-end.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/crbegin.md b/docs/mkdocs/docs/api/basic_json/crbegin.md
index 545f86044..ab7562cfa 100644
--- a/docs/mkdocs/docs/api/basic_json/crbegin.md
+++ b/docs/mkdocs/docs/api/basic_json/crbegin.md
@@ -6,7 +6,9 @@ const_reverse_iterator crbegin() const noexcept;
Returns an iterator to the reverse-beginning; that is, the last element.
-
+
+--8<-- "images/range-rbegin-rend.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/crend.md b/docs/mkdocs/docs/api/basic_json/crend.md
index 56cc40c52..d23e41b31 100644
--- a/docs/mkdocs/docs/api/basic_json/crend.md
+++ b/docs/mkdocs/docs/api/basic_json/crend.md
@@ -7,7 +7,9 @@ const_reverse_iterator crend() const noexcept;
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
attempting to access it results in undefined behavior.
-
+
+--8<-- "images/range-rbegin-rend.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/end.md b/docs/mkdocs/docs/api/basic_json/end.md
index e94ec78a2..19b1441e2 100644
--- a/docs/mkdocs/docs/api/basic_json/end.md
+++ b/docs/mkdocs/docs/api/basic_json/end.md
@@ -7,7 +7,9 @@ const_iterator end() const noexcept;
Returns an iterator to one past the last element.
-
+
+--8<-- "images/range-begin-end.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/parse_event_t.md b/docs/mkdocs/docs/api/basic_json/parse_event_t.md
index f8c12eff8..14f5840a6 100644
--- a/docs/mkdocs/docs/api/basic_json/parse_event_t.md
+++ b/docs/mkdocs/docs/api/basic_json/parse_event_t.md
@@ -22,7 +22,9 @@ The parser callback distinguishes the following events:
## Examples
-
+
+--8<-- "images/callback_events.svg"
+
??? example
diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md
index 727905e5a..52bc2a595 100644
--- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md
+++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md
@@ -23,7 +23,9 @@ table describes the values of the parameters `depth`, `event`, and `parsed`.
| `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array |
| `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value |
-
+
+--8<-- "images/callback_events.svg"
+
Discarding a value (i.e., returning `#!cpp false`) has different effects depending on the context in which function was
called:
diff --git a/docs/mkdocs/docs/api/basic_json/rbegin.md b/docs/mkdocs/docs/api/basic_json/rbegin.md
index 544b8a643..c625211f1 100644
--- a/docs/mkdocs/docs/api/basic_json/rbegin.md
+++ b/docs/mkdocs/docs/api/basic_json/rbegin.md
@@ -7,7 +7,9 @@ const_reverse_iterator rbegin() const noexcept;
Returns an iterator to the reverse-beginning; that is, the last element.
-
+
+--8<-- "images/range-rbegin-rend.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/api/basic_json/rend.md b/docs/mkdocs/docs/api/basic_json/rend.md
index 12f68e787..668247fea 100644
--- a/docs/mkdocs/docs/api/basic_json/rend.md
+++ b/docs/mkdocs/docs/api/basic_json/rend.md
@@ -8,7 +8,9 @@ const_reverse_iterator rend() const noexcept;
Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder,
attempting to access it results in undefined behavior.
-
+
+--8<-- "images/range-rbegin-rend.svg"
+
## Return value
diff --git a/docs/mkdocs/docs/css/brand.css b/docs/mkdocs/docs/css/brand.css
new file mode 100644
index 000000000..5aefef421
--- /dev/null
+++ b/docs/mkdocs/docs/css/brand.css
@@ -0,0 +1,739 @@
+/* nlohmann/json brand for Material for MkDocs (values from brand/kit/tokens.css) */
+
+@font-face {
+ font-family: "Inter";
+ src: url("fonts/Inter.woff2") format("woff2");
+ font-weight: 100 900;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Inter";
+ src: url("fonts/Inter-Italic.woff2") format("woff2");
+ font-style: italic;
+ font-weight: 100 900;
+ font-display: swap;
+}
+@font-face {
+ font-family: "JetBrains Mono";
+ src: url("fonts/JetBrainsMono.woff2") format("woff2");
+ font-weight: 100 800;
+ font-display: swap;
+}
+@font-face {
+ font-family: "JetBrains Mono";
+ src: url("fonts/JetBrainsMono-Italic.woff2") format("woff2");
+ font-style: italic;
+ font-weight: 400;
+ font-display: swap;
+}
+
+:root {
+ --md-text-font: "Inter";
+ --md-code-font: "JetBrains Mono";
+}
+
+/* light */
+[data-md-color-scheme="default"] {
+ --nj-blue: #1747a6;
+ --nj-blue-deep: #10357d;
+ --nj-blue-soft: #eaf1fd;
+ --nj-band: #1747a6;
+ --nj-red: #d93a3f;
+ --nj-red-soft: #fdeced;
+ --nj-string: #b4322a;
+ --nj-comment: #646d7a;
+
+ --md-primary-fg-color: var(--nj-band);
+ --md-primary-fg-color--light: #1d55c4;
+ --md-primary-fg-color--dark: #10357d;
+ --md-accent-fg-color: var(--nj-blue-deep);
+ --md-accent-fg-color--transparent: rgba(23, 71, 166, .1);
+ --md-typeset-a-color: var(--nj-blue);
+ --md-default-fg-color: #0f1419;
+ --md-default-fg-color--light: #4a515c;
+ --md-default-fg-color--lighter: #8a919c;
+ --md-default-fg-color--lightest: #e3e6eb;
+ --md-typeset-color: #0f1419;
+ --md-code-bg-color: #f5f7fa;
+ --md-code-fg-color: #0f1419;
+}
+
+/* dark */
+[data-md-color-scheme="slate"] {
+ --nj-blue: #7aa7f5;
+ --nj-blue-deep: #a9c6f8;
+ --nj-blue-soft: #16233b;
+ --nj-band: #173e8c;
+ --nj-red: #f06a6e;
+ --nj-red-soft: #3a1c1e;
+ --nj-string: #f08a7e;
+ --nj-comment: #8b929e;
+
+ --md-hue: 220;
+ --md-primary-fg-color: var(--nj-band);
+ --md-primary-fg-color--light: #163f93;
+ --md-primary-fg-color--dark: #0c2556;
+ --md-accent-fg-color: var(--nj-blue-deep);
+ --md-accent-fg-color--transparent: rgba(122, 167, 245, .12);
+ --md-typeset-a-color: var(--nj-blue);
+ --md-default-bg-color: #0f1218;
+ --md-default-fg-color: #f2f4f7;
+ --md-default-fg-color--light: #b3b9c4;
+ --md-default-fg-color--lighter: #7b828e;
+ --md-default-fg-color--lightest: #272c36;
+ --md-typeset-color: #f2f4f7;
+ --md-code-bg-color: #171b23;
+ --md-code-fg-color: #f2f4f7;
+ --md-footer-bg-color: #0b0e13;
+ --md-footer-bg-color--dark: #08090d;
+}
+
+/* syntax colours (all at least 4.5:1 on the code panel in both schemes) */
+[data-md-color-scheme="default"] {
+ --nj-hl-keyword: #1747a6;
+ --nj-hl-type: #0d6e72;
+ --nj-hl-function: #6b3fb8;
+ --nj-hl-string: #23793a;
+ --nj-hl-escape: #0f5d2a;
+ --nj-hl-key: #10357d;
+ --nj-hl-number: #b04a07;
+ --nj-hl-preproc: #a3247a;
+}
+[data-md-color-scheme="slate"] {
+ --nj-hl-keyword: #7aa7f5;
+ --nj-hl-type: #4fd1c5;
+ --nj-hl-function: #c4a3ff;
+ --nj-hl-string: #7ed491;
+ --nj-hl-escape: #b2ecc0;
+ --nj-hl-key: #a9c6f8;
+ --nj-hl-number: #f5a65b;
+ --nj-hl-preproc: #f48fc8;
+}
+.md-typeset .highlight :is(.k, .kd, .kn, .kr, .kp, .kv, .ow) { color: var(--nj-hl-keyword); font-weight: 600; }
+.md-typeset .highlight :is(.kt, .nc, .nn, .na) { color: var(--nj-hl-type); }
+.md-typeset .highlight :is(.nf, .fm, .nb, .bp) { color: var(--nj-hl-function); }
+.md-typeset .highlight :is(.s, .s1, .s2, .sb, .sc, .sd, .sh, .sx, .sr, .ss, .sa, .dl, .cpf) { color: var(--nj-hl-string); }
+.md-typeset .highlight :is(.se, .si) { color: var(--nj-hl-escape); font-weight: 600; }
+.md-typeset .highlight .nt { color: var(--nj-hl-key); }
+.md-typeset .highlight :is(.m, .mi, .mf, .mh, .mo, .mb, .il, .kc) { color: var(--nj-hl-number); }
+.md-typeset .highlight .kc { font-weight: 500; }
+.md-typeset .highlight .cp { color: var(--nj-hl-preproc); }
+.md-typeset .highlight :is(.c, .c1, .cm, .ch, .cs) { color: var(--nj-comment); font-style: italic; }
+.md-typeset .highlight :is(.o, .p) { color: var(--md-default-fg-color--light); }
+.md-typeset .highlight :is(.n, .nx, .nv, .no, .ni, .nl, .py) { color: var(--md-code-fg-color); }
+.md-typeset .highlight .err { color: inherit; background: none; }
+
+/* typography: Inter with cv11/ss03, bold headlines with negative tracking */
+body {
+ font-feature-settings: "cv11", "ss03";
+}
+.md-typeset {
+ font-size: .85rem; /* 17 px body */
+ line-height: 1.65;
+}
+.md-typeset h1 {
+ color: var(--md-typeset-color);
+ font-weight: 800;
+ letter-spacing: -.035em;
+ line-height: 1.1;
+}
+.md-typeset h2 {
+ font-weight: 800;
+ letter-spacing: -.025em;
+ line-height: 1.15;
+}
+.md-typeset h3 {
+ font-weight: 700;
+ letter-spacing: -.015em;
+}
+.md-typeset table:not([class]) {
+ font-variant-numeric: tabular-nums;
+}
+
+/* header and tabs: one blue band with the faint syntax pattern; wordmark instead of the site name.
+ A veil over the tile keeps the glyphs from running through the navigation text; the hero shows the pattern at full strength. */
+.md-header, .md-tabs {
+ --nj-veil: linear-gradient(color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent), color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent));
+ background-color: var(--md-primary-fg-color);
+ background-image: var(--nj-veil), url("pattern-on-blue.svg");
+ background-size: auto, 160px 160px;
+ background-position: 0 0, 0 0;
+}
+.md-tabs {
+ background-position: 0 0, 0 -48px; /* continue the header's tile */
+}
+.md-header__button.md-logo img {
+ width: auto;
+ height: 1.5rem;
+}
+.md-header__topic:first-child .md-ellipsis { /* the wordmark replaces the site name at every width */
+ display: none;
+}
+@media screen and (max-width: 76.234375em) { /* Material hides the header logo here: show the wordmark on phones, too */
+ .md-header__button.md-logo {
+ display: block;
+ margin: 0 0 0 .2rem;
+ padding: .4rem .2rem;
+ }
+ .md-header__button[for="__drawer"] {
+ order: -1;
+ }
+}
+.md-nav__button.md-logo img {
+ width: auto;
+ height: 1.4rem;
+}
+
+/* shapes: 6 px corners on blocks, 4 px on inline code */
+.md-typeset pre > code, .md-typeset .highlight, .md-typeset .highlighttable,
+.md-typeset .admonition, .md-typeset details, .md-typeset .tabbed-set,
+.md-typeset .grid.cards > ul > li, .md-typeset .md-button {
+ border-radius: 6px;
+}
+.md-typeset code {
+ border-radius: 4px;
+}
+
+/* inline code: the font alone marks it, no grey box; linked code keeps the link colour */
+.md-typeset :not(pre) > code {
+ padding: 0;
+ background-color: transparent;
+ box-shadow: none;
+ color: inherit;
+ font-size: .88em;
+}
+.md-typeset .highlight pre > code, .md-typeset .highlighttable {
+ border: 1px solid var(--md-default-fg-color--lightest);
+}
+
+/* callouts: 3 px left border; note/info/tip in blue on blue-soft, danger/bug in red on red-soft */
+.md-typeset .admonition, .md-typeset details {
+ border-width: 0 0 0 3px;
+ box-shadow: none;
+ font-size: .8rem;
+}
+.md-typeset :is(.admonition, details):is(.note, .info, .tip, .abstract, .example, .question) {
+ border-color: var(--nj-blue);
+ background-color: var(--nj-blue-soft);
+}
+.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary) {
+ background-color: transparent;
+}
+.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary)::before {
+ background-color: var(--nj-blue);
+}
+.md-typeset :is(.admonition, details):is(.danger, .bug, .failure) {
+ border-color: var(--nj-red);
+ background-color: var(--nj-red-soft);
+}
+.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary) {
+ background-color: transparent;
+}
+.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary)::before {
+ background-color: var(--nj-red);
+}
+
+/* buttons */
+.md-typeset .md-button--primary {
+ background-color: var(--nj-blue);
+ border-color: var(--nj-blue);
+}
+
+/* home page hero (overrides/home.html): blue band with the syntax pattern, headline, lead, buttons, code panel */
+.nj-hero {
+ --nj-hero-to: #123a8a;
+ color: #fff;
+ background-color: var(--md-primary-fg-color);
+ background-image: url("pattern-on-blue.svg"), linear-gradient(160deg, var(--md-primary-fg-color), var(--nj-hero-to));
+ background-size: 160px 160px, auto;
+ background-position: 0 -96px, 0 0; /* continue the tile of header and tabs */
+}
+[data-md-color-scheme="slate"] .nj-hero {
+ --nj-hero-to: #0c2556;
+}
+.nj-hero__inner {
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
+ gap: 2.4rem;
+ align-items: center;
+ padding: 3rem .8rem 3.6rem;
+}
+.nj-hero__kicker {
+ display: inline-block;
+ margin: 0;
+ padding: .15rem .55rem;
+ border: 1px solid rgba(255, 255, 255, .35);
+ border-radius: 4px;
+ color: #bcd3fb;
+ font-family: var(--md-code-font-family);
+ font-size: .65rem; /* 13 px */
+ font-weight: 500;
+ letter-spacing: .02em;
+}
+.nj-hero__title {
+ margin: 0 0 .8rem;
+ color: #fff;
+ font-size: clamp(1.7rem, 4.2vw, 3.2rem); /* 34-64 px */
+ font-weight: 800;
+ line-height: 1.02;
+ letter-spacing: -.035em;
+}
+.nj-hero__title span {
+ color: #bcd3fb;
+}
+.nj-hero__lead {
+ max-width: 22em;
+ margin: 0 0 2rem;
+ color: rgba(255, 255, 255, .9);
+ font-size: clamp(1.1rem, 2.2vw, 1.6rem); /* 22-32 px */
+ font-style: italic;
+ font-weight: 500;
+ line-height: 1.35;
+ letter-spacing: -.015em;
+}
+.nj-hero__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: .6rem;
+ margin: 0;
+}
+.nj-hero .md-button.nj-hero__button {
+ margin: 0;
+ padding: .55em 1.3em;
+ border: 1px solid rgba(255, 255, 255, .6);
+ border-radius: 6px;
+ color: #fff;
+ font-size: .75rem;
+ font-weight: 600;
+}
+.nj-hero .md-button--primary.nj-hero__button {
+ border-color: #fff;
+ background-color: #fff;
+ color: var(--md-primary-fg-color);
+}
+.nj-hero .md-button.nj-hero__button:is(:hover, :focus) {
+ border-color: #fff;
+ background-color: rgba(255, 255, 255, .12);
+ color: #fff;
+}
+.nj-hero .md-button--primary.nj-hero__button:is(:hover, :focus) {
+ background-color: #eaf1fd;
+ color: var(--md-primary-fg-color);
+}
+.nj-hero__code {
+ border-radius: 6px;
+ background-color: var(--md-code-bg-color);
+ box-shadow: 0 12px 32px rgba(5, 18, 48, .35);
+}
+.nj-hero__code pre {
+ margin: 0;
+ padding: 1.1rem 1.3rem;
+ overflow-x: auto;
+ color: var(--md-code-fg-color);
+ font-family: var(--md-code-font-family);
+ font-size: .7rem; /* 14 px */
+ line-height: 1.7;
+}
+.nj-hero__code .nj-k { color: var(--nj-hl-type); }
+.nj-hero__code .nj-s { color: var(--nj-hl-string); }
+.nj-hero__code .nj-n { color: var(--nj-hl-number); }
+.nj-hero__code .nj-c { color: var(--nj-comment); font-style: italic; }
+@media screen and (max-width: 59.984375em) {
+ .nj-hero__inner {
+ grid-template-columns: minmax(0, 1fr);
+ gap: 1.8rem;
+ padding: 2rem .8rem 2.4rem;
+ }
+}
+@media screen and (max-width: 29.984375em) {
+ .nj-hero__code pre {
+ font-size: .6rem;
+ }
+}
+
+/* the hero carries the headline: keep the page's h1 for the title and screen readers only */
+body:has(.nj-hero) .md-content__inner > h1 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ white-space: nowrap;
+}
+
+/* cards: 1 px line border, 6 px corners, blue icon on soft blue, blue border on hover */
+.md-typeset .grid.cards > ul > li {
+ padding: 1rem 1.1rem;
+ border: 1px solid var(--md-default-fg-color--lightest);
+ background-color: var(--md-default-bg-color);
+ transition: border-color 150ms, box-shadow 150ms, transform 150ms;
+}
+.md-typeset .grid.cards > ul > li:is(:hover, :focus-within) {
+ border-color: var(--nj-blue);
+ box-shadow: 0 6px 18px rgba(23, 71, 166, .12);
+ transform: translateY(-1px);
+}
+.md-typeset .grid.cards > ul > li > hr {
+ display: none;
+}
+.md-typeset .grid.cards .twemoji.lg {
+ box-sizing: content-box;
+ margin-right: .5rem;
+ padding: .3rem;
+ border-radius: 6px;
+ background-color: var(--nj-blue-soft);
+ color: var(--nj-blue);
+}
+.md-typeset .grid.cards > ul > li > p:first-child strong {
+ font-size: .9rem;
+ font-weight: 700;
+ letter-spacing: -.01em;
+}
+.md-typeset .grid.cards > ul > li > p:last-child a {
+ font-weight: 600;
+}
+@media (prefers-reduced-motion: reduce) {
+ .md-typeset .grid.cards > ul > li { transition: none; transform: none !important; }
+}
+
+/* home page: no table of contents, so keep the text at reading width and the cards in three columns */
+body:has(.nj-hero) .md-content {
+ max-width: 58rem;
+ margin: 0 auto;
+}
+@media screen and (min-width: 60em) {
+ body:has(.nj-hero) .md-typeset .grid.cards {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+/* lists: Material's indent, bullets as small blue squares, numbers in the secondary text colour */
+.md-typeset ul:not(.task-list, .md-annotation-list) > li {
+ position: relative;
+ list-style: none;
+}
+.md-typeset ul:not(.task-list, .md-annotation-list) > li::before {
+ content: "";
+ position: absolute;
+ top: .65em; /* middle of the first line at line-height 1.65 */
+ left: -.95em;
+ width: .36em;
+ height: .36em;
+ background-color: var(--nj-blue);
+}
+.md-typeset ol > li::marker {
+ color: var(--md-default-fg-color--light);
+ font-variant-numeric: tabular-nums;
+}
+.md-typeset .grid.cards > ul > li::before {
+ display: none;
+}
+
+/* wider page: Material's grid stops at 61rem (1220 px) */
+.md-grid {
+ max-width: 72rem; /* 1440 px */
+}
+
+/* tables: full width, 1 px line border with 6 px corners, header row on the panel colour, 15 px text */
+.md-typeset__table {
+ display: block; /* Material: inline-block, so tables only grow to their content */
+}
+.md-typeset table:not([class]) {
+ display: table;
+ width: 100%;
+ border: 1px solid var(--md-default-fg-color--lightest);
+ border-radius: 6px;
+ font-size: .75rem;
+ line-height: 1.55;
+}
+.md-typeset table:not([class]) th {
+ padding: .65em 1em;
+ border-bottom: 1px solid var(--md-default-fg-color--lightest);
+ background-color: var(--md-code-bg-color);
+ color: var(--md-typeset-color);
+ font-weight: 600;
+ vertical-align: bottom;
+}
+.md-typeset table:not([class]) th:first-child { border-top-left-radius: 5px; }
+.md-typeset table:not([class]) th:last-child { border-top-right-radius: 5px; }
+.md-typeset table:not([class]) td {
+ padding: .65em 1em;
+ border-top: 1px solid var(--md-default-fg-color--lightest);
+}
+.md-typeset table:not([class]) tbody tr:first-child td {
+ border-top: 0;
+}
+.md-typeset table:not([class]) tbody tr:hover {
+ background-color: var(--nj-blue-soft);
+ box-shadow: none;
+}
+.md-typeset table:not([class]) td code {
+ white-space: nowrap;
+}
+/* numbers and header code stay on one line; a table wider than the screen scrolls sideways instead */
+.md-typeset table:not([class]) :is(td, th):is([align="right"], [style*="right"]),
+.md-typeset table:not([class]) th code {
+ white-space: nowrap;
+}
+@media screen and (max-width: 44.984375em) {
+ .md-typeset table:not([class]) {
+ font-size: .7rem;
+ }
+ .md-typeset table:not([class]) :is(th, td) {
+ padding: .55em .75em;
+ }
+}
+
+/* more room before headings (Material: h2 1.6em, h3 1.6em, h4-h6 1-1.25em) and at the end of each page (1.2rem) */
+.md-typeset h2 { margin-top: 2.4em; }
+.md-typeset h3 { margin-top: 2.2em; }
+.md-typeset h2 + h3 { margin-top: .8em; }
+.md-typeset :is(h4, h5, h6) { margin-top: 1.8em; }
+.md-content__inner {
+ margin-bottom: 4.8rem; /* 96 px */
+}
+
+/* Mermaid diagrams: white boxes with a blue outline, grey lines, Inter; shapes and spacing in js/mermaid-brand.js */
+:root > * {
+ --md-mermaid-font-family: var(--md-text-font-family), sans-serif;
+ --md-mermaid-edge-color: var(--md-default-fg-color--lighter);
+ --md-mermaid-node-bg-color: var(--md-default-bg-color);
+ --md-mermaid-node-fg-color: var(--nj-blue);
+ --md-mermaid-label-bg-color: var(--md-default-bg-color);
+ --md-mermaid-label-fg-color: var(--md-default-fg-color);
+ --md-mermaid-sequence-actor-bg-color: var(--nj-blue-soft);
+ --md-mermaid-sequence-actor-line-color: var(--md-default-fg-color--lighter);
+ --md-mermaid-sequence-note-bg-color: var(--md-code-bg-color);
+ --md-mermaid-sequence-note-border-color: var(--md-default-fg-color--lightest);
+ --md-mermaid-sequence-number-fg-color: #fff;
+}
+
+/* a diagram wider than the column scrolls sideways (js/mermaid-brand.js stops shrinking it at 80 %) */
+.md-typeset .mermaid {
+ overflow-x: auto;
+}
+
+
+/* ---- polish pass ---- */
+
+/* header: a search field that reads as a field on the band, tabs with an active marker, readable GitHub facts */
+.md-header .md-search__form {
+ border: 1px solid rgba(255, 255, 255, .22);
+ border-radius: 6px;
+ background-color: rgba(255, 255, 255, .12);
+ transition: background-color 150ms, border-color 150ms;
+}
+.md-header .md-search__form:hover {
+ border-color: rgba(255, 255, 255, .4);
+ background-color: rgba(255, 255, 255, .2);
+}
+.md-search__output {
+ border-radius: 0 0 6px 6px;
+ box-shadow: 0 16px 40px rgba(5, 18, 48, .28);
+}
+.md-search-result__link:focus-visible, .md-search-result__more summary:focus-visible {
+ outline: 2px solid var(--nj-blue);
+ outline-offset: -2px;
+}
+.md-tabs__link {
+ font-size: .72rem;
+ font-weight: 500;
+ opacity: .85;
+}
+.md-tabs__item--active .md-tabs__link, .md-tabs__link:hover {
+ opacity: 1;
+}
+.md-tabs__item--active {
+ box-shadow: inset 0 -2px 0 #fff;
+}
+.md-source__facts {
+ font-size: .62rem;
+}
+
+/* footer: deep brand blue instead of neutral black (the dark scheme keeps its own near-black) */
+[data-md-color-scheme="default"] {
+ --md-footer-bg-color: #0d2a66;
+ --md-footer-bg-color--dark: #0a2152;
+}
+.md-footer-meta .md-copyright, .md-footer-meta .md-copyright a {
+ color: rgba(255, 255, 255, .72);
+}
+.md-footer__link {
+ border-radius: 6px;
+ transition: background-color 150ms;
+}
+.md-footer__link:is(:hover, :focus-visible) {
+ background-color: rgba(255, 255, 255, .08);
+}
+
+/* page title: tighter to the first paragraph; page actions (view/edit source) stay in the background */
+.md-typeset h1 {
+ margin-bottom: 1rem;
+}
+.md-content__button {
+ opacity: .55;
+ transition: opacity 150ms;
+}
+.md-content__button:is(:hover, :focus-visible) {
+ opacity: 1;
+}
+body:has(.nj-hero) .md-content__button {
+ display: none;
+}
+
+/* sidebars: bold marker for the current page, rail and marker for the table of contents */
+.md-nav__link--active {
+ font-weight: 600;
+}
+.md-nav--secondary > .md-nav__list {
+ margin-left: -.1rem;
+ border-left: 1px solid color-mix(in srgb, var(--md-default-fg-color) 16%, transparent);
+}
+.md-nav--secondary .md-nav__link {
+ margin-left: -1px;
+ padding-left: .6rem;
+ border-left: 2px solid transparent;
+}
+.md-nav--secondary .md-nav__link--active {
+ border-left-color: var(--nj-blue);
+ color: var(--nj-blue);
+}
+.md-nav--secondary .md-nav__list .md-nav__list {
+ padding-left: .6rem;
+}
+.md-nav--secondary .md-nav__title {
+ font-size: .64rem;
+ letter-spacing: .04em;
+ text-transform: uppercase;
+}
+
+/* keyboard focus: one visible ring in the brand blue */
+.md-typeset a:focus-visible, .md-typeset .md-button:focus-visible, .md-typeset summary:focus-visible,
+.md-nav__link:focus-visible, .md-tabs__link:focus-visible, .md-top:focus-visible {
+ outline: 2px solid var(--nj-blue);
+ outline-offset: 2px;
+ border-radius: 3px;
+}
+.md-header :is(a, button, label):focus-visible, .nj-hero a:focus-visible {
+ outline: 2px solid #fff;
+ outline-offset: 2px;
+}
+
+/* inline code must not stretch the line box (JetBrains Mono has tall metrics), abbreviations stay quiet */
+.md-typeset :not(pre) > code {
+ line-height: 1;
+}
+.md-typeset abbr {
+ border-bottom-color: var(--md-default-fg-color--lighter);
+}
+.md-typeset :is(h1, h2, h3, h4, a, .grid.cards > ul > li > p:first-child) abbr {
+ border-bottom-color: transparent;
+}
+
+/* number railroad diagram: inline SVG (docs/images/json_syntax_number.svg), follows the colour scheme */
+.md-typeset .railroad-wrap, .md-typeset .nj-fig-wrap {
+ overflow-x: auto;
+ margin: 1.5rem 0;
+}
+.md-typeset svg.railroad {
+ display: block;
+ width: 100%;
+ min-width: 36rem;
+ max-width: 52rem;
+ margin-inline: auto;
+ color: var(--md-default-fg-color);
+ font-family: var(--md-text-font-family), sans-serif;
+}
+.railroad .rr-t { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, transparent); stroke: var(--md-typeset-a-color); stroke-width: 2; }
+.railroad .rr-track, .railroad .rr-n { stroke: color-mix(in srgb, currentColor 60%, var(--md-default-bg-color)); stroke-opacity: 1; } /* opaque, so overlapping segments do not darken */
+.railroad .rr-n { fill: color-mix(in srgb, currentColor 6%, transparent); stroke-width: 2; }
+.railroad .rr-bg { fill: var(--md-default-bg-color); }
+.railroad .rr-arrow { fill: currentColor; fill-opacity: .6; }
+.railroad text { fill: currentColor; text-anchor: middle; }
+.railroad .rr-c { font-family: var(--md-code-font-family), monospace; font-size: 16px; font-weight: 500; }
+.railroad .rr-b { font-size: 14.5px; font-weight: 500; }
+.railroad .rr-l { font-size: 14px; text-anchor: start; fill-opacity: .6; }
+.railroad .rr-name { font-size: 17px; font-weight: 700; fill-opacity: 1; }
+/* figures drawn as inline SVG (docs/images/callback_events.svg, range-*.svg): they follow the colour scheme */
+.md-typeset svg.nj-fig {
+ display: block;
+ width: 100%;
+ height: auto;
+ margin-inline: auto;
+ color: var(--md-default-fg-color);
+ font-family: var(--md-text-font-family), sans-serif;
+}
+.md-typeset svg.nj-fig--range { min-width: 30rem; max-width: 40rem; }
+.md-typeset svg.nj-fig--cb { max-width: 30rem; }
+.nj-fig { --fg-line: color-mix(in srgb, currentColor 55%, var(--md-default-bg-color)); }
+.nj-fig .fg-cell { fill: color-mix(in srgb, var(--md-typeset-a-color) 14%, var(--md-default-bg-color)); stroke: var(--fg-line); stroke-width: 1.5; }
+.nj-fig .fg-cell--end { fill: none; stroke-dasharray: 4 3; }
+.nj-fig .fg-pill { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1.5; }
+.nj-fig .fg-pilltext { fill: var(--md-typeset-a-color); font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 500; text-anchor: middle; }
+.nj-fig .fg-arrow { stroke: var(--fg-line); stroke-width: 1.5; fill: none; }
+.nj-fig .fg-arrow--dash { stroke-dasharray: 3 3; }
+.nj-fig .fg-head { fill: var(--fg-line); }
+.nj-fig .fg-note { fill: currentColor; fill-opacity: .7; font-size: 13.5px; text-anchor: middle; }
+.nj-fig .fg-note--r { text-anchor: end; }
+.nj-fig .fg-note--l { text-anchor: start; }
+.nj-fig .fg-h { fill: currentColor; fill-opacity: .7; font-size: 15px; font-weight: 600; }
+.nj-fig .fg-code { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 16px; white-space: pre; }
+.nj-fig .fg-leg { font-size: 15px; }
+.nj-fig { --cb-os: #4eb05a; --cb-oe: #1a6b34; --cb-k: #e5534b; --cb-as: #f0c000; --cb-ae: #b07d00; --cb-v: #1f6fd1; }
+[data-md-color-scheme="slate"] .nj-fig { --cb-os: #7ee787; --cb-oe: #2ea043; --cb-k: #ff7b72; --cb-as: #f2cc60; --cb-ae: #c69026; --cb-v: #58a6ff; }
+.nj-fig .cb-os { fill: var(--cb-os); }
+.nj-fig .cb-oe { fill: var(--cb-oe); }
+.nj-fig .cb-k { fill: var(--cb-k); }
+.nj-fig .cb-as { fill: var(--cb-as); }
+.nj-fig .cb-ae { fill: var(--cb-ae); }
+.nj-fig .cb-v { fill: var(--cb-v); }
+.md-typeset img[src*="json-light"], .md-typeset img[src*="json-dark"] {
+ border-radius: 8px;
+}
+.md-typeset img {
+ max-width: 100%;
+ height: auto;
+}
+
+/* home page: content uses the same column as every other page */
+body:has(.nj-hero) .md-content {
+ max-width: none;
+ margin: 0;
+}
+.md-typeset .grid.cards {
+ width: auto;
+}
+
+/* 404 page (overrides/404.html) */
+.nj-404 {
+ max-width: 30rem;
+ padding: 2rem 0 3rem;
+}
+.nj-404__code {
+ margin: 0 0 .4rem;
+ color: var(--nj-blue);
+ font-family: var(--md-code-font-family);
+ font-size: .8rem;
+ font-weight: 500;
+}
+.md-typeset .nj-404 h1 {
+ margin-bottom: .8rem;
+}
+.nj-404 .md-button {
+ margin: .4rem .4rem 0 0;
+}
+
+/* touch targets and phones */
+@media screen and (max-width: 59.984375em) {
+ .md-nav__link {
+ padding-block: .2rem;
+ }
+}
+@media screen and (max-width: 29.984375em) {
+ .md-typeset h1 {
+ font-size: 1.7em;
+ }
+ .md-typeset .highlight pre > code {
+ font-size: .68rem;
+ }
+}
diff --git a/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2
new file mode 100644
index 000000000..b3530f3f5
Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 differ
diff --git a/docs/mkdocs/docs/css/fonts/Inter.woff2 b/docs/mkdocs/docs/css/fonts/Inter.woff2
new file mode 100644
index 000000000..21d9a7f9e
Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter.woff2 differ
diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2
new file mode 100644
index 000000000..d60c270e8
Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 differ
diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2
new file mode 100644
index 000000000..6b604cec6
Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 differ
diff --git a/docs/mkdocs/docs/css/pattern-on-blue.svg b/docs/mkdocs/docs/css/pattern-on-blue.svg
new file mode 100644
index 000000000..1c3c16de3
--- /dev/null
+++ b/docs/mkdocs/docs/css/pattern-on-blue.svg
@@ -0,0 +1 @@
+{ ] : [ , } : {
diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md
index 9d918506f..4ead2aa4a 100644
--- a/docs/mkdocs/docs/features/arbitrary_types.md
+++ b/docs/mkdocs/docs/features/arbitrary_types.md
@@ -124,20 +124,20 @@ For all the macros, the first parameter is the name of the class/struct. The `DE
```mermaid
flowchart TD
- A["choosing a NLOHMANN_DEFINE_* macro"] --> B{"adding fields to a base class?"}
+ A["choosing a NLOHMANN_DEFINE_* macro"] --> B{{"adding fields to a base class?"}}
B -->|"yes"| C["...DERIVED_TYPE..."]
B -->|"no"| D["...TYPE..."]
- C --> E{"need access to private members?"}
+ C --> E{{"need access to private members?"}}
D --> E
E -->|"yes"| F["...INTRUSIVE... (used inside the class)"]
E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"]
- F --> H{"only serializing, never parsing back?"}
+ F --> H{{"only serializing, never parsing back?"}}
G --> H
H -->|"yes"| I["...ONLY_SERIALIZE"]
- H -->|"no"| J{"allow missing keys when parsing?"}
+ H -->|"no"| J{{"allow missing keys when parsing?"}}
J -->|"yes"| K["...WITH_DEFAULT"]
J -->|"no"| L["plain (missing keys throw)"]
- I --> M{"need custom JSON key names?"}
+ I --> M{{"need custom JSON key names?"}}
K --> M
L --> M
M -->|"yes"| N["...WITH_NAMES"]
diff --git a/docs/mkdocs/docs/features/element_access/index.md b/docs/mkdocs/docs/features/element_access/index.md
index 4ec625a20..bb1029715 100644
--- a/docs/mkdocs/docs/features/element_access/index.md
+++ b/docs/mkdocs/docs/features/element_access/index.md
@@ -14,11 +14,11 @@ the value, or `end()` if it is not found).
```mermaid
flowchart TD
- A["accessing a value"] --> B{"must it exist?"}
+ A["accessing a value"] --> B{{"must it exist?"}}
B -->|"yes, missing is an error"| C["at() -- throws"]
B -->|"yes, but checking is my job"| D["operator[] -- unchecked"]
B -->|"no, a fallback is fine"| E["value() -- default value"]
- A --> F{"just testing first?"}
+ A --> F{{"just testing first?"}}
F -->|"yes"| G["contains() / find()"]
```
diff --git a/docs/mkdocs/docs/features/iterators.md b/docs/mkdocs/docs/features/iterators.md
index de493dd72..4523d2775 100644
--- a/docs/mkdocs/docs/features/iterators.md
+++ b/docs/mkdocs/docs/features/iterators.md
@@ -9,7 +9,9 @@ As for other containers, [`begin()`](../api/basic_json/begin.md) returns an iter
placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return
`end()`.
-
+
+--8<-- "images/range-begin-end.svg"
+
### Iteration order for objects
@@ -112,7 +114,9 @@ for (auto& [key, val] : j_object.items())
[`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse
sequence.
-
+
+--8<-- "images/range-rbegin-rend.svg"
+
??? example "Example: reverse iteration with `rbegin()` and `rend()`"
diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md
index d11ad2747..d5e4ca6a5 100644
--- a/docs/mkdocs/docs/features/types/number_handling.md
+++ b/docs/mkdocs/docs/features/types/number_handling.md
@@ -27,7 +27,9 @@ JSON defines the syntax of numbers as follows:
The following railroad diagram from [json.org](https://json.org) visualizes the number syntax:
-
+
+--8<-- "images/json_syntax_number.svg"
+
### Number interoperability
@@ -69,9 +71,9 @@ otherwise, it uses unsigned integer storage.
```mermaid
flowchart TD
- A["number literal"] --> B{"has a fraction (.) or exponent (e/E)?"}
+ A["number literal"] --> B{{"has a fraction (.) or exponent (e/E)?"}}
B -->|"yes"| F["number_float_t"]
- B -->|"no"| C{"has a leading minus sign?"}
+ B -->|"no"| C{{"has a leading minus sign?"}}
C -->|"yes"| D["try number_integer_t"]
C -->|"no"| E["try number_unsigned_t"]
D -->|"overflow"| F
diff --git a/docs/mkdocs/docs/images/callback_events.png b/docs/mkdocs/docs/images/callback_events.png
deleted file mode 100644
index 09aa2b383..000000000
Binary files a/docs/mkdocs/docs/images/callback_events.png and /dev/null differ
diff --git a/docs/mkdocs/docs/images/callback_events.svg b/docs/mkdocs/docs/images/callback_events.svg
new file mode 100644
index 000000000..544bf4f6a
--- /dev/null
+++ b/docs/mkdocs/docs/images/callback_events.svg
@@ -0,0 +1,39 @@
+
+Example of the parse events a callback sees for the JSON text {"name": "Berlin", "location": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.
+JSON to parse
+callback events
+{
+
+"name"
+
+: "Berlin"
+
+,
+"location"
+
+: [
+
+52.519444
+
+,
+13.406667
+
+]
+
+
+}
+
+
+
+object_start
+
+key
+
+object_end
+
+array_start
+
+array_end
+
+value
+
diff --git a/docs/mkdocs/docs/images/json-dark.svg b/docs/mkdocs/docs/images/json-dark.svg
new file mode 100644
index 000000000..5757e976e
--- /dev/null
+++ b/docs/mkdocs/docs/images/json-dark.svg
@@ -0,0 +1 @@
+JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each)
\ No newline at end of file
diff --git a/docs/mkdocs/docs/images/json-light.svg b/docs/mkdocs/docs/images/json-light.svg
new file mode 100644
index 000000000..97bd55bed
--- /dev/null
+++ b/docs/mkdocs/docs/images/json-light.svg
@@ -0,0 +1 @@
+JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each)
\ No newline at end of file
diff --git a/docs/mkdocs/docs/images/json_syntax_number.png b/docs/mkdocs/docs/images/json_syntax_number.png
deleted file mode 100644
index be23ffa69..000000000
Binary files a/docs/mkdocs/docs/images/json_syntax_number.png and /dev/null differ
diff --git a/docs/mkdocs/docs/images/json_syntax_number.svg b/docs/mkdocs/docs/images/json_syntax_number.svg
new file mode 100644
index 000000000..3a837a08c
--- /dev/null
+++ b/docs/mkdocs/docs/images/json_syntax_number.svg
@@ -0,0 +1,66 @@
+
+Railroad diagram of the JSON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+−
+0
+digit 1–9
+digit
+.
+digit
+e
+E
+−
++
+digit
+number
+fraction
+exponent
+
diff --git a/docs/mkdocs/docs/images/range-begin-end.svg b/docs/mkdocs/docs/images/range-begin-end.svg
index 99c9f8662..61a4cc721 100644
--- a/docs/mkdocs/docs/images/range-begin-end.svg
+++ b/docs/mkdocs/docs/images/range-begin-end.svg
@@ -1,447 +1,28 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Past-the-last element
-
-
-
- begin
-
-
-
- end
-
-
-
-
+
+A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+begin
+
+
+end
+
+Past-the-last element
+
diff --git a/docs/mkdocs/docs/images/range-rbegin-rend.svg b/docs/mkdocs/docs/images/range-rbegin-rend.svg
index 9667efd89..1e91f7158 100644
--- a/docs/mkdocs/docs/images/range-rbegin-rend.svg
+++ b/docs/mkdocs/docs/images/range-rbegin-rend.svg
@@ -1,1244 +1,54 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Past-the-last element
-
-
-
- begin
-
-
-
- end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Reversed sequence
-
- Reverse p ast-the-last element
-
-
-
-
-
-
-
- rend
-
-
-
- rbegin
-
-
- Reverse iterator stores an iterator to the next element than the one it actually refers to
-
-
-
-
-
-
-
-
+
+Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+rend
+
+
+rbegin
+
+Reverse past-the-last element
+
+
+A reverse iterator stores an iterator to the next element
+
+
+begin
+
+
+end
+
+Past-the-last element
diff --git a/docs/mkdocs/docs/images/wordmark-header.svg b/docs/mkdocs/docs/images/wordmark-header.svg
new file mode 100644
index 000000000..c69773e72
--- /dev/null
+++ b/docs/mkdocs/docs/images/wordmark-header.svg
@@ -0,0 +1 @@
+nlohmann/json
\ No newline at end of file
diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md
index f53b8dcab..51d3c97a6 100644
--- a/docs/mkdocs/docs/index.md
+++ b/docs/mkdocs/docs/index.md
@@ -1,63 +1,11 @@
+---
+template: home.html
+hide:
+ - toc
+---
+
# JSON for Modern C++
-
-
-JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-class C++ data type, using the operator
-magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like
-Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no
-complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
-down, `` holds the user-defined string literals on their own, and
-`` adds zero-copy views and editable documents. See
-[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
-coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
-OSS-Fuzz.
-
-## Quick start
-
-Add the single header to your project and use the library like this:
-
-```cpp
-#include
-#include
-
-using nlohmann::json;
-
-int main()
-{
- // parse a JSON string
- json j = json::parse(R"({"happy": true, "pi": 3.141})");
-
- // access and modify values
- j["name"] = "Niels";
- j["list"] = {1, 0, 2};
-
- // read a value into a C++ variable and use it
- double pi = j["pi"].get();
- std::cout << "2 * pi = " << 2 * pi << '\n';
-
- // serialize with an indent of 4 spaces
- std::cout << j.dump(4) << '\n';
-}
-```
-
-Get the library by copying the single header [`json.hpp`](https://github.com/nlohmann/json/releases) from the
-releases page into a directory `nlohmann` on your include path, or by installing it with a package manager:
-
-```sh
-brew install nlohmann-json # Homebrew
-vcpkg install nlohmann-json # vcpkg
-```
-
-```cmake
-find_package(nlohmann_json 3.12.0 REQUIRED)
-target_link_libraries(myproject PRIVATE nlohmann_json::nlohmann_json)
-```
-
-See [Integration](integration/index.md) for CMake in detail, all supported package managers (Conan, Meson, Bazel,
-Conda, and more), and pkg-config.
-
-## Explore the documentation
-
- :octicons-rocket-24:{ .lg .middle } __Features__
@@ -112,11 +60,5 @@ Conda, and more), and pkg-config.
-!!! info "Unreleased changes"
-
- This documentation is built from the `develop` branch and may describe changes that are not part of a release
- yet. Their version numbers are followed by an unreleased badge; see
- [Releases](home/releases/index.md) for what shipped in each version.
-
-The library is licensed under the [MIT License](home/license.md). The source code, issue tracker, and discussions
-are on [GitHub](https://github.com/nlohmann/json).
+
+
diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md
index 6b461ff99..5993b0e23 100644
--- a/docs/mkdocs/docs/integration/index.md
+++ b/docs/mkdocs/docs/integration/index.md
@@ -5,13 +5,13 @@ pick one:
```mermaid
flowchart TD
- A[Add the library to a C++ project] --> B{Already using CMake?}
- B -- no --> C{Using pkg-config or plain Makefiles?}
+ A[Add the library to a C++ project] --> B{{Already using CMake?}}
+ B -- no --> C{{Using pkg-config or plain Makefiles?}}
C -- yes --> D[pkg-config]
C -- no --> E[Copy the single header]
- B -- yes --> F{Library installed system-wide?}
+ B -- yes --> F{{Library installed system-wide?}}
F -- yes --> G["find_package()"]
- F -- no --> H{Use a package manager?}
+ F -- no --> H{{Use a package manager?}}
H -- yes --> I[Package manager]
H -- no --> J["add_subdirectory() or FetchContent"]
```
diff --git a/docs/mkdocs/docs/js/mermaid-brand.js b/docs/mkdocs/docs/js/mermaid-brand.js
new file mode 100644
index 000000000..2605db4c5
--- /dev/null
+++ b/docs/mkdocs/docs/js/mermaid-brand.js
@@ -0,0 +1,100 @@
+/*
+ * Brand look for Mermaid diagrams.
+ *
+ * Material for MkDocs loads Mermaid on demand and calls mermaid.initialize() with its own theme CSS. This script
+ * catches Mermaid when it arrives on window.mermaid and wraps two calls:
+ * - initialize(): adds the brand's font, spacing and shape rules below to Material's options;
+ * - render(): marks hexagons ({{...}} in a flowchart) with the class "nj-decision", since in the SVG a hexagon and
+ * a parallelogram are both a that CSS cannot tell apart; and keeps a wide diagram at 80 % of its size
+ * or more, so its text stays readable and the diagram scrolls sideways instead (see .mermaid in brand.css).
+ * The colours come from the --md-mermaid-* variables in brand.css, so the diagrams follow the light/dark toggle.
+ */
+(function () {
+ "use strict";
+
+ var css = [
+ // text: Inter everywhere, labels on boxes semibold
+ ".label, .nodeLabel, .nodeLabel p, .edgeLabel, .edgeLabel p, .cluster span, text { font-family: var(--md-mermaid-font-family) !important; }",
+ ".nodeLabel, .nodeLabel p { font-weight: 600; }",
+ ".edgeLabel, .edgeLabel p { color: var(--md-default-fg-color--light) !important; font-size: 13px; font-weight: 500; }",
+
+ // boxes: 1.5 px outline, 6 px corners; "(...)" nodes stay rounder than "[...]" nodes
+ ".node rect, .node path, .node polygon, .node circle, .node ellipse { stroke-width: 1.5px !important; }",
+ ".node rect:not([rx]) { rx: 6px; ry: 6px; }",
+ ".node rect[rx] { rx: 14px; ry: 14px; }",
+
+ // decisions: solid blue hexagons with white text
+ ".node.nj-decision polygon { fill: var(--nj-band) !important; stroke: var(--nj-band) !important; stroke-linejoin: round; }",
+ ".node.nj-decision .nodeLabel, .node.nj-decision .nodeLabel p { color: #fff !important; }",
+
+ // lines: 1.5 px, solid unless written as -.-
+ ".edgePath .path, .flowchart-link { stroke-width: 1.5px !important; }",
+ "marker path { stroke: none !important; }",
+
+ // class diagrams; a node with its own fill ("style X fill:#CCCCFF" on the exception pages) is the highlighted
+ // one and gets soft blue, which also works in dark mode
+ "g.classGroup rect, .classGroup line, g.node.default > g > path { stroke-width: 1.5px !important; }",
+ ".node path[style*='fill'], .node rect[style*='fill'] { fill: var(--nj-blue-soft) !important; }",
+ ".relation { stroke-width: 1.5px !important; }",
+
+ // sequence diagrams
+ ".actor { stroke-width: 1.5px !important; rx: 6px; ry: 6px; }",
+ ".actor-line { stroke-width: 1px; }",
+ ".messageLine0, .messageLine1 { stroke-width: 1.5px !important; }",
+ ".note { rx: 6px; ry: 6px; }"
+ ].join("\n");
+
+ var options = {
+ theme: "base",
+ fontFamily: "Inter, sans-serif",
+ themeVariables: { fontFamily: "Inter, sans-serif", fontSize: "15px" },
+ flowchart: { curve: "basis", padding: 12, nodeSpacing: 36, rankSpacing: 48, wrappingWidth: 240 }
+ };
+
+ var minScale = 0.8;
+
+ function postProcess(svg) {
+ // shrink to fit the column, but not below minScale
+ svg = svg.replace(/^(]*style="[^"]*)max-width:\s*([\d.]+)px;/, function (all, head, width) {
+ return head + "max-width: " + width + "px; min-width: " + Math.round(width * minScale) + "px;";
+ });
+ // add class "nj-decision" to every flowchart node drawn as a hexagon (a polygon with six corners)
+ return svg.replace(/]*)>
+ // error 404
+ This page does not exist
+ The page may have moved or been renamed. Try the search in the header, or start from one of these pages.
+
+ Home
+ Features
+ API documentation
+
+
+{% endblock %}
diff --git a/docs/mkdocs/overrides/home.html b/docs/mkdocs/overrides/home.html
new file mode 100644
index 000000000..5fb50dfbc
--- /dev/null
+++ b/docs/mkdocs/overrides/home.html
@@ -0,0 +1,33 @@
+{#- Home page: the brand's blue hero band below the tabs (selected with "template: home.html" in index.md) -#}
+{% extends "main.html" %}
+
+{% block tabs %}
+ {{ super() }}
+
+
+
+
JSON for Modern C++
+
What if JSON was part of modern C++?
+
+ Get started
+ API documentation
+
+
+
+
#include <iostream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+int main()
+{
+ json j = json::parse(R"({"happy": true})" );
+ j["pi" ] = 3.141 ;
+ j["list" ] = {1 , 0 , 2 };
+
+ std::cout << j.dump(4 ) << "\n" ;
+}
+
+
+
+{% endblock %}
diff --git a/docs/mkdocs/scripts/figures/iterator_callback_figures.py b/docs/mkdocs/scripts/figures/iterator_callback_figures.py
new file mode 100644
index 000000000..49d92ab28
--- /dev/null
+++ b/docs/mkdocs/scripts/figures/iterator_callback_figures.py
@@ -0,0 +1,103 @@
+"""Draws the parser-callback and iterator illustrations as an inline SVG into docs/images/.
+
+The SVG is not an image: the pages include its text with `--8<-- "images/callback_events.svg, range-begin-end.svg and range-rbegin-rend.svg"`, so it picks up the colours and fonts of the
+page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/iterator_callback_figures.py` from docs/mkdocs after
+changing the drawing, and commit the generated file. In SVG text write the word JSON as `JSON`, otherwise the
+abbreviation extension wraps it in an and the text disappears.
+"""
+import html
+from pathlib import Path
+
+OUT = Path(__file__).resolve().parents[2] / "docs" / "images"
+CW = 9.6 # JetBrains Mono advance at 16px
+
+def write(name, W, H, cls, tid, title, body, defs=""):
+ s = ['' % (cls, W, H, tid),
+ '%s ' % (tid, html.escape(title).replace('J​SON', 'JSON'))]
+ if defs: s.append(defs)
+ s += body
+ s.append(' ')
+ open(OUT / name, "w").write("\n".join(s) + "\n")
+
+# ---------- callback events ----------
+def callback():
+ body, y0, lh = [], 74, 28
+ body.append('JSON to parse ')
+ body.append('callback events ')
+ lines = [
+ (0, [("{", None), (None, "os")]),
+ (2, [('"name"', None), (None, "k"), (': "Berlin"', None), (None, "v"), (",", None)]),
+ (2, [('"location"', None), (None, "k"), (": [", None), (None, "as")]),
+ (4, [("52.519444", None), (None, "v"), (",", None)]),
+ (4, [("13.406667", None), (None, "v")]),
+ (2, [("]", None), (None, "ae"), (None, "v")]),
+ (0, [("}", None), (None, "oe"), (None, "v")]),
+ ]
+ for i, (indent, parts) in enumerate(lines):
+ y = y0 + i * lh
+ x = 16 + indent * CW
+ for text, ev in parts:
+ if text is not None:
+ body.append('%s ' % (x, y, html.escape(text)))
+ x += CW * len(text)
+ else:
+ body.append(' ' % (ev, x + 10, y - 5))
+ x += 20 if text is None else 0
+ legend = [("os", "object_start"), ("k", "key"), ("oe", "object_end"), ("as", "array_start"), ("ae", "array_end"), ("v", "value")]
+ for i, (ev, label) in enumerate(legend):
+ y = 74 + i * 30
+ body.append(' ' % (ev, y - 5))
+ body.append('%s ' % (y, label))
+ write("callback_events.svg", 480, 262, "nj-fig--cb", "fig-cb-title",
+ "Example of the parse events a callback sees for the JSON text {\"name\": \"Berlin\", \"location\": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.", body)
+
+# ---------- ranges ----------
+BW, GAP, X0 = 30, 4, 30
+def cell_x(i): return X0 + i * (BW + GAP)
+def cell(i, y, kind):
+ c = "fg-cell" + (" fg-cell--end" if kind == "end" else "")
+ return ' ' % (c, cell_x(i), y, BW)
+def pill(cx, y, label, W, label_w):
+ x = max(4, min(W - 4 - label_w, cx - label_w / 2))
+ return [' ' % (x, y, label_w),
+ '%s ' % (x + label_w / 2, y + 16.5, label)]
+def arrow(x, ya, yb, mid, dashed=False):
+ return ' ' % (" fg-arrow--dash" if dashed else "", x, ya, x, yb, mid)
+
+def ranges1():
+ W, H, mid = 600, 150, "fig-be-arrow"
+ defs = ' ' % mid
+ body = []
+ for i in range(15): body.append(cell(i, 62, "elem"))
+ body.append(cell(15, 62, "end"))
+ body += pill(cell_x(0) + BW / 2, 8, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid))
+ body += pill(cell_x(15) + BW / 2, 8, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid))
+ body.append('Past-the-last element ' % (cell_x(15) + BW / 2 - 18))
+ body.append(arrow(cell_x(15) + BW / 2, 124, 96, mid))
+ write("range-begin-end.svg", W, H, "nj-fig--range", "fig-be-title",
+ "A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.", body, defs)
+
+def ranges2():
+ W, H, mid = 600, 236, "fig-rbe-arrow"
+ defs = ' ' % mid
+ body = []
+ ytop, ybot = 62, 144
+ body.append(cell(0, ytop, "end"))
+ for i in range(1, 16): body.append(cell(i, ytop, "elem"))
+ for i in range(15): body.append(cell(i, ybot, "elem"))
+ body.append(cell(15, ybot, "end"))
+ body += pill(cell_x(0) + BW / 2, 8, "rend", W, 52); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid))
+ body += pill(cell_x(15) + BW / 2, 8, "rbegin", W, 68); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid))
+ body.append('Reverse past-the-last element ' % (cell_x(0) + BW / 2 + 36))
+ for col in (0, 15):
+ x = cell_x(col) + BW / 2
+ body.append(' ' % (x, x, ybot - 2))
+ body.append('A reverse iterator stores an iterator to the next element ')
+ body.append(' ')
+ body += pill(cell_x(0) + BW / 2, 204, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 202, 178, mid))
+ body += pill(cell_x(15) + BW / 2, 204, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 202, 178, mid))
+ body.append('Past-the-last element ' % (cell_x(15) + BW / 2 - 40))
+ write("range-rbegin-rend.svg", W, H, "nj-fig--range", "fig-rbe-title",
+ "Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().", body, defs)
+
+callback(); ranges1(); ranges2()
diff --git a/docs/mkdocs/scripts/figures/railroad_number.py b/docs/mkdocs/scripts/figures/railroad_number.py
new file mode 100644
index 000000000..033bab87b
--- /dev/null
+++ b/docs/mkdocs/scripts/figures/railroad_number.py
@@ -0,0 +1,76 @@
+"""Draws the JSON number railroad diagram as an inline SVG into docs/images/.
+
+The SVG is not an image: the pages include its text with `--8<-- "images/json_syntax_number.svg"`, so it picks up the colours and fonts of the
+page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/railroad_number.py` from docs/mkdocs after
+changing the drawing, and commit the generated file. In SVG text write the word JSON as `JSON`, otherwise the
+abbreviation extension wraps it in an and the text disappears.
+"""
+import math
+from pathlib import Path
+
+OUT = Path(__file__).resolve().parents[2] / "docs" / "images"
+Y0, Y1, Y2, Y3 = 44, 92, 136, 180
+R = 10
+paths, shapes, texts = [], [], []
+
+def rounded(pts, r=R):
+ d = "M%g %g" % pts[0]
+ for i in range(1, len(pts) - 1):
+ (x0, y0), (x1, y1), (x2, y2) = pts[i-1], pts[i], pts[i+1]
+ l1 = math.hypot(x1-x0, y1-y0); l2 = math.hypot(x2-x1, y2-y1)
+ rr = min(r, l1/2, l2/2)
+ ax, ay = x1 - (x1-x0)/l1*rr, y1 - (y1-y0)/l1*rr
+ bx, by = x1 + (x2-x1)/l2*rr, y1 + (y2-y1)/l2*rr
+ d += " L%g %g Q%g %g %g %g" % (ax, ay, x1, y1, bx, by)
+ d += " L%g %g" % pts[-1]
+ return d
+
+def path(*pts): paths.append(rounded(list(pts)))
+def circle(cx, cy, ch):
+ shapes.append(' ' % (cx, cy))
+ shapes.append(' ' % (cx, cy))
+ texts.append('%s ' % (cx, cy+5, ch))
+def box(x0, x1, cy, label):
+ shapes.append(' ' % (x0, cy-14, x1-x0))
+ shapes.append(' ' % (x0, cy-14, x1-x0))
+ texts.append('%s ' % ((x0+x1)/2, cy+5, label))
+def loop(xr, xl, cy): # return path below a box row
+ path((xr, cy), (xr, cy+34), (xl, cy+34), (xl, cy))
+
+# start / main
+path((8, Y0), (850, Y0))
+# A: optional minus
+path((24, Y0), (38, Y0), (38, Y1), (59, Y1)); circle(72, Y1, "−"); path((85, Y1), (106, Y1), (106, Y0), (120, Y0))
+# B: 0 | 1-9 digit*
+circle(257, Y0, "0")
+path((120, Y0), (134, Y0), (134, Y1), (160, Y1)); box(160, 242, Y1, "digit 1–9")
+path((242, Y1), (380, Y1), (380, Y0), (396, Y0))
+path((242, Y1), (258, Y1), (258, Y2), (278, Y2)); box(278, 332, Y2, "digit")
+path((332, Y2), (348, Y2), (348, Y1)); loop(340, 270, Y2)
+# C: fraction
+path((396, Y0), (410, Y0), (410, Y1), (425, Y1)); circle(438, Y1, ".")
+path((451, Y1), (462, Y1)); box(462, 516, Y1, "digit"); loop(524, 456, Y1)
+path((516, Y1), (530, Y1), (530, Y0), (546, Y0))
+# D: exponent
+path((546, Y0), (560, Y0), (560, Y1), (577, Y1)); circle(590, Y1, "e")
+path((546, Y0), (560, Y0), (560, Y2), (577, Y2)); circle(590, Y2, "E")
+path((603, Y1), (728, Y1)); path((603, Y2), (620, Y2), (620, Y1))
+path((640, Y1), (654, Y1), (654, Y2), (670, Y2)); circle(683, Y2, "−")
+path((640, Y1), (654, Y1), (654, Y3), (670, Y3)); circle(683, Y3, "+")
+path((696, Y2), (712, Y2), (712, Y1)); path((696, Y3), (712, Y3), (712, Y1))
+path((728, Y1), (746, Y1)); box(746, 800, Y1, "digit"); loop(808, 738, Y1)
+path((800, Y1), (816, Y1), (816, Y0), (832, Y0))
+
+svg = ['',
+ 'Railroad diagram of the JSON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits). ',
+ '']
+for d in paths: svg.append(' ' % d)
+svg.append(' ')
+svg.append(' ' % (Y0-6, Y0, Y0+6))
+svg.append(' ' % (Y0-6, Y0, Y0+6))
+svg += shapes + texts
+svg.append('number ' % (Y0-12))
+svg.append('fraction ' % (Y0-12))
+svg.append('exponent ' % (Y0-12))
+svg.append(' ')
+open(OUT / "json_syntax_number.svg", "w").write("\n".join(svg) + "\n")
diff --git a/docs/mkdocs/scripts/slideshow/.gitignore b/docs/mkdocs/scripts/slideshow/.gitignore
new file mode 100644
index 000000000..0480ebe75
--- /dev/null
+++ b/docs/mkdocs/scripts/slideshow/.gitignore
@@ -0,0 +1 @@
+fonts/
diff --git a/docs/mkdocs/scripts/slideshow/Makefile b/docs/mkdocs/scripts/slideshow/Makefile
new file mode 100644
index 000000000..ff343ef4e
--- /dev/null
+++ b/docs/mkdocs/scripts/slideshow/Makefile
@@ -0,0 +1,73 @@
+# Rebuild the animated feature slideshow of the home page (docs/images/json-light.svg and json-dark.svg) when
+# slides.md, build.py or the fonts change. Needs uv (build.py declares its dependencies inline, PEP 723), curl,
+# unzip and shasum. Run it from docs/mkdocs/scripts/slideshow.
+#
+# make download missing fonts and build both SVGs
+# make fonts download only the fonts
+# make SECONDS=12 change the time per slide
+# make clean remove the generated SVGs
+# make distclean also remove the downloaded fonts
+
+SECONDS ?= 15
+
+IMAGES := ../../docs/images
+LIGHT := $(IMAGES)/json-light.svg
+DARK := $(IMAGES)/json-dark.svg
+
+RUN := uv run --script build.py
+FONTS := fonts/Inter.ttf fonts/JetBrainsMono.ttf
+DEPS := build.py slides.md $(FONTS)
+
+# Pinned sources; the checksums make a changed upstream file fail instead of silently changing the output.
+# Inter: Google Fonts repository at a fixed commit (variable font with opsz and wght axes).
+INTER_URL := https://raw.githubusercontent.com/google/fonts/0b58fb370093f9a9f4ff785d94405710b79de67c/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf
+# JetBrains Mono: release 2.304 (the variable font in fonts/variable/ of the zip).
+JBM_URL := https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip
+JBM_ZIP := fonts/JetBrainsMono-2.304.zip
+
+SHA256_Inter.ttf := 29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031
+SHA256_JetBrainsMono.ttf := 662a196d58f1183bf2d77428b6d5283fe3f45161ab021bea4036bc98e5cac016
+SHA256_JetBrainsMono-2.304.zip := 6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf
+
+# Check $@.tmp against its pinned checksum, then move it into place.
+define verify
+ echo '$(SHA256_$(notdir $@)) $@.tmp' | shasum -a 256 -c --quiet - || { rm -f $@.tmp; exit 1; }
+ mv $@.tmp $@
+endef
+
+.PHONY: all fonts clean distclean
+.INTERMEDIATE: $(JBM_ZIP)
+
+all: $(LIGHT) $(DARK)
+
+fonts: $(FONTS)
+
+# fixed colours for the docs: an cannot see Material's theme toggle, so the home page includes both
+# and shows the one that matches
+$(LIGHT): $(DEPS)
+ $(RUN) $@.tmp $(SECONDS) light
+ mv $@.tmp $@
+
+$(DARK): $(DEPS)
+ $(RUN) $@.tmp $(SECONDS) dark
+ mv $@.tmp $@
+
+fonts/Inter.ttf:
+ mkdir -p fonts
+ curl -fsSL -m 300 -o $@.tmp '$(INTER_URL)'
+ $(verify)
+
+$(JBM_ZIP):
+ mkdir -p fonts
+ curl -fsSL -m 300 -o $@.tmp '$(JBM_URL)'
+ $(verify)
+
+fonts/JetBrainsMono.ttf: | $(JBM_ZIP)
+ unzip -p $(JBM_ZIP) 'fonts/variable/JetBrainsMono[[]wght].ttf' > $@.tmp
+ $(verify)
+
+clean:
+ rm -f $(LIGHT) $(DARK) $(IMAGES)/*.svg.tmp
+
+distclean: clean
+ rm -f $(FONTS) fonts/*.tmp $(JBM_ZIP)
diff --git a/docs/mkdocs/scripts/slideshow/README.md b/docs/mkdocs/scripts/slideshow/README.md
new file mode 100644
index 000000000..cd3ede5c8
--- /dev/null
+++ b/docs/mkdocs/scripts/slideshow/README.md
@@ -0,0 +1,27 @@
+# Feature slideshow of the home page
+
+`make` builds `docs/images/json-light.svg` and `json-dark.svg`, the animated feature tour on the home page. They replace
+`json.gif` there. All text is outlined, so the SVGs need no fonts. Material's theme toggle cannot be seen from an
+` `, so the home page includes both files (`#only-light`, `#only-dark`) and Material shows the one that matches.
+
+The slides are in [`slides.md`](slides.md): one `# Title` per slide (`{badge: TEXT}` at the end adds a red chip), `- `
+bullets, and one fenced code block. `build.py` adds the title and closing slides around them, so the slide count follows
+the file. Every snippet should compile against the current `develop`. Use a non-breaking space to keep words together
+("16 bytes").
+
+```bash
+make # download missing fonts, build both SVGs
+make SECONDS=12
+make clean # remove the SVGs; make distclean also removes the fonts
+```
+
+Run it from this directory. It needs [uv](https://docs.astral.sh/uv/), plus curl, unzip and shasum for the fonts:
+`build.py` declares its dependencies inline (PEP 723, pinned, so the output stays byte-identical) and `uv run --script`
+provides them. `fonts/` is git-ignored: Inter (variable, Google Fonts repository) and JetBrains Mono 2.304 (variable)
+are downloaded from pinned URLs and checked against their SHA-256; a changed upstream file stops the build.
+`./build.py OUT.svg [seconds] [auto|light|dark]` works directly too. A mistake in `slides.md` stops the build with
+`slides.md:LINE` and leaves the old SVGs in place.
+
+Layout: title 48 px, bullets 24 px, code 19 px; the build warns when a slide's code would drop below 18 px. Syntax colours
+are the `code-*` tokens of the brand (also used by `css/brand.css`). A CSS animation dissolves each slide over the
+previous one and fills the progress bar; with reduced motion only the first slide shows.
diff --git a/docs/mkdocs/scripts/slideshow/build.py b/docs/mkdocs/scripts/slideshow/build.py
new file mode 100755
index 000000000..1bd45197a
--- /dev/null
+++ b/docs/mkdocs/scripts/slideshow/build.py
@@ -0,0 +1,461 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.9"
+# dependencies = ["fonttools==4.67.0", "uharfbuzz==0.57.0"]
+# ///
+"""Animated SVG slideshow prototype for nlohmann/json, all text outlined (harfbuzz shaping, glyphs as + ).
+
+Usage: uv run --script build.py OUT.svg [seconds-per-slide] [auto|light|dark]
+
+auto (the default) follows prefers-color-scheme. light and dark fix the colours, for pages with their own theme
+toggle (MkDocs Material), which an cannot see.
+
+The pinned versions keep the output byte-identical; a different harfbuzz may shape slightly differently.
+"""
+import re, sys, os
+import uharfbuzz as hb
+from fontTools.pens.svgPathPen import SVGPathPen
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+FONTS = os.path.join(HERE, "fonts")
+W, H = 1200, 675
+TOTAL = 0 # number of slides, set once slides.md is read (for the "02 / 20" labels and the progress segments)
+
+# ---------------------------------------------------------------- glyphs
+
+class Face:
+ def __init__(self, file, key, feats=None):
+ blob = hb.Blob.from_file_path(os.path.join(FONTS, file))
+ self.face = hb.Face(blob)
+ self.upem = self.face.upem
+ self.key = key
+ self.feats = feats or {}
+
+ def font(self, var):
+ f = hb.Font(self.face)
+ f.set_variations(var)
+ return f
+
+
+INTER = Face("Inter.ttf", "i", {"cv11": True, "ss03": True, "kern": True})
+MONO = Face("JetBrainsMono.ttf", "m", {"calt": False, "liga": False})
+
+GLYPHS = {} # (face, var, gid) -> id
+DEFS = []
+
+
+def gid_ref(face, font, var, gid):
+ k = (face.key, tuple(sorted(var.items())), gid)
+ if k not in GLYPHS:
+ pen = SVGPathPen(None, ntos=lambda v: str(round(v)))
+ font.draw_glyph_with_pen(gid, pen)
+ d = pen.getCommands()
+ gidname = f"g{len(GLYPHS):x}"
+ GLYPHS[k] = gidname if d else None
+ if d:
+ DEFS.append(f' ')
+ return GLYPHS[k]
+
+
+def shape(face, text, wght, size, track=0.0):
+ if not text:
+ return [], 0, face.upem
+ var = {"wght": wght}
+ if face is INTER:
+ var["opsz"] = max(14, min(32, size))
+ font = face.font(var)
+ buf = hb.Buffer()
+ buf.add_str(text)
+ buf.guess_segment_properties()
+ hb.shape(font, buf, face.feats)
+ out, x = [], 0
+ tr = track * face.upem
+ for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
+ out.append((gid_ref(face, font, var, info.codepoint), x + pos.x_offset, pos.y_offset))
+ x += pos.x_advance + tr
+ return out, x - tr, face.upem
+
+
+def width(text, face=INTER, wght=400, size=16, track=0.0):
+ _, w, upem = shape(face, text, wght, size, track)
+ return w * size / upem
+
+
+def run(text, x, y, size, wght=400, cls="tx", face=INTER, track=0.0):
+ """One line of text, baseline at y. Returns (svg, width_px)."""
+ glyphs, w, upem = shape(face, text, wght, size, track)
+ s = size / upem
+ uses = "".join(f' "
+ for g, gx, gy in glyphs if g)
+ return (f'{uses} ', w * s)
+
+
+def runs(parts, x, y, size, face=INTER, track=0.0):
+ """Several (text, wght, cls) pieces on one line."""
+ out = []
+ for text, wght, cls in parts:
+ svg, w = run(text, x, y, size, wght, cls, face, track)
+ out.append(svg)
+ x += w + track * size
+ return "".join(out), x
+
+
+def wrap(text, maxw, size, wght=400):
+ lines = _wrap(text, maxw, size, wght)
+ # no one-word widow: pull the last word of the previous line down if it still fits
+ if len(lines) > 1 and " " not in lines[-1] and " " in lines[-2]:
+ head, last = lines[-2].rsplit(" ", 1)
+ if width(last + " " + lines[-1], size=size, wght=wght) <= maxw:
+ lines[-2:] = [head, last + " " + lines[-1]]
+ return lines
+
+
+def _wrap(text, maxw, size, wght=400):
+ lines, cur = [], ""
+ for word in text.split(" "):
+ t = (cur + " " + word).strip()
+ if cur and width(t, size=size, wght=wght) > maxw:
+ lines.append(cur)
+ cur = word
+ else:
+ cur = t
+ return lines + [cur]
+
+# ---------------------------------------------------------------- pieces
+
+
+def wordmark(x, y, size, on="page"):
+ """The wordmark with hand kerning around the slash (see the logo in the brand kit). Below 32 px it switches to the
+ small optical master (nlohmann 250 / slash 450), because Thin falls apart at footer size."""
+ a, sl = ("wm-a", "wm-s") if on == "page" else ("on-a", "on-s")
+ small = size < 32
+ w = (250, 450, 800) if small else (100, 400, 800)
+ k = (0.004, 0.024) if small else (-0.006, 0.012)
+ out = []
+ for i, (t, wg, c) in enumerate([("nlohmann", w[0], a), ("/", w[1], sl), ("json", w[2], a)]):
+ svg, wd = run(t, x, y, size, wg, c, track=-0.02)
+ out.append(svg)
+ x += wd - 0.02 * size + (k[i] * size if i < 2 else 0)
+ return "".join(out), x
+
+
+KW = {"using", "auto", "return", "const", "struct", "for", "override"}
+TYPES = {"int", "void", "bool", "double", "char", "float"}
+CONSTANTS = {"true", "false", "nullptr"}
+TOKEN = re.compile(r'(//.*$)|(R"\(.*?\)"|R"\(.*$|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)\')|(<[\w/.]+>(?=\s*$))|(#\w+)'
+ r'|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)|(\s+)|(.)')
+
+
+def code_block(lines, x, y, size, lh):
+ """Lines of C++; a raw string literal may span lines."""
+ out, in_raw = [], False
+ for line in lines:
+ if line:
+ svg, in_raw = code_line(line, x, y, size, in_raw)
+ out.append(svg)
+ y += lh
+ else:
+ y += lh * BLANK
+ return "".join(out)
+
+
+BLANK = 0.55 # an empty code line is a paragraph break, not a full line
+
+
+def code_height(lines, size, lh):
+ n_full = sum(1 for l in lines if l)
+ n_blank = len(lines) - n_full
+ return lh * (n_full - 1) + lh * BLANK * n_blank + size
+
+
+def code_line(line, x, y, size, in_raw=False):
+ if line.startswith("$ "):
+ return runs([("$ ", 400, "tx3"), (line[2:], 400, "tx")], x, y, size, MONO)[0], False
+ out, cx = [], x
+ if in_raw:
+ end = line.find(')"')
+ head = line if end < 0 else line[:end + 2]
+ svg, w = run(head, cx, y, size, 400, "str", MONO)
+ out.append(svg)
+ if end < 0:
+ return "".join(out), True
+ cx += w
+ line = line[end + 2:]
+ in_raw = False
+ for m in TOKEN.finditer(line):
+ com, st, inc, pp, num, ident, ws, other = m.groups()
+ t = m.group(0)
+ if st and st.startswith('R"(') and not st.endswith(')"'):
+ in_raw = True
+ face, wght, cls = MONO, 400, "tx"
+ if com:
+ cls = "com" # upright: JetBrains Mono Italic's cursive f reads as a florin at README size
+ elif st or inc:
+ cls = "str"
+ elif pp:
+ cls = "pp"
+ elif ident in KW:
+ wght, cls = 600, "kw"
+ elif ident in TYPES:
+ cls = "ty"
+ elif num or ident in CONSTANTS:
+ cls = "num"
+ elif ident and line[m.end():m.end() + 1] == "(":
+ cls = "fn" # a call or definition: name directly followed by "("
+ svg, w = run(t, cx, y, size, wght, cls, face)
+ if not ws:
+ out.append(svg)
+ cx += w
+ return "".join(out), in_raw
+
+
+SEG, SEG_GAP = 20, 5
+
+
+def progress(idx, n_proto, y):
+ """TOTAL segments at the bottom right; earlier slides full, current one fills over the slide's time."""
+ seg, gap = SEG, SEG_GAP
+ x0 = W - 64 - TOTAL * seg - (TOTAL - 1) * gap
+ out = []
+ for k in range(TOTAL):
+ x = x0 + k * (seg + gap)
+ out.append(f' ')
+ if k < idx:
+ out.append(f' ')
+ elif k == idx:
+ out.append(f' ')
+ return "".join(out)
+
+
+def pattern_def():
+ tile = [("{", 14, 34), ("]", 92, 22), (":", 136, 60), ("[", 52, 82), (",", 118, 112),
+ ("}", 20, 140), (":", 78, 150), ("{", 140, 158)]
+ glyphs = "".join(run(c, x, y, 26, 600, "pat", MONO)[0] for c, x, y in tile)
+ return f'{glyphs} '
+
+# ---------------------------------------------------------------- slides
+
+
+CODE_SIZES = []
+SLOGAN = "What if JSON was part of modern C++?"
+
+
+def band_bg():
+ """Hero gradient plus the syntax pattern, faded out towards the text side (left)."""
+ return (' '
+ ' ')
+
+
+def footer_band(idx, n, left=None):
+ out = []
+ if left:
+ out.append(run(left, 64, FOOT_Y, 20, 600, "on-tx")[0])
+ out.append(progress(idx, n, FOOT_Y - 7.5).replace('class="seg"', 'class="seg on-band"').replace('class="seg-on', 'class="seg-on on-band'))
+ return "".join(out)
+
+
+FOOT_Y = 640 # one footer baseline on every slide: wordmark or URL left, progress right
+
+
+def slide_title(idx, n):
+ out = [band_bg()]
+ x = 96
+ label = "VERSION 3.13.0"
+ lw = width(label, MONO, 500, 15, 0.04)
+ out.append(f' ')
+ out.append(run(label, x + 14, 208.5, 15, 500, "on-tx", MONO, 0.04)[0])
+ out.append(wordmark(x - 6, 336, 112, on="band")[0])
+ out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 404, 26, MONO)[0])
+ out.append(footer_band(idx, n, "github.com/nlohmann/json"))
+ return "".join(out)
+
+
+def slide_feature(idx, n, title, bullets, code, badge=None):
+ out = [' ']
+ x, top, bottom = 64, 136, 596
+ # title row: 48/800, -3 %
+ svg, tw = run(title, x - 2, 98, 48, 800, "tx", track=-0.03)
+ out.append(svg)
+ if badge:
+ cx = x + tw + 16
+ cw = width(badge, MONO, 600, 13, 0.06) + 18
+ out.append(f' ')
+ out.append(run(badge, cx + 9, 86.2, 13, 600, "on-tx", MONO, 0.06)[0])
+ # code panel: fixed left edge, fixed width, fixed top on every slide; only its height follows the code
+ pad, lf = 28, 1.45
+ px, pw = 488, 1136 - 488
+ em = max(width(l, MONO, 400, 1) for l in code)
+ cs = 19.0
+ while cs > 14 and (em * cs > pw - 2 * pad or code_height(code, cs, lf * cs) > bottom - top - 2 * pad):
+ cs -= 0.5
+ CODE_SIZES.append((title, cs))
+ if cs < 18:
+ print(f" warning: {title!r} code at {cs} px ({em * 19:.0f} px wide at 19 px)", file=sys.stderr)
+ lh = lf * cs
+ ph = max(2 * pad + code_height(code, cs, lh), 240)
+ out.append(f' ')
+ first = top + pad + cs * 0.78
+ out.append(code_block(code, px + pad, first, cs, lh))
+ # bullets: 24/400 on 34, first baseline level with the first code baseline
+ colw = px - x - 40
+ by = first
+ for b in bullets:
+ out.append(f' ')
+ for line in wrap(b, colw - 24, 24):
+ out.append(run(line, x + 24, by, 24, 400, "tx2")[0])
+ by += 34
+ by += 16
+ # footer: small-master wordmark left, progress right, no rule, no numeral
+ out.append(wordmark(64, FOOT_Y, 22)[0])
+ out.append(progress(idx, n, FOOT_Y - 7.5))
+ return "".join(out)
+
+
+def slide_closing(idx, n):
+ out = [band_bg()]
+ x = 96
+ out.append(wordmark(x - 4, 146, 80, on="band")[0])
+ out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 200, 22, MONO)[0])
+ info = [
+ ("{", None),
+ (' "copyright": ', "(c) 2013-2026 Niels Lohmann"),
+ (' "license": ', "MIT"),
+ (' "docs": ', "json.nlohmann.me"),
+ (' "github": ', "github.com/nlohmann/json"),
+ (' "sponsor": ', "github.com/sponsors/nlohmann"),
+ (' "mail": ', "mail@nlohmann.me"),
+ ("}", None),
+ ]
+ cs, lh, pad = 21, 33, 28
+ py = 244
+ pw = max(width(k + '"' + (v or "") + '",', MONO, 400, cs) for k, v in info) + 2 * pad
+ out.append(f' ')
+ y = py + pad + cs * 0.8
+ for k, (key, val) in enumerate(info):
+ if val is None:
+ out.append(run(key, x + pad, y, cs, 400, "on-tx", MONO)[0])
+ else:
+ comma = "," if k < len(info) - 2 else ""
+ out.append(runs([(key, 400, "on-tx"), (f'"{val}"', 400, "on-s"), (comma, 400, "on-tx")], x + pad, y, cs, MONO)[0])
+ y += lh
+ out.append(footer_band(idx, n))
+ return "".join(out)
+
+
+def load_slides(path):
+ """Feature slides from Markdown: '# Title {badge: TEXT}', '- bullet' lines, one fenced code block."""
+ text = re.sub(r"", "", open(path).read(), flags=re.S)
+ slides, cur, in_code = [], None, False
+ for no, line in enumerate(text.split("\n"), 1):
+ if in_code:
+ if line.startswith("```"):
+ in_code = False
+ else:
+ cur["code"].append(line)
+ elif line.startswith("```"):
+ in_code = True
+ elif line.startswith("# "):
+ title, badge = line[2:].strip(), None
+ m = re.search(r"\s*\{badge:\s*(.*?)\}$", title)
+ if m:
+ title, badge = title[:m.start()], m.group(1)
+ cur = {"title": title, "bullets": [], "code": [], "badge": badge}
+ slides.append(cur)
+ elif line.startswith("- ") and cur:
+ cur["bullets"].append(line[2:].strip())
+ elif line[:1] in " \t" and line.strip() and cur and cur["bullets"]:
+ cur["bullets"][-1] += " " + line.strip()
+ elif line.strip():
+ raise SystemExit(f"{path}:{no}: unexpected line: {line!r}")
+ for sl in slides:
+ while sl["code"] and not sl["code"][-1].strip():
+ sl["code"].pop()
+ if not sl["code"]:
+ raise SystemExit(f"{path}: slide {sl['title']!r} has no code block")
+ return slides
+
+
+FEATURES = load_slides(os.path.join(HERE, "slides.md"))
+
+
+def _feature(f):
+ return lambda i, n: slide_feature(i, n, f["title"], f["bullets"], f["code"], f["badge"])
+
+
+SLIDES = [slide_title] + [_feature(f) for f in FEATURES] + [slide_closing]
+TOTAL = len(SLIDES)
+
+
+# ---------------------------------------------------------------- assemble
+
+
+def build(sec):
+ n = len(SLIDES)
+ T = n * sec
+ fade = 0.6
+ a, b, c = fade / T * 100, sec / T * 100, (sec + fade) / T * 100
+ def cls(i):
+ return "s s0" if i == 0 else "s sl" if i == n - 1 else "s"
+ bodies = [f'{fn(i, n)} ' for i, fn in enumerate(SLIDES)]
+ css = f"""
+.bg{{fill:#fff}} .tx{{fill:#0f1419}} .tx2{{fill:#3d444f}} .tx3{{fill:#8a919c}} .com{{fill:#646d7a}}
+.bl{{fill:#1747a6}} .blk{{fill:#1747a6}} .kw{{fill:#1747a6}} .ty{{fill:#0d6e72}} .fn{{fill:#6b3fb8}} .str{{fill:#23793a}} .num{{fill:#b04a07}} .pp{{fill:#a3247a}}
+.panel{{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1}} .rule{{stroke:#e3e6eb;stroke-width:1}}
+.wm-a{{fill:#0f1419}} .wm-s{{fill:#1747a6}}
+.on-a,.on-tx{{fill:#fff}} .red{{fill:#d93a3f}} .on-panel{{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16}} .on-s{{fill:#bcd3fb}} .w70{{fill:#fff;fill-opacity:.7}} .on-tx2{{fill:#dbe6fb}} .chip{{fill:#fff;fill-opacity:.14}}
+.pat{{fill:#fff;fill-opacity:.09}}
+.h1{{stop-color:#1d55c4}} .h2{{stop-color:#123a8a}}
+.seg{{fill:#e3e6eb}} .seg-on{{fill:#1747a6}}
+.seg.on-band{{fill:#fff;fill-opacity:.25}} .seg-on.on-band{{fill:#fff;fill-opacity:1}}
+@media (prefers-color-scheme:dark){{
+ .bg{{fill:#0f1218}} .tx,.wm-a{{fill:#f2f4f7}} .tx2{{fill:#c3c8d1}} .tx3{{fill:#7b828e}} .com{{fill:#8b929e}}
+ .bl,.blk,.wm-s,.seg-on,.kw{{fill:#7aa7f5}} .ty{{fill:#4fd1c5}} .fn{{fill:#c4a3ff}} .str{{fill:#7ed491}} .num{{fill:#f5a65b}} .pp{{fill:#f48fc8}}
+ .panel{{fill:#171b23;stroke:#272c36}} .rule{{stroke:#272c36}} .seg{{fill:#272c36}}
+ .red{{fill:#c8383d}} .h1{{stop-color:#163f93}} .h2{{stop-color:#0c2556}}
+}}
+.s{{opacity:0;animation:show {T:g}s linear infinite backwards}}
+.fill{{transform-box:fill-box;transform-origin:left;animation:fill {T:g}s linear infinite backwards;animation-delay:inherit}}
+.s0{{animation-name:show0}} .sl{{animation-name:showl}}
+@keyframes show{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
+@keyframes show0{{0%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
+@keyframes showl{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {b:.4f}%{{opacity:1}} {c:.4f}%{{opacity:0}} 100%{{opacity:0}}}}
+@keyframes fill{{0%{{transform:scaleX(0)}} {b:.4f}%{{transform:scaleX(1)}} 100%{{transform:scaleX(1)}}}}
+@media (prefers-reduced-motion:reduce){{.s,.fill{{animation:none}} .s:first-of-type{{opacity:1}}}}
+"""
+ css = re.sub(r"\n\s*", "", css)
+ pat = pattern_def()
+ return (f''
+ f'JSON for Modern C++: feature slideshow (animated, {n} slides, {sec:g} s each) '
+ f''
+ f'{"".join(DEFS)}{pat}'
+ f' '
+ f' '
+ f' '
+ f' '
+ f'{"".join(bodies)} ')
+
+
+def fix_scheme(svg, scheme):
+ """Drop the dark-mode block (light) or apply it unconditionally (dark)."""
+ if scheme == "auto":
+ return svg
+ start = svg.index("@media (prefers-color-scheme:dark){")
+ i, depth = svg.index("{", start), 0
+ for j in range(i, len(svg)):
+ depth += {"{": 1, "}": -1}.get(svg[j], 0)
+ if depth == 0:
+ break
+ inner = svg[i + 1:j] if scheme == "dark" else ""
+ return svg[:start] + inner + svg[j + 1:]
+
+
+if __name__ == "__main__":
+ out = sys.argv[1]
+ sec = float(sys.argv[2]) if len(sys.argv) > 2 else 15
+ scheme = sys.argv[3] if len(sys.argv) > 3 else "auto"
+ if scheme not in ("auto", "light", "dark"):
+ sys.exit(f"unknown colour scheme {scheme!r}: use auto, light or dark")
+ svg = fix_scheme(build(sec), scheme)
+ # defs are appended while slides render, so build the slides first (done inside build via f-string order)
+ open(out, "w").write(svg)
+ print(out, len(svg.encode()) // 1024, "KB,", len(GLYPHS), "glyphs; code px:", " ".join(f"{c:g}" for _, c in CODE_SIZES))
diff --git a/docs/mkdocs/scripts/slideshow/slides.md b/docs/mkdocs/scripts/slideshow/slides.md
new file mode 100644
index 000000000..fcfe55d75
--- /dev/null
+++ b/docs/mkdocs/scripts/slideshow/slides.md
@@ -0,0 +1,412 @@
+
+
+# Trivial integration
+
+- single header file
+- no build system needed
+- vanilla C++11
+- works with GCC 4.8+, Clang 3.4+ and MSVC 2015+
+
+```cpp
+#include
+
+// for convenience
+using json = nlohmann::json;
+
+int main()
+{
+ // a JSON pretty-printer in 3 lines
+ json j;
+ std::cin >> j;
+ std::cout << std::setw(4) << j << std::endl;
+}
+
+$ g++ pretty.cpp -std=c++11 -o pretty
+```
+
+# First-class datatype
+
+- write JSON like any other C++ literal
+- initializer lists become arrays and objects
+
+```cpp
+json j = {
+ {"pi", 3.141},
+ {"happy", true},
+ {"name", "Niels"},
+ {"nothing", nullptr},
+ {"answer", {
+ {"everything", 42}
+ }},
+ {"list", {1, 0, 2}},
+ {"object", {
+ {"currency", "USD"},
+ {"value", 42.99}
+ }}
+};
+```
+
+# Array construction
+
+- arrays work the way you expect
+- adding to a null value makes it an array
+
+```cpp
+// create null value
+json j;
+
+// add via member function
+j.push_back("Hello");
+j.emplace_back("World");
+
+// add via += operator
+j += false;
+
+// add via index
+j[2] = 42.3;
+
+// gaps are filled with null
+j[4] = "World";
+```
+
+# Object construction
+
+- objects work the way you expect
+- adding to a null value makes it an object
+
+```cpp
+// create null value
+json j;
+
+// add via member function
+j.emplace("key2", "value");
+j.push_back({"key", "value"});
+
+// add via += operator
+j += {"another key", false};
+
+// add via key
+j["pi"] = 3.1415926;
+j["answer"]["everything"] = 42;
+```
+
+# Container operations
+
+- use JSON like an STL container
+- satisfies the ReversibleContainer requirements
+
+```cpp
+// container empty?
+j.empty();
+
+// current number of elements
+j.size();
+
+// maximal number of elements
+j.max_size();
+
+// swap elements
+j.swap(j2);
+std::swap(j, j2);
+
+// clear a container
+j.clear();
+```
+
+# STL containers
+
+- switch between STL and JSON containers
+- conversion works in both directions
+
+```cpp
+// some STL containers
+std::vector v;
+std::unordered_map m;
+
+// automatic transformation
+json j_array(v);
+json j_object(m);
+
+// works both ways
+auto l = j_array.get>();
+auto u = j_object
+ .get>();
+```
+
+# Type conversion
+
+- convert your own types to and from JSON
+- one macro writes to_json and from_json
+- or write both functions for full control
+
+```cpp
+// some user-defined type
+struct person {
+ std::string name;
+ int age;
+};
+
+// defines to_json and from_json
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person,
+ name, age)
+
+// conversion in action
+person p {"Ned Flanders", 60};
+json j = p;
+// {"age": 60, "name": "Ned Flanders"}
+
+auto p2 = j.get();
+```
+
+# Type deduction
+
+- types are deduced automatically
+- member functions tell you the type
+
+```cpp
+// type deduction
+json j1 = nullptr;
+json j2 = false;
+json j3 = 12.23;
+json j4 = {1, 2, 3};
+json j5 = {{"key", "value"}};
+json j6 = "Hello";
+
+// quick checks
+j1.is_null();
+j2.is_boolean();
+j3.is_number();
+j4.is_array();
+j5.is_object();
+j6.is_string();
+```
+
+# Serialization
+
+- to a string or a stream
+- pretty-printing included
+
+```cpp
+// default serialization
+auto s1 = j.dump();
+
+// pretty-printed
+int indent = 4;
+std::string s2 = j.dump(indent);
+
+// to stream
+std::cout << j << std::endl;
+
+// pretty-printed
+std::cout << std::setw(indent) << j << std::endl;
+```
+
+# Deserialization
+
+- parse strings, streams and files
+- the _json literal puts plain JSON in your code
+- comments and trailing commas on request
+
+```cpp
+// from string
+json j1 = json::parse("[1,null,false]");
+
+// from file
+std::ifstream f("example.json");
+json j2 = json::parse(f);
+
+// from string literal
+json j3 = "[3.14159]"_json;
+
+// from raw string literal
+json j4 = R"(
+ {"one": 1, "two": 2, "three": [1, 2, 3]}
+)"_json;
+```
+
+# SAX parsing
+
+- the parser can emit SAX events
+- process input without building a JSON value
+
+```cpp
+// define a SAX event processor
+struct sax_processor : json::json_sax_t
+{
+ bool boolean(bool val) override
+ {
+ return true;
+ }
+
+ // implement other functions...
+};
+
+// create object
+sax_processor my_processor;
+
+// use it to process input
+json::sax_parse(input, &my_processor);
+```
+
+# Zero-copy views {badge: NEW IN 3.13}
+
+- parse without building a tree: 16 bytes per value
+- strings and numbers stay in the source text
+- materialize a json value only where you need one
+
+```cpp
+#include
+
+// parse into a flat index, not a tree
+std::ifstream f("big.json");
+auto doc = nlohmann::json_document::parse(f);
+
+// read-only views with familiar access
+auto view = doc.root();
+double pi = view["pi"].get();
+for (auto v : view["list"])
+ std::cout << v.get() << '\n';
+
+// build a json value only where needed
+json part = view["list"].materialize();
+```
+
+# JSON Pointer
+
+- address any element with a JSON Pointer
+- flatten a value to pointer/value pairs, and back
+
+```cpp
+json j = {"good", "bad", "ugly"};
+
+// JSON pointer to the 3rd list element
+auto ptr = json::json_pointer("/2");
+
+// use JSON pointer to access element
+j[ptr] = {{"it", "cattivo"}};
+
+// user-defined string literal
+j["/2/en"_json_pointer] = "ugly";
+// ["good","bad",{"en":"ugly","it":"cattivo"}]
+
+json flat = j.flatten();
+// {"/0":"good","/1":"bad","/2/en":"ugly",
+// "/2/it":"cattivo"}
+
+json j2 = flat.unflatten(); // j == j2
+```
+
+# JSON Patches
+
+- use JSON Patch to describe edit operations
+- create diffs from two JSON values
+- or use a JSON Merge Patch that looks like the document
+
+```cpp
+json source = R"({"F": "Paris", "D": "Berlin"})"_json;
+
+// a JSON Patch (RFC 6902)
+json p1 = R"([
+ {"op": "add", "path": "/GB", "value": "London"}
+])"_json;
+json target = source.patch(p1);
+
+// create a diff from two JSON values
+json p2 = json::diff(target, source);
+// [{"op": "remove", "path": "/GB"}]
+
+// a JSON Merge Patch (RFC 7396)
+target.merge_patch(R"({"F": null})"_json);
+// {"D": "Berlin", "GB": "London"}
+```
+
+# Binary formats
+
+- compact binary representations
+- BJData, BON8, BSON, CBOR, MessagePack and UBJSON
+
+```cpp
+// a JSON value
+json j = R"({"compact": true, "schema": 0})"_json;
+
+// serialize to CBOR
+std::vector v = json::to_cbor(j);
+// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61,
+// 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68,
+// 0x65, 0x6D, 0x61, 0x00
+
+// roundtrip
+json j_from_cbor = json::from_cbor(v);
+
+// the same for the other formats
+auto b = json::to_bon8(j);
+auto m = json::to_msgpack(j);
+```
+
+# Algorithms
+
+- the standard algorithms just work
+- sort, find, count, merge and more
+
+```cpp
+// sort JSON containers
+std::sort(j.begin(), j.end());
+
+// find element
+auto pos = std::find(j.begin(), j.end(),
+ json("needle"));
+
+// count all numbers
+auto n = std::count_if(j.begin(), j.end(),
+ [](const json& v) { return v.is_number(); });
+
+// merge two sorted containers
+std::merge(j1.begin(), j1.end(),
+ j2.begin(), j2.end(),
+ std::back_inserter(j3));
+```
+
+# Iterators
+
+- iterators, const iterators, and reverse iterators
+- random access for arrays
+- items() gives you keys and values
+
+```cpp
+json j = {1, 2, 3, 4, 5};
+
+// range for
+for (auto& el : j)
+{
+ // print all values
+ std::cerr << el << '\n';
+}
+
+// classical iterators
+for (auto it = j.begin(); it != j.end(); ++it)
+{
+ // double all values
+ *it = it->get() * 2;
+}
+```
+
+# Structured bindings
+
+- since C++17, unpack keys and values with structured bindings
+
+```cpp
+// JSON object
+auto j = R"({"foo": 23, "bar": 42})"_json;
+
+// structured bindings
+for (auto& [key, val] : j.items())
+{
+ std::cerr << key << ':' << val << '\n';
+}
+```