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
32 changes: 24 additions & 8 deletions scripts/build-hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

import hashlib
import html
import json
import sys
Expand All @@ -17,7 +18,7 @@ def esc(value: str) -> str:
return html.escape(value, quote=True)


def page(slug: str, title: str, description: str, content: str, script: str = "") -> str:
def page(slug: str, title: str, description: str, content: str, social_image: str, script: str = "") -> str:
if slug == "observatory":
content = content.replace('<section class="hub-section commons-pulse"', (SOURCE / "fragments/activity.html").read_text(encoding="utf-8") + '<section class="hub-section commons-pulse"', 1)
nav = "".join(f'<a href="/{key}/"' + (' aria-current="page"' if key == slug else '') + f'>{label}</a>' for key, label in NAV)
Expand Down Expand Up @@ -52,11 +53,17 @@ def page(slug: str, title: str, description: str, content: str, script: str = ""
<meta property="og:title" content="{esc(title)} — OSS Singularity">
<meta property="og:description" content="{esc(description)}">
<meta property="og:url" content="{ORIGIN}/{slug}/">
<meta property="og:image" content="{ORIGIN}/assets/social/oss-singularity-social-preview.png">
<meta property="og:image" content="{social_image}">
<meta property="og:image:secure_url" content="{social_image}">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="OSS Singularity event-horizon mark. Many minds. One open horizon. An open home for every entity.">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{esc(title)} — OSS Singularity">
<meta name="twitter:description" content="{esc(description)}">
<meta name="twitter:image" content="{social_image}">
<meta name="twitter:image:alt" content="OSS Singularity event-horizon mark. Many minds. One open horizon. An open home for every entity.">
</head>
<body class="hub-page hub-{slug}">
<a class="skip-link" href="#main">Skip to main content</a>
Expand Down Expand Up @@ -121,11 +128,11 @@ def atlas(data: dict) -> str:
for i, entry in enumerate(data["entries"], 1):
tags = "".join(f'<span>{esc(tag)}</span>' for tag in entry["tags"][:3])
cards.append(f'''<article class="atlas-entry" id="{esc(entry['id'])}" data-category="{esc(entry['category'])}" data-name="{esc(entry['name'])}">
<div class="entry-coordinate"><span>{i:02d}</span><span>{esc(labels[entry['category']])}</span></div>
<div class="entry-body"><h2>{esc(entry['name'])}</h2><p>{esc(entry['summary'])}</p><div class="entry-tags">{tags}</div></div>
<div class="entry-fit"><span class="micro-label">A starting point for</span><p>{esc(entry['use_case'])}</p><span class="entry-license">{esc(entry['license'])}</span></div>
<div class="entry-actions"><a class="button button-secondary" href="{esc(entry['website'])}">Explore <span class="sr-only">{esc(entry['name'])}</span> ↗</a><a class="source-link" href="{esc(entry['source_url'])}">Official reference ↗</a><label class="compare-label" hidden><input type="checkbox" value="{esc(entry['id'])}"> Compare <span class="sr-only">{esc(entry['name'])}</span></label></div>
</article>''')
<div class="entry-coordinate"><span>{i:02d}</span><span>{esc(labels[entry['category']])}</span></div>
<div class="entry-body"><h2>{esc(entry['name'])}</h2><p>{esc(entry['summary'])}</p><div class="entry-tags">{tags}</div></div>
<div class="entry-fit"><span class="micro-label">A starting point for</span><p>{esc(entry['use_case'])}</p><span class="entry-license">{esc(entry['license'])}</span></div>
<div class="entry-actions"><a class="button button-secondary" href="{esc(entry['website'])}">Explore <span class="sr-only">{esc(entry['name'])}</span> ↗</a><a class="source-link" href="{esc(entry['source_url'])}">Official reference ↗</a><label class="compare-label" hidden><input type="checkbox" value="{esc(entry['id'])}"> Compare <span class="sr-only">{esc(entry['name'])}</span></label></div>
</article>''')
return heading("01", "Agent Atlas", 'Many paths.<br><em>Find yours.</em>', "An independent map of the open agent ecosystem. Pick a starting point for your next idea — whatever model, machine or background you bring.") + f'''
<div class="atlas-meta"><span><span class="status-dot" aria-hidden="true"></span> {len(data['entries'])} curated entries</span><span>Sources checked <time datetime="{esc(data['updated'])}">{esc(data['updated'])}</time></span><a href="/data/atlas.json">Read as JSON ↗</a></div>
<section class="atlas-controls" aria-label="Find an ecosystem project" hidden>
Expand Down Expand Up @@ -207,7 +214,16 @@ def mission() -> str:

def main() -> None:
output = Path(sys.argv[1]).resolve()
social_path = "assets/social/oss-singularity-social-preview.png"
social_bytes = (output / social_path).read_bytes()
social_version = hashlib.sha256(social_bytes).hexdigest()[:12]
social_versioned_path = f"assets/social/oss-singularity-social-preview.{social_version}.png"
(output / social_versioned_path).write_bytes(social_bytes)
social_image = f"{ORIGIN}/{social_versioned_path}"
home = (SOURCE / "index.html").read_text(encoding="utf-8")
if home.count("{{SOCIAL_IMAGE_URL}}") != 3:
raise ValueError("Homepage must declare all three social preview image URLs")
home = home.replace("{{SOCIAL_IMAGE_URL}}", social_image)
if home.count("<!-- COMMONS_ACTIVITY -->") != 1:
raise ValueError("Homepage activity insertion point must occur exactly once")
(output / "index.html").write_text(home.replace("<!-- COMMONS_ACTIVITY -->", (SOURCE / "fragments/activity.html").read_text(encoding="utf-8")), encoding="utf-8")
Expand All @@ -227,7 +243,7 @@ def main() -> None:
for slug, title, description, content, script in pages:
target = output / slug / "index.html"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(page(slug, title, description, content, script), encoding="utf-8")
target.write_text(page(slug, title, description, content, social_image, script), encoding="utf-8")


if __name__ == "__main__":
Expand Down
19 changes: 19 additions & 0 deletions scripts/check-site.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,18 @@ def main() -> int:
"singularity/index.html", "assets/styles/singularity-v1.css",
"assets/scripts/singularity-v1.js", "assets/scripts/singularity-participation-v1.js",
}
social_path = "assets/social/oss-singularity-social-preview.png"
social_bytes = (root / social_path).read_bytes()
social_version = hashlib.sha256(social_bytes).hexdigest()[:12]
social_versioned_path = f"assets/social/oss-singularity-social-preview.{social_version}.png"
required.add(social_versioned_path)
actual = {str(path.relative_to(root)) for path in root.rglob("*") if path.is_file()}
if actual != required:
fail(f"build allowlist mismatch: missing={sorted(required - actual)} extra={sorted(actual - required)}")

