feat(landing): add pricing and paid windows flow

This commit is contained in:
Benjamin Shafii
2026-04-01 15:07:57 -07:00
parent ecb773e6e6
commit b9b0f83aa2
18 changed files with 370 additions and 88 deletions

View File

@@ -106,10 +106,6 @@ jobs:
os_type: linux
target: aarch64-unknown-linux-gnu
args: "--target aarch64-unknown-linux-gnu --bundles deb,rpm"
- platform: windows-2022
os_type: windows
target: x86_64-pc-windows-msvc
args: "--target x86_64-pc-windows-msvc --bundles msi"
steps:
- name: Log runner selection
@@ -202,9 +198,6 @@ jobs:
aarch64-unknown-linux-gnu)
opencode_asset="opencode-linux-arm64.tar.gz"
;;
x86_64-pc-windows-msvc)
opencode_asset="opencode-windows-x64-baseline.zip"
;;
*)
echo "Unsupported target: ${{ matrix.target }}"
exit 1
@@ -243,9 +236,6 @@ jobs:
fi
target_name="opencode-${{ matrix.target }}"
if [ "${{ matrix.os_type }}" = "windows" ]; then
target_name="${target_name}.exe"
fi
mkdir -p apps/desktop/src-tauri/sidecars
cp "$bin_path" "apps/desktop/src-tauri/sidecars/${target_name}"

View File

@@ -275,10 +275,6 @@ jobs:
os_type: linux
target: aarch64-unknown-linux-gnu
args: "--target aarch64-unknown-linux-gnu --bundles deb,rpm"
- platform: windows-2022
os_type: windows
target: x86_64-pc-windows-msvc
args: "--target x86_64-pc-windows-msvc --bundles msi"
steps:
- name: Log runner selection
@@ -290,11 +286,6 @@ jobs:
RUNNER_LABEL: ${{ vars.OPENWORK_LINUX_X64_RUNNER_LABEL }}
EFFECTIVE_RUNS_ON: ${{ matrix.target == 'x86_64-unknown-linux-gnu' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL != '' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL || matrix.platform }}
- name: Enable git long paths (Windows)
if: matrix.os_type == 'windows'
shell: pwsh
run: git config --global core.longpaths true
- name: Checkout
uses: actions/checkout@v4
with:
@@ -404,9 +395,6 @@ jobs:
aarch64-unknown-linux-gnu)
opencode_asset="opencode-linux-arm64.tar.gz"
;;
x86_64-pc-windows-msvc)
opencode_asset="opencode-windows-x64-baseline.zip"
;;
*)
echo "Unsupported target: ${{ matrix.target }}" >&2
exit 1
@@ -445,9 +433,6 @@ jobs:
fi
target_name="opencode-${{ matrix.target }}"
if [ "${{ matrix.os_type }}" = "windows" ]; then
target_name="${target_name}.exe"
fi
mkdir -p apps/desktop/src-tauri/sidecars
cp "$bin_path" "apps/desktop/src-tauri/sidecars/${target_name}"

View File

