Skip to content

Commit b4cd8c0

Browse files
hsyl20claude
andcommitted
Docs: use a 3D-rendered plinth mark
Replaces the raster favicons with renders of the plinth pedestal, and puts a rendered PLINTH wordmark at the top of the front page. The POV-Ray sources live in _logo/ (underscore-prefixed, so Jekyll leaves them out of the built site) and share a single include, so the wordmark and the icon cannot drift apart. Palette is taken from the site's own CSS. The flat artwork stays in use where rendering does not help: the 24px header logo and the Safari pinned tab, both of which need to stay crisp and single-colour. assets/images/favicon-source.svg remains the geometry the 3D mark was matched against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6f25216 commit b4cd8c0

15 files changed

Lines changed: 625 additions & 9 deletions

_includes/svg/logo.svg

Lines changed: 5 additions & 4 deletions
Loading

_logo/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Logo sources
2+
3+
POV-Ray scenes for the 3D Plinth marks. Underscore-prefixed, so Jekyll does not
4+
copy any of it into the built site.
5+
6+
| File | What |
7+
|---|---|
8+
| `plinth-common.inc` | palette, geometry, materials, light rig -- shared, so the two marks cannot drift apart |
9+
| `plinth-logo.pov` | the PLINTH wordmark |
10+
| `plinth-icon.pov` | the plinth alone on a brand tile, for the favicons |
11+
| `make-icons.py` | rounds the tile corners and cuts the favicon sizes |
12+
13+
Rendering, from this directory:
14+
15+
```sh
16+
# wordmark -> assets/images/plinth-wordmark.png (downscaled to 1200px wide)
17+
povray +Iplinth-logo.pov +Oplinth-logo.png +W1800 +H700 +A0.25 +AM2 +R3 +Q11
18+
19+
# favicons -> assets/
20+
povray +Iplinth-icon.pov +Oplinth-icon.png +W1024 +H1024 +A0.2 +AM2 +R4 +Q11
21+
./make-icons.py plinth-icon.png
22+
```
23+
24+
`Declare=Theme=1` renders either scene white-on-charcoal instead of
25+
coral-on-white. The site is light-skinned, so only the light variants are
26+
committed.
27+
28+
The palette comes from the site's own CSS (`#fc4d50`, `#ffffff`, `#f2f2f2`,
29+
`#222222`). Two things in `plinth-common.inc` are worth reading before changing
30+
the lighting: `Note [sRGB hex under assumed_gamma 1.0]` and
31+
`Note [Hitting the brand colour]` -- the coral albedo is hand-calibrated against
32+
this light rig, and moving the lights invalidates it.
33+
34+
The flat artwork these were matched against is still in the repo and still in
35+
use: `assets/images/favicon-source.svg` (geometry), `_includes/svg/logo.svg`
36+
(24px header mark) and `assets/safari-pinned-tab.svg` (monochrome mask).

_logo/make-icons.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
"""Cut the favicon set from the square render of plinth-icon.pov.
3+
4+
POV-Ray renders a square tile with hard corners; this rounds them off with an
5+
alpha mask and resamples down to the sizes the site uses. Corner radius is 22%
6+
of the edge, which is the iOS proportion and close to the existing flat mark.
7+
8+
povray +Iplinth-icon.pov +Oplinth-icon.png +W1024 +H1024 +A0.2 +AM2 +R4 +Q11
9+
./make-icons.py plinth-icon.png
10+
11+
Outputs, named to drop straight into the site's assets directory:
12+
apple-touch-icon.png 180
13+
favicon-32x32.png 32
14+
favicon-16x16.png 16
15+
android-chrome-192x192.png 192
16+
android-chrome-512x512.png 512
17+
favicon.ico 16, 32, 48
18+
<stem>-rounded.png full size, for READMEs and slides
19+
"""
20+
21+
import sys
22+
from pathlib import Path
23+
24+
from PIL import Image, ImageDraw
25+
26+
# 12/64, the corner radius of the flat master artwork the site already ships in
27+
# assets/images/favicon-source.svg, so the 3D tile lines up with the header logo.
28+
RADIUS_FRACTION = 12 / 64
29+
SUPERSAMPLE = 4 # the mask is drawn large and shrunk, to antialias the corners
30+
PNG_SIZES = {
31+
"apple-touch-icon.png": 180,
32+
"favicon-32x32.png": 32,
33+
"favicon-16x16.png": 16,
34+
"android-chrome-192x192.png": 192,
35+
"android-chrome-512x512.png": 512,
36+
}
37+
ICO_SIZES = [(16, 16), (32, 32), (48, 48)]
38+
39+
40+
def rounded_mask(size: int) -> Image.Image:
41+
big = size * SUPERSAMPLE
42+
mask = Image.new("L", (big, big), 0)
43+
ImageDraw.Draw(mask).rounded_rectangle(
44+
(0, 0, big - 1, big - 1), radius=int(big * RADIUS_FRACTION), fill=255
45+
)
46+
return mask.resize((size, size), Image.LANCZOS)
47+
48+
49+
def main(argv: list[str]) -> int:
50+
src = Path(argv[1] if len(argv) > 1 else "plinth-icon.png")
51+
prefix = argv[2] if len(argv) > 2 else ""
52+
53+
tile = Image.open(src).convert("RGBA")
54+
if tile.width != tile.height:
55+
print(f"{src}: expected a square render, got {tile.size}", file=sys.stderr)
56+
return 1
57+
tile.putalpha(rounded_mask(tile.width))
58+
59+
written = []
60+
full = src.with_name(f"{src.stem}-rounded.png")
61+
tile.save(full)
62+
written.append((full.name, tile.width))
63+
64+
for name, size in PNG_SIZES.items():
65+
out = src.with_name(prefix + name)
66+
tile.resize((size, size), Image.LANCZOS).save(out, optimize=True)
67+
written.append((out.name, size))
68+
69+
ico = src.with_name(prefix + "favicon.ico")
70+
tile.save(ico, sizes=ICO_SIZES)
71+
written.append((ico.name, ", ".join(str(w) for w, _ in ICO_SIZES)))
72+
73+
for name, size in written:
74+
print(f" {name:<24} {size}")
75+
return 0
76+
77+
78+
if __name__ == "__main__":
79+
raise SystemExit(main(sys.argv))

0 commit comments

Comments
 (0)