Skip to content

Commit f70afd3

Browse files
lesnik512claude
andauthored
Boosty profile cover banner (green colorway) (#38)
* docs(planning): boosty profile cover design + plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(brand): boosty_cover wide banner geometry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(brand): boosty profile cover render target Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(brand): promote boosty cover to architecture Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(brand): correct Boosty cover to 8:1 header (1920x240) Boosty's profile header slot is 1920x240 (8:1), per Boosty creator guides; the initial 4:1/1920x480 would have been cropped. The 240px strip is too short to stack, so the lockup and tagline now sit side by side, centered as a pair using the measured tagline width. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(brand): minimal centered Boosty cover, drop tagline The live header showed two problems: the cover tagline duplicated the bio Boosty already renders below the avatar, and Boosty's avatar overlay plus narrow-viewport cropping clipped the spread-out composition. Reduce the cover to just the MODERN/PYTHON lockup, centered on the green field so it stays clear of the avatar and survives the crop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 940a7f7 commit f70afd3

9 files changed

Lines changed: 360 additions & 0 deletions

File tree

architecture/brand-marks.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ The org's logo assets, generated by `brand/build/` (no frontmatter; living prose
66
Favicon, avatar, social cards — the interlocked-snakes pinwheel with a chevron.
77
Used everywhere small (favicons, avatars). See `site-branding.md` for site wiring.
88

9+
`boosty-cover.svg|png` is a 1920×240 (8:1, Boosty's header slot) green-colorway
10+
profile-header banner for the org's Boosty page (`boosty.to/lesnik512`): just the
11+
MODERN/PYTHON `geometry.lockup_body`, centered on the green surface, no tagline
12+
(Boosty renders the bio as text below the avatar, so a cover tagline would
13+
duplicate it). Generated by `geometry.boosty_cover`; the lockup stays
14+
horizontally centered so Boosty's lower-left avatar overlay and its
15+
narrow-viewport crop never clip it. Not served by the docs site (excluded from
16+
`just sync-assets`); re-export at other sizes by changing only the `export_png`
17+
dimensions in `render.py`.
18+
919
## Per-project marks (`brand/projects/<repo>/`)
1020
One large-format logo per repo: the constant green+gold snake-frame
1121
(`geometry.py::project_frame`, margin 9 / arm 53 / stroke 11) with a single

brand/build/geometry.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,25 @@ def social_square(*, bg: str, struct: str, gold: str) -> str:
138138
)
139139

140140

141+
def boosty_cover(*, bg: str, struct: str, gold: str) -> str:
142+
"""Boosty profile-header banner — 8:1 (1920x240), Boosty's documented header
143+
size. Just the MODERN/PYTHON lockup, centered on a full-bleed field. It is
144+
kept in the horizontal center (visual box 540 wide, centered on x=960) so
145+
Boosty's responsive side-cropping and the lower-left avatar overlay never
146+
clip it; no tagline, since Boosty renders the bio as text below the avatar."""
147+
w, h = 1920, 240
148+
s = 1.1 # lockup box is 540x250 (visual y-center 125)
149+
tx = round(w / 2 - 270 * s, 1) # box-center horizontally
150+
ty = round(h / 2 - 125 * s, 1) # box-center vertically
151+
body = lockup_body(struct=struct, gold=gold)
152+
return (
153+
_SVG_OPEN.format(w=w, h=h)
154+
+ f'<rect width="{w}" height="{h}" fill="{bg}"/>'
155+
+ f'<g transform="translate({tx},{ty}) scale({s})">{body}</g>'
156+
+ "</svg>"
157+
)
158+
159+
141160
def project_frame(
142161
*,
143162
struct: str,

brand/build/render.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ def render() -> None:
7272
height=640,
7373
)
7474

75+
# Boosty profile cover (green colorway) — brand/org/boosty-cover.*
76+
_write(
77+
ORG / "boosty-cover.svg",
78+
g.boosty_cover(bg=t.GREEN_SURFACE, struct=t.CREAM, gold=t.GOLD_DARK),
79+
)
80+
export_png(
81+
ORG / "boosty-cover.svg", ORG / "boosty-cover.png", width=1920, height=240
82+
)
83+
7584
# Square (Telegram / square social) — cream + green.
7685
_write(
7786
ORG / "social-square.svg",

brand/org/boosty-cover.png

3.8 KB
Loading

brand/org/boosty-cover.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
summary: Add a Boosty profile cover to the brand generator — an 8:1 1920x240 green-colorway header banner with the MODERN/PYTHON lockup centered on a green field (no tagline), kept in the crop-safe center clear of Boosty's avatar overlay, as SVG + PNG in brand/org/.
3+
---
4+
5+
# Design: modern-python Boosty profile cover
6+
7+
## Summary
8+
9+
Add a new **Boosty cover** output to the brand kit (`brand/build/`) that renders
10+
a wide profile-header banner for the org's Boosty page (`boosty.to/lesnik512`).
11+
It is **8:1 at 1920×240** — Boosty's documented profile-header size. It uses the
12+
green colorway (green surface `#2f5e4a` with cream + gold-dark ink) — the same
13+
treatment as `social-card-green` and the org avatar. The banner is deliberately
14+
**minimal**: just the existing MODERN/PYTHON `lockup_body`, centered on the green
15+
field, **no tagline** (Boosty already renders the bio as text below the avatar,
16+
so a tagline in the cover would duplicate it). Centering matters — Boosty
17+
overlays the avatar on the lower-left and crops the header responsively on
18+
narrow viewports, so the lockup is kept in the horizontal center where nothing
19+
clips it. Outputs land in `brand/org/` as vector SVG plus a 1920×240 PNG,
20+
regenerable via `uv run python -m brand.build.render`.
21+
22+
## Motivation
23+
24+
The org profile now links to Boosty for support (commit 940a7f7), but the Boosty
25+
page has no branded cover. Boosty profiles show a wide header banner behind the
26+
avatar and display name; a blank or off-brand cover is a missed identity cue on
27+
the one page asking visitors for money. Producing it by hand would drift from the
28+
repo's "every asset is generated from `brand/build/`" convention, so it is wired
29+
into the generator like every other asset.
30+
31+
The colorway (green) and composition (centered lockup + tagline) were settled
32+
interactively against a rendered draft.
33+
34+
## Non-goals
35+
36+
- No cream colorway variant — green only, one banner.
37+
- No changes to org marks, project marks, social cards, apparel, or site wiring.
38+
- No new palette tokens — reuses `GREEN_SURFACE`, `CREAM`, `GOLD_DARK`.
39+
- Not added to `just sync-assets`' docs copy list — the site does not serve it.
40+
- No Boosty account/profile changes — file only; the upload is a manual step.
41+
42+
## Design
43+
44+
### 1. Cover geometry — `brand/build/geometry.py`
45+
46+
Add `boosty_cover(*, bg, struct, gold)`: a complete `<svg>` (viewBox
47+
`0 0 1920 240`) with a full-bleed `bg` rect and the `lockup_body()` lockup scaled
48+
`1.1` and centered — nothing else. It reuses `lockup_body()` (the MODERN/PYTHON
49+
crop-mark lockup in the 540×250 space); the lockup's box center (270, 125) is
50+
placed on the banner center (960, 120), so `translate` resolves to
51+
`translate(663.0,-17.5) scale(1.1)`.
52+
53+
```python
54+
def boosty_cover(*, bg: str, struct: str, gold: str) -> str:
55+
"""Boosty profile-header banner — 8:1 (1920x240). Just the MODERN/PYTHON
56+
lockup, centered on a full-bleed field; no tagline."""
57+
w, h = 1920, 240
58+
s = 1.1 # lockup box is 540x250 (visual y-center 125)
59+
tx = round(w / 2 - 270 * s, 1) # box-center horizontally
60+
ty = round(h / 2 - 125 * s, 1) # box-center vertically
61+
body = lockup_body(struct=struct, gold=gold)
62+
return (
63+
_SVG_OPEN.format(w=w, h=h)
64+
+ f'<rect width="{w}" height="{h}" fill="{bg}"/>'
65+
+ f'<g transform="translate({tx},{ty}) scale({s})">{body}</g>'
66+
+ "</svg>"
67+
)
68+
```
69+
70+
MODERN is `struct` (cream) and PYTHON is `gold` (gold-dark), so the banner stays
71+
within the on-green two-ink treatment. There is no tagline text, so `outline_text`
72+
is not used here.
73+
74+
### 2. Render wiring — `brand/build/render.py`
75+
76+
In `render()`, after the existing green social card block, write
77+
`boosty-cover.svg` via `g.boosty_cover(bg=GREEN_SURFACE, struct=CREAM,
78+
gold=GOLD_DARK)` and export `boosty-cover.png` at 1920×240 through the existing
79+
`export_png``_quantize_png` path. Without `rsvg-convert` the SVG still writes
80+
and the PNG is skipped, as elsewhere.
81+
82+
### 3. Architecture promotion — `architecture/brand-marks.md`
83+
84+
Extend the **Org marks** section to note the Boosty cover: what it is, its
85+
colorway and dimensions (8:1, 1920×240), and that it is generated by
86+
`geometry.boosty_cover` and intended for the `boosty.to/lesnik512` profile header.
87+
88+
## Operations
89+
90+
Upload `brand/org/boosty-cover.png` as the profile cover at `boosty.to/lesnik512`
91+
(Boosty's header slot is 1920×240, 8:1). Re-export at a different size if Boosty
92+
changes the slot — the SVG is resolution-independent, so only the `export_png`
93+
width/height change.
94+
95+
## Testing
96+
97+
- New `tests/test_geometry.py::test_boosty_cover`: viewBox is `0 0 1920 240`;
98+
full-bleed `bg` rect (`width="1920" height="240"`) present; centered lockup
99+
placement (`translate(663.0,-17.5) scale(1.1)`); carries the lockup crops
100+
(`M138 122 L138 50 L210 50`); **no** `<text>`; the same `<path>` count as a
101+
bare `wordmark` (minimal — no tagline glyphs); only cream + gold-dark inks over
102+
the green; no `var(`.
103+
- Extend `tests/test_assets.py::test_render_writes_social_cards` (or a sibling)
104+
to assert `brand/org/boosty-cover.svg` parses and the green colors are present,
105+
and that the PNG magic bytes are present when `rsvg-convert` is available.
106+
- `just test` green; `uv run python -m brand.build.render` is a deterministic
107+
no-op diff apart from the two new `boosty-cover` files.
108+
- Visual check of `boosty-cover.svg`/`.png` before committing.
109+
- `just check-planning` passes before pushing.
110+
111+
## Risk
112+
113+
- **Low: banner ratio wrong for Boosty's crop.** Resolved — Boosty's header slot
114+
is 1920×240 (8:1), per Boosty creator guides; this ships at exactly that. The
115+
SVG is resolution-independent, so a future slot change is only an `export_png`
116+
dimension edit.
117+
- **Low: avatar overlaps the lockup.** The avatar sits lower-left; the lockup is
118+
horizontally centered, well clear of it, and Boosty's narrow-viewport crop
119+
keeps the center — so the centered mark survives. Mitigated by the composition.
120+
- **Resolved: tagline duplicated the bio.** An earlier draft set the tagline in
121+
the cover; Boosty already shows the bio (`Open-source Python for production`)
122+
as text below the avatar, so the cover tagline was dropped.
123+
- **Low: PNG absent in CI** if `rsvg-convert` is unavailable. Same behavior as
124+
every other target — the SVG still generates; the PNG is committed from a dev
125+
machine with librsvg.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# boosty-cover — implementation plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use
4+
> superpowers:subagent-driven-development (recommended) or
5+
> superpowers:executing-plans to implement this plan task-by-task. Steps
6+
> use checkbox (`- [ ]`) syntax for tracking.
7+
8+
**Goal:** Add a Boosty profile-cover target to the brand generator that emits an
9+
8:1 1920×240 green-colorway header banner — the MODERN/PYTHON lockup centered on
10+
a green field, no tagline — as SVG + PNG in `brand/org/`.
11+
12+
**Spec:** [`design.md`](./design.md)
13+
14+
**Branch:** `boosty-profile-cover`
15+
16+
**Commit strategy:** Per-task commits.
17+
18+
## Global Constraints
19+
20+
- Green colorway only: `bg = #2f5e4a` (`tokens.GREEN_SURFACE`), `struct = #f4f1e8`
21+
(`tokens.CREAM`), `gold = #f0b528` (`tokens.GOLD_DARK`). No other colors.
22+
- Cover SVG viewBox is exactly `0 0 1920 240` (8:1, Boosty's header slot); the
23+
lockup is `scale(1.1)`, centered — its box center (270, 125) placed on the
24+
banner center (960, 120), i.e. `translate(663.0,-17.5)`. No tagline.
25+
- PNG size: `width=1920, height=240`.
26+
- All imports at module level; annotate all function arguments; `ty: ignore`
27+
never `type: ignore`.
28+
- Brand casing in prose: `modern-python`, `modern-di`. Tagline has no trailing
29+
period.
30+
- Generated SVG **and** PNG under `brand/org/` are committed.
31+
- No dependency changes, so `uv.lock` must not change (leave it alone).
32+
- Commit only on the `boosty-profile-cover` branch, never `main`. End commit
33+
messages with the `Co-Authored-By: Claude Opus 4.8 (1M context)
34+
<noreply@anthropic.com>` trailer. Do not mention Claude Code in the body.
35+
- Run `just check-planning` and `just test` before pushing. Finish via PR.
36+
37+
---
38+
39+
### Task 1: Cover geometry (`geometry.boosty_cover`)
40+
41+
**Files:**
42+
- Modify: `brand/build/geometry.py` (add function after `social_square()`)
43+
- Test: `tests/test_geometry.py` (add one test)
44+
45+
- [ ] **Step 1: Write the failing test** in `tests/test_geometry.py`:
46+
47+
```python
48+
def test_boosty_cover(parse_svg):
49+
svg = g.boosty_cover(bg="#2f5e4a", struct="#f4f1e8", gold="#f0b528")
50+
el = parse_svg(svg)
51+
assert el.attrib["viewBox"] == "0 0 1920 240" # 8:1 Boosty header
52+
assert 'fill="#2f5e4a"' in svg # full-bleed green bg
53+
assert 'width="1920" height="240"' in svg # full-bleed rect
54+
assert "translate(663.0,-17.5) scale(1.1)" in svg # lockup centered on x=960
55+
assert "M138 122 L138 50 L210 50" in svg # carries the lockup crops
56+
assert "<text" not in svg # no live text (and no tagline)
57+
assert "#f4f1e8" in svg and "#f0b528" in svg
58+
assert "var(" not in svg
59+
# minimal: just the lockup, so the same glyph-path count as a bare wordmark
60+
assert svg.count("<path") == g.wordmark(struct="#f4f1e8", gold="#f0b528").count("<path")
61+
```
62+
63+
- [ ] **Step 2: Run test, verify it fails**
64+
`uv run pytest tests/test_geometry.py::test_boosty_cover -v`
65+
Expected: `AttributeError: ... has no attribute 'boosty_cover'`.
66+
67+
- [ ] **Step 3: Implement** `boosty_cover` in `brand/build/geometry.py` per the
68+
spec's §1 code block.
69+
70+
- [ ] **Step 4: Run test, verify it passes.**
71+
72+
- [ ] **Step 5: Commit**
73+
74+
```bash
75+
git add brand/build/geometry.py tests/test_geometry.py
76+
git commit -m "feat(brand): boosty_cover wide banner geometry"
77+
```
78+
79+
---
80+
81+
### Task 2: Render target + regenerate assets
82+
83+
**Files:**
84+
- Modify: `brand/build/render.py` (write SVG + export PNG after the green card)
85+
- Test: `tests/test_assets.py` (add a boosty-cover render test)
86+
87+
- [ ] **Step 1: Write the failing test** in `tests/test_assets.py`:
88+
89+
```python
90+
def test_render_writes_boosty_cover():
91+
_render()
92+
cover = ORG / "boosty-cover.svg"
93+
assert cover.exists()
94+
ET.parse(cover)
95+
text = cover.read_text()
96+
assert 'viewBox="0 0 1920 240"' in text
97+
assert 'fill="#2f5e4a"' in text and "#f4f1e8" in text and "#f0b528" in text
98+
assert "<text" not in text and "var(" not in text
99+
if shutil.which("rsvg-convert"):
100+
assert (ORG / "boosty-cover.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
101+
```
102+
103+
- [ ] **Step 2: Run test, verify it fails**
104+
`uv run pytest tests/test_assets.py::test_render_writes_boosty_cover -v`
105+
Expected: `assert cover.exists()` is False.
106+
107+
- [ ] **Step 3: Wire render.py.** After the `social-card-green` block in
108+
`render()`, add:
109+
110+
```python
111+
# Boosty profile cover (green colorway) — brand/org/boosty-cover.*
112+
_write(
113+
ORG / "boosty-cover.svg",
114+
g.boosty_cover(bg=t.GREEN_SURFACE, struct=t.CREAM, gold=t.GOLD_DARK),
115+
)
116+
export_png(
117+
ORG / "boosty-cover.svg", ORG / "boosty-cover.png", width=1920, height=240
118+
)
119+
```
120+
121+
- [ ] **Step 4: Run test, verify it passes.**
122+
123+
- [ ] **Step 5: Regenerate and commit**
124+
125+
```bash
126+
uv run python -m brand.build.render
127+
git add brand/build/render.py tests/test_assets.py brand/org/boosty-cover.svg brand/org/boosty-cover.png
128+
git commit -m "feat(brand): boosty profile cover render target"
129+
```
130+
131+
Expected: `git status` shows only the two new `brand/org/boosty-cover.*` files
132+
plus the edited sources — no other asset churn.
133+
134+
---
135+
136+
### Task 3: Architecture promotion
137+
138+
**Files:**
139+
- Modify: `architecture/brand-marks.md` (note the Boosty cover under Org marks)
140+
141+
- [ ] **Step 1: Promote the capability** — add to the Org marks description that
142+
`brand/org/boosty-cover.svg|png` is a 1920×240 (8:1) green-colorway
143+
profile-header banner for `boosty.to/lesnik512`, generated by
144+
`geometry.boosty_cover` (centered `lockup_body`, no tagline).
145+
146+
- [ ] **Step 2: Verify**`just test` (all pass) and `just check-planning`
147+
(`planning: OK`).
148+
149+
- [ ] **Step 3: Commit**
150+
151+
```bash
152+
git add architecture/brand-marks.md
153+
git commit -m "docs(brand): promote boosty cover to architecture"
154+
```
155+
156+
---
157+
158+
## Notes for the executor
159+
160+
- Follow the existing one-function-per-asset pattern; do not restructure
161+
`geometry.py` or `render.py` beyond the additions above.
162+
- The cover is **not** added to `just sync-assets` — the site does not serve it.
163+
- If Boosty's uploader wants a different size, change only the `export_png`
164+
width/height in `render.py` and re-render.
165+
- Finish via PR (push `boosty-profile-cover`, open a PR); do not local-merge.
166+
Watch CI after pushing.

tests/test_assets.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ def test_render_writes_social_cards():
9090
assert (ORG / f"{name}.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
9191

9292

93+
def test_render_writes_boosty_cover():
94+
_render()
95+
cover = ORG / "boosty-cover.svg"
96+
assert cover.exists()
97+
ET.parse(cover)
98+
text = cover.read_text()
99+
assert 'viewBox="0 0 1920 240"' in text
100+
assert 'fill="#2f5e4a"' in text and "#f4f1e8" in text and "#f0b528" in text
101+
assert "<text" not in text and "var(" not in text
102+
if shutil.which("rsvg-convert"):
103+
assert (ORG / "boosty-cover.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
104+
105+
93106
def test_render_writes_apparel():
94107
_render()
95108
chest = APPAREL / "chest-mark.svg"

0 commit comments

Comments
 (0)