@@ -74,9 +74,6 @@ type PolarProduct = {
prices?: PolarProductPrice[]
}
const CHECKOUT_TRIAL_INTERVAL = "day"
const CHECKOUT_TRIAL_INTERVAL_COUNT = 14
export type CloudWorkerAccess =
| {
allowed: true
@@ -194,9 +191,6 @@ function assertPaywallConfig() {
if (!env.polar.productId) {
throw new Error("POLAR_PRODUCT_ID is required when POLAR_FEATURE_GATE_ENABLED=true")
}
if (!env.polar.benefitId) {
throw new Error("POLAR_BENEFIT_ID is required when POLAR_FEATURE_GATE_ENABLED=true")
}
if (!env.polar.successUrl) {
throw new Error("POLAR_SUCCESS_URL is required when POLAR_FEATURE_GATE_ENABLED=true")
}
@@ -290,9 +284,6 @@ async function createCheckoutSession(input: CloudAccessInput): Promise<string> {
products: [env.polar.productId],
success_url: env.polar.successUrl,
return_url: env.polar.returnUrl,
allow_trial: true,
trial_interval: CHECKOUT_TRIAL_INTERVAL,
trial_interval_count: CHECKOUT_TRIAL_INTERVAL_COUNT,
external_customer_id: input.userId,
customer_email: input.email,
customer_name: input.name,

View File

@@ -10,10 +10,9 @@ import { useDenFlow } from "../_providers/den-flow-provider";
// Enable with: NEXT_PUBLIC_DEN_MOCK_BILLING=1
const MOCK_BILLING = process.env.NEXT_PUBLIC_DEN_MOCK_BILLING === "1";
const MOCK_CHECKOUT_URL = (process.env.NEXT_PUBLIC_DEN_MOCK_CHECKOUT_URL ?? "").trim() || null;
const TRIAL_DAYS = 14;
function formatSubscriptionStatus(value: string | null | undefined) {
if (!value) return "Trial ready";
if (!value) return "Purchase required";
return value
.split(/[_\s]+/)
.filter(Boolean)
@@ -60,9 +59,9 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
featureGateEnabled: true,
hasActivePlan: false,
checkoutRequired: true,
checkoutUrl: MOCK_CHECKOUT_URL,
portalUrl: null,
price: { amount: 5000, currency: "usd", recurringInterval: "month", recurringIntervalCount: 1 },
checkoutUrl: MOCK_CHECKOUT_URL,
portalUrl: null,
price: { amount: 5000, currency: "usd", recurringInterval: "month", recurringIntervalCount: 1 },
subscription: null,
invoices: [],
productId: null,
@@ -175,16 +174,16 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
<div className="flex flex-col gap-4 lg:max-w-3xl">
<div className="grid gap-3">
<p className="den-eyebrow">OpenWork Cloud</p>
<h1 className="den-title-xl max-w-[12ch]">Provision shared setups for your team.</h1>
<h1 className="den-title-xl max-w-[12ch]">Purchase worker access before launch.</h1>
<p className="den-copy max-w-2xl">
Share your setup across your org, launch background agents in alpha, and prepare for team-wide provider provisioning.
Workers are disabled by default. Add one hosted OpenWork worker for $50/month, then launch it from your dashboard.
</p>
</div>
<div className="flex flex-wrap gap-3">
{checkoutHref ? (
<a href={checkoutHref} rel="noreferrer" className="den-button-primary w-full sm:w-auto">
Start free trial
Purchase worker $50/month
</a>
) : (
<button
@@ -193,7 +192,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
onClick={() => void refreshBilling({ includeCheckout: true, quiet: false })}
disabled={billingBusy || billingCheckoutBusy}
>
Refresh trial link
Refresh purchase link
</button>
)}
<a href="https://openworklabs.com/download" className="den-button-secondary w-full sm:w-auto">
@@ -202,9 +201,9 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
</div>
<div className="flex flex-wrap items-center gap-3 text-sm text-[var(--dls-text-secondary)]">
<span>{TRIAL_DAYS}-day free trial</span>
<span>$50/month per worker</span>
<span aria-hidden="true"></span>
<span>{planAmountLabel} after trial</span>
<span>{planAmountLabel} billed monthly</span>
<span aria-hidden="true"></span>
<span>{user?.email ?? "Signed in"}</span>
</div>
@@ -280,7 +279,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
<p className="den-eyebrow">Billing status</p>
<h2 className="text-2xl font-semibold tracking-tight text-[var(--dls-text-primary)]">{subscriptionStatus}</h2>
<p className="den-copy text-sm">
{billingSummary.hasActivePlan ? "Your Cloud plan is active." : `${TRIAL_DAYS}-day free trial before billing starts.`}
{billingSummary.hasActivePlan ? "Your worker billing is active." : "Purchase a worker to enable hosted launches."}
</p>
</div>
@@ -304,7 +303,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
<div className="grid gap-3">
{checkoutHref && !billingSummary.hasActivePlan ? (
<a href={checkoutHref} rel="noreferrer" className="den-button-primary w-full">
Start free trial
Purchase worker
</a>
) : null}
{billingSummary.portalUrl ? (
@@ -329,7 +328,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
</a>
) : null}
<span>Invoices {billingSummary.invoices.length > 0 ? `(${billingSummary.invoices.length})` : ""}</span>
<span>Cancel anytime</span>
<span>Monthly billing</span>
</div>
</aside>
</div>

View File

@@ -557,8 +557,8 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
<p className="text-[15px] leading-relaxed text-[var(--dls-text-secondary)]">
{billingSummary?.featureGateEnabled
? billingSummary.hasActivePlan
? "Your account has an active OpenWork Cloud plan."
: "Your account needs billing before the next launch."
? "Your account has active worker billing."
: "Workers stay disabled until you purchase one for $50/month."
: "Billing gates are disabled in this environment."}
</p>
<Link
@@ -575,7 +575,13 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
<div className="rounded-[24px] border border-[var(--dls-border)] bg-[var(--dls-surface)] p-8">
<div className="mx-auto max-w-[30rem] text-center">
<h2 className="text-2xl font-semibold tracking-tight text-[var(--dls-text-primary)]">No workers yet</h2>
<p className="mt-3 text-sm leading-6 text-[var(--dls-text-secondary)]">Create your first worker to unlock connection details and runtime controls.</p>
<p className="mt-3 text-sm leading-6 text-[var(--dls-text-secondary)]">Purchase your first worker to unlock connection details and runtime controls.</p>
<Link
href="/checkout"
className="mt-5 inline-flex rounded-[12px] border border-[var(--dls-border)] bg-[var(--dls-surface)] px-3 py-2 text-xs font-semibold text-[var(--dls-text-secondary)] transition hover:bg-[var(--dls-hover)] hover:text-[var(--dls-text-primary)]"
>
Purchase worker billing
</Link>
</div>
</div>
)}
@@ -653,14 +659,14 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
{workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">Loading workers...</p> : null}
{workersError ? <p className="text-xs font-medium text-rose-600">{workersError}</p> : null}
{workers.length === 0 && !workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">No workers yet. Create one to get started.</p> : null}
{workers.length === 0 && !workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">No workers yet. Purchase one to get started.</p> : null}
</div>
<div className="text-xs text-[var(--dls-text-secondary)] md:text-sm">
Signed in as <span className="font-medium text-[var(--dls-text-primary)]">{user.email}</span>
<div className="mt-2 text-xs text-[var(--dls-text-secondary)]">
{billingSummary?.featureGateEnabled && !billingSummary.hasActivePlan
? "Billing required before the next launch."
? "Purchase required before the next launch."
: `${ownedWorkerCount} worker${ownedWorkerCount === 1 ? "" : "s"} in your account.`}
</div>
</div>

View File

@@ -1299,7 +1299,7 @@ export function DenFlowProvider({ children }: { children: ReactNode }) {
setLaunchBusy(true);
setLaunchError(null);
setCheckoutUrl(null);
setLaunchStatus(options.source === "signup_auto" ? "Creating your first worker..." : "Checking subscription and launch eligibility...");
setLaunchStatus(options.source === "signup_auto" ? "Creating your first worker..." : "Checking worker billing and launch eligibility...");
appendEvent("info", "Launch requested", resolvedLaunchName);
try {

View File

@@ -144,7 +144,7 @@ export function BillingDashboardScreen() {
rel="noreferrer"
className="rounded-full bg-gray-900 px-5 py-2.5 text-[14px] font-medium text-white transition-colors hover:bg-gray-800"
>
Start free trial
Purchase worker
</a>
) : null}

View File

@@ -11,6 +11,7 @@
- `NEXT_PUBLIC_CAL_URL` - enterprise booking link
- `NEXT_PUBLIC_DEN_CHECKOUT_URL` - Polar checkout URL for the Den preorder CTA
- `NEXT_PUBLIC_WINDOWS_CHECKOUT_URL` - Polar checkout URL for the Windows support plan CTA
- `LOOPS_API_KEY` - Loops API key for feedback/contact submissions
- `LOOPS_TRANSACTIONAL_ID_APP_FEEDBACK` - Loops transactional template ID for app feedback emails
- `LOOPS_INTERNAL_FEEDBACK_EMAIL` - optional override for the internal feedback recipient (defaults to `team@openworklabs.com`)

View File

@@ -5,13 +5,16 @@ import { getGithubData } from "../../lib/github";
export const metadata = {
title: "OpenWork - Download",
description:
"Download OpenWork desktop for macOS, Windows, and Linux. Includes AUR install instructions and direct package downloads.",
"Download OpenWork desktop for macOS and Linux, or purchase Windows support for binary access and updates.",
};
export default async function Download() {
const github = await getGithubData();
const releaseLabel = github.releaseTag || "latest";
const releaseUrl = github.releaseUrl;
const windowsCheckoutUrl =
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/pricing#windows-support";
const windowsCheckoutExternal = /^https?:\/\//.test(windowsCheckoutUrl);
return (
<div className="min-h-screen">
@@ -60,7 +63,7 @@ export default async function Download() {
className="feature-card border-violet-100 bg-violet-50/50 transition hover:border-violet-200"
>
<h2 className="mb-2 text-[16px] font-semibold text-gray-900">Windows</h2>
<p className="text-[14px] text-gray-700">x64 MSI installer</p>
<p className="text-[14px] text-gray-700">$99/year support plan with manual build delivery</p>
</a>
<a
href="#linux"
@@ -111,16 +114,31 @@ export default async function Download() {
<section id="windows" className="py-6">
<h2 className="mb-2 text-2xl font-bold md:text-3xl">Windows</h2>
<p className="mb-6 text-[15px] text-gray-700">
OpenWork for Windows is available as an x64 MSI installer.
Windows access is sold as an annual support plan. It includes one seat,
Windows binary access, and one year of updates.
</p>
<a
href={github.installers.windows.x64}
className="doc-button"
rel="noreferrer"
target="_blank"
>
Download Windows x64 (.msi)
</a>
<div className="feature-card max-w-2xl border-violet-100 bg-white/90 ring-1 ring-violet-100/70">
<h3 className="mb-2 text-[18px] font-semibold text-gray-900">
Windows support $99/year
</h3>
<p className="mb-5 text-[14px] leading-7 text-gray-600">
Purchase through Polar, then we will send your Windows build link manually in phase one.
</p>
<div className="flex flex-wrap gap-3">
<a
href={windowsCheckoutUrl}
className="doc-button"
rel={windowsCheckoutExternal ? "noreferrer" : undefined}
target={windowsCheckoutExternal ? "_blank" : undefined}
>
Purchase Windows support
</a>
<a href="/pricing#windows-support" className="secondary-button">
See pricing details
</a>
</div>
</div>
</section>
<hr />

View File

@@ -3,7 +3,7 @@ import { getGithubData } from "../../lib/github";
export const metadata = {
title: "OpenWork — Enterprise",
description: "Secure hosting for safe, permissioned AI employees."
description: "Enterprise OpenWork licensing with deployment support, rollout help, and included Windows support."
};
export default async function Enterprise() {

View File

@@ -5,6 +5,8 @@ import { headers } from "next/headers";
export default async function Home() {
const github = await getGithubData();
const cal = process.env.NEXT_PUBLIC_CAL_URL || "/enterprise#book";
const windowsCheckoutUrl =
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/download#windows-support";
const userAgent = headers().get("user-agent")?.toLowerCase() || "";
const isMobileVisitor = /android|iphone|ipad|ipod|mobile/.test(userAgent);
@@ -13,6 +15,7 @@ export default async function Home() {
stars={github.stars}
downloadHref={github.downloads.macos}
callHref={cal}
windowsCheckoutUrl={windowsCheckoutUrl}
isMobileVisitor={isMobileVisitor}
/>
);

View File

@@ -0,0 +1,53 @@
import { LandingBackground } from "../../components/landing-background";
import { PricingGrid } from "../../components/pricing-grid";
import { SiteFooter } from "../../components/site-footer";
import { SiteNav } from "../../components/site-nav";
import { getGithubData } from "../../lib/github";
export const metadata = {
title: "OpenWork — Pricing",
description:
"Free solo desktop usage, annual Windows support, monthly cloud workers, and enterprise licensing."
};
export default async function PricingPage() {
const github = await getGithubData();
const callUrl = process.env.NEXT_PUBLIC_CAL_URL || "/enterprise#book";
const windowsCheckoutUrl =
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/download#windows-support";
return (
<div className="relative min-h-screen overflow-hidden text-[#011627]">
<LandingBackground />
<div className="relative z-10 flex min-h-screen flex-col items-center pb-3 pt-1 md:pb-4 md:pt-2">
<div className="w-full">
<SiteNav
stars={github.stars}
callUrl={callUrl}
downloadHref={github.downloads.macos}
active="pricing"
/>
</div>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-16 px-6 pb-24 md:gap-20 md:px-8 md:pb-28">
<section className="max-w-4xl pt-6 md:pt-10">
<div className="mb-4 inline-flex rounded-full border border-white/70 bg-white/80 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500 shadow-sm">
OpenWork pricing
</div>
<h1 className="mb-6 text-4xl font-medium leading-[1.05] tracking-tight md:text-5xl lg:text-6xl">
Pricing that keeps desktop free and makes paid access explicit.
</h1>
<p className="max-w-3xl text-lg leading-relaxed text-slate-600 md:text-xl">
Start solo for free. Purchase Windows support when you need it. Add hosted workers when you want cloud runtime. Talk to us for enterprise licensing.
</p>
</section>
<PricingGrid windowsCheckoutUrl={windowsCheckoutUrl} callUrl={callUrl} />
<SiteFooter />
</main>
</div>
</div>
);
}

View File

@@ -71,7 +71,8 @@ export function LandingEnterprise(props: Props) {
Run agentic workflows through your existing gateway, with
approved tools, clear permissions, and a rollout path your
non-technical teams can actually use, whether you self-host in
your own infrastructure or deploy with OpenWork.
your own infrastructure or deploy with OpenWork. Enterprise
licensing includes Windows support.
</p>
<div className="mt-8 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
@@ -185,7 +186,8 @@ export function LandingEnterprise(props: Props) {
<p className="text-[14px] leading-relaxed text-slate-600">
Deploy inside your own environment or work with us on a
managed rollout, with your gateway, MCP servers, skills, and
internal data sources connected.
internal data sources connected. Windows support is included in
enterprise licensing.
</p>
</div>
</div>

View File

@@ -11,6 +11,7 @@ import {
landingDemoFlows,
landingDemoFlowTimes
} from "./landing-demo-flows";
import { PricingGrid } from "./pricing-grid";
import { LandingSharePackageCard } from "./landing-share-package-card";
import { SiteFooter } from "./site-footer";
import { SiteNav } from "./site-nav";
@@ -20,6 +21,7 @@ type Props = {
stars: string;
downloadHref: string;
callHref: string;
windowsCheckoutUrl: string;
isMobileVisitor: boolean;
};
@@ -70,8 +72,6 @@ export function LandingHome(props: Props) {
[activeDemoId]
);
const downloadLinkProps = externalLinkProps(props.downloadHref);
const callLinkProps = externalLinkProps(props.callHref);
const primaryCtaHref = props.isMobileVisitor
? "https://app.openworklabs.com"
: "/download";
@@ -108,6 +108,14 @@ export function LandingHome(props: Props) {
own keys, and share your setups seamlessly with your team.
</p>
<div className="mb-6 flex flex-wrap items-center gap-x-3 gap-y-2 text-[13px] font-medium text-gray-500 md:text-[14px]">
<span>Solo free forever</span>
<span aria-hidden="true"></span>
<span>Windows support $99/year</span>
<span aria-hidden="true"></span>
<span>Workers $50/month</span>
</div>
<div className="mt-6 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
<div className="flex w-full flex-col gap-3 sm:w-auto sm:flex-row">
<a
@@ -118,11 +126,10 @@ export function LandingHome(props: Props) {
{primaryCtaLabel} <Download size={18} />
</a>
<a
href={props.callHref}
href="/pricing"
className="secondary-button"
{...callLinkProps}
>
Contact sales
See pricing
</a>
</div>
@@ -247,8 +254,8 @@ export function LandingHome(props: Props) {
</div>
<h2 className="mb-3 text-xl font-medium tracking-tight">Local AI worker</h2>
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
Start free on desktop with no signup, then automate email, Slack,
and the work you do every day.
Start free on desktop forever with macOS and Linux downloads,
then automate email, Slack, and the work you do every day.
</p>
<div>
<a href="/download" className="secondary-button">Learn more</a>
@@ -262,8 +269,8 @@ export function LandingHome(props: Props) {
</div>
<h2 className="mb-3 text-xl font-medium tracking-tight">Hosted sandboxed workers</h2>
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
Share your setup with your team and run your local setup in the
cloud.
Workers are disabled by default. Purchase one for $50/month when
you need hosted runtime.
</p>
<div>
<a href="/den" className="secondary-button">Learn more</a>
@@ -277,8 +284,8 @@ export function LandingHome(props: Props) {
</div>
<h2 className="mb-3 text-xl font-medium tracking-tight">Safe workflow sharing</h2>
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
Deploy on your infrastructure. Full audit trails, admin controls,
and data that never leaves your network.
Deploy on your infrastructure. Enterprise licensing includes
Windows support plus rollout guidance.
</p>
<div>
<a href="/enterprise" className="secondary-button">Learn more</a>
@@ -286,6 +293,14 @@ export function LandingHome(props: Props) {
</div>
</section>
<section className="landing-shell rounded-[2.5rem] p-8 md:p-12">
<PricingGrid
windowsCheckoutUrl={props.windowsCheckoutUrl}
callUrl={props.callHref}
showHeader={true}
/>
</section>
<section className="landing-shell-soft rounded-[2.5rem] p-8 md:p-12">
<div className="mb-4 flex items-center gap-2.5 text-[11px] font-semibold uppercase tracking-[0.2em] text-gray-400">
<PlugZap size={18} />

View File

@@ -0,0 +1,222 @@
import { ArrowUpRight, Cloud, Download, Monitor, Shield } from "lucide-react";
type PricingGridProps = {
windowsCheckoutUrl: string;
callUrl: string;
showHeader?: boolean;
};
type PricingCard = {
id: string;
eyebrow: string;
title: string;
price: string;
priceSub: string;
description: string;
ctaLabel: string;
href: string;
external?: boolean;
features: string[];
footer: string;
icon: typeof Download;
accent: string;
};
function PricingAction(props: { href: string; label: string; external?: boolean }) {
return (
<a
href={props.href}
{...(props.external ? { rel: "noreferrer", target: "_blank" as const } : {})}
className="inline-flex w-full items-center justify-center gap-2 rounded-full bg-[#011627] px-4 py-2.5 text-sm font-medium text-white transition hover:bg-[#16293f]"
>
{props.label}
<ArrowUpRight size={15} />
</a>
);
}
function PricingCardView(card: PricingCard) {
const Icon = card.icon;
return (
<article
id={card.id}
className="group relative flex h-full flex-col overflow-hidden rounded-[28px] border border-dotted border-gray-300/80 bg-[#efefef] p-6 transition duration-300 hover:-translate-y-1 hover:border-gray-400/80 hover:shadow-[0_28px_60px_-30px_rgba(15,23,42,0.35)]"
>
<div
className="pointer-events-none absolute inset-0 opacity-0 transition duration-300 group-hover:opacity-100"
style={{ background: card.accent }}
/>
<div className="relative z-10 flex h-full flex-col">
<div className="mb-8 rounded-[22px] border border-white/70 bg-white/80 p-5 shadow-[0_8px_24px_-20px_rgba(15,23,42,0.3)] transition group-hover:border-white/20 group-hover:bg-white/10 group-hover:shadow-none">
<div className="mb-8 flex items-start justify-between gap-4">
<div>
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.22em] text-gray-500 transition group-hover:text-white/70">
{card.eyebrow}
</div>
<h3 className="text-xl font-medium tracking-tight text-[#011627] transition group-hover:text-white">
{card.title}
</h3>
</div>
<div className="rounded-2xl border border-gray-200 bg-white/80 p-2.5 text-gray-600 transition group-hover:border-white/20 group-hover:bg-white/10 group-hover:text-white/90">
<Icon size={18} />
</div>
</div>
<div className="flex items-end gap-2">
<span className="text-3xl font-semibold tracking-tight text-[#011627] transition group-hover:text-white">
{card.price}
</span>
<span className="pb-1 text-xs font-medium text-gray-500 transition group-hover:text-white/70">
{card.priceSub}
</span>
</div>
<p className="mt-4 text-sm leading-relaxed text-gray-600 transition group-hover:text-white/80">
{card.description}
</p>
<div className="mt-6">
<PricingAction href={card.href} label={card.ctaLabel} external={card.external} />
</div>
</div>
<div className="flex flex-1 flex-col">
<div className="mb-3 inline-flex items-center gap-1.5 border-b border-dotted border-gray-300/90 pb-3 text-[12px] font-medium text-gray-500 transition group-hover:border-white/20 group-hover:text-white/70">
Included
</div>
<div className="flex flex-1 flex-col">
{card.features.map((feature) => (
<div
key={feature}
className="border-b border-dotted border-gray-300/80 py-3 text-[13px] font-medium text-gray-700 transition last:border-b-0 group-hover:border-white/15 group-hover:text-white/88"
>
{feature}
</div>
))}
</div>
<div className="mt-8 text-sm font-medium text-gray-700 transition group-hover:text-white/88">
{card.footer}
</div>
</div>
</div>
</article>
);
}
export function PricingGrid(props: PricingGridProps) {
const cards: PricingCard[] = [
{
id: "solo",
eyebrow: "Solo",
title: "Free forever",
price: "$0",
priceSub: "open source",
description:
"Start on desktop for free with macOS and Linux downloads, local models, and bring-your-own-provider workflows.",
ctaLabel: "Download free",
href: "/download",
features: [
"Open source desktop app",
"macOS and Linux downloads",
"Bring your own keys and local models"
],
footer: "Best for individual builders and local-first workflows.",
icon: Download,
accent: "linear-gradient(135deg, #4b5563 0%, #111827 100%)"
},
{
id: "windows-support",
eyebrow: "Windows",
title: "Windows support",
price: "$99",
priceSub: "per year · 1 seat",
description:
"Annual Windows access includes the binary plus one year of updates. In phase one we send the build link manually after purchase.",
ctaLabel: "Purchase Windows support",
href: props.windowsCheckoutUrl,
external: /^https?:\/\//.test(props.windowsCheckoutUrl),
features: [
"1 Windows seat",
"Binary access",
"1 year of updates"
],
footer: "Manual fulfillment first, hosted delivery later.",
icon: Monitor,
accent: "linear-gradient(135deg, #7c3aed 0%, #1f2937 100%)"
},
{
id: "cloud-workers",
eyebrow: "Cloud workers",
title: "One worker at a time",
price: "$50",
priceSub: "per month · per worker",
description:
"Workers stay disabled by default. Buy worker access when you want a hosted OpenWork worker for your account.",
ctaLabel: "Purchase worker",
href: "https://app.openworklabs.com/checkout",
external: true,
features: [
"Hosted OpenWork worker",
"Monthly billing",
"Purchase required before launch"
],
footer: "Designed for cloud usage without forcing hosted billing on solo desktop users.",
icon: Cloud,
accent: "linear-gradient(135deg, #2563eb 0%, #0f172a 100%)"
},
{
id: "enterprise-license",
eyebrow: "Enterprise",
title: "Talk to us",
price: "Custom",
priceSub: "licensing",
description:
"Enterprise licensing includes Windows support, rollout help, and managed or self-hosted deployment paths for larger teams.",
ctaLabel: "Talk to us",
href: props.callUrl,
external: /^https?:\/\//.test(props.callUrl),
features: [
"Includes Windows support",
"Deployment and rollout guidance",
"Custom commercial terms"
],
footer: "Use this when you need org-wide rollout, controls, or custom terms.",
icon: Shield,
accent: "linear-gradient(135deg, #fb923c 0%, #111827 100%)"
}
];
return (
<section className="grid gap-8">
{props.showHeader !== false ? (
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="max-w-3xl">
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.24em] text-gray-500">
Pricing
</div>
<h2 className="text-3xl font-medium leading-[1.1] tracking-tight text-[#011627] md:text-4xl lg:text-5xl">
Gray by default. Clear when you hover.
</h2>
</div>
<p className="max-w-xl text-sm leading-7 text-gray-600 md:text-right md:text-base">
Solo stays free forever. Windows is annual. Cloud workers are monthly. Enterprise starts with a conversation.
</p>
</div>
) : null}
<div className="grid gap-6 xl:grid-cols-4 md:grid-cols-2">
{cards.map((card) => (
<PricingCardView key={card.id} {...card} />
))}
</div>
<p className="text-center text-[12px] font-medium text-gray-500">
Prices exclude taxes. Windows delivery is manual in phase one.
</p>
</section>
);
}

View File

@@ -21,6 +21,9 @@ export function SiteFooter() {
<Link href="/docs" target="_blank" className="transition-colors hover:text-gray-800">
Docs
</Link>
<Link href="/pricing" className="transition-colors hover:text-gray-800">
Pricing
</Link>
<Link href="/download" className="transition-colors hover:text-gray-800">
Desktop
</Link>

View File

@@ -11,7 +11,7 @@ type Props = {
downloadHref?: string;
mobilePrimaryHref?: string;
mobilePrimaryLabel?: string;
active?: "home" | "download" | "enterprise" | "den" | "docs";
active?: "home" | "pricing" | "download" | "enterprise" | "den" | "docs";
};
export function SiteNav(props: Props) {
@@ -33,6 +33,7 @@ export function SiteNav(props: Props) {
const mobilePrimaryExternal = /^https?:\/\//.test(mobilePrimaryHref);
const navItems = [
{ href: "/docs", label: "Docs", key: "docs" },
{ href: "/pricing", label: "Pricing", key: "pricing" },
{ href: "/download", label: "Desktop", key: "download" },
{ href: "https://app.openworklabs.com", label: "Cloud", key: "den" },
{ href: "/enterprise", label: "Enterprise", key: "enterprise" }

View File

@@ -105,13 +105,10 @@ export const getGithubData = async () => {
const assets = Array.isArray(pick?.assets) ? pick.assets : [];
const releaseUrl = pick?.html_url || FALLBACK_RELEASE;
const dmg = selectAsset(assets, [".dmg"]);
const exe = selectAsset(assets, [".exe", ".msi"], ["win", "windows"]);
const appImage = selectAsset(assets, [".appimage"], ["linux"]);
const macosApple = selectAsset(assets, [".dmg"], ["darwin-aarch64"]);
const macosIntel = selectAsset(assets, [".dmg"], ["darwin-x64"]);
const windowsX64 =
selectAsset(assets, [".msi", ".exe"], ["windows-x64"]) || exe;
const linuxDebX64 = selectAsset(assets, [".deb"], ["linux-amd64", "linux-x64"]);
const linuxDebArm64 = selectAsset(assets, [".deb"], ["linux-arm64", "linux-aarch64"]);
@@ -124,7 +121,6 @@ export const getGithubData = async () => {
releaseTag: pick?.tag_name || "",
downloads: {
macos: dmg?.browser_download_url || FALLBACK_RELEASE,
windows: exe?.browser_download_url || FALLBACK_RELEASE,
linux:
appImage?.browser_download_url ||
linuxDebX64?.browser_download_url ||
@@ -136,9 +132,6 @@ export const getGithubData = async () => {
appleSilicon: macosApple?.browser_download_url || dmg?.browser_download_url || releaseUrl,
intel: macosIntel?.browser_download_url || dmg?.browser_download_url || releaseUrl
},
windows: {
x64: windowsX64?.browser_download_url || releaseUrl
},
linux: {
aur: "https://aur.archlinux.org/packages/openwork",
debX64: linuxDebX64?.browser_download_url || releaseUrl,