diff --git a/architecture/brand-marks.md b/architecture/brand-marks.md index b362db7..42908d7 100644 --- a/architecture/brand-marks.md +++ b/architecture/brand-marks.md @@ -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//`) 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 diff --git a/brand/build/geometry.py b/brand/build/geometry.py index 0db3fab..f354ff9 100644 --- a/brand/build/geometry.py +++ b/brand/build/geometry.py @@ -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'' + + f'{body}' + + "" + ) + + def project_frame( *, struct: str, diff --git a/brand/build/render.py b/brand/build/render.py index d59727c..4231eeb 100644 --- a/brand/build/render.py +++ b/brand/build/render.py @@ -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", diff --git a/brand/org/boosty-cover.png b/brand/org/boosty-cover.png new file mode 100644 index 0000000..12c0de2 Binary files /dev/null and b/brand/org/boosty-cover.png differ diff --git a/brand/org/boosty-cover.svg b/brand/org/boosty-cover.svg new file mode 100644 index 0000000..bb87657 --- /dev/null +++ b/brand/org/boosty-cover.svg @@ -0,0 +1 @@ + diff --git a/planning/changes/2026-07-07.01-boosty-cover/design.md b/planning/changes/2026-07-07.01-boosty-cover/design.md new file mode 100644 index 0000000..f5891ee --- /dev/null +++ b/planning/changes/2026-07-07.01-boosty-cover/design.md @@ -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 `` (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'' + + f'{body}' + + "" + ) +``` + +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** ``; the same `` 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. diff --git a/planning/changes/2026-07-07.01-boosty-cover/plan.md b/planning/changes/2026-07-07.01-boosty-cover/plan.md new file mode 100644 index 0000000..72f5327 --- /dev/null +++ b/planning/changes/2026-07-07.01-boosty-cover/plan.md @@ -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) + ` 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 " g.wordmark(struct="#f4f1e8", gold="#f0b528").count("