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 ( + '' + '' + _body("#fff") + _glyph("#000", "#5a5a5a") + "" + + _pins("#fff") + _body("#fff", ' mask="url(#glyph)"') + "" + ) + + +def favicon_svg() -> str: + return ( + '' + "" + + _pins(HEADER).replace(f'fill="{HEADER}"', 'class="c"') + + _body(HEADER).replace(f'fill="{HEADER}"', 'class="c"') + + _glyph(GLYPH, GLYPH_DIM) + "" + ) + + +def touch_icon_svg() -> str: + """Opaque square: iOS fills transparency with black.""" + return ( + '' + f'' + + logo_svg().split(">", 1)[1].rsplit("", 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("{sized}') + page.screenshot(path=str(OUT / name), omit_background=True, clip={"x": 0, "y": 0, "width": size, "height": size}) + page.close() + print((OUT / name).relative_to(ROOT)) + browser.close() + + +if __name__ == "__main__": + main()