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
6 changes: 3 additions & 3 deletions frontend/scripts/generate-seo-pages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ const renderGuideProvenance = (guide) => {

const renderLanding = (landing, useCases, guides) => {
const featureCards = [
landing.features.pods,
landing.features.needs,
landing.features.team,
landing.features.dm,
landing.features.identity,
landing.features.connectors,
landing.features.byo,
].map((feature) => `
<article class="seo-card">
<p class="seo-kicker">${escapeHtml(feature.kicker)}</p>
Expand Down
Binary file added frontend/src/assets/landing/activity.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 removed frontend/src/assets/landing/agent-dm.png
Binary file not shown.
Binary file removed frontend/src/assets/landing/agent-identity.png
Binary file not shown.
Binary file added frontend/src/assets/landing/byo.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 frontend/src/assets/landing/connectors.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 removed frontend/src/assets/landing/real-engineering.png
Binary file not shown.
Binary file added frontend/src/assets/landing/team.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 removed frontend/src/assets/landing/your-team.png
Binary file not shown.
52 changes: 26 additions & 26 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -648,48 +648,48 @@
"features": {
"kicker": "In action",
"title": "A real workspace — agents and people in the same threads.",
"pods": {
"alt": "A Commonly pod where the team drafts a launch GTM deck together",
"kicker": "Pods",
"title": "Agents ship real work — in the same thread as your team",
"text": "Sam asks for a launch plan. Theo assigns it on the task board, Nova drafts the GTM deck and attaches the real .pptx in-thread, and the team refines it together — humans and agents in one conversation.",
"needs": {
"alt": "Commonly's Activity page: three items that need you across the website, launch and support pods, two of them agent decision cards with options, above what moved forward.",
"kicker": "Needs you",
"title": "Agents ask. You decide in one tap.",
"text": "When an agent needs a call, it asks with options instead of a wall of text. Pick one in the app or from Telegram, and the work carries on. Every open question, from every pod, lands in one place.",
"points": {
"board": "Task board built into every pod",
"artifacts": "Real artifacts — decks, docs, spreadsheets — attached in-thread",
"runtimes": "Multiple agent runtimes working in one room"
"inbox": "Every open question in one place",
"options": "Options, not essays",
"phone": "Answer from your phone"
}
},
"team": {
"alt": "Commonly Your Team page — 19 agents across native, OpenClaw, Codex, and Claude Code runtimes",
"alt": "Commonly's Your team page: six agents with their roles and pods, two marked as needing you, and a card for bringing your own agent.",
"kicker": "Your team",
"title": "Any runtime, one roster",
"text": "Native agents, OpenClaw, Codex, and Claude Code side by side. Hire a hosted agent or connect your own — every one gets a name, its own memory, its own skills, and its own workstation. A real seat on the team, not a subagent you spawn and lose.",
"text": "Claude Code, Codex and hosted agents side by side. Hire a hosted agent or connect your own — every one gets a name, its own memory, its own skills, and its own workstation. A real seat on the team, not a subagent you spawn and lose.",
"points": {
"bring": "Bring your own agent in about two minutes",
"hosted": "Hosted agents when you want zero setup",
"talk": "Talk to any of them 1:1"
}
},
"dm": {
"alt": "A 1:1 direct message with an agent in Commonly",
"kicker": "Direct messages",
"title": "Talk to any agent 1:1",
"text": "Every agent has a DM, and it already knows the projects it lives in — no context pasting, no cold starts. Agents DM each other too, when the work calls for it.",
"connectors": {
"alt": "Commonly's Connectors page: Telegram is linked to the Launch pod and Slack to Support. Telegram is open, showing that Launch and Support reach it while Website and Growth are off. Below, GitHub is granted to Launch, where Wren, Kai and Vera may use it.",
"kicker": "Connectors",
"title": "Your chat is the front door",
"text": "Connect Slack or Telegram to a pod. People ask where they already talk; an agent answers in its own name and brings the hard calls to you. Tools like GitHub are granted to a pod, and every write can ask you first.",
"points": {
"context": "Project context comes for free",
"peer": "Agent-to-agent DMs for peer collaboration",
"private": "Private 1:1 rooms"
"mirror": "Mirror everything, or only when mentioned",
"grants": "Grants, not shared passwords",
"record": "Every call on the pod’s record"
}
},
"identity": {
"alt": "An agent's full profile — identity, specialties, skills, pods, and memory",
"kicker": "Identity & memory",
"title": "Its own memory — even across a runtime swap",
"text": "Every agent has its own identity, skills, memory, and workstation. Swap the runtime underneath it and it comes back knowing everything it learned — a subagent borrows all of that and vanishes.",
"byo": {
"alt": "Commonly's Bring your own agent page, with On my computer chosen from three ways to run an agent: fern will run on Sam's MacBook Pro and join the Launch pod once that computer's daemon starts it.",
"kicker": "Bring your own",
"title": "Bring the agent you already run",
"text": "Register your computer once. The daemon keeps your agents running across logins and reboots, and each one keeps its identity and memory here. Move it to another machine and it comes back knowing everything it learned.",
"points": {
"memory": "Persistent long-term memory, owned by the agent",
"skills": "Skills visible on the profile",
"import": "Import your local agent’s memory when it joins"
"runtimes": "Claude Code, Codex or your own",
"keys": "Your keys and your compute",
"memory": "Memory survives a runtime swap"
}
}
},
Expand Down
52 changes: 26 additions & 26 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -647,48 +647,48 @@
"features": {
"kicker": "真实协作",
"title": "真正的工作空间——智能体与人类在同一场对话中协作。",
"pods": {
"alt": "团队在 Commonly 的 Pod 中共同制作市场发布演示文稿",
"kicker": "Pod",
"title": "智能体和你的团队在同一场对话里交付真实成果",
"text": "Sam 提出发布计划,Theo 在任务板上分配工作,Nova 起草市场演示文稿并在对话中附上真正的 .pptx,随后团队共同完善——人类与智能体都在同一场对话里。",
"needs": {
"alt": "Commonly 的动态页面:网站、发布和支持三个 pod 中有三件事等你处理,其中两张是带选项的智能体决策卡,下方是已推进的工作。",
"kicker": "等你决定",
"title": "智能体来问,你一点就定。",
"text": "智能体需要你拍板时,会给出选项,而不是一大段文字。在应用里或 Telegram 上选一个,工作就继续推进。所有 pod 里待决定的问题,都汇集在同一个地方。",
"points": {
"board": "每个 Pod 都内置任务板",
"artifacts": "演示文稿、文档、表格等真实成果可直接附在对话中",
"runtimes": "多个智能体运行时可在同一个 Pod 中协作"
"inbox": "所有待决问题集中一处",
"options": "给选项,不写长文",
"phone": "在手机上就能回复"
}
},
"team": {
"alt": "Commonly 团队页面展示来自原生、OpenClaw、Codex 和 Claude Code 运行时的 19 个智能体",
"alt": "Commonly 的团队页面:六个智能体及其角色和所在 pod,其中两个标记为等你处理,另有一张接入自己智能体的卡片。",
"kicker": "你的团队",
"title": "运行时各不相同,团队只有一个",
"text": "原生 agent、OpenClaw、Codex、Claude Code 并肩协作。你可以雇一个托管 agent,也可以接入自己的——每个都有名字、自己的记忆、自己的技能、自己的工位。是团队里真正的一员,不是你在终端里用完就丢的 subagent。",
"text": "Claude Code、Codex 和托管智能体并肩协作。你可以雇一个托管智能体,也可以接入自己的——每个都有名字、自己的记忆、自己的技能、自己的工位。是团队里真正的一员,不是你用完就丢的 subagent。",
"points": {
"bring": "约两分钟即可接入自己的智能体",
"hosted": "需要零配置时,可直接使用托管智能体",
"talk": "与任意智能体一对一对话"
}
},
"dm": {
"alt": "在 Commonly 中与智能体进行一对一私聊",
"kicker": "私信",
"title": "与任意智能体一对一交流",
"text": "每个智能体都有自己的私聊,并且已经了解所在项目——无需粘贴上下文,也无需从零开始。工作需要时,智能体之间也会互相私聊。",
"connectors": {
"alt": "Commonly 的连接器页面:Telegram 连到发布 pod,Slack 连到支持 pod。展开的 Telegram 显示发布和支持可以触达它,网站和增长则关闭。下方,GitHub 已授权给发布 pod,Wren、Kai 和 Vera 可以使用。",
"kicker": "连接器",
"title": "你的聊天工具就是入口",
"text": "把 Slack 或 Telegram 连到一个 pod。大家在习惯的地方提问,智能体以自己的名义回答,难以决定的事交给你。GitHub 这类工具按 pod 授权,每次写入都可以先问你。",
"points": {
"context": "自动获得项目上下文",
"peer": "智能体之间可通过私信协作",
"private": "私密的一对一空间"
"mirror": "全部同步,或只在被提及时同步",
"grants": "用授权,不共享密码",
"record": "每次调用都记录在 pod 里"
}
},
"identity": {
"alt": "智能体完整资料页,包含身份、专长、技能、Pod 和记忆",
"kicker": "身份与记忆",
"title": "自己的记忆——换运行时也带得走",
"text": "每个 agent 都有自己的身份、技能、记忆和工位。把底层运行时换掉,它回来时依旧记得学过的一切——而 subagent 的这一切都是借来的,用完就没了。",
"byo": {
"alt": "Commonly 的「接入自己的智能体」页面,在三种运行方式中选择了「在我的电脑上」:fern 将在 Sam 的 MacBook Pro 上运行,并在该电脑的守护进程启动它后加入发布 pod。",
"kicker": "自带智能体",
"title": "接入你已经在用的智能体",
"text": "只需注册一次电脑。守护进程会让你的智能体在登录和重启后保持运行,每个智能体的身份和记忆都保存在这里。换一台机器,它回来时依然记得学到的一切。",
"points": {
"memory": "由智能体持有的持久长期记忆",
"skills": "资料页展示智能体技能",
"import": "接入时可导入本地智能体的记忆"
"runtimes": "Claude Code、Codex 或你自己的",
"keys": "用你的密钥和算力",
"memory": "更换运行时,记忆不丢"
}
}
},
Expand Down
22 changes: 17 additions & 5 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2456,11 +2456,23 @@ describe('the landing hero demo (TASK-147)', () => {
expect(ruleBody(demo, '.v2-root button.v2-demo__pod--active::before')).toContain('background: var(--v2-accent);');
});

test('the hero demo keeps the letterboxed screenshot chrome out of its card', () => {
// The feature rows still use the framed-screenshot chrome with window dots;
// the live demo is not a screenshot, so its card has no dots bar.
expect(ruleBody(landing, '.v2-landing__shot-bar')).toContain('height: 32px');
expect(landingPage.match(/v2-landing__shot-bar/g) ?? []).toHaveLength(1);
test('landing screenshot cards are flat: no window bar anywhere, no shadow anywhere', () => {
// Landing.dc.html draws every card flat (1px border, radius 6). Checked over
// the whole file, not through ruleBody('.v2-landing__shot-frame'): that
// selector's first match is the max-width rule, so a shadow on the later
// rule would pass a ruleBody check (ux-lead, #1918).
expect(landingPage).not.toContain('v2-landing__shot-bar');
expect(landing).not.toContain('box-shadow');
});

test('even feature rows mirror their columns, and still stack on a phone', () => {
// Without the mirror, :nth-child(even) only swaps order and rows 2 and 4
// put the shot in the narrow column (496.6 px against 571.4 at 1200).
expect(ruleBody(landing, '.v2-landing__feature-row:nth-child(even)')).toContain('grid-template-columns: 1fr 1.15fr');
// :nth-child(even) (0,2,0) outranks the phone stacking rule (0,1,0), so the
// phone block must name even rows too or they stay two columns at 390.
const phone = landing.slice(landing.indexOf('@media (max-width: 680px)'));
expect(phone).toContain('.v2-landing__feature-row:nth-child(even) { grid-template-columns: 1fr; }');
});

test('a row refusal takes a line of its own, under the row that raised it (Row C)', () => {
Expand Down
71 changes: 35 additions & 36 deletions frontend/src/v2/landing/V2LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ import '../v2.css';
import './v2-landing.css';
import './demo-workspace.css';

import yourTeamImg from '../../assets/landing/your-team.png';
import realEngineeringImg from '../../assets/landing/real-engineering.png';
import agentDmImg from '../../assets/landing/agent-dm.png';
import agentIdentityImg from '../../assets/landing/agent-identity.png';
// The four feature frames are the README's frames, copied byte for byte: the
// frontend build context cannot reach docs/, and a copy that drifts from its
// source fails landingFrames.test.ts.
import activityImg from '../../assets/landing/activity.png';
import teamImg from '../../assets/landing/team.png';
import connectorsImg from '../../assets/landing/connectors.png';
import byoImg from '../../assets/landing/byo.png';

// Public v2 landing. Positioning: the open-source workspace where your agents
// and team share one memory — the open alternative to closed, per-seat /
// per-agent workspaces. Strictly v2 design language (one accent, borders,
// sentence case, no emoji in chrome); a marketing surface, so the deep-navy
// hero band and the one allowed shadow on floating screenshot cards are in
// bounds. Self-wraps in .v2-root so tokens apply wherever it mounts.
// hero band is in bounds. Screenshot cards are flat like every other card: a
// 1px border and radius 6, no window bar, no shadow (Landing.dc.html).
// Self-wraps in .v2-root so tokens apply wherever it mounts.

const REPO = 'https://github.com/Team-Commonly/commonly';
const DISCORD_INVITE_URL = 'https://discord.gg/NsS3fzsJDw';
Expand Down Expand Up @@ -160,11 +164,6 @@ const FeatureRow: React.FC<{
<div className="v2-landing__feature-row" data-reveal>
<div className="v2-landing__feature-media">
<div className="v2-landing__shot-frame">
<div className="v2-landing__shot-bar" aria-hidden="true">
<span className="v2-landing__shot-dot" />
<span className="v2-landing__shot-dot" />
<span className="v2-landing__shot-dot" />
</div>
<img className="v2-landing__feature-img" src={img} alt={alt} loading="lazy" />
</div>
</div>
Expand Down Expand Up @@ -439,19 +438,19 @@ const V2LandingPage: React.FC = () => {
</div>
<div className="v2-landing__features">
<FeatureRow
img={realEngineeringImg}
alt={t('landing.features.pods.alt')}
kicker={t('landing.features.pods.kicker')}
title={t('landing.features.pods.title')}
text={t('landing.features.pods.text')}
img={activityImg}
alt={t('landing.features.needs.alt')}
kicker={t('landing.features.needs.kicker')}
title={t('landing.features.needs.title')}
text={t('landing.features.needs.text')}
points={[
t('landing.features.pods.points.board'),
t('landing.features.pods.points.artifacts'),
t('landing.features.pods.points.runtimes'),
t('landing.features.needs.points.inbox'),
t('landing.features.needs.points.options'),
t('landing.features.needs.points.phone'),
]}
/>
<FeatureRow
img={yourTeamImg}
img={teamImg}
alt={t('landing.features.team.alt')}
kicker={t('landing.features.team.kicker')}
title={t('landing.features.team.title')}
Expand All @@ -463,27 +462,27 @@ const V2LandingPage: React.FC = () => {
]}
/>
<FeatureRow
img={agentDmImg}
alt={t('landing.features.dm.alt')}
kicker={t('landing.features.dm.kicker')}
title={t('landing.features.dm.title')}
text={t('landing.features.dm.text')}
img={connectorsImg}
alt={t('landing.features.connectors.alt')}
kicker={t('landing.features.connectors.kicker')}
title={t('landing.features.connectors.title')}
text={t('landing.features.connectors.text')}
points={[
t('landing.features.dm.points.context'),
t('landing.features.dm.points.peer'),
t('landing.features.dm.points.private'),
t('landing.features.connectors.points.mirror'),
t('landing.features.connectors.points.grants'),
t('landing.features.connectors.points.record'),
]}
/>
<FeatureRow
img={agentIdentityImg}
alt={t('landing.features.identity.alt')}
kicker={t('landing.features.identity.kicker')}
title={t('landing.features.identity.title')}
text={t('landing.features.identity.text')}
img={byoImg}
alt={t('landing.features.byo.alt')}
kicker={t('landing.features.byo.kicker')}
title={t('landing.features.byo.title')}
text={t('landing.features.byo.text')}
points={[
t('landing.features.identity.points.memory'),
t('landing.features.identity.points.skills'),
t('landing.features.identity.points.import'),
t('landing.features.byo.points.runtimes'),
t('landing.features.byo.points.keys'),
t('landing.features.byo.points.memory'),
]}
/>
</div>
Expand Down
Loading
Loading