From 427a8183e3f3910c9e0076aa03781f246605a864 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:34:44 +0200 Subject: [PATCH] feat: draw the site mark and favicons as vectors The header logo was a 256 px compressed raster whose navy chip vanished on the indigo bar. The chip is now an SVG, white with the glyph cut out, and the favicon set (SVG, 32 px and an opaque 180 px touch icon) comes from scripts/make_site_icons.py. --- .../assets/brand/apple-touch-icon.png | Bin 0 -> 2665 bytes .../overrides/assets/brand/favicon-32.png | Bin 0 -> 1354 bytes .../overrides/assets/brand/favicon.svg | 1 + docs_assets/overrides/assets/brand/logo.svg | 1 + docs_assets/overrides/main.html | 6 + mkdocs.yml | 4 +- scripts/generate_site.py | 4 +- scripts/make_site_icons.py | 134 ++++++++++++++++++ 8 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 docs_assets/overrides/assets/brand/apple-touch-icon.png create mode 100644 docs_assets/overrides/assets/brand/favicon-32.png create mode 100644 docs_assets/overrides/assets/brand/favicon.svg create mode 100644 docs_assets/overrides/assets/brand/logo.svg create mode 100644 scripts/make_site_icons.py 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 0000000000000000000000000000000000000000..86f6b6f22dd21504690b7ddd1abdf91048fb15e7 GIT binary patch literal 2665 zcmb7`c{CK-1IGuC(Fl`lWsrnIjS{k?_eNP?22IG=%D$6jjIkBr6$T|Tn#{{Sw(Mju zmXV4`_GXwyrjZ$2WC>5F_dDnH`{Vb{?~i-$Irn?-xy!lt^Zou|VQwS{It&5;0D>mQ z7p?Z%+V6#*d#|ppOm_nSKr$v5e?x@l&Qs9{#HciIQHAKs7lar&uwOYzfFqgZ?e`AB zUAtDR^z3qu;$Wedudm+?T<|6we!mdsSI6?IvrD1WQxGY5yyER*Q8gGMNfmVTIBvS? zgzm%?U06M@?JF(&(fe1p1?Hk~0O!0;Kx<3W0&NQO#)m8stqkDwm;uzlLe6rKcG$Vt zJAnWJIzYUGOEsRymLFL4e+mXiAOr##d#sxm2pkLW^>595jI^>D_2qDG3SRkkSBp5j z+|LnwPlW5&HIPI}T9 ze1@O29k<}0qZyiO5hp_D7Zju|Z!z`s681G1>C($j`p6%J#dFUj#wAD`zS_v~8l4&u z)*+|EqR+jxVs5NEpYo5TcanyzWdC&ohSu%(BNU1?we{@g(}S$_#uv|# zr9{8yYmfyNXNYaa6os}{y&WAzHUiiXC=_mIkevbk$YPaH$J~8%A6=b!zwfL^LR{gw zuM3mL3q8(TF(jNcc$zpkZFQ|Y-8HSXb+LMWMI#ZUdJ%fw7MY^+wEzfrX<>}cw^o#Lc@TDBh&th)O)gwYF*$jZ38gSD45 zbUMlty}KWXfkJ^{W=>BClIJC<4z6z|Cx-}Y&AI3q{{11ov;;*%UeB8$60B%~?k_d) zpZ|*o{-umwtl_#wAN~DA;rNmx+AqKEvB;C-?pQib~kbi7GmDf zXa&@pk-vY+QR1_2*3#Br&P?&@QNJ+gPz}$Ox$oO>Luu2D{=7Ltxx!u|#}fjUl{j#q z;^OHl72{Ta1(g*E?r?#u7R4Fz-r_%a@JJhS)kaDkJ`jr_N+$B01-pH9>2vpGsZ@%TA3WNphD>Qgudwpb{pbX8N!f>kN>E9!NB5DN%bTt& zi6YN5duLU9MHS*$IOEE-OZ_0DYv^+F!}4pdM-tg@{?1rAhnRPMm=`Y0h zT6TyADhvG0Muc#WAQBm2cpXQJGG;-p({G0d1OQ7-?BgkI*LGD}?JX;*s>fdhj7t=y zf-E=X5T=Q7h2QYev)I0qY{SEq=o?D{pRM`F>le<|qoz#cPfpP=AEvgPf{OL#f(yTt zAi~T`$gW8{Iu--~V>hL+5QpCQ2)k}7A&CugO?@#nWpjL*rt9Xj$DjfKSZ)_*Lc65x zkK_Ml`4hXjT?&3^?%kK~CSmWXo@#Q>53-xnBgFRD^qnAuzc4n7@cF`5H#sdtv7=2? zuPvslH^+cYRuq-3akZu#es zTz|Vpp&+oYao|W>@p=yD{ympJdtSqHW*9513eSLriLY=NOLk>hHRqlbo?%rI(K+7{pp6Gowc>*`qozeBWVokRr*u3 zF3L@kg;A`Wr!NuKI^Rg^4(+Z?)fjHhGP7j$s!Y$lFZ>#9RT`zTVQqr#q%1=YVv zT3hQ+A0Z%7(KZ+()bOw&K`mK3)@?#p636r7E&b>IF8ySMpybTi5nc*W1}6rd&d4lk zG&!II7h};xk_IB?le@SYa_pOKP=G!Cpl_>Q(WuLm z_4qDat|(!-cV~Wv4J93E8yiz&Yc>@@{UbxO(u|g~<2pFafdjyRlWtu&z8IZS?qaB#LFRkMDn`o6^Lm)mB73QfVo@MIM0LpQUt0oXh*Y9)QUu^NSTQ=i7e)3q=XE literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e869124c65889f2807aa5afdae6735df5c4d276b GIT binary patch literal 1354 zcmV-Q1-1H#P)-y&UV=i@nuv+fNc6SF2OmWGQetY$n*<;9 zNhL-TGz1e9lm~^jyK9An5H2{^x=d`}JDNV?h`MFpy z6grwP)3YL4Y|!gFup2l(Cq6rU2RenU)>CpsQRvU0DyMVXP)-Y3ji>ammEIizAWR)M zYyimJGJr{46g4JtzNGhNt@Q2)0JCdpqtBFVU(49!JcpF<{4onXQzBLaUf=tB35D0t z(!;uuNM?lFfP)+P6f!Y=x*O-&a5dzmTp%ph^?u_Fz6=icfB&xmcsqK!DDV@oj>H*x zF)-Bk9hd{?r7i6#A7g1sslo`bHxc<8Vx;P!P1q%nT28{kwOMf2R)DKwgVYL7{Dnu% zz*b!*`E)D}Z1J`^01ijc+=;A>z@Tjy?J{`{TiTNQ(8kvo$zrPb_Y3(0ds?7r-;;6y z#!xtL3?ALT7fL- zPO}^+JVWsjCy^yNp2u^}EUhE7S`gp_Bj|rbiHs-&KZi?Y8 zfRlvs3t1a1>G(Jp>|*`FD? z3`?^&3&uGv>Dsy(h)t0BLNEwlv}Cr( z-AqJ!5EfQLC=xd1T|Q8&GSx7ONv6KO7Mfq*E&0gQ91Qn;0nZ+N7ZkfHQ}^HppTVu) z|CHk`?LF-)tD#I+nQF+-ig%r8QPX1uKwdAM#4S-Aw%l$U4j_smjmx>{Z`Enn4*>4V zw}I90dAWwJYg!G>DDNSL=@q@o@(LFAR~96mD^{k}aLotFN}}a%P+Sh#Pt7laimA8! z{yNFWBT-nI3q$#qdQgQ6@Gm?zodLo5ryS^4D_!t^d6Joc$UaK(VWf+6H*t~d%1{&U z7fI+c2g+P1TaZ~lL#28m_b84qAzDxE4&S_~&OC3v7^*lao z!r6HmgSYUJQ%RY2)k@EbiaulS2K@s70RR8yAmT*;000I_L_t&o0N#3dE)u`Ca{vGU M07*qoM6N<$f}y8&e*gdg literal 0 HcmV?d00001 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()