The official site for the Open Tech Foundation, built with our own framework, AFW.
Live site: https://opentechf.org/
Advocacy • Collaboration • Open Innovation
We are a community-driven, non-profit initiative advancing open technologies — including open source, open data, open standards, and open protocols — for the public good.
- AFW (
@opentf/afw) — zero-VDOM framework; JSX compiles to native DOM @opentf/afw-docs— supplies the navbar, footer, and the light/dark theme tokens, so this site and the framework site share one visual language- Tailwind CSS v4 — utilities, on top of the theme's
--afw-*design tokens - Cloudflare Workers — static asset hosting
Every route is pre-rendered to static HTML (esdev build via the site-ssg target), so
the pages carry their full content and <head> before any JavaScript runs.
app/
layout.jsx root layout: navbar, dark footer, site-wide SEO + JSON-LD
page.jsx / home: mission, principles, featured projects
projects/
page.jsx /projects full index, grouped by category
[slug]/ /projects/<id> one page per project (pre-rendered)
proposals/
software/ /proposals/software
hardware/ /proposals/hardware
specifications/
software/ /specifications/software
hardware/ /specifications/hardware
get-involved/ /get-involved how to contribute
contact/ /contact contact + security disclosure
404.jsx the 404 page (the root layout wraps it — see the file)
components/ shared UI (cards, banners, division tabs, icons, mobile nav)
data/
projects.js ← SINGLE SOURCE OF TRUTH for everything project-related
principles.js the eight principles (rendered on the home page)
global.css Tailwind entry, brand + accessible accent tokens, footer tokens
afw.config.js site URL, navbar links, brand
esdev.json build config: @opentf/esdev-plugin-afw + the SSG site target
entry.js client bootstrap (mountApp)
index.html app shell + the no-flash theme bootstrap
install.sh ES-Runtime's esdev installer (copied verbatim from upstream)
scripts/build-site.sh Cloudflare Workers Builds entry (see Deploy)
public/img/banners/ one SVG per project, drawn to show what that project does
Everything the site says about projects lives in this one file, and every page renders from it: the software / hardware divisions, the categories, the five-state status scale (with the text the index legend shows), the projects, the proposals, and the specifications. Counts, tabs, category sections, and the per-project pages are all derived — there is nothing to keep in sync by hand.
Append an entry to projects:
division—"software"or"hardware". Decides which tab it appears under. Software is the default active tab.category—web,web-development,developer-tools,data, ormedia. The index groups projects under these headings; the home page shows a flat grid of the featured ones with a category tag on each card.featured— settrueand it appears on the home page. This is the only edit needed to promote or demote a project.status— earliest first:"draft","under-development","alpha","beta","stable". The index legend renders fromSTATUS, so the badge and its explanation cannot drift apart.href— the GitHub repository.site— the project's own site, if it has one (e.g.https://tsr.opentechf.org); it becomes a Website link on the project page.license— the SPDX id as published on the repository; check it there rather than assuming. The projects are not on a single licence (currently 7 MIT, 4 Apache-2.0, 2 AGPL-3.0), and it is shown on every card and project page.banner—"/img/banners/<id>.svg". Every project has one, drawn to show what it actually does. A project added without one falls back to a generated panel in the same visual language until its own banner is drawn.
A page at /projects/<id> is generated automatically — the SSG crawler enumerates the
projects array, so no route needs adding.
Both are documents with the same shape (id, division, title, status, summary,
href) and both render through the same components; they differ only in the wording held
in DOC_KINDS. Add to the proposals or specifications array and the counts, cards,
and index pages follow. Statuses are the Proposals repo's own scale: Draft, Review,
Accepted, Rejected, Superseded.
A specification is a document, not a project entry — it graduates into projects if and
when it grows an implementation worth listing on its own.
pnpm install
pnpm dev # esdev startpnpm build # esdev build — client bundle + SSG passOutputs to dist/ — one pre-rendered .html per route, plus sitemap.xml,
robots.txt, 404.html, and llms.txt.
Production deploys run through Cloudflare Workers Builds (git-connected), using
scripts/build-site.sh — the same pattern as the ES-Runtime
and AFW sites:
- Root directory: (repository root)
- Build command:
bash scripts/build-site.sh - Deploy command:
npx wrangler deploy - Variables:
SKIP_DEPENDENCY_INSTALL=1(the script installs), plus the Cloudflare API token Workers Builds provides automatically
The script fetches full git history when the clone is shallow (so "Last updated" dates
and sitemap.xml's <lastmod> come from real commit history), installs esdev through
this checkout's install.sh (the @opentf/afw-cli package is the build library, not
the esdev CLI), pins pnpm from packageManager, and runs
pnpm install --frozen-lockfile before the build. install.sh is copied verbatim from
ES-Runtime; re-copy it to pick up upstream fixes.
Local one-shot deploy (requires npx wrangler login or CLOUDFLARE_API_TOKEN):
pnpm deploy # pnpm run build && wrangler deploypnpm preview- Colour.
--afw-accent(the brand orange) is a fill colour. For accent-coloured text use--accent-text, for text sitting on an accent fill use--accent-on, and for muted text on an accent-soft surface use--text-muted-on-accent. The raw orange fails WCAG AA as text; see the comment inglobal.cssfor the measured ratios. Every text/background pair on the site has been checked against AA in both themes. - The footer is dark in both themes by design, via its own
--footer-*tokens scoped to.site-footer. - Tailwind's
dark:variant is bound to[data-theme="dark"], notprefers-color-scheme— the theme toggle writes that attribute, and the two disagree whenever a visitor's explicit choice differs from their OS setting. - Third-party requests. There are none — no CDN fonts, no analytics, no remote assets. The Privacy principle applies to this site too.
- The 404 page is wrapped by the root layout (AFW applies the layout chain to the
notFoundfallback), so it carries no navbar or footer of its own. - One
returnper component. The compiler builds each component into a single element factory; a secondreturnrenders correctly under SSG but leaves the custom element empty once the client hydrates. Use a ternary, not an early return. - Whole-card links use the
card-linkoverlay (global.css) rather than wrapping the card in an<a>— an<a>cannot nest inside an<a>. Anything that must stay separately clickable inside a card needsrelative z-10. - Language marks are single-path glyphs from simple-icons (SVG files are CC0; the marks remain their owners' trademarks, used only to identify a project's language).