Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions architecture/brand-marks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The org's logo assets, generated by `brand/build/` (no frontmatter; living prose
Favicon, avatar, social cards — the interlocked-snakes pinwheel with a chevron.
Used everywhere small (favicons, avatars). See `site-branding.md` for site wiring.

`boosty-cover.svg|png` is a 1920×240 (8:1, Boosty's header slot) green-colorway
profile-header banner for the org's Boosty page (`boosty.to/lesnik512`): just the
MODERN/PYTHON `geometry.lockup_body`, centered on the green surface, no tagline
(Boosty renders the bio as text below the avatar, so a cover tagline would
duplicate it). Generated by `geometry.boosty_cover`; the lockup stays
horizontally centered so Boosty's lower-left avatar overlay and its
narrow-viewport crop never clip it. Not served by the docs site (excluded from
`just sync-assets`); re-export at other sizes by changing only the `export_png`
dimensions in `render.py`.

## Per-project marks (`brand/projects/<repo>/`)
One large-format logo per repo: the constant green+gold snake-frame
(`geometry.py::project_frame`, margin 9 / arm 53 / stroke 11) with a single
Expand Down
19 changes: 19 additions & 0 deletions brand/build/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,25 @@ def social_square(*, bg: str, struct: str, gold: str) -> str:
)


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


def project_frame(
*,
struct: str,
Expand Down
9 changes: 9 additions & 0 deletions brand/build/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ def render() -> None:
height=640,
)

# Boosty profile cover (green colorway) — brand/org/boosty-cover.*
_write(
ORG / "boosty-cover.svg",
g.boosty_cover(bg=t.GREEN_SURFACE, struct=t.CREAM, gold=t.GOLD_DARK),
)
export_png(
ORG / "boosty-cover.svg", ORG / "boosty-cover.png", width=1920, height=240
)

