Refine landing trust center into one-screen review flow (#1363)

* Add trust review pages for enterprise follow-up

* Simplify trust page into decision-maker summary

* Refine landing trust center review flow

* Simplify trust center into one-screen flow

* Remove unused trust topic page component

* trust me, I'm compliant (WIP)

* good copy

* eliminating dead space

* mobile
This commit is contained in:
Jan Carbonell
2026-04-05 18:21:40 -06:00
committed by GitHub
parent 9365e7d397
commit e06dcce546
7 changed files with 548 additions and 2 deletions

View File

@@ -0,0 +1,21 @@
import { LandingTrustOverview } from "../../components/landing-trust";
import { getGithubData } from "../../lib/github";
export const metadata = {
title: "OpenWork — Trust",
description:
"How OpenWork approaches deployment control, local-first architecture, provider choice, and enterprise trust."
};
export default async function TrustPage() {
const github = await getGithubData();
const cal = process.env.NEXT_PUBLIC_CAL_URL ?? "";
return (
<LandingTrustOverview
stars={github.stars}
downloadHref={github.downloads.macos}
calUrl={cal}
/>
);
}

View File

@@ -99,6 +99,12 @@ export function LandingEnterprise(props: Props) {
</div>
</div>
</div>
<div className="mt-4">
<a href="/trust" className="text-[14px] font-medium text-[#011627] transition-colors hover:text-slate-700">
Trust details
</a>
</div>
</section>
<section className="space-y-6">

View File

@@ -0,0 +1,257 @@
"use client";
import Link from "next/link";
import { ChevronLeft } from "lucide-react";
import { useMemo, useState } from "react";
import { type Subprocessor, subprocessors } from "./trust-content";
import { SiteFooter } from "./site-footer";
import { LandingBackground } from "./landing-background";
import { SiteNav } from "./site-nav";
import {
defaultTrustTopicSlug,
getTrustTopic,
statusPageRequestHref,
trustTopics,
type TrustTopic
} from "./trust-content";
const categoryColors: Record<string, string> = {
Analytics: "bg-orange-50 text-orange-700",
Payments: "bg-violet-50 text-violet-700",
Authentication: "bg-blue-50 text-blue-700",
Infrastructure: "bg-cyan-50 text-cyan-700"
};
function SubprocessorRows() {
return (
<div className="mt-5 flex flex-col divide-y divide-slate-200/70 overflow-hidden rounded-2xl border border-slate-200/70 bg-white/85">
{subprocessors.map((sp) => (
<a
key={sp.name}
href={sp.href}
rel="noreferrer"
target="_blank"
className="flex min-w-0 items-center gap-3 px-4 py-2.5 transition-colors hover:bg-slate-50/80"
>
<div
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-[10px] font-bold tracking-wide"
style={{ backgroundColor: sp.brandColor, color: sp.textColor }}
>
{sp.initial}
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-[13px] leading-relaxed text-slate-500">
<span className="font-semibold text-[#011627]">{sp.name}</span>
{": "}
{sp.purpose}
</p>
</div>
<div className="hidden shrink-0 items-center gap-2 sm:flex">
<span
className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${categoryColors[sp.category] ?? "bg-slate-100 text-slate-600"}`}
>
{sp.category}
</span>
<span className="text-[11px] text-slate-400">{sp.location} Region</span>
</div>
</a>
))}
</div>
);
}
type SharedProps = {
stars: string;
downloadHref: string;
calUrl: string;
};
function externalLinkProps(href: string) {
return /^https?:\/\//.test(href) || href.startsWith("mailto:")
? { rel: "noreferrer", target: "_blank" as const }
: {};
}
function TopicChip({
topic,
active,
onSelect
}: {
topic: TrustTopic;
active: boolean;
onSelect: (slug: string) => void;
}) {
return (
<button
type="button"
onClick={() => onSelect(topic.slug)}
className={`shrink-0 rounded-full border px-3.5 py-1.5 text-[13px] font-medium tracking-tight transition-all ${
active
? "border-[#011627] bg-[#011627] text-white shadow-[0_8px_20px_-8px_rgba(1,22,39,0.45)]"
: "border-slate-200/80 bg-white/80 text-slate-600 hover:border-slate-300 hover:text-[#011627]"
}`}
aria-pressed={active}
>
{topic.label}
</button>
);
}
function TopicRailItem({
topic,
active,
onSelect
}: {
topic: TrustTopic;
active: boolean;
onSelect: (slug: string) => void;
}) {
return (
<button
type="button"
onClick={() => onSelect(topic.slug)}
className={`w-full rounded-[1.2rem] border px-4 py-3 text-left transition-all ${
active
? "border-[#011627] bg-[#011627] text-white shadow-[0_14px_32px_-18px_rgba(1,22,39,0.55)]"
: "border-slate-200/80 bg-white/80 text-slate-600 hover:border-slate-300 hover:text-[#011627]"
}`}
aria-pressed={active}
>
<div className="text-[14px] font-medium tracking-tight">{topic.label}</div>
</button>
);
}
function TopicPanel({ topic, callHref }: { topic: TrustTopic; callHref: string }) {
const Icon = topic.icon;
return (
<div className="landing-shell flex h-full flex-col rounded-[1.75rem] p-5 md:p-6">
<div>
<div className="flex items-center gap-3">
<div
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ${topic.toneClassName}`}
>
<Icon size={16} />
</div>
<h2 className="max-w-2xl text-[1.2rem] font-medium tracking-tight text-[#011627] md:text-[1.35rem]">
{topic.title}
</h2>
</div>
<p className="mt-2 max-w-2xl text-[13px] leading-relaxed text-slate-600 md:text-[14px]">
{topic.panelIntro}
</p>
</div>
{topic.slug === "subprocessors" ? (
<SubprocessorRows />
) : (
<div className="mt-3 flex flex-1 flex-col gap-2">
{topic.bullets.map((bullet) => (
<div
key={bullet}
className="flex flex-1 items-center rounded-2xl border border-slate-200/70 bg-white/85 px-4 py-2 text-[13px] leading-relaxed text-slate-600"
>
{bullet}
</div>
))}
</div>
)}
<div className="mt-3 flex flex-col items-start gap-3 sm:flex-row sm:items-center">
{topic.slug === "status-page-access" ? (
<a
href={statusPageRequestHref}
className="doc-button text-sm"
{...externalLinkProps(statusPageRequestHref)}
>
Request status page
</a>
) : topic.slug === "subprocessors" ? null : (
<a href={callHref} className="doc-button text-sm" {...externalLinkProps(callHref)}>
Book a call
</a>
)}
</div>
</div>
);
}
export function LandingTrustOverview(props: SharedProps) {
const callHref = props.calUrl || "/enterprise#book";
const [activeSlug, setActiveSlug] = useState(defaultTrustTopicSlug);
const activeTopic = useMemo(
() => getTrustTopic(activeSlug) ?? trustTopics[0],
[activeSlug]
);
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={props.stars}
callUrl={callHref}
downloadHref={props.downloadHref}
/>
</div>
<main className="mx-auto flex min-h-[calc(100vh-6rem)] w-full max-w-5xl flex-1 flex-col justify-between gap-4 px-6 pb-6 md:px-8 md:pb-8">
<section className="max-w-4xl pt-2 md:pt-3">
<h1 className="max-w-4xl text-4xl font-medium leading-[1.05] tracking-tight md:text-[2.7rem] lg:text-[3rem]">
Openwork's Trust Center
</h1>
<p className="mt-3 max-w-4xl text-[15px] leading-relaxed text-slate-600 md:text-[16px]">
Moving at startup speed without compromising on enterprise protection.
We treat security not as an afterthought, but as a foundational pillar of everything we build.
This mindset drives our development processes, infrastructure decisions, and organizational policies.
We treat the data entrusted to us with the utmost care and responsibility.
</p>
</section>
<section className="flex flex-col gap-4 xl:grid xl:grid-cols-[220px_minmax(0,1fr)]">
{/* Mobile: horizontal scrollable chip row */}
<div className="flex gap-2 overflow-x-auto pb-1 [&::-webkit-scrollbar]:hidden xl:hidden">
{trustTopics.map((topic) => (
<TopicChip
key={topic.slug}
topic={topic}
active={topic.slug === activeTopic.slug}
onSelect={setActiveSlug}
/>
))}
</div>
{/* Desktop: sidebar rail */}
<div className="landing-shell hidden rounded-[1.75rem] p-5 xl:block">
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">
Topics
</div>
<div className="grid gap-2">
{trustTopics.map((topic) => (
<TopicRailItem
key={topic.slug}
topic={topic}
active={topic.slug === activeTopic.slug}
onSelect={setActiveSlug}
/>
))}
</div>
</div>
<TopicPanel topic={activeTopic} callHref={callHref} />
</section>
<SiteFooter />
</main>
</div>
</div>
);
}

View File

@@ -33,13 +33,16 @@ export function SiteFooter() {
<Link href="/enterprise" className="transition-colors hover:text-gray-800">
Enterprise
</Link>
<Link href="/trust" className="transition-colors hover:text-gray-800">
Trust Center
</Link>
<Link href="/privacy" className="transition-colors hover:text-gray-800">
Privacy
</Link>
<Link href="/terms" className="transition-colors hover:text-gray-800">
Terms
</Link>
<div>© 2026 OpenWork Project.</div>
<div>© 2026 Different AI</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,259 @@
import {
Building2,
Database,
HardDrive,
KeyRound,
LifeBuoy,
ShieldCheck,
type LucideIcon
} from "lucide-react";
export const statusPageRequestHref =
"mailto:team@openworklabs.com?subject=Status%20page%20access%20request&body=Hi%20OpenWork%20team%2C%0A%0AWe%27re%20evaluating%20OpenWork%20and%20would%20like%20access%20to%20the%20status%20page%20for%20operational%20review.%0A%0ACompany%3A%20%5Byour%20company%5D%0AUse%20case%3A%20%5Bbrief%20description%5D%0A%0AThanks";
type TrustLink = {
label: string;
href: string;
external?: boolean;
};
export type TrustTopic = {
slug: string;
label: string;
title: string;
panelIntro: string;
bullets: string[];
icon: LucideIcon;
toneClassName: string;
links: TrustLink[];
};
export type Subprocessor = {
name: string;
purpose: string;
category: string;
location: string;
brandColor: string;
textColor: string;
initial: string;
href: string;
};
export const subprocessors: Subprocessor[] = [
{
name: "PostHog",
purpose: "Anonymous website analytics and product telemetry",
category: "Analytics",
location: "US / EU",
brandColor: "#FFF0EB",
textColor: "#C93C00",
initial: "PH",
href: "https://posthog.com"
},
{
name: "Polar",
purpose: "Subscription billing and payment processing",
category: "Payments",
location: "US",
brandColor: "#F3EEFF",
textColor: "#6D28D9",
initial: "PO",
href: "https://polar.sh"
},
{
name: "Google",
purpose: "OAuth sign-in and authentication services",
category: "Authentication",
location: "US",
brandColor: "#EAF1FB",
textColor: "#1A56C4",
initial: "G",
href: "https://google.com"
},
{
name: "GitHub",
purpose: "OAuth sign-in and source code hosting",
category: "Authentication",
location: "US",
brandColor: "#F0F0F0",
textColor: "#24292E",
initial: "GH",
href: "https://github.com"
},
{
name: "Daytona",
purpose: "Virtual sandbox infrastructure for the Cloud Service",
category: "Infrastructure",
location: "EU",
brandColor: "#E0F7FA",
textColor: "#0277BD",
initial: "D",
href: "https://daytona.io"
}
];
export const trustTopics: TrustTopic[] = [
{
slug: "self-hosted-deployment",
label: "Self-hosting",
title: "Your infra, your rules.",
panelIntro:
"Enterprise governance, regional compliance, and internal security reviews get simpler when you own the stack (and the data!). Self-host with confidence, not as a fallback.",
bullets: [
"OpenWork supports desktop-hosted, CLI-hosted, and hosted cloud server paths.",
"We help enterpises avoid lock-in with a single LLM provider and retain their data ownership.",
"Your team keeps infrastructure ownership when you deploy in your own environment.",
"Enterprise review does not require adopting a hosted-only control plane."
],
icon: Building2,
toneClassName: "bg-blue-50 text-blue-700",
links: [
{ label: "Enterprise", href: "/enterprise" },
{
label: "Infrastructure",
href: "https://github.com/different-ai/openwork/blob/dev/INFRASTRUCTURE.md",
external: true
}
]
},
{
slug: "local-first-workflows",
label: "Local-first workflows",
title: "Local-first by design.",
panelIntro:
"Most AI tooling defaults to cloud and asks you to opt out. We are local-first, and cloud connections are explicit choices your team makes, not hidden defaults.",
bullets: [
"The desktop-hosted app/server path is a first-class way to run OpenWork.",
"Hosted and self-hosted modes share the same user-level connect flow instead of separate products.",
"OpenWork stays open, local-first, and standards-based in the product vision.",
"Runtime boundaries stay legible and transparent for enterprise review."
],
icon: HardDrive,
toneClassName: "bg-emerald-50 text-emerald-700",
links: [
{
label: "Vision",
href: "https://github.com/different-ai/openwork/blob/dev/VISION.md",
external: true
},
{
label: "Architecture",
href: "https://github.com/different-ai/openwork/blob/dev/ARCHITECTURE.md",
external: true
}
]
},
{
slug: "provider-and-key-control",
label: "BYOK",
title: "Bring your own keys.",
panelIntro:
"Keep your existing model provider agreements intact. When you connect directly with your own credentials, your usage stays between you and your provider.",
bullets: [
"We do not collect or analyze usage or performance analytics. No telemetry without consent.",
"Individual Teams can keep provider choice aligned with internal approvals and procurement.",
"Third-party model providers connected with your own credentials are governed by their own terms.",
"We also support custom proxy gateways such as LiteLLM and Cloudflare AI Gateway. "
],
icon: KeyRound,
toneClassName: "bg-violet-50 text-violet-700",
links: [
{ label: "Privacy", href: "/privacy" },
{ label: "Terms", href: "/terms" }
]
},
{
slug: "data-residency-controls",
label: "Data residency",
title: "The Data stays where your company or customers are.",
panelIntro:
"Data regulations shouldn't restrict innovation. Self-hosted environments give your team full authority over region, network boundary, and egress policy without negotiating with a control plane.",
bullets: [
"We let you choose your own data Residency instead of imposing ours.",
"Self-hosted environments keep infrastructure location under customer control.",
"OpenWork avoids forcing a cloud-only lock-in model for teams that need residency control.",
"Provider choice and deployment choice stay separate for cleaner review."
],
icon: Database,
toneClassName: "bg-cyan-50 text-cyan-700",
links: [
{
label: "Infrastructure",
href: "https://github.com/different-ai/openwork/blob/dev/INFRASTRUCTURE.md",
external: true
},
{ label: "Privacy", href: "/privacy" }
]
},
{
slug: "incident-response",
label: "Incident response",
title: "We'll notify of any major security incident withtin 72 hours.",
panelIntro:
"We commit to a 3-day acknowledgment and notification of any major security incident. By default, we also commit to a 7-day triage and resolution for high priority issues.",
bullets: [
"Security issues can be reported privately or via a github issue if you're a security researcher.",
"The public security policy asks reporters to include impact and reproduction details.",
"OpenWork commits to acknowledge receipt within 3 business days.",
"OpenWork commits to share an initial triage status within 7 business days."
],
icon: ShieldCheck,
toneClassName: "bg-amber-50 text-amber-700",
links: [
{
label: "Security policy",
href: "https://github.com/different-ai/openwork/blob/dev/SECURITY.md",
external: true
},
{
label: "Support",
href: "https://github.com/different-ai/openwork/blob/dev/SUPPORT.md",
external: true
}
]
},
{
slug: "status-page-access",
label: "Status page access",
title: "Real operational data, on request.",
panelIntro:
"We uphold SLAs, uptime and other reliability parameters via measurable metrics, not a marketing copy.",
bullets: [
"Status information is available by request.",
"Operational review can happen without padded uptime claims.",
"Request access to live status data during procurement so you can evaluate our reliability.",
"Feel free to request access to our status page by clicking at the button below:"
],
icon: LifeBuoy,
toneClassName: "bg-rose-50 text-rose-700",
links: [
{ label: "Request status page", href: statusPageRequestHref, external: true },
{ label: "Enterprise", href: "/enterprise" }
]
},
{
slug: "subprocessors",
label: "Subprocessors",
title: "No hidden third parties.",
panelIntro:
"Every vendor that touches data in the OpenWork cloud path is named in the privacy policy. Procurement teams can evaluate each subprocessor's data handling before signing, not after.",
bullets: [
"PostHog handles anonymous website analytics.",
"Polar handles subscription billing and payment processing.",
"Google and GitHub provide OAuth sign-in services.",
"Daytona provides virtual sandbox infrastructure for the Cloud Service."
],
icon: ShieldCheck,
toneClassName: "bg-slate-100 text-slate-700",
links: [
{ label: "Privacy", href: "/privacy" },
{ label: "Terms", href: "/terms" }
]
}
];
export const defaultTrustTopicSlug = trustTopics[0].slug;
export function getTrustTopic(slug: string) {
return trustTopics.find((topic) => topic.slug === slug);
}

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB