mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
feat(website): show sponsors on paseo.sh and at the top of the README (#6956)
This commit is contained in:
+13
-4
@@ -53,6 +53,17 @@
|
||||
- **クロスデバイス:** iOS、Android、デスクトップ、Web、CLI に対応。机で作業を始め、スマートフォンで確認し、ターミナルから自動化できます。
|
||||
- **プライバシー優先:** Paseo にはテレメトリー・トラッキング・強制ログインは一切ありません。
|
||||
|
||||
## スポンサー
|
||||
|
||||
<!-- Sponsors: one cell per sponsor in the order of packages/website/src/data/sponsors.ts, then "become a sponsor" cells up to three. -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="33%"><a href="https://hoplite.sh/?utm_source=paseo&utm_medium=readme&utm_campaign=sponsor"><picture><source media="(prefers-color-scheme: dark)" srcset="packages/website/public/sponsors/hoplite-on-dark.svg"><img src="packages/website/public/sponsors/hoplite-on-light.svg" alt="Hoplite" height="40"></picture></a><br><sub>Cloud coding agents</sub></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Paseo のスポンサーになる</a></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Paseo のスポンサーになる</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## はじめかた
|
||||
|
||||
Paseo はコーディングエージェントを管理するローカルサーバー(デーモン)を起動します。デスクトップアプリ・モバイルアプリ・Web アプリ・CLI などのクライアントがこのデーモンに接続します。
|
||||
@@ -151,11 +162,9 @@ npm run build:server
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## スポンサー
|
||||
## Paseo を支援する
|
||||
|
||||
Paseo は一人で開発しており、使ってくれる人たちの支援で成り立っています。[GitHub Sponsors](https://github.com/sponsors/boudra) で支援できます。企業として毎月[スポンサー](https://paseo.sh/sponsor#spot)になっていただくと、ロゴをここと paseo.sh のホームページに掲載します。
|
||||
|
||||
<!-- Sponsor logos go here, in the same order as packages/website/src/data/sponsors.ts -->
|
||||
Paseo は一人で開発しており、使ってくれる人たちの支援で成り立っています。[GitHub Sponsors](https://github.com/sponsors/boudra) で支援できます。企業として毎月[スポンサー](https://paseo.sh/sponsor#spot)になっていただくと、ロゴをこの README の冒頭と、paseo.sh のトップページおよびドキュメントに掲載します。
|
||||
|
||||
## 関連プロジェクト
|
||||
|
||||
|
||||
+13
-4
@@ -47,6 +47,17 @@
|
||||
- **여러 기기 지원:** iOS, Android, 데스크톱, 웹, CLI를 지원합니다. 데스크톱에서 시작해 휴대폰으로 확인하고 터미널에서 자동화할 수 있습니다.
|
||||
- **개인정보 보호 우선:** Paseo는 텔레메트리, 추적, 강제 로그인을 사용하지 않습니다.
|
||||
|
||||
## 스폰서
|
||||
|
||||
<!-- Sponsors: one cell per sponsor in the order of packages/website/src/data/sponsors.ts, then "become a sponsor" cells up to three. -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="33%"><a href="https://hoplite.sh/?utm_source=paseo&utm_medium=readme&utm_campaign=sponsor"><picture><source media="(prefers-color-scheme: dark)" srcset="packages/website/public/sponsors/hoplite-on-dark.svg"><img src="packages/website/public/sponsors/hoplite-on-light.svg" alt="Hoplite" height="40"></picture></a><br><sub>Cloud coding agents</sub></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Paseo 스폰서 되기</a></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Paseo 스폰서 되기</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 시작하기
|
||||
|
||||
Paseo는 코딩 에이전트를 관리하는 로컬 서버인 데몬을 실행합니다. 데스크톱 앱, 모바일 앱, 웹 앱, CLI 같은 클라이언트가 이 데몬에 연결합니다.
|
||||
@@ -161,11 +172,9 @@ npm run build:server
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## 스폰서
|
||||
## Paseo 후원하기
|
||||
|
||||
Paseo는 한 사람이 개발하며, 사용하는 분들의 후원으로 운영됩니다. [GitHub Sponsors](https://github.com/sponsors/boudra)로 후원할 수 있습니다. 회사는 매월 [Paseo를 후원](https://paseo.sh/sponsor#spot)하고 로고를 이곳과 paseo.sh 홈페이지에 게재할 수 있습니다.
|
||||
|
||||
<!-- Sponsor logos go here, in the same order as packages/website/src/data/sponsors.ts -->
|
||||
Paseo는 한 사람이 개발하며, 사용하는 분들의 후원으로 운영됩니다. [GitHub Sponsors](https://github.com/sponsors/boudra)로 후원할 수 있습니다. 회사는 매월 [Paseo를 후원](https://paseo.sh/sponsor#spot)하고 로고를 이 README 상단과 paseo.sh 홈페이지 및 문서에 게재할 수 있습니다.
|
||||
|
||||
## 관련 프로젝트
|
||||
|
||||
|
||||
@@ -52,6 +52,17 @@ Paseo is an open source agentic development environment for desktop, mobile, web
|
||||
|
||||
[Run parallel tasks in Paseo](https://paseo.sh/docs/parallel-development): start agents in separate worktrees, review their diffs, run each app, and check it in the built-in browser.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<!-- Sponsors: one cell per sponsor in the order of packages/website/src/data/sponsors.ts, then "become a sponsor" cells up to three. -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="33%"><a href="https://hoplite.sh/?utm_source=paseo&utm_medium=readme&utm_campaign=sponsor"><picture><source media="(prefers-color-scheme: dark)" srcset="packages/website/public/sponsors/hoplite-on-dark.svg"><img src="packages/website/public/sponsors/hoplite-on-light.svg" alt="Hoplite" height="40"></picture></a><br><sub>Cloud coding agents</sub></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Become a sponsor</a></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">Become a sponsor</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Plugins
|
||||
|
||||
Plugins run on the daemon and show up in every client you connect, with the same UI on desktop, web,
|
||||
@@ -207,11 +218,9 @@ npm run build:server
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## Sponsors
|
||||
## Support Paseo
|
||||
|
||||
Paseo is an independent project used by tens of thousands of developers daily, built by one person and funded by the people who use it. Support the work on [GitHub Sponsors](https://github.com/sponsors/boudra). Companies can [sponsor Paseo](https://paseo.sh/sponsor#spot) monthly and have their logo shown here and on the paseo.sh homepage.
|
||||
|
||||
<!-- Sponsor logos go here, in the same order as packages/website/src/data/sponsors.ts -->
|
||||
Paseo is an independent project used by tens of thousands of developers daily, built by one person and funded by the people who use it. Support the work on [GitHub Sponsors](https://github.com/sponsors/boudra). Companies can [sponsor Paseo](https://paseo.sh/sponsor#spot) monthly and have their logo shown at the top of this README and on the paseo.sh homepage and docs.
|
||||
|
||||
## Related projects
|
||||
|
||||
|
||||
+13
-4
@@ -53,6 +53,17 @@
|
||||
- **跨设备:** 支持 iOS、Android、桌面端、Web 和 CLI。在桌前开始工作,用手机查看进度,也可以从终端脚本化操作。
|
||||
- **隐私优先:** Paseo 没有遥测、追踪,也不会强制登录。
|
||||
|
||||
## 赞助商
|
||||
|
||||
<!-- Sponsors: one cell per sponsor in the order of packages/website/src/data/sponsors.ts, then "become a sponsor" cells up to three. -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="33%"><a href="https://hoplite.sh/?utm_source=paseo&utm_medium=readme&utm_campaign=sponsor"><picture><source media="(prefers-color-scheme: dark)" srcset="packages/website/public/sponsors/hoplite-on-dark.svg"><img src="packages/website/public/sponsors/hoplite-on-light.svg" alt="Hoplite" height="40"></picture></a><br><sub>Cloud coding agents</sub></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">成为 Paseo 赞助商</a></td>
|
||||
<td align="center" width="33%"><a href="https://paseo.sh/sponsor">成为 Paseo 赞助商</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 快速开始
|
||||
|
||||
Paseo 会运行一个名为 daemon 的本地服务,用来管理你的 coding agents。桌面 app、移动 app、Web app 和 CLI 等客户端都会连接到它。
|
||||
@@ -151,11 +162,9 @@ npm run build:server
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## 赞助
|
||||
## 支持 Paseo
|
||||
|
||||
Paseo 由一个人开发,靠使用它的人来资助。你可以通过 [GitHub Sponsors](https://github.com/sponsors/boudra) 支持这项工作。公司也可以按月[赞助 Paseo](https://paseo.sh/sponsor#spot),logo 会展示在这里和 paseo.sh 首页上。
|
||||
|
||||
<!-- Sponsor logos go here, in the same order as packages/website/src/data/sponsors.ts -->
|
||||
Paseo 由一个人开发,靠使用它的人来资助。你可以通过 [GitHub Sponsors](https://github.com/sponsors/boudra) 支持这项工作。公司也可以按月[赞助 Paseo](https://paseo.sh/sponsor#spot),logo 会展示在本 README 顶部,以及 paseo.sh 的首页和文档中。
|
||||
|
||||
## 相关项目
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
|
||||
const PAGES = ["/", "/docs/connectivity", "/changelog"];
|
||||
|
||||
for (const path of PAGES) {
|
||||
test(`${path} shows the sponsors and invites the next one`, async ({ page }) => {
|
||||
await page.goto(path);
|
||||
|
||||
const sponsors = page.getByRole("region", { name: /^(Sponsors|Sponsored by)$/ });
|
||||
await expect(sponsors.getByRole("link", { name: "Hoplite" })).toHaveAttribute(
|
||||
"href",
|
||||
"https://hoplite.sh/?utm_source=paseo&utm_medium=website&utm_campaign=sponsor",
|
||||
);
|
||||
await expect(sponsors.getByText("Cloud coding agents", { exact: true })).toBeVisible();
|
||||
await expect(sponsors.getByRole("link", { name: "Become a sponsor" })).toHaveAttribute(
|
||||
"href",
|
||||
"/sponsor#spot",
|
||||
);
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.4 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.4 KiB |
@@ -1,3 +1,4 @@
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import type { Doc } from "~/docs";
|
||||
|
||||
const GITHUB_BASE_URL = "https://github.com/getpaseo/paseo/blob/main";
|
||||
@@ -6,9 +7,15 @@ export function DocsSourceFooter({ doc }: { doc: Doc }) {
|
||||
const sourceUrl = `${GITHUB_BASE_URL}/${doc.sourcePath}`;
|
||||
|
||||
return (
|
||||
<footer className="docs-source-footer">
|
||||
<a href={sourceUrl} target="_blank" rel="noreferrer">
|
||||
View this page on GitHub
|
||||
<footer className="mt-12 border-t border-white/10 py-8 text-sm">
|
||||
<a
|
||||
href={sourceUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-1.5 font-medium text-white/60 underline-offset-4 transition-colors hover:text-foreground hover:underline"
|
||||
>
|
||||
View on GitHub
|
||||
<ExternalLink aria-hidden="true" className="size-3.5" />
|
||||
</a>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -134,11 +134,11 @@ export function LandingPage({ title, subtitle }: LandingPageProps) {
|
||||
<ExtensibleSection />
|
||||
<FAQ />
|
||||
<PhilosophySection />
|
||||
<SponsorsSection placement="page" />
|
||||
<SponsorSection />
|
||||
<SponsorsSection />
|
||||
</div>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
<SiteFooter sponsors={false} />
|
||||
</div>
|
||||
</CursorFieldProvider>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { PLUGINS_LINKED } from "~/plugins/launch";
|
||||
import { getAlternativePages } from "~/data/alternative-pages";
|
||||
import { appStoreUrl, playStoreUrl, webAppUrl } from "~/downloads";
|
||||
import { SponsorsSection } from "~/components/sponsorship";
|
||||
|
||||
interface SiteFooterProps {
|
||||
width?: "default" | "prose" | "wide";
|
||||
/** Pages that show the sponsors in their content turn the footer band off. */
|
||||
sponsors?: boolean;
|
||||
}
|
||||
|
||||
const WIDTH_CLASSES: Record<NonNullable<SiteFooterProps["width"]>, string> = {
|
||||
@@ -12,11 +15,12 @@ const WIDTH_CLASSES: Record<NonNullable<SiteFooterProps["width"]>, string> = {
|
||||
wide: "max-w-7xl p-6 md:p-20 md:pt-0",
|
||||
};
|
||||
|
||||
export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
export function SiteFooter({ width = "default", sponsors = true }: SiteFooterProps) {
|
||||
const widthClasses = WIDTH_CLASSES[width];
|
||||
const alternatives = getAlternativePages();
|
||||
return (
|
||||
<footer className={`${widthClasses} mx-auto mt-12 md:mt-16`}>
|
||||
{sponsors ? <SponsorsSection placement="footer" /> : null}
|
||||
<div className="border-t border-white/10 pt-8 pb-4 grid grid-cols-2 sm:grid-cols-5 gap-8 text-sm">
|
||||
<div className="space-y-3">
|
||||
<p className="text-extra-muted-foreground">Product</p>
|
||||
|
||||
@@ -3,12 +3,13 @@ import { BuyMeACoffeeIcon, GitHubIcon, OpenCollectiveIcon } from "~/components/b
|
||||
import {
|
||||
BUY_ME_A_COFFEE_URL,
|
||||
GITHUB_SPONSORS_URL,
|
||||
HOMEPAGE_SPONSORS,
|
||||
HOMEPAGE_SPOT_PRICE,
|
||||
OPEN_COLLECTIVE_URL,
|
||||
SPONSORS,
|
||||
SPONSOR_CONTACT_EMAIL,
|
||||
SPONSOR_SPOT_CHECKOUT_URL,
|
||||
SPONSOR_SPOT_PRICE,
|
||||
openSpotCount,
|
||||
sponsorHref,
|
||||
} from "~/data/sponsors";
|
||||
|
||||
function SectionHeading({
|
||||
@@ -148,30 +149,11 @@ export function BackingOptions() {
|
||||
);
|
||||
}
|
||||
|
||||
function SponsorLogoRow() {
|
||||
return (
|
||||
<ul className="grid grid-cols-2 gap-4">
|
||||
{HOMEPAGE_SPONSORS.map((sponsor) => (
|
||||
<li key={sponsor.href}>
|
||||
<a
|
||||
href={sponsor.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="flex h-32 items-center justify-center rounded-xl border border-white/10 bg-white/[0.02] px-6 transition-colors hover:border-white/20 hover:bg-white/[0.05]"
|
||||
>
|
||||
<img src={sponsor.logo} alt={sponsor.name} className="max-h-10 max-w-full opacity-80" />
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
const SPOT_FEATURES: ReadonlyArray<string> = [
|
||||
"Your logo on the homepage of paseo.sh, around 70,000 visitors a month",
|
||||
"Your logo on the paseo.sh homepage and docs, around 70,000 visitors a month",
|
||||
"Your logo in the GitHub README, around 30,000 visitors a month",
|
||||
"An announcement in the Discord, around 2,000 members",
|
||||
"A mention in the changelog",
|
||||
"An announcement from me on X",
|
||||
];
|
||||
|
||||
export function SponsorSpotSection() {
|
||||
@@ -181,11 +163,11 @@ export function SponsorSpotSection() {
|
||||
<SectionHeading
|
||||
as="h2"
|
||||
title="Sponsor as a company"
|
||||
description="If your team relies on Paseo, a monthly sponsorship funds its development directly. As a thank you, your logo goes on the homepage and in the README."
|
||||
description="Support Paseo financially and get visibility for your company"
|
||||
/>
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-end gap-2">
|
||||
<span className="text-4xl font-medium tracking-tight">{HOMEPAGE_SPOT_PRICE}</span>
|
||||
<span className="text-4xl font-medium tracking-tight">{SPONSOR_SPOT_PRICE}</span>
|
||||
<span className="mb-1 text-sm text-white/45">a month</span>
|
||||
</div>
|
||||
<ul className="grid max-w-2xl gap-3 text-white/70">
|
||||
@@ -259,23 +241,73 @@ export function SponsorSection() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Homepage: the companies sponsoring Paseo. Renders nothing until there is one. */
|
||||
export function SponsorsSection() {
|
||||
if (HOMEPAGE_SPONSORS.length === 0) return null;
|
||||
const SPONSORS_PLACEMENTS = {
|
||||
page: {
|
||||
title: "Sponsors",
|
||||
section: "space-y-10",
|
||||
heading: "text-3xl font-medium tracking-tight",
|
||||
link: "",
|
||||
logo: "h-14",
|
||||
description: "mt-2 text-sm text-white/55",
|
||||
button: "h-12 px-5 text-sm",
|
||||
},
|
||||
footer: {
|
||||
title: "Sponsored by",
|
||||
section: "space-y-5 border-t border-white/10 py-8",
|
||||
heading: "text-sm text-extra-muted-foreground",
|
||||
link: "opacity-85 transition-opacity hover:opacity-100",
|
||||
logo: "h-10",
|
||||
description: "mt-1.5 text-xs text-white/45",
|
||||
button: "h-9 px-3.5 text-sm",
|
||||
},
|
||||
} as const;
|
||||
|
||||
interface SponsorsSectionProps {
|
||||
/** `page` is a homepage section; `footer` is the band above other pages' footers. */
|
||||
placement: keyof typeof SPONSORS_PLACEMENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The companies sponsoring Paseo: one monochrome logo per sponsor and, while
|
||||
* spots are open, a quiet invitation. Every page shows it once.
|
||||
*/
|
||||
export function SponsorsSection({ placement }: SponsorsSectionProps) {
|
||||
const style = SPONSORS_PLACEMENTS[placement];
|
||||
const open = openSpotCount();
|
||||
return (
|
||||
<section>
|
||||
<SectionHeading as="h2" title="Sponsors" />
|
||||
<div className="space-y-4">
|
||||
<SponsorLogoRow />
|
||||
{openSpotCount() > 0 ? (
|
||||
<a
|
||||
href="/sponsor#spot"
|
||||
className="inline-block text-sm text-white/50 underline transition-colors hover:text-white/80"
|
||||
>
|
||||
Become a sponsor
|
||||
</a>
|
||||
<section aria-labelledby="sponsors-heading" className={style.section}>
|
||||
<h2 id="sponsors-heading" className={style.heading}>
|
||||
{style.title}
|
||||
</h2>
|
||||
<ul className="flex flex-wrap items-center gap-x-14 gap-y-8">
|
||||
{SPONSORS.map((sponsor) => (
|
||||
<li key={sponsor.url}>
|
||||
<a
|
||||
href={sponsorHref(sponsor, "website")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className={`block ${style.link}`}
|
||||
>
|
||||
<img
|
||||
src={sponsor.logo.onDark}
|
||||
alt={sponsor.name}
|
||||
className={`${style.logo} w-auto`}
|
||||
/>
|
||||
</a>
|
||||
<p className={style.description}>{sponsor.description}</p>
|
||||
</li>
|
||||
))}
|
||||
{open > 0 ? (
|
||||
<li>
|
||||
<a
|
||||
href="/sponsor#spot"
|
||||
className={`inline-flex items-center rounded-md border border-dashed border-white/15 text-white/45 transition-colors hover:border-white/30 hover:text-white/75 ${style.button}`}
|
||||
>
|
||||
Become a sponsor
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</div>
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SPONSORS, sponsorHref } from "./sponsors";
|
||||
|
||||
const REPO_ROOT = fileURLToPath(new URL("../../../../", import.meta.url));
|
||||
const WEBSITE_PUBLIC = fileURLToPath(new URL("../../public", import.meta.url));
|
||||
const READMES = ["README.md", "README.zh-CN.md", "README.ja.md", "README.ko.md"];
|
||||
const README_SPONSOR_CELLS = 3;
|
||||
const SPONSOR_PAGE_URL = "https://paseo.sh/sponsor";
|
||||
|
||||
describe("sponsorHref", () => {
|
||||
it("tags the link with the placement it is shown in", () => {
|
||||
const sponsor = {
|
||||
name: "Acme",
|
||||
url: "https://acme.dev",
|
||||
logo: { onDark: "", onLight: "" },
|
||||
description: "",
|
||||
};
|
||||
|
||||
expect(sponsorHref(sponsor, "website")).toBe(
|
||||
"https://acme.dev/?utm_source=paseo&utm_medium=website&utm_campaign=sponsor",
|
||||
);
|
||||
expect(sponsorHref(sponsor, "x")).toBe(
|
||||
"https://acme.dev/?utm_source=paseo&utm_medium=x&utm_campaign=sponsor",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function readmeSponsorCells(readme: string) {
|
||||
const text = readFileSync(`${REPO_ROOT}${readme}`, "utf8");
|
||||
const table = text.slice(text.indexOf("<!-- Sponsors:")).split("</table>")[0];
|
||||
return [...table.matchAll(/<td[^>]*>(.*?)<\/td>/g)].map(([, cell]) => {
|
||||
const logo = cell.match(
|
||||
/^<a href="([^"]+)"><picture><source media="\(prefers-color-scheme: dark\)" srcset="([^"]+)"><img src="([^"]+)"[^>]*><\/picture><\/a><br><sub>([^<]+)<\/sub>$/,
|
||||
);
|
||||
if (logo) {
|
||||
const [, href, onDark, onLight, description] = logo;
|
||||
return { href, onDark, onLight, description };
|
||||
}
|
||||
return { invite: cell.match(/^<a href="([^"]+)">/)?.[1] };
|
||||
});
|
||||
}
|
||||
|
||||
describe("SPONSORS", () => {
|
||||
it.each(SPONSORS)("ships both logo inks for $name", (sponsor) => {
|
||||
expect(existsSync(`${WEBSITE_PUBLIC}${sponsor.logo.onDark}`)).toBe(true);
|
||||
expect(existsSync(`${WEBSITE_PUBLIC}${sponsor.logo.onLight}`)).toBe(true);
|
||||
});
|
||||
|
||||
it.each(READMES)("fill the sponsors table in %s, followed by invitations", (readme) => {
|
||||
const openCells = Math.max(0, README_SPONSOR_CELLS - SPONSORS.length);
|
||||
|
||||
expect(readmeSponsorCells(readme)).toEqual([
|
||||
...SPONSORS.map((sponsor) => ({
|
||||
href: sponsorHref(sponsor, "readme"),
|
||||
onDark: `packages/website/public${sponsor.logo.onDark}`,
|
||||
onLight: `packages/website/public${sponsor.logo.onLight}`,
|
||||
description: sponsor.description,
|
||||
})),
|
||||
...Array.from({ length: openCells }, () => ({ invite: SPONSOR_PAGE_URL })),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,31 +1,59 @@
|
||||
/**
|
||||
* Homepage sponsor spots. This file is the inventory: the site shows one open
|
||||
* placeholder for every spot not listed in `HOMEPAGE_SPONSORS`.
|
||||
* Company sponsors. This file is the inventory: every page of the site shows
|
||||
* these logos above the footer, and the open spots invite the next sponsor.
|
||||
*
|
||||
* Approving a sponsor means three edits, or the site promises something it does
|
||||
* not deliver:
|
||||
*
|
||||
* 1. Add the entry below and drop its logo in `public/sponsors/`.
|
||||
* 2. Add the logo under the `## Sponsors` marker in `README.md`,
|
||||
* `README.zh-CN.md`, `README.ja.md` and `README.ko.md`, in this same order.
|
||||
* 1. Add the entry below and drop both logo inks in `public/sponsors/`.
|
||||
* 2. In the sponsors table after the intro of `README.md`, `README.zh-CN.md`,
|
||||
* `README.ja.md` and `README.ko.md`, replace a "become a sponsor" cell with
|
||||
* the logo and its description, in this same order, linked to `sponsorHref(sponsor, "readme")`
|
||||
* with both inks referenced from `packages/website/public`, so the README
|
||||
* renders from the repository and never waits on a site deploy.
|
||||
* `sponsors.test.ts` fails until they match.
|
||||
* 3. Remove all of it when the subscription ends.
|
||||
*
|
||||
* The Stripe payment link caps itself at `HOMEPAGE_SPOT_COUNT` completed
|
||||
* The Stripe payment link caps itself at `SPONSOR_SPOT_COUNT` completed
|
||||
* checkouts, and a checkout you decline and refund still counts against that
|
||||
* cap. When you refund one, raise the link's completed-session limit by one, or
|
||||
* the page will offer a spot the link refuses to sell.
|
||||
*/
|
||||
export interface HomepageSponsor {
|
||||
export interface Sponsor {
|
||||
name: string;
|
||||
href: string;
|
||||
/** Path under `public/`, e.g. `/sponsors/acme.svg`. Keep logos monochrome white. */
|
||||
logo: string;
|
||||
/** The sponsor's own URL, without tracking. Links go through `sponsorHref`. */
|
||||
url: string;
|
||||
/** Paths under `public/`, one per ground. Use the sponsor's official lockup. */
|
||||
logo: { onDark: string; onLight: string };
|
||||
/** What the product is, in a few words from the sponsor's own site. Shown under the logo. */
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const HOMEPAGE_SPONSORS: ReadonlyArray<HomepageSponsor> = [];
|
||||
export const SPONSORS: ReadonlyArray<Sponsor> = [
|
||||
{
|
||||
name: "Hoplite",
|
||||
url: "https://hoplite.sh",
|
||||
logo: {
|
||||
onDark: "/sponsors/hoplite-on-dark.svg",
|
||||
onLight: "/sponsors/hoplite-on-light.svg",
|
||||
},
|
||||
description: "Cloud coding agents",
|
||||
},
|
||||
];
|
||||
|
||||
export const HOMEPAGE_SPOT_COUNT = 4;
|
||||
export const HOMEPAGE_SPOT_PRICE = "$500";
|
||||
/** Where a sponsor link is shown. Becomes the link's `utm_medium`. */
|
||||
export type SponsorPlacement = "website" | "readme" | "discord" | "x";
|
||||
|
||||
export function sponsorHref(sponsor: Sponsor, placement: SponsorPlacement): string {
|
||||
const url = new URL(sponsor.url);
|
||||
url.searchParams.set("utm_source", "paseo");
|
||||
url.searchParams.set("utm_medium", placement);
|
||||
url.searchParams.set("utm_campaign", "sponsor");
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export const SPONSOR_SPOT_COUNT = 4;
|
||||
export const SPONSOR_SPOT_PRICE = "$500";
|
||||
|
||||
/** Stripe Payment Link for the monthly spot subscription. */
|
||||
export const SPONSOR_SPOT_CHECKOUT_URL = "https://buy.stripe.com/8x24gBczR7LNaokcve2sM00";
|
||||
@@ -38,5 +66,5 @@ export const OPEN_COLLECTIVE_URL =
|
||||
export const BUY_ME_A_COFFEE_URL = "https://buymeacoffee.com/paseo";
|
||||
|
||||
export function openSpotCount(): number {
|
||||
return Math.max(0, HOMEPAGE_SPOT_COUNT - HOMEPAGE_SPONSORS.length);
|
||||
return Math.max(0, SPONSOR_SPOT_COUNT - SPONSORS.length);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from "react";
|
||||
import { DocsBreadcrumbs } from "~/components/docs-breadcrumbs";
|
||||
import { DocsNav } from "~/components/docs-nav";
|
||||
import { DocsOutline } from "~/components/docs-outline";
|
||||
import { SponsorsSection } from "~/components/sponsorship";
|
||||
import { buildDocsNavTree, getDoc, getDocs } from "~/docs";
|
||||
import "~/styles.css";
|
||||
|
||||
@@ -63,6 +64,7 @@ function DocsLayout() {
|
||||
<div className="max-w-prose mx-auto">
|
||||
{doc && <DocsBreadcrumbs doc={doc} tree={tree} />}
|
||||
<Outlet />
|
||||
<SponsorsSection placement="footer" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -561,23 +561,6 @@
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.docs-source-footer {
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.docs-source-footer a {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.docs-source-footer a:hover {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* Blog post prose styles — adapted from moboudra.com */
|
||||
.blog-prose {
|
||||
color: rgba(250, 250, 250, 0.82);
|
||||
|
||||
Reference in New Issue
Block a user