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()