Skip to content

Commit 87c6a73

Browse files
lesnik512claude
andauthored
feat: transparent SVG logo/favicon + dark-mode palette gap fix (#6)
- Vector wordmark (Futura outlines + corner brackets), generated by scripts/gen_logo_svg.py: currentColor for the hero, white for the header, and an adaptive bracket-mark favicon (green on light tabs, white on dark via prefers-color-scheme). - Homepage hero now inlines the currentColor wordmark (green on the light page, lighter green in dark) instead of the green-background PNG. - Fill the dark scheme's --md-primary-fg-color--light/--dark vars that the earlier review flagged. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
1 parent 6a1d7be commit 87c6a73

7 files changed

Lines changed: 144 additions & 3 deletions

File tree

docs/assets/modern-python-mark.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

docs/assets/modern-python.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ hide:
66

77
<div class="mp-hero" markdown>
88

9-
![Modern Python](assets/modern-python.png){ .mp-logo }
9+
<svg class="mp-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5.773 2.382" fill="currentColor" role="img" aria-label="Modern Python"><g transform="scale(0.000488) translate(696.3,2255.3) scale(1,-1)"><path transform="translate(0.0,0)" d="M131 0 456 1660 986 460 1535 1660 1827 0H1588L1439 932L982 -71L538 933L372 0Z"/><path transform="translate(2173.8,0)" d="M108 779Q108 1105 347 1339Q585 1573 919 1573Q1249 1573 1485 1337Q1722 1101 1722 770Q1722 437 1484 205Q1245 -28 907 -28Q608 -28 370 179Q108 408 108 779ZM343 776Q343 520 515 355Q686 190 910 190Q1153 190 1320 358Q1487 528 1487 772Q1487 1019 1322 1186Q1158 1354 916 1354Q675 1354 509 1186Q343 1020 343 776Z"/><path transform="translate(4208.6,0)" d="M185 0V1544H509Q742 1544 877 1498Q1022 1453 1140 1345Q1379 1127 1379 772Q1379 416 1130 196Q1005 86 868 43Q740 0 505 0ZM418 219H523Q680 219 784 252Q888 287 972 363Q1144 520 1144 772Q1144 1026 974 1184Q821 1325 523 1325H418Z"/><path transform="translate(5900.4,0)" d="M1037 1325H418V954H1019V735H418V219H1037V0H185V1544H1037Z"/><path transform="translate(7263.2,0)" d="M708 658 1186 0H901L460 632H418V0H185V1544H458Q764 1544 900 1429Q1050 1301 1050 1091Q1050 927 956.0 809.0Q862 691 708 658ZM418 835H492Q823 835 823 1088Q823 1325 501 1325H418Z"/><path transform="translate(8701.0,0)" d="M185 0V1649L1311 471V1544H1544V-94L418 1081V0Z"/></g><g transform="scale(0.000488) translate(1207.3,4183.0) scale(1,-1)"><path transform="translate(0.0,0)" d="M418 627V0H185V1544H449Q643 1544 742 1517Q842 1490 918 1415Q1051 1285 1051 1087Q1051 875 909.0 751.0Q767 627 526 627ZM418 843H505Q826 843 826 1090Q826 1329 495 1329H418Z"/><path transform="translate(1310.8,0)" d="M497 667 -7 1544H261L614 927L968 1544H1236L730 667V0H497Z"/><path transform="translate(2744.6,0)" d="M611 1325V0H378V1325H23V1544H965V1325Z"/><path transform="translate(3937.4,0)" d="M418 940H1084V1544H1317V0H1084V721H418V0H185V1544H418Z"/><path transform="translate(5644.2,0)" d="M108 779Q108 1105 347 1339Q585 1573 919 1573Q1249 1573 1485 1337Q1722 1101 1722 770Q1722 437 1484 205Q1245 -28 907 -28Q608 -28 370 179Q108 408 108 779ZM343 776Q343 520 515 355Q686 190 910 190Q1153 190 1320 358Q1487 528 1487 772Q1487 1019 1322 1186Q1158 1354 916 1354Q675 1354 509 1186Q343 1020 343 776Z"/><path transform="translate(7679.0,0)" d="M185 0V1649L1311 471V1544H1544V-94L418 1081V0Z"/></g><path fill="none" stroke="currentColor" stroke-width="0.07" d="M 0.035 0.550 L 0.035 0.035 L 0.550 0.035"/><path fill="none" stroke="currentColor" stroke-width="0.07" d="M 5.738 1.832 L 5.738 2.347 L 5.223 2.347"/></svg>
1010

1111
Open-source templates and libraries for building production-ready Python
1212
applications — web services, microservices, and the dependency injection that

docs/stylesheets/extra.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
/* Dark scheme keeps the same brand hue for links/accents */
1010
[data-md-color-scheme="slate"] {
1111
--md-primary-fg-color: #356852;
12+
--md-primary-fg-color--light: #4a8a6e;
13+
--md-primary-fg-color--dark: #234738;
1214
--md-accent-fg-color: #e0a300;
1315
}
1416

@@ -21,6 +23,11 @@
2123
max-width: 420px;
2224
width: 70%;
2325
height: auto;
26+
color: var(--md-primary-fg-color); /* brand green on the light page */
27+
}
28+
/* Lighter green in dark mode so the wordmark keeps contrast on the dark bg */
29+
[data-md-color-scheme="slate"] .mp-hero .mp-logo {
30+
color: #7fb79f;
2431
}
2532
.mp-hero .mp-tagline {
2633
color: var(--md-default-fg-color--light);

mkdocs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ exclude_docs: |
1212
theme:
1313
name: material
1414
custom_dir: overrides
15-
logo: assets/modern-python.png
16-
favicon: assets/modern-python.png
15+
logo: assets/modern-python-white.svg
16+
favicon: assets/modern-python-mark.svg
1717
features:
1818
- navigation.instant
1919
- navigation.top

scripts/gen_logo_svg.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""Generate a transparent, vector SVG wordmark for Modern Python.
2+
3+
Extracts the "MODERN PYTHON" glyph outlines from Futura (present on macOS) as
4+
real vector paths — no font dependency at render time — and frames them with the
5+
brand's corner brackets. Fill is `currentColor` so the same file can be tinted
6+
per theme (green on light surfaces, white on the green header) via CSS/attr.
7+
8+
Run locally:
9+
10+
uv run --with fonttools python scripts/gen_logo_svg.py
11+
12+
Outputs:
13+
docs/assets/modern-python.svg (two-line wordmark + brackets)
14+
docs/assets/modern-python-mark.svg (compact bracket mark, for the favicon)
15+
"""
16+
17+
import pathlib
18+
19+
from fontTools.pens.svgPathPen import SVGPathPen
20+
from fontTools.ttLib import TTFont
21+
22+
FONT_PATH = "/System/Library/Fonts/Supplemental/Futura.ttc"
23+
FONT_NUMBER = 0 # Futura Medium
24+
ASSETS = pathlib.Path(__file__).resolve().parent.parent / "docs" / "assets"
25+
26+
LINES = ["MODERN", "PYTHON"]
27+
TRACKING = 0.10 # fraction of em added between glyphs
28+
LINE_GAP = 0.18 # fraction of em between the two lines
29+
PAD = 0.34 # padding (em) inside the bracket frame
30+
BRACKET = 0.55 # bracket arm length (em)
31+
STROKE = 0.07 # bracket stroke width (em)
32+
33+
34+
def layout_line(font, glyph_set, cmap, text, upm):
35+
"""Return (svg_paths, width_em) for one tracked line, baseline at y=0, y-up."""
36+
paths, x = [], 0.0
37+
track = TRACKING * upm
38+
for ch in text:
39+
gname = cmap[ord(ch)]
40+
pen = SVGPathPen(glyph_set)
41+
glyph_set[gname].draw(pen)
42+
d = pen.getCommands()
43+
if d:
44+
paths.append(f'<path transform="translate({x:.1f},0)" d="{d}"/>')
45+
x += glyph_set[gname].width + track
46+
return paths, (x - track) / upm # drop trailing track
47+
48+
49+
def main() -> None:
50+
font = TTFont(FONT_PATH, fontNumber=FONT_NUMBER)
51+
glyph_set = font.getGlyphSet()
52+
cmap = font.getBestCmap()
53+
upm = font["head"].unitsPerEm
54+
cap = font["OS/2"].sCapHeight if hasattr(font["OS/2"], "sCapHeight") else int(0.7 * upm)
55+
cap_em = cap / upm
56+
57+
# Lay out each line; track widest for centering.
58+
laid = [layout_line(font, glyph_set, cmap, line, upm) for line in LINES]
59+
text_w = max(w for _, w in laid)
60+
61+
line_h = cap_em + LINE_GAP
62+
text_h = line_h * len(LINES) - LINE_GAP
63+
64+
# Frame geometry (em units, y-down in final SVG).
65+
frame_w = text_w + 2 * PAD
66+
frame_h = text_h + 2 * PAD
67+
vb_w = frame_w
68+
vb_h = frame_h
69+
70+
groups = []
71+
# Each line: flip y-up glyphs into y-down SVG space and position.
72+
for i, (paths, w) in enumerate(laid):
73+
if not paths:
74+
continue
75+
x0 = (frame_w - w) / 2 * upm
76+
baseline = (PAD + cap_em + i * line_h) * upm # y of baseline in y-down em*upm
77+
# translate to (x0, baseline) then scale(1,-1) maps glyph y-up to y-down.
78+
groups.append(
79+
f'<g transform="scale({1/upm:.6f}) translate({x0:.1f},{baseline:.1f}) '
80+
f'scale(1,-1)">{"".join(paths)}</g>'
81+
)
82+
83+
# Corner brackets: top-left and bottom-right, stroked (no fill).
84+
sw, arm = STROKE, BRACKET
85+
86+
def wordmark(color: str) -> str:
87+
tl = (
88+
f'<path fill="none" stroke="{color}" stroke-width="{sw}" '
89+
f'd="M {sw/2:.3f} {arm:.3f} L {sw/2:.3f} {sw/2:.3f} L {arm:.3f} {sw/2:.3f}"/>'
90+
)
91+
br = (
92+
f'<path fill="none" stroke="{color}" stroke-width="{sw}" '
93+
f'd="M {frame_w-sw/2:.3f} {frame_h-arm:.3f} L {frame_w-sw/2:.3f} {frame_h-sw/2:.3f} '
94+
f'L {frame_w-arm:.3f} {frame_h-sw/2:.3f}"/>'
95+
)
96+
return (
97+
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {vb_w:.3f} {vb_h:.3f}" '
98+
f'fill="{color}" role="img" aria-label="Modern Python">'
99+
f'{"".join(groups)}{tl}{br}</svg>\n'
100+
)
101+
102+
# currentColor variant — inlined in the homepage hero, themed via CSS.
103+
(ASSETS / "modern-python.svg").write_text(wordmark("currentColor"))
104+
# White variant — used as the header logo (the header sits on brand green).
105+
(ASSETS / "modern-python-white.svg").write_text(wordmark("#ffffff"))
106+
print(f"wrote modern-python.svg + modern-python-white.svg (viewBox 0 0 {vb_w:.2f} {vb_h:.2f})")
107+
108+
# Compact favicon mark: the two corner brackets only, in a tight square.
109+
# Adapts to the browser/OS theme: brand green on light tabs, white on dark.
110+
m_sw, m_arm = 0.12, 0.62
111+
paths = (
112+
f'<path d="M {m_sw/2:.3f} {m_arm:.3f} L {m_sw/2:.3f} {m_sw/2:.3f} L {m_arm:.3f} {m_sw/2:.3f}"/>'
113+
f'<path d="M {1-m_sw/2:.3f} {1-m_arm:.3f} L {1-m_sw/2:.3f} {1-m_sw/2:.3f} '
114+
f'L {1-m_arm:.3f} {1-m_sw/2:.3f}"/>'
115+
)
116+
style = (
117+
"<style>:root{--mp:#356852}"
118+
"@media(prefers-color-scheme:dark){:root{--mp:#ffffff}}</style>"
119+
)
120+
mark = (
121+
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1" '
122+
f'fill="none" stroke="var(--mp)" stroke-width="{m_sw}" '
123+
f'stroke-linecap="square" role="img" aria-label="Modern Python">'
124+
f'{style}{paths}</svg>\n'
125+
)
126+
(ASSETS / "modern-python-mark.svg").write_text(mark)
127+
print("wrote modern-python-mark.svg")
128+
129+
130+
if __name__ == "__main__":
131+
main()

0 commit comments

Comments
 (0)