From b665eeee36de9f5209c22314fa33dd333f76b1a6 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 4 Jul 2026 15:30:14 +0300 Subject: [PATCH 1/5] docs(planning): org t-shirt apparel design + plan Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-04.01-org-tshirt/design.md | 149 +++++++++ .../changes/2026-07-04.01-org-tshirt/plan.md | 304 ++++++++++++++++++ 2 files changed, 453 insertions(+) create mode 100644 planning/changes/2026-07-04.01-org-tshirt/design.md create mode 100644 planning/changes/2026-07-04.01-org-tshirt/plan.md 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..85499ff --- /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. From 4312720a0c596ff661f09baf2ddafd8bb864b0bb Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 4 Jul 2026 15:32:18 +0300 Subject: [PATCH 2/5] feat(brand): apparel_back wordmark+URL lockup geometry Co-Authored-By: Claude Opus 4.8 (1M context) --- brand/build/geometry.py | 22 ++++++++++++++++++++++ tests/test_geometry.py | 14 ++++++++++++++ 2 files changed, 36 insertions(+) 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/tests/test_geometry.py b/tests/test_geometry.py index d86c148..c1d0c73 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -68,6 +68,20 @@ def test_social_square(parse_svg): assert "translate(-193.0,82.5) scale(1.9)" in svg +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(" Date: Sat, 4 Jul 2026 15:37:43 +0300 Subject: [PATCH 3/5] feat(brand): apparel render target for the org t-shirt Co-Authored-By: Claude Opus 4.8 (1M context) --- brand/apparel/back-lockup-2400.png | Bin 0 -> 34931 bytes brand/apparel/back-lockup.svg | 1 + brand/apparel/chest-mark-1050.png | Bin 0 -> 5661 bytes brand/apparel/chest-mark.svg | 1 + brand/build/apparel.py | 25 +++++++++++++++++++++++++ brand/build/render.py | 4 ++++ tests/test_assets.py | 22 ++++++++++++++++++++++ 7 files changed, 53 insertions(+) create mode 100644 brand/apparel/back-lockup-2400.png create mode 100644 brand/apparel/back-lockup.svg create mode 100644 brand/apparel/chest-mark-1050.png create mode 100644 brand/apparel/chest-mark.svg create mode 100644 brand/build/apparel.py diff --git a/brand/apparel/back-lockup-2400.png b/brand/apparel/back-lockup-2400.png new file mode 100644 index 0000000000000000000000000000000000000000..cd56c8efd1dff040207f781b5f97abc3d0dd0287 GIT binary patch literal 34931 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 diff --git a/brand/apparel/chest-mark-1050.png b/brand/apparel/chest-mark-1050.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6c6f3a142cd1d2fda247cfb34aa96cde64f2aa GIT binary patch literal 5661 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 literal 0 HcmV?d00001 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/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/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 " Date: Sat, 4 Jul 2026 15:42:57 +0300 Subject: [PATCH 4/5] docs(brand): apparel print spec + architecture promotion Co-Authored-By: Claude Opus 4.8 (1M context) --- architecture/brand-marks.md | 12 ++++++++++++ brand/apparel/README.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 brand/apparel/README.md 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. From 56147d014f2c05bbf82e3e93d13f2c0a4598db59 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 4 Jul 2026 15:48:57 +0300 Subject: [PATCH 5/5] docs(planning): match design snippet aria-label to shipped comma form Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/changes/2026-07-04.01-org-tshirt/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/changes/2026-07-04.01-org-tshirt/design.md b/planning/changes/2026-07-04.01-org-tshirt/design.md index 85499ff..1985779 100644 --- a/planning/changes/2026-07-04.01-org-tshirt/design.md +++ b/planning/changes/2026-07-04.01-org-tshirt/design.md @@ -58,7 +58,7 @@ def apparel_back(*, struct: str, gold: str) -> str: ) return ( '' + 'role="img" aria-label="Modern Python, modern-python.org">' + lockup_body(struct=struct, gold=gold) + url + ""