feat(brand): replace the circular seal with the shield mark - #37
Merged
Merged
Conversation
Every brand asset carried the old circular VGI seal — including the logo the landing, describe, 404 and 401 pages request by URL. The shield artwork was already in the repo as logo-shield.png but only the README pointed at it, and it sat on an opaque white background. The master (docs/assets/logo-master.png, 1197x880) is now committed and every other asset is cut from it by scripts/regenerate_logo_assets.py, so a future artwork change is one command rather than eight hand edits that drift. Keying the background is a global near-white threshold rather than a fill from the border. Keying only what the border reaches leaves the enclosed gaps opaque — the sky between the tree and the barn wall, the holes between the cloud's connector traces — which read as white specks on the cream page and glare on a dark one. The mark's lightest ink is cream at min-channel 193, far below the threshold, so nothing real is at risk; the script reports how much keyed area was enclosed rather than marginal, so a future master carrying genuine white ink shows up as a large number instead of a few hundred pixels. Edge pixels get partial alpha from their distance to white and are then un-premultiplied, so the mark composites back over white exactly as the master did instead of wearing a pale outline on a coloured background. The CSS had to move with it. Four rules sized the logo as a square and clipped it round — `width/height` both fixed, `border-radius: 50%`, a rectangular `box-shadow`, and `object-fit: cover` on the docs header. The shield is 1.36:1 and transparent, so unchanged they would squash it into a circle and float a box shadow around a shield-shaped hole. Each now sizes by width with the height following, and shadows with a `drop-shadow` that traces the alpha silhouette. Asset filenames are unchanged, so the URLs the pages request and the tests assert on still resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the project’s branding across HTTP-served pages and documentation to use the shield mark (transparent, non-square aspect) instead of the legacy circular seal, and introduces a script to regenerate all derived logo assets from a single committed master.
Changes:
- Update inline page CSS (landing/describe + shared error pages) to size the new logo by width and use
filter: drop-shadow(...)instead of circular clipping +box-shadow. - Update MkDocs theme overrides to prevent square cropping and apply a silhouette-tracing shadow.
- Add
scripts/regenerate_logo_assets.pyto regenerate all derived assets indocs/assets/fromlogo-master.png.
Reviewed changes
Copilot reviewed 4 out of 12 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
vgi_rpc/http/server/_pages.py |
Adjust landing/describe page logo sizing and shadowing for the shield mark. |
vgi_rpc/http/_common.py |
Update shared 401/404-style logo rules to match the new transparent, landscape logo. |
docs/stylesheets/extra.css |
Update docs hero/header logo sizing and shadowing to avoid cropping and match new aspect ratio. |
scripts/regenerate_logo_assets.py |
Add an asset regeneration script to derive all brand images from a single master. |
Suppressed comments (1)
scripts/regenerate_logo_assets.py:112
_dilate()usesnp.roll()for neighbourhood growth, which wraps around the array edges. That makes dilation “teleport” across left/right and top/bottom borders, which can create an edge band on the opposite side of the image. Use pad+slice (non-wrapping) shifts for the 8-neighbour expansion instead.
def _dilate(mask: np.ndarray, radius: int) -> np.ndarray:
"""Grow *mask* by *radius* pixels in 8-connectivity."""
out = mask.copy()
for _ in range(radius):
grown = out.copy()
for dy in (-1, 0, 1):
for dx in (-1, 0, 1):
grown |= np.roll(np.roll(out, dy, axis=0), dx, axis=1)
out = grown
return out
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+87
to
+100
| # Iterated 8-connected dilation clipped to the candidate set. Simple enough | ||
| # to trust by reading, and a few seconds on a 1200x900 master. | ||
| while True: | ||
| grown = reached.copy() | ||
| for shift, axis in ((1, 0), (-1, 0), (1, 1), (-1, 1)): | ||
| grown |= np.roll(reached, shift, axis=axis) | ||
| # Diagonals, so a 1px-wide gap between two shapes still lets the fill through. | ||
| for dy in (-1, 1): | ||
| for dx in (-1, 1): | ||
| grown |= np.roll(np.roll(reached, dy, axis=0), dx, axis=1) | ||
| grown &= candidate | ||
| if np.array_equal(grown, reached): | ||
| return reached | ||
| reached = grown |
Comment on lines
+234
to
+235
| logo = key_out_background(Image.open(_MASTER)) | ||
| print(f"master {Image.open(_MASTER).size} -> keyed mark {logo.size}") |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every brand asset carried the old circular VGI seal — including the logo the landing, describe, 404 and 401 pages request by URL. The shield artwork was already in the repo as
logo-shield.png, but only the README pointed at it and it sat on an opaque white background.One master, eight derived assets
docs/assets/logo-master.png(1197×880) is now committed, and everything else is cut from it byscripts/regenerate_logo_assets.py:logo-shield.pnglogo-hero.pnglogo.pngapple-touch-icon.pngfavicon-16/32.png,favicon.icosocial-card.png--master PATHcuts from a different source and replaces the committed one, so the next artwork change is one command instead of eight hand edits that drift apart. The social card is rebuilt by replacing only its logo block, so the text baked into it stays byte-identical rather than being re-typeset with whatever fonts happen to be installed.Keying the background
A global near-white threshold, not a fill from the border. Keying only what the border can reach leaves the enclosed gaps opaque — the sky between the tree and the barn wall, the holes between the cloud's connector traces — which read as white specks on the cream page and glare on a dark one. The mark's lightest real ink is cream at min-channel 193, far below the 244 threshold, so a global key cannot eat it.
That margin isn't guaranteed for a future master, so the script reports how much keyed area was enclosed rather than marginal. Genuine white ink would show up there as a large number instead of the 1,206 stray pixels this master has:
Edge pixels take partial alpha from their distance to white and are then un-premultiplied, so the result composites back over white exactly as the master did — and over the cream page or a dark background without a pale outline.
The CSS had to move with it
Four rules sized the logo as a square and clipped it round: fixed
width/height,border-radius: 50%, a rectangularbox-shadow, andobject-fit: coveron the docs header. The shield is 1.36:1 and transparent, so left alone they would squash it into a circle and float a box shadow around a shield-shaped hole.vgi_rpc/http/_common.py— shared 404/401 page stylevgi_rpc/http/server/_pages.py— landing and describedocs/stylesheets/extra.css— docs hero and header navEach now sizes by width with the height following, and shadows with
drop-shadow, which traces the alpha silhouette.Verification
Rendered all four pages locally with the src rewritten to the working-copy asset — the templates hardcode
https://vgi-rpc-python.query.farm/assets/..., which keeps serving the old mark until the docs site deploys, so a plain local server would have shown the wrong logo and proved nothing.Filenames are unchanged, so the URLs the pages request and the three
tests/test_http.pyassertions still resolve. Gate: ruff, pydoclint, mypy strict, ty (3 pre-existingnacl.bindingsdiagnostics), 3828 passed / 163 skipped.Independent of #36 — that PR touches the same
_ERROR_PAGE_STYLEblock but different rules, so they merge in either order.🤖 Generated with Claude Code