diff --git a/architecture/brand-marks.md b/architecture/brand-marks.md index fd3f88a..b3cbc74 100644 --- a/architecture/brand-marks.md +++ b/architecture/brand-marks.md @@ -45,3 +45,15 @@ Repo READMEs embed these as a centered `` banner that replaces the leading `# ` heading. The `` elements reference the assets via absolute `raw.githubusercontent.com/modern-python/.github/main/brand/projects//` URLs so no asset files are committed to the individual repos. + +## Apparel (`brand/apparel/`) + +Print-ready artwork for the black org t-shirt, generated by +`brand/build/apparel.py::render_apparel` from the same primitives as the org +marks, in the cream + gold-dark colorway (a 2-ink spot print). Two artworks: +`chest-mark.svg` (the transparent chevron `geometry.mark`, left chest) and +`back-lockup.svg` (`geometry.apparel_back` — the MODERN/PYTHON wordmark with +`modern-python.org` outlined beneath it, viewBox `118 32 304 228`, back print). +Each ships a 300 DPI PNG fallback (`chest-mark-1050.png` 3.5 in, +`back-lockup-2400.png` 8 in). Regenerate via `uv run python -m brand.build.render`; +placement and vendor notes live in `brand/apparel/README.md`. diff --git a/brand/apparel/README.md b/brand/apparel/README.md new file mode 100644 index 0000000..9c1715a --- /dev/null +++ b/brand/apparel/README.md @@ -0,0 +1,29 @@ +# Apparel artwork + +Print-ready artwork for the **black** modern-python org t-shirt, generated by +`brand/build/apparel.py`. Regenerate with: + +```bash +uv run python -m brand.build.render +``` + +## Colorway + +Two-ink spot print, cream + gold-dark (the on-dark org treatment): + +| Token | Hex | Role | +|-------|-----|------| +| Cream | `#f4f1e8` | MODERN, top crop, chevron snake | +| Gold-dark | `#f0b528` | PYTHON, bottom crop, chevron, URL | + +## Files + +| File | Placement | Print size | +|------|-----------|-----------| +| `chest-mark.svg` / `chest-mark-1050.png` | Left chest, standard placement | ~3.5 in wide | +| `back-lockup.svg` / `back-lockup-2400.png` | Back, centered, ~4-5 in below collar | ~8 in wide | + +The transparent chevron mark is `geometry.mark`; the back lockup (wordmark + +`modern-python.org`) is `geometry.apparel_back`. PNGs are 300 DPI fallbacks; +prefer the SVGs (vector) for print. Send the vendor a black garment with a +2-color print in the two hexes above. diff --git a/brand/apparel/back-lockup-2400.png b/brand/apparel/back-lockup-2400.png new file mode 100644 index 0000000..cd56c8e Binary files /dev/null and b/brand/apparel/back-lockup-2400.png differ diff --git a/brand/apparel/back-lockup.svg b/brand/apparel/back-lockup.svg new file mode 100644 index 0000000..8a3c3ba --- /dev/null +++ b/brand/apparel/back-lockup.svg @@ -0,0 +1 @@ + diff --git a/brand/apparel/chest-mark-1050.png b/brand/apparel/chest-mark-1050.png new file mode 100644 index 0000000..8f6c6f3 Binary files /dev/null and b/brand/apparel/chest-mark-1050.png differ diff --git a/brand/apparel/chest-mark.svg b/brand/apparel/chest-mark.svg new file mode 100644 index 0000000..c31e2ff --- /dev/null +++ b/brand/apparel/chest-mark.svg @@ -0,0 +1 @@ + diff --git a/brand/build/apparel.py b/brand/build/apparel.py new file mode 100644 index 0000000..0415b8d --- /dev/null +++ b/brand/build/apparel.py @@ -0,0 +1,25 @@ +from pathlib import Path + +from brand.build import geometry as g +from brand.build import tokens as t +from brand.build.raster import export_png + +ROOT = Path(__file__).resolve().parents[2] +APPAREL = ROOT / "brand" / "apparel" + + +def _write(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content + "\n", encoding="utf-8") + + +def render_apparel() -> None: + """Print-ready artwork for the black org t-shirt: a transparent left-chest + chevron mark and a back wordmark+URL lockup, both cream + gold-dark. SVG is + the master; PNGs are 300 DPI print fallbacks (chest 3.5 in, back 8 in).""" + APPAREL.mkdir(parents=True, exist_ok=True) + ink = dict(struct=t.CREAM, gold=t.GOLD_DARK) + _write(APPAREL / "chest-mark.svg", g.mark(**ink)) + export_png(APPAREL / "chest-mark.svg", APPAREL / "chest-mark-1050.png", width=1050) + _write(APPAREL / "back-lockup.svg", g.apparel_back(**ink)) + export_png(APPAREL / "back-lockup.svg", APPAREL / "back-lockup-2400.png", width=2400) diff --git a/brand/build/geometry.py b/brand/build/geometry.py index 96fe650..0db3fab 100644 --- a/brand/build/geometry.py +++ b/brand/build/geometry.py @@ -83,6 +83,28 @@ def mark(*, struct: str, gold: str) -> str: return _SVG_OPEN.format(w=100, h=100) + _icon_mark(struct, gold) + "" +def apparel_back(*, struct: str, gold: str) -> str: + """Back-of-shirt lockup: the MODERN/PYTHON wordmark with the full domain + outlined beneath it, transparent, for the cream+gold-dark colorway. Extends + the `wordmark` viewBox downward to seat the URL centered on the same axis.""" + url, _ = outline_text( + "modern-python.org", + 18, + x=270, + baseline_y=240, + anchor="middle", + color=gold, + letter_spacing=3, + ) + return ( + '' + + lockup_body(struct=struct, gold=gold) + + url + + "" + ) + + def social_card(*, bg: str, struct: str, gold: str, url_color: str) -> str: body = lockup_body(struct=struct, gold=gold) url, _ = outline_text( diff --git a/brand/build/render.py b/brand/build/render.py index 4248c1e..d59727c 100644 --- a/brand/build/render.py +++ b/brand/build/render.py @@ -2,6 +2,7 @@ from brand.build import geometry as g from brand.build import tokens as t +from brand.build.apparel import render_apparel from brand.build.projects import render_projects from brand.build.raster import export_png @@ -93,6 +94,9 @@ def render() -> None: # Per-project marks (brand/projects//). render_projects() + # Apparel artwork (brand/apparel/). + render_apparel() + def main() -> None: render() diff --git a/planning/changes/2026-07-04.01-org-tshirt/design.md b/planning/changes/2026-07-04.01-org-tshirt/design.md new file mode 100644 index 0000000..1985779 --- /dev/null +++ b/planning/changes/2026-07-04.01-org-tshirt/design.md @@ -0,0 +1,149 @@ +--- +summary: Add an apparel target to the brand generator — a transparent left-chest chevron mark and a back wordmark+URL lockup, in the cream/gold-dark colorway, as print-ready SVG + 300 DPI PNG for a black org t-shirt. +--- + +# Design: modern-python org t-shirt artwork + +## Summary + +Add a new **apparel** output to the brand kit (`brand/build/`) that renders the +two print-ready artworks for a black org t-shirt: a small **left-chest chevron +mark** and a large **back lockup** (the MODERN/PYTHON wordmark with +`modern-python.org` beneath it). Both print in the two-ink cream + gold-dark +colorway (`#f4f1e8` + `#f0b528`) — the same treatment as the org avatar and site +header — so the whole garment is a 2-color spot print. Everything is drawn from +existing `geometry.py` primitives; the only new geometry is a back-lockup +composition that stacks the existing wordmark over an outlined URL line. Outputs +land in `brand/apparel/` as vector SVG plus 300 DPI PNG, regenerable via +`uv run python -m brand.build.render`. + +## Motivation + +The org has a full generated brand kit but no apparel artwork. A physical +t-shirt (org swag) needs files a print shop can use: vector art in the correct +colorway for a dark garment, at real print sizes. Producing these by hand would +drift from the repo's "every asset is generated from `brand/build/`" convention; +wiring them into the generator keeps them reproducible and on-palette. + +The visual was settled interactively (front/back mockups): black garment, +transparent chevron mark on the left chest, and a ~8 in back lockup with the +full domain set small beneath the wordmark. + +## Non-goals + +- No physical/print ordering, vendor choice, or garment sourcing — files only. +- No proof/mockup asset committed (reviewed via the live mockups; skipped). +- No new colorways or garment colors — black only, one colorway. +- No changes to org marks, project marks, social cards, or site wiring. +- No new palette tokens — reuses `CREAM` and `GOLD_DARK`. + +## Design + +### 1. Back-lockup geometry — `brand/build/geometry.py` + +Add `apparel_back(*, struct, gold)`: the transparent wordmark lockup with the +full domain outlined beneath it, in one SVG. It reuses `lockup_body()` (the +MODERN/PYTHON crop-mark lockup, drawn 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). The viewBox extends the wordmark's `118 32 304 184` down to +fit the URL line, keeping the same horizontal center (x=270). + +```python +def apparel_back(*, struct: str, gold: str) -> str: + """Back-of-shirt lockup: the MODERN/PYTHON wordmark with the full domain + outlined beneath it, transparent, for the cream+gold-dark colorway.""" + url, _ = outline_text( + "modern-python.org", 18, x=270, baseline_y=240, + anchor="middle", color=gold, letter_spacing=3, + ) + return ( + '' + + lockup_body(struct=struct, gold=gold) + + url + + "" + ) +``` + +The URL is set in `gold` (gold-dark) — the wordmark supplies `struct` (cream) for +MODERN and gold for PYTHON, so the graphic stays within the two allowed inks. The +size (18u) and baseline (240u, ~40u below the crop bottom) reproduce the +signed-off "S2" mockup: clearly secondary to the wordmark, still legible. + +The chest art needs no new geometry — it is the existing +`mark(struct=CREAM, gold=GOLD_DARK)` (transparent chevron, viewBox `0 0 100 100`). + +### 2. Apparel render module — `brand/build/apparel.py` + +New module mirroring `projects.py`'s shape, exposing `render_apparel()`. Writes +to `brand/apparel/`: + +```python +def render_apparel() -> None: + APPAREL.mkdir(parents=True, exist_ok=True) + ink = dict(struct=t.CREAM, gold=t.GOLD_DARK) + _write(APPAREL / "chest-mark.svg", g.mark(**ink)) + export_png(APPAREL / "chest-mark.svg", APPAREL / "chest-mark-1050.png", width=1050) + _write(APPAREL / "back-lockup.svg", g.apparel_back(**ink)) + export_png(APPAREL / "back-lockup.svg", APPAREL / "back-lockup-2400.png", width=2400) +``` + +PNG widths are the print sizes at 300 DPI: chest 3.5 in → 1050 px (→ 1050×1050); +back 8 in → 2400 px (→ 2400×1800, aspect preserved by `rsvg-convert -w`). PNGs +go through the existing `export_png` → `_quantize_png` path (indexed color, alpha +preserved). Without `rsvg-convert` the SVGs still write and PNGs are skipped, as +elsewhere. + +### 3. Render wiring — `brand/build/render.py` + +Import `render_apparel` and call it after `render_projects()` at the end of +`render()`. No other change. + +### 4. Print spec — `brand/apparel/README.md` + +Short prose the print shop reads: black garment; 2-color spot print (cream +`#f4f1e8` + gold-dark `#f0b528`); left-chest mark ~3.5 in wide at standard +left-chest placement; back lockup ~8 in wide, centered, ~4–5 in below the +collar; prefer the SVGs (vector), PNGs are 300 DPI fallbacks. Regenerate with +`uv run python -m brand.build.render`. + +### 5. Architecture promotion — `architecture/brand-marks.md` + +Add an **Apparel** section documenting the new capability: what +`brand/apparel/` holds, the colorway, and that it is generated by +`apparel.py` / `geometry.apparel_back` from the same primitives as the org +marks. + +## Out of scope + +Other garment colors (would need the light green-ink/gold-light colorway), +long-sleeve/sleeve prints, stickers, and any non-shirt merch. Each is a clean +follow-up reusing the same primitives if wanted later. + +## Testing + +- New `tests/test_geometry.py::test_apparel_back`: viewBox is + `118 32 304 228`; carries the lockup crops (`M138 122 L138 50 L210 50`); + contains the outlined domain (glyph ``s, **no** ``); is transparent + (no full-bleed background rect); uses only `#f4f1e8` and `#f0b528`; no `var(`. +- Extend the asset test (`tests/test_assets.py`) to assert the four + `brand/apparel/` files parse as valid SVG / exist after render, mirroring how + org and project assets are checked. +- `just test` green; `just sync-assets` (or `python -m brand.build.render`) is a + deterministic no-op diff apart from the new apparel files. +- Visual check of `chest-mark.svg` and `back-lockup.svg` (and the PNGs) on a dark + background before committing. +- `just check-planning` passes before pushing. + +## Risk + +- **Low: URL baseline/size looks off against the wordmark** once rendered with + the real Jost outline (mockup used a fallback font). Mitigated — `baseline_y` + and `size` are single constants in `apparel_back`; adjust and re-render if the + vertical gap reads wrong. The visual target is the approved S2 mockup. +- **Low: PNGs absent in CI** if `rsvg-convert` is unavailable. Same behavior as + existing targets — SVGs still generate; PNGs are the machine-produced + fallback, committed from a dev machine that has librsvg. +- **Low: back graphic exceeds the shirt print area at 8 in.** Mitigated — 8 in + is a conservative standard back-print width; the size lives in one place and is + trivially changed. diff --git a/planning/changes/2026-07-04.01-org-tshirt/plan.md b/planning/changes/2026-07-04.01-org-tshirt/plan.md new file mode 100644 index 0000000..a46921f --- /dev/null +++ b/planning/changes/2026-07-04.01-org-tshirt/plan.md @@ -0,0 +1,304 @@ +# Org T-Shirt Artwork 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 an apparel target to the brand generator that emits print-ready artwork (transparent left-chest chevron mark + back wordmark/URL lockup) for a black org t-shirt. + +**Architecture:** A new pure geometry function `geometry.apparel_back()` composes the existing `lockup_body()` wordmark over an outlined `modern-python.org` line in one transparent SVG. A new `brand/build/apparel.py` render module writes both artworks (chest reuses the existing `mark()`) as SVG + 300 DPI PNG into `brand/apparel/`, wired into `render.py`. Docs and the architecture capability file are updated in the same branch. + +**Tech Stack:** Python 3, `uv` (packaging), `pytest`, `ruff`, `ty`; SVG generated as strings; `rsvg-convert` (librsvg) for PNG rasterization; `fontTools` for text outlining (already used). + +## Global Constraints + +- Colorway is exactly two inks: cream `struct = #f4f1e8` (`tokens.CREAM`) and gold-dark `gold = #f0b528` (`tokens.GOLD_DARK`). No other colors; no background rect (transparent art). +- Back-lockup SVG viewBox is exactly `118 32 304 228`; URL is `outline_text("modern-python.org", 18, x=270, baseline_y=240, anchor="middle", color=gold, letter_spacing=3)`. +- PNG print sizes at 300 DPI: chest `width=1050` (3.5 in), back `width=2400` (8 in). +- All imports at module level, never inside function bodies. Annotate all function arguments. +- For type-checker suppressions use `ty: ignore`, never `type: ignore`. +- Brand casing in prose: `modern-python`, `modern-di`. Domain rendered as `modern-python.org`. No trailing period on the canonical one-liner. +- Generated SVG **and** PNG assets under `brand/apparel/` are committed, same as `brand/org/` and `brand/projects/`. +- This change touches no dependencies, so `uv.lock` must not change (it is tracked in this repo; leave it alone). +- Commit only on a feature branch (e.g. `org-tshirt-apparel`), never on `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` (or `uv run pytest`) before pushing. + +--- + +### Task 1: Back-lockup geometry (`geometry.apparel_back`) + +**Files:** +- Modify: `brand/build/geometry.py` (add function after `mark()`, before `social_card()`) +- Test: `tests/test_geometry.py` (add one test) + +**Interfaces:** +- Consumes: existing `geometry.lockup_body(*, struct: str, gold: str) -> str`, `text.outline_text(...)`, `geometry.wordmark(*, struct: str, gold: str) -> str`. +- Produces: `geometry.apparel_back(*, struct: str, gold: str) -> str` — a complete transparent `` string (viewBox `118 32 304 228`) with the wordmark lockup plus the outlined `modern-python.org` line beneath it. Consumed by Task 2's `apparel.py`. + +- [ ] **Step 1: Write the failing test** + +Add to `tests/test_geometry.py`: + +```python +def test_apparel_back_wordmark_plus_url(parse_svg): + svg = g.apparel_back(struct="#f4f1e8", gold="#f0b528") + el = parse_svg(svg) + assert el.attrib["viewBox"] == "118 32 304 228" # wordmark viewBox extended for the URL + assert el.attrib["aria-label"] == "Modern Python, modern-python.org" + assert " g.wordmark(struct="#f4f1e8", gold="#f0b528").count(" str: + """Back-of-shirt lockup: the MODERN/PYTHON wordmark with the full domain + outlined beneath it, transparent, for the cream+gold-dark colorway. Extends + the `wordmark` viewBox downward to seat the URL centered on the same axis.""" + url, _ = outline_text( + "modern-python.org", + 18, + x=270, + baseline_y=240, + anchor="middle", + color=gold, + letter_spacing=3, + ) + return ( + '' + + lockup_body(struct=struct, gold=gold) + + url + + "" + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `uv run pytest tests/test_geometry.py::test_apparel_back_wordmark_plus_url -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add brand/build/geometry.py tests/test_geometry.py +git commit -m "feat(brand): apparel_back wordmark+URL lockup geometry" +``` + +--- + +### Task 2: Apparel render target (`brand/build/apparel.py` + render wiring) + +**Files:** +- Create: `brand/build/apparel.py` +- Modify: `brand/build/render.py` (import + call after `render_projects()`) +- Test: `tests/test_assets.py` (add `APPAREL` path + one render test) + +**Interfaces:** +- Consumes: `geometry.mark(*, struct, gold)`, `geometry.apparel_back(*, struct, gold)` (Task 1), `tokens.CREAM`, `tokens.GOLD_DARK`, `raster.export_png(svg_path, png_path, *, width)`. +- Produces: `apparel.render_apparel() -> None`, writing `brand/apparel/{chest-mark.svg, chest-mark-1050.png, back-lockup.svg, back-lockup-2400.png}`. + +- [ ] **Step 1: Write the failing test** + +In `tests/test_assets.py`, add the apparel path next to `ORG` (top of file, after `ORG = Path("brand/org")`): + +```python +APPAREL = Path("brand/apparel") +``` + +Then add this test: + +```python +def test_render_writes_apparel(): + _render() + chest = APPAREL / "chest-mark.svg" + back = APPAREL / "back-lockup.svg" + assert chest.exists() and back.exists() + ET.parse(chest) + ET.parse(back) + ctext = chest.read_text() + assert 'points="45,40 57,50 45,60"' in ctext # chevron + assert 'width="100" height="100"' not in ctext # transparent, no bg rect + assert "#f4f1e8" in ctext and "#f0b528" in ctext + btext = back.read_text() + assert 'aria-label="Modern Python, modern-python.org"' in btext + assert " None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content + "\n", encoding="utf-8") + + +def render_apparel() -> None: + """Print-ready artwork for the black org t-shirt: a transparent left-chest + chevron mark and a back wordmark+URL lockup, both cream + gold-dark. SVG is + the master; PNGs are 300 DPI print fallbacks (chest 3.5 in, back 8 in).""" + APPAREL.mkdir(parents=True, exist_ok=True) + ink = dict(struct=t.CREAM, gold=t.GOLD_DARK) + _write(APPAREL / "chest-mark.svg", g.mark(**ink)) + export_png(APPAREL / "chest-mark.svg", APPAREL / "chest-mark-1050.png", width=1050) + _write(APPAREL / "back-lockup.svg", g.apparel_back(**ink)) + export_png(APPAREL / "back-lockup.svg", APPAREL / "back-lockup-2400.png", width=2400) +``` + +- [ ] **Step 4: Wire it into `render.py`** + +In `brand/build/render.py`, add the import beside the existing `render_projects` import: + +```python +from brand.build.apparel import render_apparel +``` + +Then, at the end of `render()`, immediately after the `render_projects()` call, add: + +```python + # Apparel artwork (brand/apparel/). + render_apparel() +``` + +- [ ] **Step 5: Run test to verify it passes** + +Run: `uv run pytest tests/test_assets.py::test_render_writes_apparel -v` +Expected: PASS (PNG assertions run only if `rsvg-convert` is installed). + +- [ ] **Step 6: Regenerate assets and commit** + +```bash +uv run python -m brand.build.render +git add brand/build/apparel.py brand/build/render.py tests/test_assets.py brand/apparel/ +git commit -m "feat(brand): apparel render target for the org t-shirt" +``` + +Expected: `git status` shows the four new `brand/apparel/` files (two SVG, two PNG) staged. If `rsvg-convert` is not installed, the two PNGs will be absent — install librsvg (`brew install librsvg`) and re-run `render` before committing so the print fallbacks are included. + +--- + +### Task 3: Print spec + architecture promotion + +**Files:** +- Create: `brand/apparel/README.md` +- Modify: `architecture/brand-marks.md` (add an Apparel section) + +**Interfaces:** +- Consumes: the files produced by Task 2. No code. +- Produces: documentation only. + +- [ ] **Step 1: Write the print spec** + +Create `brand/apparel/README.md`: + +```markdown +# Apparel artwork + +Print-ready artwork for the **black** modern-python org t-shirt, generated by +`brand/build/apparel.py`. Regenerate with: + +```bash +uv run python -m brand.build.render +``` + +## Colorway + +Two-ink spot print, cream + gold-dark (the on-dark org treatment): + +| Token | Hex | Role | +|-------|-----|------| +| Cream | `#f4f1e8` | MODERN, top crop, chevron snake | +| Gold-dark | `#f0b528` | PYTHON, bottom crop, chevron, URL | + +## Files + +| File | Placement | Print size | +|------|-----------|-----------| +| `chest-mark.svg` / `chest-mark-1050.png` | Left chest, standard placement | ~3.5 in wide | +| `back-lockup.svg` / `back-lockup-2400.png` | Back, centered, ~4-5 in below collar | ~8 in wide | + +The transparent chevron mark is `geometry.mark`; the back lockup (wordmark + +`modern-python.org`) is `geometry.apparel_back`. PNGs are 300 DPI fallbacks; +prefer the SVGs (vector) for print. Send the vendor a black garment with a +2-color print in the two hexes above. +``` + +- [ ] **Step 2: Promote the capability into `architecture/brand-marks.md`** + +Append a new section to `architecture/brand-marks.md`: + +```markdown +## Apparel (`brand/apparel/`) + +Print-ready artwork for the black org t-shirt, generated by +`brand/build/apparel.py::render_apparel` from the same primitives as the org +marks, in the cream + gold-dark colorway (a 2-ink spot print). Two artworks: +`chest-mark.svg` (the transparent chevron `geometry.mark`, left chest) and +`back-lockup.svg` (`geometry.apparel_back` — the MODERN/PYTHON wordmark with +`modern-python.org` outlined beneath it, viewBox `118 32 304 228`, back print). +Each ships a 300 DPI PNG fallback (`chest-mark-1050.png` 3.5 in, +`back-lockup-2400.png` 8 in). Regenerate via `uv run python -m brand.build.render`; +placement and vendor notes live in `brand/apparel/README.md`. +``` + +- [ ] **Step 3: Verify the full suite and planning** + +Run: `just test` +Expected: all tests PASS, including `test_apparel_back_wordmark_plus_url` and `test_render_writes_apparel`. + +Run: `just check-planning` +Expected: `planning: OK` + +- [ ] **Step 4: Commit** + +```bash +git add brand/apparel/README.md architecture/brand-marks.md +git commit -m "docs(brand): apparel print spec + architecture promotion" +``` + +--- + +## Notes for the executor + +- The `design.md` in this bundle is the *why*; its `summary` is already written + as the realized result and needs no edit at ship. +- Do not restructure `geometry.py` or `render.py` beyond the additions above — + follow the existing one-function-per-asset pattern. +- If the rendered URL sits too close to or too far from PYTHON, adjust only + `baseline_y` (currently 240) in `apparel_back` and re-render; the viewBox + height (228) has ~16u of margin below a 240 baseline. +- Finish via PR (push the `org-tshirt-apparel` branch, open a PR); do not + local-merge. Watch CI after pushing. diff --git a/tests/test_assets.py b/tests/test_assets.py index bb8eca2..e22489c 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -5,6 +5,7 @@ from pathlib import Path ORG = Path("brand/org") +APPAREL = Path("brand/apparel") def _render(): @@ -87,3 +88,24 @@ def test_render_writes_social_cards(): "social-square-green", ): assert (ORG / f"{name}.png").read_bytes()[:8] == b"\x89PNG\r\n\x1a\n" + + +def test_render_writes_apparel(): + _render() + chest = APPAREL / "chest-mark.svg" + back = APPAREL / "back-lockup.svg" + assert chest.exists() and back.exists() + ET.parse(chest) + ET.parse(back) + ctext = chest.read_text() + assert 'points="45,40 57,50 45,60"' in ctext # chevron + assert 'width="100" height="100"' not in ctext # transparent, no bg rect + assert "#f4f1e8" in ctext and "#f0b528" in ctext + btext = back.read_text() + assert 'aria-label="Modern Python, modern-python.org"' in btext + assert " g.wordmark(struct="#f4f1e8", gold="#f0b528").count("