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 1/2] 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() From a1b62fd070c757aea711adf5a00b3c23f7910917 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:34:47 +0200 Subject: [PATCH 2/2] fix: space stacked buttons and align site tables Wrapped buttons get a row gap, the copy button fits a one-line code block, tables line up with the text, four counters share one row, the sticky sidebar label gets a rule, and a phone table no longer squeezes its first column to one letter per line. --- docs_assets/extra.css | 59 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 9 deletions(-) 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 {