diff --git a/docs/_static/favicon.svg b/docs/_static/favicon.svg
new file mode 100644
index 0000000..f1aad19
--- /dev/null
+++ b/docs/_static/favicon.svg
@@ -0,0 +1,12 @@
+
diff --git a/docs/_static/make_logo.py b/docs/_static/make_logo.py
new file mode 100644
index 0000000..fc9f5e2
--- /dev/null
+++ b/docs/_static/make_logo.py
@@ -0,0 +1,121 @@
+# /// script
+# requires-python = ">=3.11"
+# dependencies = ["resvg-py"]
+# ///
+"""Draw the is_annotated logo: ``Annotated[T, metadata]``, checked.
+
+Square brackets around a type, as a teal block, and its metadata, as a purple
+flag: the shape of ``Annotated[T, x]``. A yellow badge with a tick asks the
+question this package answers. The shapes are vector, so the logo is written as
+an SVG, sharp at any size; for a bitmap, name a .png and give its size::
+
+ uv run docs/_static/make_logo.py # favicon.svg
+ uv run docs/_static/make_logo.py --size 2048 big.png
+"""
+
+import argparse
+from pathlib import Path
+
+NAVY, TEAL, PURPLE = "#030a23", "#66a19a", "#7738eb" # GalacticDynamics' colours
+YELLOW = "#ffd43b"
+
+# In a 64-unit square: the brackets' left and right x, top and bottom.
+BRACKETS = (8, 50, 14, 50)
+TYPE = (16, 24, 12, 16) # the type block: x, y, width, height
+FLAG = (33, 22, 42) # the flag's pole: x, top, bottom
+BADGE = (50, 48, 10) # centre x, y, radius
+
+SVG = """\
+
+"""
+
+
+def brackets() -> str:
+ """Return the square brackets as an SVG path, each 5 units deep."""
+ left, right, top, bottom = BRACKETS
+ return (
+ f"M{left + 5:g} {top:g}H{left:g}V{bottom:g}H{left + 5:g}"
+ f"M{right - 5:g} {top:g}H{right:g}V{bottom:g}H{right - 5:g}"
+ )
+
+
+def flag() -> str:
+ """Return the flag's cloth as an SVG path, swallow-tailed at its free end."""
+ x, top, _ = FLAG
+ end, notch = x + 10, x + 7 # the cloth's free end, and its tail's notch
+ return f"M{x:g} {top:g}H{end:g}L{notch:g} {top + 5:g}L{end:g} {top + 10:g}H{x:g}Z"
+
+
+def tick() -> str:
+ """Return the badge's tick as an SVG path."""
+ x, y, r = BADGE
+ s = 0.42 * r
+ return f"M{x - s:g} {y:g}l{0.75 * s:g} {0.75 * s:g}l{1.4 * s:g} {-1.5 * s:g}"
+
+
+def svg() -> str:
+ """Return the logo as SVG text."""
+ fx, ftop, fbottom = FLAG
+ tx, ty, tw, th = TYPE
+ bx, by, br = BADGE
+ return SVG.format(
+ brackets=brackets(),
+ flag=flag(),
+ tick=tick(),
+ navy=NAVY,
+ teal=TEAL,
+ purple=PURPLE,
+ yellow=YELLOW,
+ fx=fx,
+ ftop=ftop,
+ fbottom=fbottom,
+ tx=tx,
+ ty=ty,
+ tw=tw,
+ th=th,
+ bx=bx,
+ by=by,
+ br=br,
+ )
+
+
+def main() -> None:
+ """Parse the command line and save the logo."""
+ parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
+ parser.add_argument(
+ "out",
+ nargs="?",
+ type=Path,
+ default=Path(__file__).with_name("favicon.svg"),
+ help="output file, SVG or PNG by its extension (default: favicon.svg)",
+ )
+ parser.add_argument(
+ "--size",
+ type=int,
+ default=512,
+ help="pixels per side, for a PNG",
+ )
+ args = parser.parse_args()
+
+ if args.out.suffix == ".svg":
+ args.out.write_text(svg())
+ else:
+ import resvg_py # noqa: PLC0415 # only a PNG needs a renderer
+
+ png = resvg_py.svg_to_bytes(svg_string=svg(), width=args.size)
+ args.out.write_bytes(bytes(png))
+
+
+if __name__ == "__main__":
+ main()