diff --git a/docs_assets/extra.css b/docs_assets/extra.css
index a3d053d61..fed41bb53 100644
--- a/docs_assets/extra.css
+++ b/docs_assets/extra.css
@@ -200,11 +200,12 @@
background: #fff;
}
-/* Headline figures, read as one line of numbers */
+/* Headline figures, read as one line of numbers: four fit on one row of
+ the text column. */
.rb-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
- gap: 0.6rem 1.5rem;
+ gap: 0.6rem 1rem;
margin: 1rem 0 1.5rem;
padding: 0.8rem 1rem;
border: 1px solid var(--rb-border);
@@ -277,6 +278,18 @@
margin: 1rem 0;
}
+/* A table lines up with the text around it. Material indents the table
+ by 0.8rem and offsets that with a negative margin on the wrapper, which
+ the margin above removes, and the table's own margin doubled the space
+ above it. */
+.md-typeset .md-typeset__table {
+ padding: 0;
+}
+
+.md-typeset .md-typeset__table > table {
+ margin: 0;
+}
+
.rb-timestamp {
font-size: 0.8rem;
color: var(--rb-text-secondary);
@@ -343,10 +356,6 @@
/* Key and value cards: the header row is empty, so it is not drawn and
not announced. */
-.rb-meta-card table {
- margin: 1rem 0 !important;
-}
-
.rb-meta-card thead,
.md-typeset table thead:not(:has(th:not(:empty))) {
display: none;
@@ -410,6 +419,24 @@
min-height: 44px;
}
+/* Buttons that wrap onto a second line keep a gap between the rows. */
+.md-typeset .md-button {
+ margin-block: 0.25rem;
+}
+
+/* The copy button stays inside a one-line code block: at 44 px it hung
+ below the block and covered the end of the line. 24 px is the minimum
+ target size of WCAG 2.2. */
+.md-typeset .md-code__button {
+ min-height: 0;
+ width: 24px;
+ height: 24px;
+}
+
+.md-typeset pre > code {
+ padding-right: 2.6rem;
+}
+
.rb-visually-hidden {
position: absolute !important;
width: 1px !important;
@@ -760,13 +787,21 @@
}
}
-/* The sidebar title stays legible over the list scrolling under it. */
+/* The sidebar title stays legible over the list scrolling under it. With
+ tabs the title shown is the active section's label, held at the top of
+ the sidebar: a rule under it ends the list cleanly instead of a half
+ line of text touching the label. */
@media (min-width: 76.25em) {
.md-nav--primary > .md-nav__title,
- .md-nav--secondary > .md-nav__title {
+ .md-nav--secondary > .md-nav__title,
+ .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {
background: var(--md-default-bg-color);
border-bottom: 1px solid var(--rb-border);
}
+
+ .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {
+ padding-bottom: 0.4rem;
+ }
}
/* Material's compact footer defaults sit just below WCAG AA on the dark bar. */
@@ -853,8 +888,15 @@
.md-typeset table:not([class]):not([data-rb-stack]) :is(td, th):first-child {
position: sticky;
left: 0;
+ min-width: min(6rem, 30vw);
max-width: 40vw;
background: var(--md-default-bg-color);
+ overflow-wrap: break-word;
+ }
+
+ /* A long link or path breaks anywhere, so it never squeezes the first
+ column down to one letter per line. */
+ .md-typeset table:not([class]):not([data-rb-stack]) td:not(:first-child) :is(a, code) {
overflow-wrap: anywhere;
}
@@ -868,7 +910,6 @@
.md-typeset pre > code {
white-space: pre-wrap;
overflow-wrap: anywhere;
- padding-right: 2.6rem;
}
.md-typeset :is(.rb-terminal, .language-text) pre > code {
diff --git a/docs_assets/overrides/assets/brand/apple-touch-icon.png b/docs_assets/overrides/assets/brand/apple-touch-icon.png
new file mode 100644
index 000000000..86f6b6f22
Binary files /dev/null and b/docs_assets/overrides/assets/brand/apple-touch-icon.png differ
diff --git a/docs_assets/overrides/assets/brand/favicon-32.png b/docs_assets/overrides/assets/brand/favicon-32.png
new file mode 100644
index 000000000..e869124c6
Binary files /dev/null and b/docs_assets/overrides/assets/brand/favicon-32.png differ
diff --git a/docs_assets/overrides/assets/brand/favicon.svg b/docs_assets/overrides/assets/brand/favicon.svg
new file mode 100644
index 000000000..c016a97dd
--- /dev/null
+++ b/docs_assets/overrides/assets/brand/favicon.svg
@@ -0,0 +1 @@
+
diff --git a/docs_assets/overrides/assets/brand/logo.svg b/docs_assets/overrides/assets/brand/logo.svg
new file mode 100644
index 000000000..31174ea35
--- /dev/null
+++ b/docs_assets/overrides/assets/brand/logo.svg
@@ -0,0 +1 @@
+
diff --git a/docs_assets/overrides/main.html b/docs_assets/overrides/main.html
index 893d3b1e9..426b54475 100644
--- a/docs_assets/overrides/main.html
+++ b/docs_assets/overrides/main.html
@@ -29,6 +29,12 @@
{% else %}{% set section = 'reference' %}{% endif %}
{% set preview_image = config.site_url ~ 'assets/og/' ~ section ~ '.png' if config.site_url else '' %}
+ {# The theme's favicon is the 32 px PNG. Browsers that read an SVG icon
+ take this one, listed after it, and a home-screen shortcut takes the
+ opaque 180 px icon (scripts/make_site_icons.py makes all three). #}
+
+
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 74e266494..9d1da04b8 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -39,8 +39,8 @@ theme:
icon: material/brightness-4
name: Switch to auto
font: false
- logo: assets/images/logo.png
- favicon: assets/images/favicon.png
+ logo: assets/brand/logo.svg
+ favicon: assets/brand/favicon-32.png
icon:
logo: material/chip
features:
diff --git a/scripts/generate_site.py b/scripts/generate_site.py
index ac529ac80..3a70aac12 100644
--- a/scripts/generate_site.py
+++ b/scripts/generate_site.py
@@ -6194,8 +6194,8 @@ theme:
icon: material/brightness-4
name: Switch to auto
font: false
- logo: assets/images/logo.png
- favicon: assets/images/favicon.png
+ logo: assets/brand/logo.svg
+ favicon: assets/brand/favicon-32.png
icon:
logo: material/chip
features:
diff --git a/scripts/make_site_icons.py b/scripts/make_site_icons.py
new file mode 100644
index 000000000..551791bdf
--- /dev/null
+++ b/scripts/make_site_icons.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+"""Write the site's own mark: header logo, favicon and touch icon.
+
+The mark is the chip of the project logo drawn as vector shapes: a body,
+four pins per side and the pixel glyph on a 4 by 4 grid. Three variants:
+
+- ``logo.svg``: white, the glyph cut out so the header colour shows through.
+ It sits on the indigo header in both schemes.
+- ``favicon.svg``: indigo chip with a cyan glyph, lighter in a dark browser.
+- ``favicon-32.png`` and ``apple-touch-icon.png`` (180 px, opaque): the same
+ shapes rasterised by a headless Chromium through Playwright, for browsers
+ that take no SVG icon and for home-screen shortcuts.
+
+The files land in ``docs_assets/overrides/assets/brand/``, which MkDocs copies
+with the theme. The site build never runs this script.
+
+ python scripts/make_site_icons.py
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent.parent
+OUT = ROOT / "docs_assets" / "overrides" / "assets" / "brand"
+
+HEADER = "#3b3f7a"
+GLYPH = "#8be0f7"
+GLYPH_DIM = "#4fa3c4"
+
+# (column, row, dim) on the 4 by 4 grid, read from the project logo.
+PIXELS = [
+ (0, 0, False), (1, 0, False), (2, 0, True),
+ (1, 1, False), (3, 1, True),
+ (0, 2, False), (3, 2, False),
+ (1, 3, True), (2, 3, False), (3, 3, False),
+]
+
+BODY = (8, 8, 32, 4) # x, y, size, corner radius in a 48 unit box
+PIN_CENTRES = (13.5, 20.5, 27.5, 34.5)
+PIN_WIDTH = 3.4
+PIN_LENGTH = 5.5
+CELL = 5.0
+GAP = 1.6
+
+
+def _pins(fill: str) -> str:
+ half = PIN_WIDTH / 2
+ rects = []
+ for centre in PIN_CENTRES:
+ start = centre - half
+ rects += [
+ (start, 8 - PIN_LENGTH, PIN_WIDTH, PIN_LENGTH),
+ (start, 40, PIN_WIDTH, PIN_LENGTH),
+ (8 - PIN_LENGTH, start, PIN_LENGTH, PIN_WIDTH),
+ (40, start, PIN_LENGTH, PIN_WIDTH),
+ ]
+ return "".join(
+ f''
+ for x, y, w, h in rects
+ )
+
+
+def _glyph(bright: str, dim: str) -> str:
+ span = 4 * CELL + 3 * GAP
+ origin = BODY[0] + (BODY[2] - span) / 2
+ return "".join(
+ f''
+ for c, r, is_dim in PIXELS
+ )
+
+
+def _body(fill: str, extra: str = "") -> str:
+ x, y, size, radius = BODY
+ return f''
+
+
+def logo_svg() -> str:
+ """White chip, glyph cut out: the header colour shows through it."""
+ return (
+ '"
+ )
+
+
+def favicon_svg() -> str:
+ return (
+ '"
+ )
+
+
+def touch_icon_svg() -> str:
+ """Opaque square: iOS fills transparency with black."""
+ return (
+ '", 1)[0] + ""
+ )
+
+
+def main() -> None:
+ try:
+ from playwright.sync_api import sync_playwright
+ except ImportError as exc:
+ raise SystemExit("Playwright with Chromium is needed: pip install playwright") from exc
+ OUT.mkdir(parents=True, exist_ok=True)
+ for name, text in (("logo.svg", logo_svg()), ("favicon.svg", favicon_svg())):
+ (OUT / name).write_text(text + "\n", encoding="utf-8")
+ print((OUT / name).relative_to(ROOT))
+ rasters = (
+ ("favicon-32.png", 32, favicon_svg().replace("@media (prefers-color-scheme: dark)", "@media not all")),
+ ("apple-touch-icon.png", 180, touch_icon_svg()),
+ )
+ with sync_playwright() as playwright:
+ browser = playwright.chromium.launch()
+ for name, size, text in rasters:
+ page = browser.new_page(viewport={"width": size, "height": size})
+ sized = text.replace("