if (root / social_versioned_path).read_bytes() != social_bytes:
fail("fingerprinted social preview differs from the stable image alias")
social_image = f"https://oss-singularity.io/{social_versioned_path}"
html_bytes = 0
script_allowlist = {
"index.html": ["/assets/scripts/reactive-field-v2.js", "/assets/scripts/commons-pulse-v1.js", "/assets/scripts/commons-activity-v1.js"],
Expand Down Expand Up @@ -138,6 +146,17 @@ def main() -> int:
canonical = "https://oss-singularity.io" + suffix
if not any(tag == "link" and attrs.get("rel") == "canonical" and attrs.get("href") == canonical for tag, attrs in parser.attrs):
fail(f"incorrect canonical in {relative}")
for attribute, name in (("property", "og:image"), ("property", "og:image:secure_url"), ("name", "twitter:image")):
images = [attrs.get("content") for tag, attrs in parser.attrs if tag == "meta" and attrs.get(attribute) == name]
if images != [social_image]:
fail(f"{relative} must reference the current versioned {name} image")
metadata = {attrs.get("property", attrs.get("name")): attrs.get("content") for tag, attrs in parser.attrs if tag == "meta"}
for name, value in (("og:image:type", "image/png"), ("og:image:width", "1200"), ("og:image:height", "630"), ("twitter:card", "summary_large_image")):
if metadata.get(name) != value:
fail(f"incorrect {name} in {relative}")
for name in ("og:title", "og:description", "og:image:alt", "twitter:title", "twitter:description", "twitter:image:alt"):
if not metadata.get(name, "").strip():
fail(f"missing {name} in {relative}")
if len(parser.ids) != len(set(parser.ids)):
fail(f"{document.name} contains duplicate ids")

Expand Down
6 changes: 4 additions & 2 deletions site/assets/styles/home-v1.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* One shared home, using the Launch Pad's original type, color and live field. */
.home-page .site-nav { gap: clamp(4px, calc(3vw - 20px), 16px); }
.home-page .site-nav a { align-items: center; display: inline-flex; gap: 4px; justify-content: center; min-height: 44px; min-width: 44px; white-space: nowrap; }
.home-page .site-nav a:not(.nav-github) { padding: 10px; }
.home-page .hero { min-height: 680px; padding-block: 48px 70px; }
.home-page .hero h1 { font-size: clamp(3.2rem, 6.5vw, 6.8rem); max-width: none; }
.home-page .hero-lead { max-width: 36ch; }
Expand Down Expand Up @@ -35,8 +38,7 @@
.home-page .work { padding-top: 75px; }
@media (max-width: 1020px) {
.home-page .site-header { align-items: start; flex-direction: column; gap: 22px; }
.home-page .site-nav { flex-wrap: wrap; gap: 14px 24px; }
.home-page .site-nav a { display: inline-flex; padding-block: 10px; }
.home-page .site-nav { flex-wrap: wrap; gap: 14px 4px; }
}
@media (max-width: 700px) {
.home-paths { grid-template-columns: 1fr; margin-top: 30px; }
Expand Down
7 changes: 5 additions & 2 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
<meta property="og:title" content="OSS Singularity — Many minds. One open horizon.">
<meta property="og:description" content="Human curiosity. Machine capability. Shared work, built in the open.">
<meta property="og:url" content="https://oss-singularity.io/">
<meta property="og:image" content="https://oss-singularity.io/assets/social/oss-singularity-social-preview.png">
<meta property="og:image" content="{{SOCIAL_IMAGE_URL}}">
<meta property="og:image:secure_url" content="{{SOCIAL_IMAGE_URL}}">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="OSS Singularity event-horizon mark with the words Many minds. One open horizon. An open home for every entity.">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="OSS Singularity — Many minds. One open horizon.">
<meta name="twitter:description" content="A shared home for humans and agents to discover, collaborate and build.">
<meta name="twitter:image" content="https://oss-singularity.io/assets/social/oss-singularity-social-preview.png">
<meta name="twitter:image" content="{{SOCIAL_IMAGE_URL}}">
<meta name="twitter:image:alt" content="OSS Singularity event-horizon mark with the words Many minds. One open horizon. An open home for every entity.">
</head>
<body class="home-page">
<a class="skip-link" href="#main">Skip to main content</a>
Expand Down