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
82 changes: 82 additions & 0 deletions frontend/design-system/preview/og-card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<!--
Link-preview card (og:image + twitter:image) — the source of
frontend/public/og-card.png. Edit this file, then regenerate from frontend/
(after npm ci, so the @fontsource files below resolve):

npx playwright screenshot --viewport-size=1200,630 --wait-for-timeout=1500 \
"file://$PWD/design-system/preview/og-card.html" public/og-card.png

Then bump ?v=N on og:image and twitter:image in index.html and
scripts/generate-seo-pages.mjs so scrapers re-fetch; og-card.test.mjs
checks that the three URLs agree and the PNG stays 1200x630.

Signal front door (docs/design/signal-identity.md): a cobalt block, the
display line in Bricolage, one sentence at 86% white, one mono footer. No
shadow, no gradient, no green.
-->
<html lang="en"><head><meta charset="utf-8"><title>og-card</title>
<style>
@font-face { font-family: "Bricolage Grotesque Variable"; font-weight: 200 800;
src: url("../../node_modules/@fontsource-variable/bricolage-grotesque/files/bricolage-grotesque-latin-wght-normal.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Sans"; font-weight: 400;
src: url("../../node_modules/@fontsource/ibm-plex-sans/files/ibm-plex-sans-latin-400-normal.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-weight: 500;
src: url("../../node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2") format("woff2"); }

html, body { margin: 0; }
.card {
box-sizing: border-box;
width: 1200px; height: 630px;
padding: 64px 80px 56px;
display: flex; flex-direction: column;
background: #1d3fd1;
color: #ffffff;
-webkit-font-smoothing: antialiased;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand svg { width: 44px; height: 44px; color: #ffffff; }
.brand-name {
font-family: "Bricolage Grotesque Variable", sans-serif;
font-size: 32px; font-weight: 800; letter-spacing: -0.03em;
}
.title {
margin: auto 0 0;
max-width: 1000px;
font-family: "Bricolage Grotesque Variable", sans-serif;
font-size: 72px; line-height: 76px; font-weight: 800; letter-spacing: -0.03em;
text-wrap: balance;
}
.sub {
margin: 28px 0 0;
max-width: 920px;
font-family: "IBM Plex Sans", sans-serif;
font-size: 26px; line-height: 36px; font-weight: 400;
color: rgba(255, 255, 255, 0.86);
text-wrap: pretty;
}
.foot {
margin-top: 40px; padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.28);
display: flex; justify-content: space-between;
font-family: "IBM Plex Mono", monospace;
font-size: 20px; font-weight: 500;
color: rgba(255, 255, 255, 0.86);
}
</style></head><body>
<div class="card">
<div class="brand">
<!-- frontend/design-system/assets/commonly-mark.svg, inlined so currentColor is white -->
<svg viewBox="0 0 64 64" aria-hidden="true">
<path d="M 50 17.7 A 22 22 0 1 0 50 46.3" fill="none" stroke="currentColor" stroke-width="9" stroke-linecap="round"></path>
<circle cx="25" cy="32" r="2.4" fill="currentColor"></circle>
<circle cx="32" cy="32" r="2.4" fill="currentColor"></circle>
<circle cx="39" cy="32" r="2.4" fill="currentColor"></circle>
</svg>
<span class="brand-name">Commonly</span>
</div>
<h1 class="title">One pod for your team and the agents each of you runs.</h1>
<p class="sub">Open source (Apache-2.0). People and their own agents share the same conversations and one task board.</p>
<div class="foot"><span>commonly.me</span><span>github.com/Team-Commonly/commonly</span></div>
</div>
</body></html>
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
<meta property="og:url" content="https://commonly.me/" />
<meta property="og:title" content="Commonly — chat with your agents, ship real work" />
<meta property="og:description" content="The open-source workspace where agents and teammates share one project memory — so nothing gets re-explained. Any runtime, your infra, no per-agent fees." />
<meta property="og:image" content="https://commonly.me/og-card.png?v=2" />
<meta property="og:image" content="https://commonly.me/og-card.png?v=3" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@sam_commonly" />
<meta name="twitter:title" content="Commonly — chat with your agents, ship real work" />
<meta name="twitter:description" content="The open-source workspace where you and your agents share one project memory — so nothing gets re-explained. Any runtime, your infra, no per-agent fees." />
<meta name="twitter:image" content="https://commonly.me/og-card.png?v=2" />
<meta name="twitter:image" content="https://commonly.me/og-card.png?v=3" />
<!-- SEO_METADATA_END -->
<!-- Progressive-enhancement gate for the prerendered SEO page below.
The #seo-page markup ships inside #root on EVERY route, so
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"start": "vite",
"build": "vite build && node scripts/generate-seo-pages.mjs",
"preview": "vite preview",
"test": "jest --watchAll=false --forceExit && node --test scripts/generate-seo-pages.test.mjs scripts/nginx-routing.test.mjs scripts/signup-source.test.mjs scripts/ui-card-catalogue.test.mjs",
"test": "jest --watchAll=false --forceExit && node --test scripts/generate-seo-pages.test.mjs scripts/nginx-routing.test.mjs scripts/signup-source.test.mjs scripts/ui-card-catalogue.test.mjs scripts/og-card.test.mjs",
"test:coverage": "jest --coverage --watchAll=false --forceExit",
"typecheck": "tsc --noEmit",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
Expand Down
Binary file modified frontend/public/og-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions frontend/scripts/generate-seo-pages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -419,15 +419,15 @@ const metadata = ({ title, description, path, schema, faqSchema, ogType = 'websi
<meta property="og:url" content="${url}" />
<meta property="og:title" content="${escapeHtml(title)}" />
<meta property="og:description" content="${escapeHtml(description)}" />
<meta property="og:image" content="${siteUrl}/og-card.png?v=2" />
<meta property="og:image" content="${siteUrl}/og-card.png?v=3" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
${articleDates}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@sam_commonly" />
<meta name="twitter:title" content="${escapeHtml(title)}" />
<meta name="twitter:description" content="${escapeHtml(description)}" />
<meta name="twitter:image" content="${siteUrl}/og-card.png?v=2" />
<meta name="twitter:image" content="${siteUrl}/og-card.png?v=3" />
<script type="application/ld+json">${structuredData}</script>${faqSchema ? `
<script type="application/ld+json" id="${faqJsonLdId}">${jsonLd(faqSchema)}</script>` : ''}`;
};
Expand Down
30 changes: 30 additions & 0 deletions frontend/scripts/og-card.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';

const read = (path) => readFile(new URL(path, import.meta.url));
const versions = (text) => [...text.matchAll(/og-card\.png\?v=(\d+)/g)].map((m) => m[1]);

// The card is regenerated from design-system/preview/og-card.html; scrapers cache
// by URL, so every og:image and twitter:image must carry the same bumped ?v=N.
test('index.html and the SEO generator point at the same og-card version', async () => {
const index = versions(String(await read('../index.html')));
const seo = versions(String(await read('./generate-seo-pages.mjs')));
assert.equal(index.length, 2, 'index.html: og:image + twitter:image');
assert.equal(seo.length, 2, 'generate-seo-pages.mjs: og:image + twitter:image');
assert.deepEqual(new Set([...index, ...seo]), new Set(['3']));
});

test('og-card.png stays 1200x630', async () => {
const png = await read('../public/og-card.png');
assert.equal(png.toString('ascii', 12, 16), 'IHDR');
assert.equal(png.readUInt32BE(16), 1200);
assert.equal(png.readUInt32BE(20), 630);
});

test('the committed card source carries the D129 copy and none of the ruled-out claims', async () => {
const html = String(await read('../design-system/preview/og-card.html'));
assert.match(html, /One pod for your team and the agents each of you runs\./);
assert.match(html, /Open source \(Apache-2\.0\)\. People and their own agents share the same conversations and one task board\./);
for (const banned of [/one thread/i, /any runtime/i, /\$\d/]) assert.doesNotMatch(html, banned);
});
Loading