From daf758a14ff21aa4a79c2a277f217d8a6498f97c Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 7 Jul 2026 22:46:29 +0300 Subject: [PATCH 1/6] docs(planning): boosty profile cover design + plan Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-07.01-boosty-cover/design.md | 128 ++++++++++++++ .../2026-07-07.01-boosty-cover/plan.md | 166 ++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 planning/changes/2026-07-07.01-boosty-cover/design.md create mode 100644 planning/changes/2026-07-07.01-boosty-cover/plan.md 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..d41ba34 --- /dev/null +++ b/planning/changes/2026-07-07.01-boosty-cover/design.md @@ -0,0 +1,128 @@ +--- +summary: Add a Boosty profile cover to the brand generator — a wide 1920x480 green-colorway banner (MODERN/PYTHON lockup + "Open-source Python for production" tagline) 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 the green colorway (green surface `#2f5e4a` with cream + gold-dark ink) — +the same treatment as `social-card-green` and the org avatar — so the banner +sits as one green field beside the (green) Boosty avatar. The composition is the +existing MODERN/PYTHON `lockup_body` centered above the outlined tagline +`Open-source Python for production`. Everything is drawn from existing +`geometry.py` primitives; the only new geometry is the wide-banner composition. +Outputs land in `brand/org/` as vector SVG plus a 1920×480 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 480`) with a full-bleed `bg` rect, the `lockup_body()` lockup scaled +1.4 and centered, and the outlined tagline beneath it. It reuses `lockup_body()` +(the MODERN/PYTHON crop-mark lockup in the 540×250 space) and `outline_text()` +(the same font-outlining path the social card URL uses, so nothing depends on a +font at serve time). + +```python +def boosty_cover(*, bg: str, struct: str, gold: str) -> str: + """Wide Boosty profile-header banner: the MODERN/PYTHON lockup centered + over an outlined tagline, on a full-bleed background. 4:1 (1920x480); the + lower-left is left clear for Boosty's avatar/name overlay.""" + body = lockup_body(struct=struct, gold=gold) + s = 1.4 + tx = round(1920 / 2 - 270 * s, 1) # box-center horizontally (lockup box 540 wide) + ty = round(200 - 125 * s, 1) # lockup center ~y200, leaving room for the tagline + tagline, _ = outline_text( + "Open-source Python for production", + 30, + x=960, + baseline_y=400, + anchor="middle", + color=gold, + letter_spacing=4, + ) + return ( + _SVG_OPEN.format(w=1920, h=480) + + f'' + + f'{body}' + + tagline + + "" + ) +``` + +`tx` resolves to `582.0`, `ty` to `25.0`. The tagline is set in `gold` +(gold-dark) on the green surface; MODERN is `struct` (cream) and PYTHON is +`gold`, so the banner stays within the on-green two-ink treatment. + +### 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×480 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, 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`. +Re-export at a different size if Boosty's uploader demands specific dimensions — +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 480`; + full-bleed `bg` rect present; carries the lockup crops + (`M138 122 L138 50 L210 50`) and the `translate(582.0,25.0) scale(1.4)` + placement; tagline is outlined (glyph ``s, **no** ``); more + ``s than a bare `wordmark`; 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.** Boosty's exact cover spec was + not verifiable at design time, so this uses a conservative 4:1 with the key + art centered and the lower-left kept clear. Mitigated — the SVG is + resolution-independent; only the `export_png` dimensions change to re-crop. +- **Low: avatar overlaps the lockup.** The avatar sits lower-left; the centered + lockup starts at x≈770, well clear. Mitigated by the centered composition. +- **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..74e3eab --- /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 a +wide 1920×480 green-colorway banner (MODERN/PYTHON lockup + 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 480`; lockup placement is + `translate(582.0,25.0) scale(1.4)`; tagline is + `outline_text("Open-source Python for production", 30, x=960, baseline_y=400, + anchor="middle", color=gold, letter_spacing=4)`. +- PNG size: `width=1920, height=480`. +- 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 480" + assert 'fill="#2f5e4a"' in svg # full-bleed green bg + assert "translate(582.0,25.0) scale(1.4)" in svg # centered lockup + assert "M138 122 L138 50 L210 50" in svg # carries the lockup crops + assert " g.wordmark(struct="#f4f1e8", gold="#f0b528").count(" Date: Tue, 7 Jul 2026 22:47:44 +0300 Subject: [PATCH 2/6] feat(brand): boosty_cover wide banner geometry Co-Authored-By: Claude Opus 4.8 (1M context) --- brand/build/geometry.py | 26 ++++++++++++++++++++++++++ tests/test_geometry.py | 16 ++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/brand/build/geometry.py b/brand/build/geometry.py index 0db3fab..02e607f 100644 --- a/brand/build/geometry.py +++ b/brand/build/geometry.py @@ -138,6 +138,32 @@ def social_square(*, bg: str, struct: str, gold: str) -> str: ) +def boosty_cover(*, bg: str, struct: str, gold: str) -> str: + """Wide Boosty profile-header banner: the MODERN/PYTHON lockup centered over + an outlined tagline, on a full-bleed background. 4:1 (1920x480); the + lower-left is left clear for Boosty's avatar/name overlay.""" + body = lockup_body(struct=struct, gold=gold) + s = 1.4 + tx = round(1920 / 2 - 270 * s, 1) # box-center horizontally (lockup box 540 wide) + ty = round(200 - 125 * s, 1) # lockup center ~y200, leaving room for the tagline + tagline, _ = outline_text( + "Open-source Python for production", + 30, + x=960, + baseline_y=400, + anchor="middle", + color=gold, + letter_spacing=4, + ) + return ( + _SVG_OPEN.format(w=1920, h=480) + + f'' + + f'{body}' + + tagline + + "" + ) + + def project_frame( *, struct: str, diff --git a/tests/test_geometry.py b/tests/test_geometry.py index c1d0c73..a822412 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -82,6 +82,22 @@ def test_apparel_back_wordmark_plus_url(parse_svg): assert svg.count(" g.wordmark(struct="#f4f1e8", gold="#f0b528").count(" g.wordmark( + struct="#f4f1e8", gold="#f0b528" + ).count(" Date: Tue, 7 Jul 2026 22:49:20 +0300 Subject: [PATCH 3/6] feat(brand): boosty profile cover render target Co-Authored-By: Claude Opus 4.8 (1M context) --- brand/build/render.py | 9 +++++++++ brand/org/boosty-cover.png | Bin 0 -> 7463 bytes brand/org/boosty-cover.svg | 1 + tests/test_assets.py | 13 +++++++++++++ 4 files changed, 23 insertions(+) create mode 100644 brand/org/boosty-cover.png create mode 100644 brand/org/boosty-cover.svg diff --git a/brand/build/render.py b/brand/build/render.py index d59727c..3b641b2 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=480 + ) + # 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 0000000000000000000000000000000000000000..ec5691f9c259b87344eb83482586da29ac71b64f GIT binary patch literal 7463 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 literal 0 HcmV?d00001 diff --git a/brand/org/boosty-cover.svg b/brand/org/boosty-cover.svg new file mode 100644 index 0000000..235e823 --- /dev/null +++ b/brand/org/boosty-cover.svg @@ -0,0 +1 @@ + diff --git a/tests/test_assets.py b/tests/test_assets.py index e22489c..f838e8e 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -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 480"' in text + assert 'fill="#2f5e4a"' in text and "#f4f1e8" in text and "#f0b528" in text + assert " Date: Tue, 7 Jul 2026 22:52:59 +0300 Subject: [PATCH 4/6] docs(brand): promote boosty cover to architecture Co-Authored-By: Claude Opus 4.8 (1M context) --- architecture/brand-marks.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/architecture/brand-marks.md b/architecture/brand-marks.md index b362db7..942b739 100644 --- a/architecture/brand-marks.md +++ b/architecture/brand-marks.md @@ -6,6 +6,14 @@ 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×480 (4:1) green-colorway profile-header banner +for the org's Boosty page (`boosty.to/lesnik512`): the centered MODERN/PYTHON +`geometry.lockup_body` over the outlined tagline `Open-source Python for +production`, on the green surface. Generated by `geometry.boosty_cover`; the +lower-left is kept clear for Boosty's avatar/name overlay. 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 From 208eefe74802c9b912b8b74afa444d209b846f80 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 7 Jul 2026 23:05:42 +0300 Subject: [PATCH 5/6] 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) --- architecture/brand-marks.md | 13 ++- brand/build/geometry.py | 42 ++++--- brand/build/render.py | 2 +- brand/org/boosty-cover.png | Bin 7463 -> 6280 bytes brand/org/boosty-cover.svg | 2 +- .../2026-07-07.01-boosty-cover/design.md | 108 ++++++++++-------- .../2026-07-07.01-boosty-cover/plan.md | 25 ++-- tests/test_assets.py | 2 +- tests/test_geometry.py | 5 +- 9 files changed, 114 insertions(+), 85 deletions(-) diff --git a/architecture/brand-marks.md b/architecture/brand-marks.md index 942b739..85bc6e9 100644 --- a/architecture/brand-marks.md +++ b/architecture/brand-marks.md @@ -6,12 +6,13 @@ 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×480 (4:1) green-colorway profile-header banner -for the org's Boosty page (`boosty.to/lesnik512`): the centered MODERN/PYTHON -`geometry.lockup_body` over the outlined tagline `Open-source Python for -production`, on the green surface. Generated by `geometry.boosty_cover`; the -lower-left is kept clear for Boosty's avatar/name overlay. Not served by the -docs site (excluded from `just sync-assets`); re-export at other sizes by +`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`): the +MODERN/PYTHON `geometry.lockup_body` and the outlined tagline `Open-source Python +for production` set side by side (the 240 px strip is too short to stack), +centered as a pair on the green surface. Generated by `geometry.boosty_cover`; +the centered row stays clear of Boosty's lower-left avatar overlay. 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//`) diff --git a/brand/build/geometry.py b/brand/build/geometry.py index 02e607f..c0ba0b2 100644 --- a/brand/build/geometry.py +++ b/brand/build/geometry.py @@ -139,25 +139,41 @@ def social_square(*, bg: str, struct: str, gold: str) -> str: def boosty_cover(*, bg: str, struct: str, gold: str) -> str: - """Wide Boosty profile-header banner: the MODERN/PYTHON lockup centered over - an outlined tagline, on a full-bleed background. 4:1 (1920x480); the - lower-left is left clear for Boosty's avatar/name overlay.""" + """Boosty profile-header banner — 8:1 (1920x240), Boosty's documented header + size. The strip is too shallow to stack, so the MODERN/PYTHON lockup and the + outlined tagline sit side by side, centered as a pair on a full-bleed + background. The tagline width is measured to center the whole row.""" + w, h = 1920, 240 + s = 0.9 # lockup scale; its visual box is x[134,406] (width 272), y-center 125 + lock_w = 272 * s + tagline_text = "Open-source Python for production" + tag_size = 40 + gap = 60 # px between lockup and tagline + _, tag_w = outline_text( + tagline_text, + tag_size, + x=0, + baseline_y=0, + anchor="start", + color=gold, + letter_spacing=4, + ) + x0 = (w - (lock_w + gap + tag_w)) / 2 # left edge of the centered row + tx = round(x0 - 134 * s, 1) # seat the lockup's visual-left at x0 + ty = round(h / 2 - 125 * s, 1) # center the lockup vertically body = lockup_body(struct=struct, gold=gold) - s = 1.4 - tx = round(1920 / 2 - 270 * s, 1) # box-center horizontally (lockup box 540 wide) - ty = round(200 - 125 * s, 1) # lockup center ~y200, leaving room for the tagline tagline, _ = outline_text( - "Open-source Python for production", - 30, - x=960, - baseline_y=400, - anchor="middle", + tagline_text, + tag_size, + x=round(x0 + lock_w + gap, 1), + baseline_y=round(h / 2 + tag_size * 0.32, 1), # optical vertical center + anchor="start", color=gold, letter_spacing=4, ) return ( - _SVG_OPEN.format(w=1920, h=480) - + f'' + _SVG_OPEN.format(w=w, h=h) + + f'' + f'{body}' + tagline + "" diff --git a/brand/build/render.py b/brand/build/render.py index 3b641b2..4231eeb 100644 --- a/brand/build/render.py +++ b/brand/build/render.py @@ -78,7 +78,7 @@ def render() -> None: 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=480 + ORG / "boosty-cover.svg", ORG / "boosty-cover.png", width=1920, height=240 ) # Square (Telegram / square social) — cream + green. diff --git a/brand/org/boosty-cover.png b/brand/org/boosty-cover.png index ec5691f9c259b87344eb83482586da29ac71b64f..3b98b24e86d3a46f932579cd216af293046b9723 100644 GIT binary patch literal 6280 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

