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
10 changes: 7 additions & 3 deletions apps/presentation/site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ Keep source-document exports, private references, and unreviewed media outside
the public tree. Follow `docs/development/design.md` and obtain first-screen
review before changing the Blog or its homepage entry.

The first-run CTA opens one setup dialog with a recommended Agent path and a
The header and primary homepage CTA download the latest Apple Silicon Mac App
preview through the release's stable `LoopX.app.zip` asset name. The homepage
states the Python 3.11+ prerequisite and links to the desktop first-launch guide.
The adjacent first-run CTA opens one setup dialog with an Agent path and a
manual Shell path. The Agent option copies the localized, public-safe setup
contract; the Shell option copies the commands shown in the terminal section.
The `See in action` CTA scrolls to the public evidence showcase and restarts the
Issue Fix replay.
The `See in action` link scrolls to the public evidence showcase and restarts
the Issue Fix replay.

The homepage control-plane diagrams are synthetic UI. Finite, tabbed terminal
replays summarize two public README trajectories; they are curated projections,
Expand Down Expand Up @@ -122,6 +125,7 @@ in tests; production collection still needs a real post-deployment check.
| `page_view` | A canonical public page was opened; hash and locale-only changes do not add views. |
| `setup_open` | Opened the homepage setup dialog. |
| `setup_copy` | Successfully copied an Agent prompt or Shell setup command (`setup_method`). |
| `desktop_download` | Clicked an official Mac App download link (`platform`, `artifact`); this records intent, not a completed download. |
| `showcase_open` | Followed the homepage demonstration CTA. |
| `github_click` | Clicked a repository or DSH plugin link (`destination`). |
| `docs_open` | Followed a link into documentation. |
Expand Down
5 changes: 5 additions & 0 deletions apps/presentation/site/scripts/analytics-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@
const anchor = target?.closest("a[href]");
if (!anchor) return;
const url = new URL(anchor.href);
if (marked === "desktop_download" && url.hostname === "github.com" &&
url.pathname === "/loopx-project/loopx/releases/latest/download/LoopX.app.zip") {
event("desktop_download", { platform: "macos", artifact: "app_zip" });
return;
}
if (url.hostname === "github.com" && /^\/(?:loopx-project|huangruiteng)\/loopx(?:\/|$)/.test(url.pathname)) {
event("github_click", { destination: url.pathname.includes("packages/dsh-loopx-plugin") ? "dsh_plugin" : "repository" });
} else if (url.origin === location.origin && url.pathname.startsWith("/loopx/docs/") && url.pathname !== location.pathname) {
Expand Down
4 changes: 4 additions & 0 deletions apps/presentation/site/scripts/analytics-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,15 @@ try {
window.dispatchEvent(new CustomEvent("loopx:setup-copy", { detail: "never-send-this" }));
});
await page.getByRole("button", { name: "Close setup", exact: true }).click();
await page.evaluate(() => document.querySelector(".hero-download")?.addEventListener("click", (event) => event.preventDefault()));
await page.locator(".hero-download").click();
await page.locator(".language-toggle").click();
const rows = await page.evaluate(() => window.dataLayer.map((args) => [...args]));
assert.equal(rows.filter((r) => r[0] === "event" && r[1] === "page_view").length, 1);
assert.equal(rows.filter((r) => r[0] === "event" && r[1] === "setup_open").length, 1);
assert.equal(rows.filter((r) => r[0] === "event" && r[1] === "setup_copy").length, 1);
assert.equal(rows.filter((r) => r[0] === "event" && r[1] === "desktop_download").length, 1);
assert.equal(rows.filter((r) => r[0] === "event" && r[1] === "github_click").length, 0, "Mac download is not a repository visit");
assert(!JSON.stringify(rows).includes("never-send-this") && !JSON.stringify(rows).includes("private-task"));
assert.equal(rows.find((r) => r[0] === "config")[2].allow_google_signals, false);
await page.evaluate(() => {
Expand Down
45 changes: 31 additions & 14 deletions apps/presentation/site/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ChevronRight,
Clipboard,
Code2,
Download,
ExternalLink,
FileCheck2,
GitBranch,
Expand Down Expand Up @@ -60,6 +61,9 @@ cd /path/to/your-project
loopx connect
loopx status`;

const macAppDownloadUrl = "https://github.com/loopx-project/loopx/releases/latest/download/LoopX.app.zip";
const macAppGuideUrl = "https://github.com/loopx-project/loopx/blob/main/apps/desktop/loopx-control-plane/README.md";

const content = {
en: {
nav: ["Product", "Workflow", "Learn"],
Expand All @@ -71,6 +75,12 @@ const content = {
},
body: "Turn complex work into durable goals, coordinated tasks, and reviewable results. LoopX keeps your agents moving across sessions. You keep the judgment.",
copy: "Get started",
mac: {
download: "Download for Mac",
compact: "Mac App",
note: "Apple Silicon preview (.app.zip) · Python 3.11+ required.",
guide: "Install and first launch",
},
setup: {
eyebrow: "Set up LoopX",
title: "Choose how you want to start.",
Expand Down Expand Up @@ -184,6 +194,12 @@ const content = {
},
body: "把复杂工作变成持久的目标、协同的任务和可审查的成果。跨越会话,Agent 接着做;关键判断,始终由你掌握。",
copy: "开始使用",
mac: {
download: "下载 Mac App",
compact: "Mac App",
note: "Apple Silicon 预览版(.app.zip)· 需要 Python 3.11+。",
guide: "安装与首次启动",
},
setup: {
eyebrow: "设置 LoopX",
title: "选择你的开始方式。",
Expand Down Expand Up @@ -303,14 +319,6 @@ function ProductMark() {
);
}

function GitHubIcon() {
return (
<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M12 .7a11.5 11.5 0 0 0-3.64 22.4c.58.1.79-.25.79-.56v-2.2c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.17.08 1.78 1.2 1.78 1.2 1.03 1.78 2.71 1.27 3.37.97.1-.75.4-1.27.74-1.56-2.57-.29-5.27-1.29-5.27-5.69 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.47.11-3.05 0 0 .97-.31 3.17 1.18A11 11 0 0 1 12 6.13c.98 0 1.95.13 2.87.39 2.2-1.49 3.17-1.18 3.17-1.18.63 1.58.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.42-2.71 5.39-5.29 5.68.42.36.79 1.06.79 2.14v3.24c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .7Z" />
</svg>
);
}

function StatusPill({
tone,
children,
Expand Down Expand Up @@ -795,6 +803,7 @@ export function App() {
<a href="#explore">{language === "zh" ? "探索" : "Explore"}</a>
<a href={`${basePath}blog/${language === "zh" ? "zh/" : ""}`}>Blog</a>
<a href={`${basePath}docs/`}>Docs</a>
<a href="https://github.com/loopx-project/loopx">GitHub</a>
</nav>
<div className="header-actions">
<button
Expand All @@ -804,9 +813,10 @@ export function App() {
>
{language === "en" ? "中文" : "EN"}
</button>
<a className="github-button" href="https://github.com/loopx-project/loopx">
<GitHubIcon />
GitHub
<a className="header-download" data-analytics-event="desktop_download" href={macAppDownloadUrl} aria-label={copy.mac.download} referrerPolicy="strict-origin-when-cross-origin">
<Download size={16} aria-hidden="true" />
<span className="header-download-full">{copy.mac.download}</span>
<span className="header-download-compact">{copy.mac.compact}</span>
</a>
<button
className="menu-button"
Expand Down Expand Up @@ -859,16 +869,23 @@ export function App() {
</h1>
<p className="hero-body">{copy.body}</p>
<div className="hero-actions">
<button className="button button-primary" data-analytics-event="setup_open" type="button" onClick={() => setSetupOpen(true)}>
<a className="button button-primary hero-download" data-analytics-event="desktop_download" href={macAppDownloadUrl} referrerPolicy="strict-origin-when-cross-origin">
<Download size={16} aria-hidden="true" />
{copy.mac.download}
</a>
<button className="button button-secondary" data-analytics-event="setup_open" type="button" onClick={() => setSetupOpen(true)}>
<span>{copy.copy}</span>
<ArrowRight className="button-trailing-icon" size={15} />
</button>
<a className="button button-secondary" data-analytics-event="showcase_open" href="#showcases" onClick={showTerminalReplay}>
<a className="hero-demo" data-analytics-event="showcase_open" href="#showcases" onClick={showTerminalReplay}>
<Play size={15} fill="currentColor" />
{copy.demo}
</a>
</div>
<p className="hero-footnote">{language === "en" ? "Open source. Runs locally. Works with your agents." : "开源 · 本地运行 · 接入你正在使用的 Agent"}</p>
<p className="hero-footnote">
{copy.mac.note}{" "}
<a href={macAppGuideUrl} target="_blank" rel="noopener" referrerPolicy="strict-origin-when-cross-origin">{copy.mac.guide} <ExternalLink size={12} aria-hidden="true" /></a>
</p>
</div>
<div className="hero-system">
<div className="runtime-rail" aria-label={language === "en" ? "Explore agent integrations" : "查看 Agent 集成"}>
Expand Down
63 changes: 60 additions & 3 deletions apps/presentation/site/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ button {
font-size: 13px;
}

.github-button {
.header-download {
display: inline-flex;
align-items: center;
gap: 8px;
Expand All @@ -174,11 +174,15 @@ button {
transition: background 160ms ease, transform 160ms ease;
}

.github-button:hover {
.header-download:hover {
background: #333;
transform: translateY(-1px);
}

.header-download-compact {
display: none;
}

.menu-button {
display: none;
width: 40px;
Expand Down Expand Up @@ -307,6 +311,15 @@ body[data-language="zh"] .hero h1 {
line-height: 1.7;
}

.hero-footnote a {
display: inline-flex;
align-items: center;
gap: 3px;
color: var(--ink);
text-decoration: underline;
text-underline-offset: 3px;
}

.hero-system {
position: relative;
z-index: 3;
Expand Down Expand Up @@ -371,6 +384,32 @@ body[data-language="zh"] .hero h1 {
margin-top: 32px;
}

.hero-demo {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 44px;
padding: 0 8px;
color: var(--body);
font-size: 13px;
font-weight: 500;
}

.hero-demo:hover {
color: var(--ink);
text-decoration: underline;
text-underline-offset: 4px;
}

.header-download:focus-visible,
.hero-actions a:focus-visible,
.hero-actions button:focus-visible,
.hero-footnote a:focus-visible {
outline: 2px solid var(--blue);
outline-offset: 3px;
}

.button {
display: inline-flex;
align-items: center;
Expand Down Expand Up @@ -1768,10 +1807,18 @@ body.dialog-open {
height: 64px;
}

.github-button {
.header-download {
padding: 0 12px;
}

.header-download-full {
display: none;
}

.header-download-compact {
display: inline;
}

.hero {
min-height: 0;
padding: 48px 0 56px;
Expand All @@ -1794,6 +1841,10 @@ body.dialog-open {
flex-direction: column;
}

.hero-demo {
align-self: flex-start;
}

.button {
width: 100%;
}
Expand Down Expand Up @@ -2019,6 +2070,12 @@ body.dialog-open {
}
}

@media (max-width: 359px) {
.header-download {
display: none;
}
}

@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
Expand Down
Loading