diff --git a/tools/render-og-card.py b/tools/render-og-card.py
index 06f0aec..1b82966 100644
--- a/tools/render-og-card.py
+++ b/tools/render-og-card.py
@@ -1,17 +1,24 @@
-"""Render /og.png, the 1200x630 social card for agentrust-io.com, with headless Chrome.
+"""Render AgenTrust social cards (1200x630 PNG) with headless Chrome.
- python tools/render-og-card.py # writes og.png
+ python tools/render-og-card.py # agentrust-io.com, writes og.png
+ python tools/render-og-card.py wcm --out PATH # wcm.agentrust-io.com card
CHROME=/path/to/chrome python tools/render-og-card.py
-The card is HTML so its words can change with an edit, not a lost design file.
-It lives here rather than as a page because every *.html in the repository is a
-published page to check-site.py and the sitemap. Keep the words in step with the
-homepage hero and with the og:image:alt text the pages use; colours are the brand
-navy and red from brand/BRAND.md.
+Every card is the same HTML template with different words, so the family stays
+consistent and the next wording change is an edit and one command, not a lost
+design file. Cards for the docs hosts are committed in their own repositories
+(the WCM card lives at docs/assets/og.png in weight-custody-manifest), so pass
+--out for those.
+
+The template lives here rather than as a page because every *.html in this
+repository is a published page to check-site.py and the sitemap. Keep the words
+in step with each site's hero and its og:image:alt; colours are the brand navy
+and red from brand/BRAND.md.
No --check: a PNG rendered on another machine differs in font hinting, so commit
the rendered file and review it by eye.
"""
+import argparse
import os
from pathlib import Path
import shutil
@@ -20,12 +27,26 @@
import tempfile
ROOT = Path(__file__).resolve().parents[1]
-
-EYEBROW = 'Open specifications for verifiable AI'
-TITLE = 'AgenTrust'
-LEAD = 'Prove what your AI ran, and what it did.'
CHAIN = ['Weights', 'Agent', 'Actions', 'Evidence']
-DOMAIN = 'agentrust-io.com'
+
+CARDS = {
+ 'hub': {
+ 'out': ROOT / 'og.png',
+ 'eyebrow': 'Open specifications for verifiable AI',
+ 'title': 'AgenTrust',
+ 'lead': 'Prove what your AI ran, and what it did.',
+ 'step': None,
+ 'domain': 'agentrust-io.com',
+ },
+ 'wcm': {
+ 'out': None,
+ 'eyebrow': 'An AgenTrust open specification',
+ 'title': 'Weight Custody Manifest',
+ 'lead': 'Release model weights only to an attested runtime.',
+ 'step': 1,
+ 'domain': 'wcm.agentrust-io.com',
+ },
+}
CARD = """
@@ -36,11 +57,12 @@
body {{ background: #15294B; color: #FFFEFA; position: relative; font-family: Inter, "Segoe UI", ui-sans-serif, sans-serif; }}
.wrap {{ position: absolute; left: 90px; right: 90px; top: 84px; }}
.eyebrow {{ font-size: 25px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #9FB3D1; }}
- h1 {{ font-size: 118px; font-weight: 700; margin: 26px 0 0; letter-spacing: -.01em; line-height: 1; }}
+ h1 {{ font-size: {title_size}px; font-weight: 700; margin: 26px 0 0; letter-spacing: -.01em; line-height: 1; }}
.bar {{ width: 120px; height: 9px; background: #B91C1C; margin: 38px 0 34px; }}
.lead {{ font-size: 44px; line-height: 1.2; color: #E9E4D8; margin: 0; }}
.chain {{ margin-top: 30px; font-size: 27px; color: #9FB3D1; letter-spacing: .01em; }}
.chain b {{ color: #FFFEFA; font-weight: 600; }}
+ .chain .on {{ color: #FFFEFA; border-bottom: 3px solid #B91C1C; padding-bottom: 2px; }}
.domain {{ position: absolute; left: 90px; bottom: 46px; font-size: 27px; font-weight: 700; color: #9FB3D1; }}
.foot {{ position: absolute; left: 0; right: 0; bottom: 0; height: 14px; background: #B91C1C; }}
@@ -73,17 +95,37 @@ def chrome():
raise SystemExit('Chrome not found. Set CHROME to its path.')
+def chain_html(step):
+ parts = []
+ for i, name in enumerate(CHAIN, 1):
+ item = f'{i:02d} {name}'
+ parts.append(f'{item}' if i == step else item)
+ return ' · '.join(parts)
+
+
def main():
- chain = ' · '.join(f'{i:02d} {step}' for i, step in enumerate(CHAIN, 1))
- html = CARD.format(eyebrow=EYEBROW, title=TITLE, lead=LEAD, chain=chain, domain=DOMAIN)
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+ parser.add_argument('card', nargs='?', default='hub', choices=sorted(CARDS))
+ parser.add_argument('--out', type=Path, help='where to write the PNG (required for docs-host cards)')
+ args = parser.parse_args()
+
+ card = CARDS[args.card]
+ out = args.out or card['out']
+ if out is None:
+ raise SystemExit(f'the {args.card} card is committed in its own repository: pass --out')
+ html = CARD.format(
+ eyebrow=card['eyebrow'], title=card['title'], lead=card['lead'], domain=card['domain'],
+ chain=chain_html(card['step']),
+ # One line at 1020px wide: long names step down rather than wrap.
+ title_size=118 if len(card['title']) <= 12 else 84,
+ )
with tempfile.TemporaryDirectory() as tmp:
- card = Path(tmp) / 'og-card.html'
- card.write_text(html, encoding='utf-8')
- out = ROOT / 'og.png'
+ page = Path(tmp) / 'og-card.html'
+ page.write_text(html, encoding='utf-8')
subprocess.run([chrome(), '--headless=new', '--disable-gpu', '--hide-scrollbars',
'--force-device-scale-factor=1', '--window-size=1200,630',
- f'--screenshot={out}', card.as_uri()], check=True, capture_output=True)
- print(f'wrote {out.relative_to(ROOT)}')
+ f'--screenshot={Path(out).resolve()}', page.as_uri()], check=True, capture_output=True)
+ print(f'wrote {out}')
if __name__ == '__main__':
diff --git a/wcm/index.html b/wcm/index.html
index 9cc838f..d3a3231 100644
--- a/wcm/index.html
+++ b/wcm/index.html
@@ -19,8 +19,9 @@
address is the drift. The Open Graph tags below point at the docs site so a
crawler that lands on an old share resolves to the same card.
- og-launch.png stays in this directory. Cards cached against
- agentrust-io.com/wcm/og-launch.png still resolve, and nothing rewrites that path.
+ The card is served from this directory as og.png, the same image the docs
+ site serves, rendered by tools/render-og-card.py wcm. og-launch.png, the old
+ card, stays here too, so shares cached against it still resolve.
-->
@@ -32,22 +33,22 @@
-
-
-
-
+
+
+
+
-
+
-
+