# Square (Telegram / square social) — cream + green.
_write(
ORG / "social-square.svg",
Expand Down
Binary file added brand/org/boosty-cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions brand/org/boosty-cover.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
125 changes: 125 additions & 0 deletions planning/changes/2026-07-07.01-boosty-cover/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
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/.
---

# Design: modern-python Boosty profile cover

## Summary

Add a new **Boosty cover** output to the brand kit (`brand/build/`) that renders
a wide profile-header banner for the org's Boosty page (`boosty.to/lesnik512`).
It is **8:1 at 1920×240** — Boosty's documented profile-header size. It uses the
green colorway (green surface `#2f5e4a` with cream + gold-dark ink) — the same
treatment as `social-card-green` and the org avatar. The banner is deliberately
**minimal**: just the existing MODERN/PYTHON `lockup_body`, centered on the green
field, **no tagline** (Boosty already renders the bio as text below the avatar,
so a tagline in the cover would duplicate it). Centering matters — Boosty
overlays the avatar on the lower-left and crops the header responsively on
narrow viewports, so the lockup is kept in the horizontal center where nothing
clips it. Outputs land in `brand/org/` as vector SVG plus a 1920×240 PNG,
regenerable via `uv run python -m brand.build.render`.

## Motivation

The org profile now links to Boosty for support (commit 940a7f7), but the Boosty
page has no branded cover. Boosty profiles show a wide header banner behind the
avatar and display name; a blank or off-brand cover is a missed identity cue on
the one page asking visitors for money. Producing it by hand would drift from the
repo's "every asset is generated from `brand/build/`" convention, so it is wired
into the generator like every other asset.

The colorway (green) and composition (centered lockup + tagline) were settled
interactively against a rendered draft.

## Non-goals

- No cream colorway variant — green only, one banner.
- No changes to org marks, project marks, social cards, apparel, or site wiring.
- No new palette tokens — reuses `GREEN_SURFACE`, `CREAM`, `GOLD_DARK`.
- Not added to `just sync-assets`' docs copy list — the site does not serve it.
- No Boosty account/profile changes — file only; the upload is a manual step.

## Design

### 1. Cover geometry — `brand/build/geometry.py`

Add `boosty_cover(*, bg, struct, gold)`: a complete `<svg>` (viewBox
`0 0 1920 240`) with a full-bleed `bg` rect and the `lockup_body()` lockup scaled
`1.1` and centered — nothing else. It reuses `lockup_body()` (the MODERN/PYTHON
crop-mark lockup in the 540×250 space); the lockup's box center (270, 125) is
placed on the banner center (960, 120), so `translate` resolves to
`translate(663.0,-17.5) scale(1.1)`.

```python
def boosty_cover(*, bg: str, struct: str, gold: str) -> str:
"""Boosty profile-header banner — 8:1 (1920x240). Just the MODERN/PYTHON
lockup, centered on a full-bleed field; no tagline."""
w, h = 1920, 240
s = 1.1 # lockup box is 540x250 (visual y-center 125)
tx = round(w / 2 - 270 * s, 1) # box-center horizontally
ty = round(h / 2 - 125 * s, 1) # box-center vertically
body = lockup_body(struct=struct, gold=gold)
return (
_SVG_OPEN.format(w=w, h=h)
+ f'<rect width="{w}" height="{h}" fill="{bg}"/>'
+ f'<g transform="translate({tx},{ty}) scale({s})">{body}</g>'
+ "</svg>"
)
```

MODERN is `struct` (cream) and PYTHON is `gold` (gold-dark), so the banner stays
within the on-green two-ink treatment. There is no tagline text, so `outline_text`
is not used here.

### 2. Render wiring — `brand/build/render.py`

In `render()`, after the existing green social card block, write
`boosty-cover.svg` via `g.boosty_cover(bg=GREEN_SURFACE, struct=CREAM,
gold=GOLD_DARK)` and export `boosty-cover.png` at 1920×240 through the existing
`export_png` → `_quantize_png` path. Without `rsvg-convert` the SVG still writes
and the PNG is skipped, as elsewhere.

### 3. Architecture promotion — `architecture/brand-marks.md`

Extend the **Org marks** section to note the Boosty cover: what it is, its
colorway and dimensions (8:1, 1920×240), and that it is generated by
`geometry.boosty_cover` and intended for the `boosty.to/lesnik512` profile header.

## Operations

Upload `brand/org/boosty-cover.png` as the profile cover at `boosty.to/lesnik512`
(Boosty's header slot is 1920×240, 8:1). Re-export at a different size if Boosty
changes the slot — the SVG is resolution-independent, so only the `export_png`
width/height change.

## Testing

- New `tests/test_geometry.py::test_boosty_cover`: viewBox is `0 0 1920 240`;
full-bleed `bg` rect (`width="1920" height="240"`) present; centered lockup
placement (`translate(663.0,-17.5) scale(1.1)`); carries the lockup crops
(`M138 122 L138 50 L210 50`); **no** `<text>`; the same `<path>` count as a
bare `wordmark` (minimal — no tagline glyphs); only cream + gold-dark inks over
the green; no `var(`.
- Extend `tests/test_assets.py::test_render_writes_social_cards` (or a sibling)
to assert `brand/org/boosty-cover.svg` parses and the green colors are present,
and that the PNG magic bytes are present when `rsvg-convert` is available.
- `just test` green; `uv run python -m brand.build.render` is a deterministic
no-op diff apart from the two new `boosty-cover` files.
- Visual check of `boosty-cover.svg`/`.png` before committing.
- `just check-planning` passes before pushing.

## Risk

- **Low: banner ratio wrong for Boosty's crop.** Resolved — Boosty's header slot
is 1920×240 (8:1), per Boosty creator guides; this ships at exactly that. The
SVG is resolution-independent, so a future slot change is only an `export_png`
dimension edit.
- **Low: avatar overlaps the lockup.** The avatar sits lower-left; the lockup is
horizontally centered, well clear of it, and Boosty's narrow-viewport crop
keeps the center — so the centered mark survives. Mitigated by the composition.
- **Resolved: tagline duplicated the bio.** An earlier draft set the tagline in
the cover; Boosty already shows the bio (`Open-source Python for production`)
as text below the avatar, so the cover tagline was dropped.
- **Low: PNG absent in CI** if `rsvg-convert` is unavailable. Same behavior as
every other target — the SVG still generates; the PNG is committed from a dev
machine with librsvg.
166 changes: 166 additions & 0 deletions planning/changes/2026-07-07.01-boosty-cover/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# boosty-cover — implementation plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use
> superpowers:subagent-driven-development (recommended) or
> superpowers:executing-plans to implement this plan task-by-task. Steps
> use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a Boosty profile-cover target to the brand generator that emits an
8:1 1920×240 green-colorway header banner — the MODERN/PYTHON lockup centered on
a green field, no tagline — as SVG + PNG in `brand/org/`.

**Spec:** [`design.md`](./design.md)

**Branch:** `boosty-profile-cover`

**Commit strategy:** Per-task commits.

## Global Constraints

- Green colorway only: `bg = #2f5e4a` (`tokens.GREEN_SURFACE`), `struct = #f4f1e8`
(`tokens.CREAM`), `gold = #f0b528` (`tokens.GOLD_DARK`). No other colors.
- Cover SVG viewBox is exactly `0 0 1920 240` (8:1, Boosty's header slot); the
lockup is `scale(1.1)`, centered — its box center (270, 125) placed on the
banner center (960, 120), i.e. `translate(663.0,-17.5)`. No tagline.
- PNG size: `width=1920, height=240`.
- All imports at module level; annotate all function arguments; `ty: ignore`
never `type: ignore`.
- Brand casing in prose: `modern-python`, `modern-di`. Tagline has no trailing
period.
- Generated SVG **and** PNG under `brand/org/` are committed.
- No dependency changes, so `uv.lock` must not change (leave it alone).
- Commit only on the `boosty-profile-cover` branch, never `main`. End commit
messages with the `Co-Authored-By: Claude Opus 4.8 (1M context)
<noreply@anthropic.com>` trailer. Do not mention Claude Code in the body.
- Run `just check-planning` and `just test` before pushing. Finish via PR.

---

### Task 1: Cover geometry (`geometry.boosty_cover`)

**Files:**
- Modify: `brand/build/geometry.py` (add function after `social_square()`)
- Test: `tests/test_geometry.py` (add one test)

- [ ] **Step 1: Write the failing test** in `tests/test_geometry.py`:

```python
def test_boosty_cover(parse_svg):
svg = g.boosty_cover(bg="#2f5e4a", struct="#f4f1e8", gold="#f0b528")
el = parse_svg(svg)
assert el.attrib["viewBox"] == "0 0 1920 240" # 8:1 Boosty header
assert 'fill="#2f5e4a"' in svg # full-bleed green bg
assert 'width="1920" height="240"' in svg # full-bleed rect
assert "translate(663.0,-17.5) scale(1.1)" in svg # lockup centered on x=960
assert "M138 122 L138 50 L210 50" in svg # carries the lockup crops
assert "<text" not in svg # no live text (and no tagline)
assert "#f4f1e8" in svg and "#f0b528" in svg
assert "var(" not in svg
# minimal: just the lockup, so the same glyph-path count as a bare wordmark
assert svg.count("<path") == g.wordmark(struct="#f4f1e8", gold="#f0b528").count("<path")
```

- [ ] **Step 2: Run test, verify it fails** —
`uv run pytest tests/test_geometry.py::test_boosty_cover -v`
Expected: `AttributeError: ... has no attribute 'boosty_cover'`.

- [ ] **Step 3: Implement** `boosty_cover` in `brand/build/geometry.py` per the
spec's §1 code block.

- [ ] **Step 4: Run test, verify it passes.**

- [ ] **Step 5: Commit**

```bash
git add brand/build/geometry.py tests/test_geometry.py
git commit -m "feat(brand): boosty_cover wide banner geometry"
```

---

### Task 2: Render target + regenerate assets

**Files:**
- Modify: `brand/build/render.py` (write SVG + export PNG after the green card)
- Test: `tests/test_assets.py` (add a boosty-cover render test)

- [ ] **Step 1: Write the failing test** in `tests/test_assets.py`:

```python
def test_render_writes_boosty_cover():
_render()
cover = ORG / "boosty-cover.svg"
assert cover.exists()
ET.parse(cover)
text = cover.read_text()
assert 'viewBox="0 0 1920 240"' in text
assert 'fill="#2f5e4a"' in text and "#f4f1e8" in text and "#f0b528" in text
assert "<text" not in text and "var(" not in text
if shutil.which("rsvg-convert"):
assert (ORG / "boosty-cover.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
```

- [ ] **Step 2: Run test, verify it fails** —
`uv run pytest tests/test_assets.py::test_render_writes_boosty_cover -v`
Expected: `assert cover.exists()` is False.

- [ ] **Step 3: Wire render.py.** After the `social-card-green` block in
`render()`, add:

```python
# Boosty profile cover (green colorway) — brand/org/boosty-cover.*
_write(
ORG / "boosty-cover.svg",
g.boosty_cover(bg=t.GREEN_SURFACE, struct=t.CREAM, gold=t.GOLD_DARK),
)
export_png(
ORG / "boosty-cover.svg", ORG / "boosty-cover.png", width=1920, height=240
)
```

- [ ] **Step 4: Run test, verify it passes.**

- [ ] **Step 5: Regenerate and commit**

```bash
uv run python -m brand.build.render
git add brand/build/render.py tests/test_assets.py brand/org/boosty-cover.svg brand/org/boosty-cover.png
git commit -m "feat(brand): boosty profile cover render target"
```

Expected: `git status` shows only the two new `brand/org/boosty-cover.*` files
plus the edited sources — no other asset churn.

---

### Task 3: Architecture promotion

**Files:**
- Modify: `architecture/brand-marks.md` (note the Boosty cover under Org marks)

- [ ] **Step 1: Promote the capability** — add to the Org marks description that
`brand/org/boosty-cover.svg|png` is a 1920×240 (8:1) green-colorway
profile-header banner for `boosty.to/lesnik512`, generated by
`geometry.boosty_cover` (centered `lockup_body`, no tagline).

- [ ] **Step 2: Verify** — `just test` (all pass) and `just check-planning`
(`planning: OK`).

- [ ] **Step 3: Commit**

```bash
git add architecture/brand-marks.md
git commit -m "docs(brand): promote boosty cover to architecture"
```

---

## Notes for the executor

- Follow the existing one-function-per-asset pattern; do not restructure
`geometry.py` or `render.py` beyond the additions above.
- The cover is **not** added to `just sync-assets` — the site does not serve it.
- If Boosty's uploader wants a different size, change only the `export_png`
width/height in `render.py` and re-render.
- Finish via PR (push `boosty-profile-cover`, open a PR); do not local-merge.
Watch CI after pushing.
13 changes: 13 additions & 0 deletions tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ def test_render_writes_social_cards():
assert (ORG / f"{name}.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"


def test_render_writes_boosty_cover():
_render()
cover = ORG / "boosty-cover.svg"
assert cover.exists()
ET.parse(cover)
text = cover.read_text()
assert 'viewBox="0 0 1920 240"' in text
assert 'fill="#2f5e4a"' in text and "#f4f1e8" in text and "#f0b528" in text
assert "<text" not in text and "var(" not in text
if shutil.which("rsvg-convert"):
assert (ORG / "boosty-cover.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"


def test_render_writes_apparel():
_render()
chest = APPAREL / "chest-mark.svg"
Expand Down
Loading