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 manifest # only web manifest + maskable icons
# make og # only Open Graph / social-share cards
# make tokens # only color tokens (brand.css + brand.json)
# make docs # only BRANDING.md
Expand All @@ -18,7 +19,7 @@ PYTHON := python3
SCRIPTS := scripts
ASSETS := logos

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

all: build docs

Expand All @@ -29,6 +30,7 @@ help:
@echo " svgs - generate only SVGs"
@echo " pngs - generate only PNGs (requires SVGs)"
@echo " favicons - generate only favicons"
@echo " manifest - generate web manifest + maskable icons"
@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"
Expand All @@ -48,6 +50,9 @@ pngs:
favicons:
$(PYTHON) $(SCRIPTS)/build.py favicon

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

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

Expand Down
18 changes: 18 additions & 0 deletions brand.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,24 @@ margin_ratio = 0.14 # safe padding as a fraction of the shorter axis
web = [1200, 630] # og:image / Twitter summary_large_image
github = [1280, 640] # GitHub repo + org social preview (2:1)

# ──────────────────────────────────────────────────────────────────────
# Web app manifest + maskable icons
#
# `make manifest` emits a site.webmanifest template plus maskable-safe
# icons (symbol padded so Android's circular/squircle mask never clips
# the phi). The plain favicon-192/512 cover purpose:"any"; these cover
# purpose:"maskable".
# ──────────────────────────────────────────────────────────────────────

[manifest]
name = "Audiophore"
short_name = "Audiophore"
theme_color = "wash_dark" # palette key → browser UI / address bar
background_color = "wash_dark" # palette key → splash background
display = "standalone"
maskable_padding = 0.2 # safe-zone padding as a fraction of the icon
maskable_sizes = [192, 512]

# ──────────────────────────────────────────────────────────────────────
# Variants (which color treatments to generate for which assets)
# ──────────────────────────────────────────────────────────────────────
Expand Down
Binary file added logos/favicon/favicon-maskable-192.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/favicon/favicon-maskable-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions logos/favicon/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "Audiophore",
"short_name": "Audiophore",
"theme_color": "#0a0e1a",
"background_color": "#0a0e1a",
"display": "standalone",
"icons": [
{
"src": "favicon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "favicon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "favicon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "favicon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
66 changes: 64 additions & 2 deletions scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- logos/png/*.png (all rasters at all configured sizes)
- logos/favicon/*.png (favicon sizes)
- logos/favicon/favicon.ico (multi-resolution legacy favicon)
- logos/favicon/site.webmanifest + maskable icons (PWA manifest)
- 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 Down Expand Up @@ -432,6 +433,63 @@ def build_favicons(cfg: dict) -> list[Path]:
return written


# ─── Web app manifest + maskable icons ──────────────────────────────

def build_manifest(cfg: dict) -> list[Path]:
"""Emit site.webmanifest + maskable icons (padded so masks don't clip)."""
mf = cfg["manifest"]
bg = color(cfg, mf["background_color"])
pad = mf["maskable_padding"]
written = []

# Maskable icons: the color symbol inset by `pad` on a solid background,
# rendered on the symbol's native 320 viewBox then scaled to each size.
sym_inner = extract_inner(symbol_svg(cfg, "color"))
vb = cfg["symbol"]["viewbox"]
scale = 1 - 2 * pad
offset = vb * pad
for size in mf["maskable_sizes"]:
svg = (
f'<?xml version="1.0" encoding="UTF-8"?>\n'
f'<svg xmlns="http://www.w3.org/2000/svg" '
f'viewBox="0 0 {vb} {vb}" width="{vb}" height="{vb}">\n'
f' <rect width="{vb}" height="{vb}" fill="{bg}"/>\n'
f' <g transform="translate({offset:g}, {offset:g}) scale({scale:g})">\n'
f' {sym_inner}\n'
f' </g>\n'
f'</svg>\n'
)
dst = FAVICON_DIR / f"favicon-maskable-{size}.png"
cairosvg.svg2png(
bytestring=svg.encode(), write_to=str(dst),
output_width=size, output_height=size,
)
written.append(dst)

# Manifest. src paths are bare filenames — the manifest sits beside the
# icons in logos/favicon/; consumers adjust src to their served path.
manifest = {
"name": mf["name"],
"short_name": mf["short_name"],
"theme_color": color(cfg, mf["theme_color"]),
"background_color": bg,
"display": mf["display"],
"icons": [
{"src": "favicon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any"},
{"src": "favicon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any"},
*[
{"src": f"favicon-maskable-{s}.png", "sizes": f"{s}x{s}",
"type": "image/png", "purpose": "maskable"}
for s in mf["maskable_sizes"]
],
],
}
dst = FAVICON_DIR / "site.webmanifest"
dst.write_text(json.dumps(manifest, indent=2) + "\n")
written.append(dst)
return written


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

def build_og(cfg: dict) -> list[Path]:
Expand Down Expand Up @@ -546,7 +604,7 @@ def main():
parser = argparse.ArgumentParser(description=__doc__.strip().split("\n")[0])
parser.add_argument(
"targets", nargs="*",
choices=["all", "symbol", "wordmark", "lockup", "png", "favicon", "og", "tokens", []],
choices=["all", "symbol", "wordmark", "lockup", "png", "favicon", "manifest", "og", "tokens", []],
default=[],
help="Which assets to build (default: all)",
)
Expand All @@ -562,7 +620,7 @@ def main():

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

if "symbol" in targets:
print("Building symbol SVGs…")
Expand All @@ -586,6 +644,10 @@ def main():
print("Building favicons…")
for p in build_favicons(cfg):
print(f" {p.relative_to(ROOT)}")
if "manifest" in targets:
print("Building web manifest + maskable icons…")
for p in build_manifest(cfg):
print(f" {p.relative_to(ROOT)}")
if "og" in targets:
print("Building OG social cards…")
for p in build_og(cfg):
Expand Down
Loading