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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# make svgs # only SVGs
# make pngs # only PNGs (requires SVGs to exist)
# make favicons # only favicons
# make og # only Open Graph / social-share cards
# make tokens # only color tokens (brand.css + brand.json)
# make docs # only BRANDING.md
# make clean # remove all generated assets
Expand All @@ -17,7 +18,7 @@ PYTHON := python3
SCRIPTS := scripts
ASSETS := logos

.PHONY: all build svgs pngs favicons tokens docs clean check install help
.PHONY: all build svgs pngs favicons og tokens docs clean check install help

all: build docs

Expand All @@ -28,6 +29,7 @@ help:
@echo " svgs - generate only SVGs"
@echo " pngs - generate only PNGs (requires SVGs)"
@echo " favicons - generate only favicons"
@echo " og - generate Open Graph / social-share cards"
@echo " tokens - generate color tokens (brand.css + brand.json)"
@echo " docs - regenerate BRANDING.md from brand.toml"
@echo " clean - remove all generated assets"
Expand All @@ -46,6 +48,9 @@ pngs:
favicons:
$(PYTHON) $(SCRIPTS)/build.py favicon

og:
$(PYTHON) $(SCRIPTS)/build.py og

tokens:
$(PYTHON) $(SCRIPTS)/build.py tokens

Expand Down
18 changes: 18 additions & 0 deletions brand.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,24 @@ px = [200, 400, 800]
px = [16, 32, 48, 64, 180, 192, 512]
ico_sizes = [16, 32, 48, 64] # multi-resolution .ico bundle

# ──────────────────────────────────────────────────────────────────────
# Open Graph / social-share cards
#
# The image link-unfurlers (Slack, Discord, iMessage, Twitter) and
# GitHub's repo/org social-preview pull. The dark-bg lockup centered on
# the dark wash, with safe margins. `make og` renders one PNG per size.
# ──────────────────────────────────────────────────────────────────────

[og]
background = "wash_dark" # full-bleed background color (palette key)
lockup_variant = "dark_bg" # which lockup treatment to center
margin_ratio = 0.14 # safe padding as a fraction of the shorter axis

[og.sizes]
# name = [width, height]
web = [1200, 630] # og:image / Twitter summary_large_image
github = [1280, 640] # GitHub repo + org social preview (2:1)

# ──────────────────────────────────────────────────────────────────────
# Variants (which color treatments to generate for which assets)
# ──────────────────────────────────────────────────────────────────────
Expand Down
Binary file added logos/og/audiophore-og-1200x630.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added logos/og/audiophore-og-1280x640.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 52 additions & 5 deletions scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- logos/png/*.png (all rasters at all configured sizes)
- logos/favicon/*.png (favicon sizes)
- logos/favicon/favicon.ico (multi-resolution legacy favicon)
- logos/og/*.png (Open Graph / social-share cards)
- tokens/brand.css (palette as :root CSS custom properties)
- tokens/brand.json (palette as structured JSON)

Expand All @@ -24,6 +25,7 @@
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
Expand All @@ -44,6 +46,7 @@
SVG_DIR = ASSETS_DIR / "svg"
PNG_DIR = ASSETS_DIR / "png"
FAVICON_DIR = ASSETS_DIR / "favicon"
OG_DIR = ASSETS_DIR / "og"
# Non-logo machine-readable exports (palette tokens) live one level up from
# logos/ so consumers can grab just the tokens without the whole asset tree.
TOKENS_DIR = ROOT / "tokens"
Expand Down Expand Up @@ -380,6 +383,46 @@ def build_favicons(cfg: dict) -> list[Path]:
return written


# ─── Open Graph / social cards ──────────────────────────────────────

def build_og(cfg: dict) -> list[Path]:
"""Render social-share cards: the lockup centered on a full-bleed wash."""
og = cfg["og"]
bg = color(cfg, og["background"])
margin_ratio = og["margin_ratio"]

# Source lockup + its intrinsic dimensions (from its own viewBox).
lockup = lockup_svg(cfg, og["lockup_variant"])
m = re.search(r'viewBox="0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)"', lockup)
lk_w, lk_h = float(m.group(1)), float(m.group(2))
lk_inner = extract_inner(lockup)

written = []
for name, (W, H) in og["sizes"].items():
margin = margin_ratio * min(W, H)
scale = min((W - 2 * margin) / lk_w, (H - 2 * margin) / lk_h)
draw_w, draw_h = lk_w * scale, lk_h * scale
tx, ty = (W - draw_w) / 2, (H - draw_h) / 2

svg = (
f'<?xml version="1.0" encoding="UTF-8"?>\n'
f'<svg xmlns="http://www.w3.org/2000/svg" '
f'viewBox="0 0 {W} {H}" width="{W}" height="{H}">\n'
f' <rect width="{W}" height="{H}" fill="{bg}"/>\n'
f' <g transform="translate({tx:g}, {ty:g}) scale({scale:g})">\n'
f' {lk_inner}\n'
f' </g>\n'
f'</svg>\n'
)
dst = OG_DIR / f"audiophore-og-{W}x{H}.png"
cairosvg.svg2png(
bytestring=svg.encode(), write_to=str(dst),
output_width=W, output_height=H,
)
written.append(dst)
return written


# ─── Token export (CSS + JSON) ──────────────────────────────────────

def _css_var(prefix: str, key: str) -> str:
Expand Down Expand Up @@ -437,24 +480,24 @@ def write_tokens(cfg: dict) -> list[Path]:

def clean():
"""Remove all generated assets."""
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, TOKENS_DIR):
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, OG_DIR, TOKENS_DIR):
if d.exists():
shutil.rmtree(d)
print(f" removed {d.relative_to(ROOT)}")
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, TOKENS_DIR):
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, OG_DIR, TOKENS_DIR):
d.mkdir(parents=True, exist_ok=True)


def ensure_dirs():
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, TOKENS_DIR):
for d in (SVG_DIR, PNG_DIR, FAVICON_DIR, OG_DIR, TOKENS_DIR):
d.mkdir(parents=True, exist_ok=True)


def main():
parser = argparse.ArgumentParser(description=__doc__.strip().split("\n")[0])
parser.add_argument(
"targets", nargs="*",
choices=["all", "symbol", "wordmark", "lockup", "png", "favicon", "tokens", []],
choices=["all", "symbol", "wordmark", "lockup", "png", "favicon", "og", "tokens", []],
default=[],
help="Which assets to build (default: all)",
)
Expand All @@ -470,7 +513,7 @@ def main():

targets = set(args.targets) if args.targets else {"all"}
if "all" in targets:
targets = {"symbol", "wordmark", "lockup", "png", "favicon", "tokens"}
targets = {"symbol", "wordmark", "lockup", "png", "favicon", "og", "tokens"}

if "symbol" in targets:
print("Building symbol SVGs…")
Expand All @@ -492,6 +535,10 @@ def main():
print("Building favicons…")
for p in build_favicons(cfg):
print(f" {p.relative_to(ROOT)}")
if "og" in targets:
print("Building OG social cards…")
for p in build_og(cfg):
print(f" {p.relative_to(ROOT)}")
if "tokens" in targets:
print("Exporting color tokens…")
for p in write_tokens(cfg):
Expand Down
Loading