{7EjI 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 diff --git a/brand/org/boosty-cover.svg b/brand/org/boosty-cover.svg index 235e823..e338415 100644 --- a/brand/org/boosty-cover.svg +++ b/brand/org/boosty-cover.svg @@ -1 +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 index d41ba34..abded79 100644 --- a/planning/changes/2026-07-07.01-boosty-cover/design.md +++ b/planning/changes/2026-07-07.01-boosty-cover/design.md @@ -1,5 +1,5 @@ --- -summary: Add a Boosty profile cover to the brand generator — a wide 1920x480 green-colorway banner (MODERN/PYTHON lockup + "Open-source Python for production" tagline) as SVG + PNG in brand/org/. +summary: Add a Boosty profile cover to the brand generator — an 8:1 1920x240 green-colorway header banner (MODERN/PYTHON lockup + "Open-source Python for production" tagline, side by side) as SVG + PNG in brand/org/. --- # Design: modern-python Boosty profile cover @@ -8,14 +8,15 @@ summary: Add a Boosty profile cover to the brand generator — a wide 1920x480 g 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 the green colorway (green surface `#2f5e4a` with cream + gold-dark ink) — -the same treatment as `social-card-green` and the org avatar — so the banner -sits as one green field beside the (green) Boosty avatar. The composition is the -existing MODERN/PYTHON `lockup_body` centered above the outlined tagline -`Open-source Python for production`. Everything is drawn from existing -`geometry.py` primitives; the only new geometry is the wide-banner composition. -Outputs land in `brand/org/` as vector SVG plus a 1920×480 PNG, regenerable via -`uv run python -m brand.build.render`. +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 — so the banner sits as one +green field beside the (green) Boosty avatar. The short strip is too shallow to +stack, so the existing MODERN/PYTHON `lockup_body` and the outlined tagline +`Open-source Python for production` sit **side by side**, centered as a pair. +Everything is drawn from existing `geometry.py` primitives; the only new geometry +is the horizontal-banner composition. Outputs land in `brand/org/` as vector SVG +plus a 1920×240 PNG, regenerable via `uv run python -m brand.build.render`. ## Motivation @@ -42,71 +43,80 @@ interactively against a rendered draft. ### 1. Cover geometry — `brand/build/geometry.py` Add `boosty_cover(*, bg, struct, gold)`: a complete `` (viewBox -`0 0 1920 480`) with a full-bleed `bg` rect, the `lockup_body()` lockup scaled -1.4 and centered, and the outlined tagline beneath it. It reuses `lockup_body()` -(the MODERN/PYTHON crop-mark lockup in the 540×250 space) and `outline_text()` -(the same font-outlining path the social card URL uses, so nothing depends on a -font at serve time). +`0 0 1920 240`) with a full-bleed `bg` rect, the `lockup_body()` lockup scaled +`0.9`, and the outlined tagline set to its right — the pair centered as a row. +It reuses `lockup_body()` (the MODERN/PYTHON crop-mark lockup in the 540×250 +space) and `outline_text()` (the same font-outlining path the social card URL +uses, so nothing depends on a font at serve time). Because 240 px is too short +to stack the lockup over a tagline, the layout is horizontal; the tagline width +is measured (from `outline_text`'s returned advance) so the whole row centers on +x=960. ```python def boosty_cover(*, bg: str, struct: str, gold: str) -> str: - """Wide Boosty profile-header banner: the MODERN/PYTHON lockup centered - over an outlined tagline, on a full-bleed background. 4:1 (1920x480); the - lower-left is left clear for Boosty's avatar/name overlay.""" + """Boosty profile-header banner — 8:1 (1920x240). The lockup and the + outlined tagline sit side by side, centered as a pair.""" + w, h = 1920, 240 + s = 0.9 # lockup visual box is x[134,406] (width 272), y-center 125 + lock_w = 272 * s + tagline_text = "Open-source Python for production" + tag_size = 40 + gap = 60 + _, tag_w = outline_text( + tagline_text, tag_size, x=0, baseline_y=0, + anchor="start", color=gold, letter_spacing=4, + ) + x0 = (w - (lock_w + gap + tag_w)) / 2 + tx = round(x0 - 134 * s, 1) # seat the lockup's visual-left at x0 + ty = round(h / 2 - 125 * s, 1) body = lockup_body(struct=struct, gold=gold) - s = 1.4 - tx = round(1920 / 2 - 270 * s, 1) # box-center horizontally (lockup box 540 wide) - ty = round(200 - 125 * s, 1) # lockup center ~y200, leaving room for the tagline tagline, _ = outline_text( - "Open-source Python for production", - 30, - x=960, - baseline_y=400, - anchor="middle", - color=gold, - letter_spacing=4, + tagline_text, tag_size, x=round(x0 + lock_w + gap, 1), + baseline_y=round(h / 2 + tag_size * 0.32, 1), + anchor="start", color=gold, letter_spacing=4, ) return ( - _SVG_OPEN.format(w=1920, h=480) - + f'' + _SVG_OPEN.format(w=w, h=h) + + f'' + f'{body}' + tagline + "" ) ``` -`tx` resolves to `582.0`, `ty` to `25.0`. The tagline is set in `gold` -(gold-dark) on the green surface; MODERN is `struct` (cream) and PYTHON is -`gold`, so the banner stays within the on-green two-ink treatment. +The tagline is set in `gold` (gold-dark) on the green surface; MODERN is `struct` +(cream) and PYTHON is `gold`, so the banner stays within the on-green two-ink +treatment. The centered row keeps content clear of Boosty's lower-left avatar +overlay. ### 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×480 through the existing +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, and that it is generated by `geometry.boosty_cover` -and intended for the `boosty.to/lesnik512` profile header. +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`. -Re-export at a different size if Boosty's uploader demands specific dimensions — -the SVG is resolution-independent, so only the `export_png` width/height change. +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 480`; - full-bleed `bg` rect present; carries the lockup crops - (`M138 122 L138 50 L210 50`) and the `translate(582.0,25.0) scale(1.4)` - placement; tagline is outlined (glyph ``s, **no** ``); more - ``s than a bare `wordmark`; only cream + gold-dark inks over the green; - no `var(`. +- New `tests/test_geometry.py::test_boosty_cover`: viewBox is `0 0 1920 240`; + full-bleed `bg` rect (`width="1920" height="240"`) present; lockup scaled + (`scale(0.9)`); carries the lockup crops (`M138 122 L138 50 L210 50`); tagline + is outlined (glyph ``s, **no** ``); more ``s than a bare + `wordmark`; 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. @@ -117,12 +127,12 @@ the SVG is resolution-independent, so only the `export_png` width/height change. ## Risk -- **Low: banner ratio wrong for Boosty's crop.** Boosty's exact cover spec was - not verifiable at design time, so this uses a conservative 4:1 with the key - art centered and the lower-left kept clear. Mitigated — the SVG is - resolution-independent; only the `export_png` dimensions change to re-crop. +- **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 centered - lockup starts at x≈770, well clear. Mitigated by the centered composition. + content row starts well right of it. Mitigated by the centered composition. - **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 index 74e3eab..88ba7b6 100644 --- a/planning/changes/2026-07-07.01-boosty-cover/plan.md +++ b/planning/changes/2026-07-07.01-boosty-cover/plan.md @@ -5,9 +5,9 @@ > 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 a -wide 1920×480 green-colorway banner (MODERN/PYTHON lockup + tagline) as SVG + -PNG in `brand/org/`. +**Goal:** Add a Boosty profile-cover target to the brand generator that emits an +8:1 1920×240 green-colorway header banner (MODERN/PYTHON lockup + tagline, side +by side) as SVG + PNG in `brand/org/`. **Spec:** [`design.md`](./design.md) @@ -19,11 +19,11 @@ PNG in `brand/org/`. - 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 480`; lockup placement is - `translate(582.0,25.0) scale(1.4)`; tagline is - `outline_text("Open-source Python for production", 30, x=960, baseline_y=400, - anchor="middle", color=gold, letter_spacing=4)`. -- PNG size: `width=1920, height=480`. +- Cover SVG viewBox is exactly `0 0 1920 240` (8:1, Boosty's header slot); the + lockup is `scale(0.9)`; the tagline `Open-source Python for production` is + outlined at `tag_size=40`, `letter_spacing=4`, set to the lockup's right with a + 60 px gap, and the pair is centered on x=960 using the measured tagline width. +- 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 @@ -49,9 +49,10 @@ PNG in `brand/org/`. 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 480" + assert el.attrib["viewBox"] == "0 0 1920 240" # 8:1 Boosty header assert 'fill="#2f5e4a"' in svg # full-bleed green bg - assert "translate(582.0,25.0) scale(1.4)" in svg # centered lockup + assert 'width="1920" height="240"' in svg # full-bleed rect + assert "scale(0.9)" in svg # lockup scaled into the short strip assert "M138 122 L138 50 L210 50" in svg # carries the lockup crops assert " Date: Tue, 7 Jul 2026 23:27:49 +0300 Subject: [PATCH 6/6] 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) --- architecture/brand-marks.md | 15 ++-- brand/build/geometry.py | 37 ++------ brand/org/boosty-cover.png | Bin 6280 -> 3887 bytes brand/org/boosty-cover.svg | 2 +- .../2026-07-07.01-boosty-cover/design.md | 79 ++++++++---------- .../2026-07-07.01-boosty-cover/plan.md | 23 +++-- tests/test_geometry.py | 8 +- 7 files changed, 64 insertions(+), 100 deletions(-) diff --git a/architecture/brand-marks.md b/architecture/brand-marks.md index 85bc6e9..42908d7 100644 --- a/architecture/brand-marks.md +++ b/architecture/brand-marks.md @@ -7,13 +7,14 @@ 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`): the -MODERN/PYTHON `geometry.lockup_body` and the outlined tagline `Open-source Python -for production` set side by side (the 240 px strip is too short to stack), -centered as a pair on the green surface. Generated by `geometry.boosty_cover`; -the centered row stays clear of Boosty's lower-left avatar overlay. 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`. +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 diff --git a/brand/build/geometry.py b/brand/build/geometry.py index c0ba0b2..f354ff9 100644 --- a/brand/build/geometry.py +++ b/brand/build/geometry.py @@ -140,42 +140,19 @@ 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. The strip is too shallow to stack, so the MODERN/PYTHON lockup and the - outlined tagline sit side by side, centered as a pair on a full-bleed - background. The tagline width is measured to center the whole row.""" + 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 = 0.9 # lockup scale; its visual box is x[134,406] (width 272), y-center 125 - lock_w = 272 * s - tagline_text = "Open-source Python for production" - tag_size = 40 - gap = 60 # px between lockup and tagline - _, tag_w = outline_text( - tagline_text, - tag_size, - x=0, - baseline_y=0, - anchor="start", - color=gold, - letter_spacing=4, - ) - x0 = (w - (lock_w + gap + tag_w)) / 2 # left edge of the centered row - tx = round(x0 - 134 * s, 1) # seat the lockup's visual-left at x0 - ty = round(h / 2 - 125 * s, 1) # center the lockup vertically + 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) - tagline, _ = outline_text( - tagline_text, - tag_size, - x=round(x0 + lock_w + gap, 1), - baseline_y=round(h / 2 + tag_size * 0.32, 1), # optical vertical center - anchor="start", - color=gold, - letter_spacing=4, - ) return ( _SVG_OPEN.format(w=w, h=h) + f'' + f'{body}' - + tagline + "" ) diff --git a/brand/org/boosty-cover.png b/brand/org/boosty-cover.png index 3b98b24e86d3a46f932579cd216af293046b9723..12c0de2e1aefff327baa6039b487c3f1b8d83e2d 100644 GIT binary patch literal 3887 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 WFX!SlXVd&dvuzIB9bz2xzWh%lIrs7a literal 6280 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

{7EjI z=eg)LxmpMz%i@H%qMUIfxO?+E6 z3RhJlNqZuda3jp$3FMT?&9&XR?xqNZf)kP+J5xesOzX!PZ!O=5)r)Ud_d->106Kl= zc=7knd<)2|7cXZz136}z?}PQPWEuP@KJv$_@L%87r(xr@>VDCAFr{RPIO=j$F%$>@ z^ECq{`~#YByrF{8^ZH{4q16l0AfcH`+sDveOY2gXRrPc}`M1|HSU+I`IT?J8rulJH z<7EAmsum<6?W4OPkQd8Sy|iqMGB7fV`o!x_c}p~&R;h^P+nvHGGZV-ac&n#w zBn{^=LWp*J*n2Or;e + diff --git a/planning/changes/2026-07-07.01-boosty-cover/design.md b/planning/changes/2026-07-07.01-boosty-cover/design.md index abded79..f5891ee 100644 --- a/planning/changes/2026-07-07.01-boosty-cover/design.md +++ b/planning/changes/2026-07-07.01-boosty-cover/design.md @@ -1,5 +1,5 @@ --- -summary: Add a Boosty profile cover to the brand generator — an 8:1 1920x240 green-colorway header banner (MODERN/PYTHON lockup + "Open-source Python for production" tagline, side by side) as SVG + PNG in brand/org/. +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 @@ -10,13 +10,14 @@ 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 — so the banner sits as one -green field beside the (green) Boosty avatar. The short strip is too shallow to -stack, so the existing MODERN/PYTHON `lockup_body` and the outlined tagline -`Open-source Python for production` sit **side by side**, centered as a pair. -Everything is drawn from existing `geometry.py` primitives; the only new geometry -is the horizontal-banner composition. Outputs land in `brand/org/` as vector SVG -plus a 1920×240 PNG, regenerable via `uv run python -m brand.build.render`. +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 @@ -43,51 +44,32 @@ interactively against a rendered draft. ### 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, the `lockup_body()` lockup scaled -`0.9`, and the outlined tagline set to its right — the pair centered as a row. -It reuses `lockup_body()` (the MODERN/PYTHON crop-mark lockup in the 540×250 -space) and `outline_text()` (the same font-outlining path the social card URL -uses, so nothing depends on a font at serve time). Because 240 px is too short -to stack the lockup over a tagline, the layout is horizontal; the tagline width -is measured (from `outline_text`'s returned advance) so the whole row centers on -x=960. +`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). The lockup and the - outlined tagline sit side by side, centered as a pair.""" + """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 = 0.9 # lockup visual box is x[134,406] (width 272), y-center 125 - lock_w = 272 * s - tagline_text = "Open-source Python for production" - tag_size = 40 - gap = 60 - _, tag_w = outline_text( - tagline_text, tag_size, x=0, baseline_y=0, - anchor="start", color=gold, letter_spacing=4, - ) - x0 = (w - (lock_w + gap + tag_w)) / 2 - tx = round(x0 - 134 * s, 1) # seat the lockup's visual-left at x0 - ty = round(h / 2 - 125 * s, 1) + 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) - tagline, _ = outline_text( - tagline_text, tag_size, x=round(x0 + lock_w + gap, 1), - baseline_y=round(h / 2 + tag_size * 0.32, 1), - anchor="start", color=gold, letter_spacing=4, - ) return ( _SVG_OPEN.format(w=w, h=h) + f'' + f'{body}' - + tagline + "" ) ``` -The tagline is set in `gold` (gold-dark) on the green surface; MODERN is `struct` -(cream) and PYTHON is `gold`, so the banner stays within the on-green two-ink -treatment. The centered row keeps content clear of Boosty's lower-left avatar -overlay. +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` @@ -113,10 +95,11 @@ 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; lockup scaled - (`scale(0.9)`); carries the lockup crops (`M138 122 L138 50 L210 50`); tagline - is outlined (glyph ``s, **no** ``); more ``s than a bare - `wordmark`; only cream + gold-dark inks over the green; no `var(`. + 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. @@ -131,8 +114,12 @@ width/height change. 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 centered - content row starts well right of it. Mitigated by the centered composition. +- **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 index 88ba7b6..72f5327 100644 --- a/planning/changes/2026-07-07.01-boosty-cover/plan.md +++ b/planning/changes/2026-07-07.01-boosty-cover/plan.md @@ -6,8 +6,8 @@ > 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 (MODERN/PYTHON lockup + tagline, side -by side) as SVG + PNG in `brand/org/`. +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) @@ -20,9 +20,8 @@ by side) as SVG + PNG in `brand/org/`. - 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(0.9)`; the tagline `Open-source Python for production` is - outlined at `tag_size=40`, `letter_spacing=4`, set to the lockup's right with a - 60 px gap, and the pair is centered on x=960 using the measured tagline width. + 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`. @@ -52,13 +51,13 @@ def test_boosty_cover(parse_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 "scale(0.9)" in svg # lockup scaled into the short strip + 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(" g.wordmark( + # minimal: just the lockup, so no glyph paths beyond the bare wordmark + assert svg.count("