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
11 changes: 2 additions & 9 deletions apps/website/src/app/docs/[library]/[section]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { tokens } from '@threadplane/design-tokens';
import { DocsSidebar } from '../../../../../components/docs/DocsSidebar';
import { MdxRenderer } from '../../../../../components/docs/MdxRenderer';
import { DocsSearch } from '../../../../../components/docs/DocsSearch';
Expand Down Expand Up @@ -86,18 +85,12 @@ export default async function DocsPage({ params }: DocsRouteProps) {
]);

return (
<div
className="flex min-h-screen overflow-x-hidden"
style={{ background: tokens.surfaces.canvas, paddingTop: 80 }}
>
<div className="flex min-h-screen overflow-x-hidden docs-shell-page">
<JsonLd data={articleData} />
<JsonLd data={breadcrumbs} />
<DocsSearch library={library as LibraryId} />
<DocsSidebar activeLibrary={library as LibraryId} activeSection={section} activeSlug={slug} />
<div
className="flex-1 flex min-w-0"
style={{ background: tokens.surfaces.surface }}
>
<div className="flex-1 flex min-w-0 docs-shell-body">
<div className="flex-1 min-w-0">
<div className="px-6 md:px-12 pt-6">
<DocsBreadcrumb library={library as LibraryId} section={section} slug={slug} title={doc.title} />
Expand Down
7 changes: 3 additions & 4 deletions apps/website/src/app/docs/choosing-an-adapter/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default function ChoosingAnAdapterPage() {
<>
<Section surface="canvas" ariaLabelledBy="choosing-an-adapter-heading">
<Container>
<div style={{ maxWidth: 720 }}>
<Eyebrow tone="accent" style={{ marginBottom: 16 }}>
<div className="adapter-hero-inner">
<Eyebrow tone="accent" className="adapter-eyebrow-spaced">
Documentation
</Eyebrow>
<div id="choosing-an-adapter-heading" />
Expand All @@ -83,10 +83,9 @@ export default function ChoosingAnAdapterPage() {
<Section surface="canvas">
<Container>
<article
className="docs-prose prose prose-slate max-w-none"
className="docs-prose prose prose-slate max-w-none adapter-article"
style={
{
maxWidth: 760,
'--tw-prose-body': tokens.colors.textSecondary,
'--tw-prose-headings': tokens.colors.textPrimary,
'--tw-prose-code': tokens.colors.accent,
Expand Down
214 changes: 57 additions & 157 deletions apps/website/src/app/docs/licensing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Link from 'next/link';
import { tokens } from '@threadplane/design-tokens';
import { Container } from '../../../components/ui/Container';
import { Section } from '../../../components/ui/Section';
import { Eyebrow } from '../../../components/ui/Eyebrow';
Expand All @@ -14,105 +13,17 @@ export const metadata = createPageMetadata({
type: 'website',
});

const headingStyle = {
fontFamily: tokens.typography.h2.family,
fontSize: tokens.typography.h2.size,
lineHeight: tokens.typography.h2.line,
fontWeight: 700,
color: tokens.colors.textPrimary,
margin: 0,
marginBottom: 16,
letterSpacing: '-0.01em',
} as const;

const h3Style = {
fontFamily: tokens.typography.h3.family,
fontSize: tokens.typography.h3.size,
lineHeight: tokens.typography.h3.line,
fontWeight: 600,
color: tokens.colors.textPrimary,
margin: 0,
marginTop: 24,
marginBottom: 8,
} as const;

const bodyStyle = {
fontFamily: tokens.typography.body.family,
fontSize: tokens.typography.body.size,
lineHeight: tokens.typography.body.line,
color: tokens.colors.textSecondary,
margin: '0 0 16px',
maxWidth: '64ch',
} as const;

const codeBlockStyle = {
fontFamily: tokens.typography.fontMono,
fontSize: 13,
lineHeight: 1.6,
background: tokens.surfaces.surfaceTinted,
border: `1px solid ${tokens.surfaces.border}`,
borderRadius: 8,
padding: 16,
overflow: 'auto',
color: tokens.colors.textPrimary,
margin: '0 0 16px',
whiteSpace: 'pre' as const,
} as const;

const tableStyle = {
width: '100%',
borderCollapse: 'collapse' as const,
fontFamily: tokens.typography.body.family,
fontSize: 14,
color: tokens.colors.textSecondary,
margin: '0 0 24px',
} as const;

const cellStyle = {
padding: '10px 12px',
borderBottom: `1px solid ${tokens.surfaces.border}`,
verticalAlign: 'top' as const,
} as const;

const headerCellStyle = {
...cellStyle,
color: tokens.colors.textPrimary,
fontWeight: 600,
background: tokens.surfaces.surfaceTinted,
} as const;

export default function LicensingPage() {
return (
<>
<Section surface="canvas" ariaLabelledBy="licensing-heading">
<Container>
<div style={{ maxWidth: 720 }}>
<Eyebrow tone="accent" style={{ marginBottom: 16 }}>Documentation</Eyebrow>
<h1
id="licensing-heading"
style={{
fontFamily: tokens.typography.h1.family,
fontSize: tokens.typography.h1.size,
lineHeight: tokens.typography.h1.line,
fontWeight: 700,
color: tokens.colors.textPrimary,
margin: 0,
marginBottom: 16,
letterSpacing: '-0.02em',
}}
>
<div className="licensing-hero-inner">
<Eyebrow tone="accent" className="licensing-eyebrow-spaced">Documentation</Eyebrow>
<h1 id="licensing-heading" className="licensing-h1">
Licensing
</h1>
<p
style={{
fontFamily: tokens.typography.bodyLg.family,
fontSize: tokens.typography.bodyLg.size,
lineHeight: tokens.typography.bodyLg.line,
color: tokens.colors.textSecondary,
margin: 0,
maxWidth: '60ch',
}}
>
<p className="licensing-subtitle">
How the Threadplane licensing model works, who needs a paid license, and how to install your license token.
</p>
</div>
Expand All @@ -121,35 +32,35 @@ export default function LicensingPage() {

<Section surface="canvas">
<Container>
<div style={{ maxWidth: 760 }}>
<h2 style={headingStyle}>The model</h2>
<p style={bodyStyle}>
<div className="licensing-section-inner">
<h2 className="licensing-h2">The model</h2>
<p className="licensing-body">
Threadplane is a suite of libraries. Most are{' '}
<strong style={{ color: tokens.colors.textPrimary }}>MIT-licensed</strong> and free for any use,
commercial or not. Only <code style={{ fontFamily: tokens.typography.fontMono }}>@threadplane/chat</code> is
<strong className="licensing-strong">MIT-licensed</strong> and free for any use,
commercial or not. Only <code className="licensing-code-inline">@threadplane/chat</code> is
dual-licensed.
</p>
<p style={bodyStyle}>
<code style={{ fontFamily: tokens.typography.fontMono }}>@threadplane/chat</code> is source-available under{' '}
<strong style={{ color: tokens.colors.textPrimary }}>PolyForm Noncommercial 1.0.0</strong> for free
noncommercial use, or a <strong style={{ color: tokens.colors.textPrimary }}>Threadplane Commercial license</strong>{' '}
<p className="licensing-body">
<code className="licensing-code-inline">@threadplane/chat</code> is source-available under{' '}
<strong className="licensing-strong">PolyForm Noncommercial 1.0.0</strong> for free
noncommercial use, or a <strong className="licensing-strong">Threadplane Commercial license</strong>{' '}
for production use inside a for-profit context. The same source ships under both — you don't get a
different build.
</p>

<h3 style={h3Style}>Do you need a paid license?</h3>
<p style={bodyStyle}>
You need a Threadplane Commercial license if you use <code style={{ fontFamily: tokens.typography.fontMono }}>@threadplane/chat</code>{' '}
<h3 className="licensing-h3">Do you need a paid license?</h3>
<p className="licensing-body">
You need a Threadplane Commercial license if you use <code className="licensing-code-inline">@threadplane/chat</code>{' '}
in any of:
</p>
<ul style={{ ...bodyStyle, paddingLeft: 24 }}>
<ul className="licensing-body licensing-list">
<li>A commercial product or SaaS</li>
<li>An internal business tool inside a for-profit company</li>
<li>An agency deliverable or paid client project</li>
<li>Any application operated by or for a for-profit entity</li>
</ul>
<p style={bodyStyle}>You do <strong style={{ color: tokens.colors.textPrimary }}>not</strong> need a paid license for:</p>
<ul style={{ ...bodyStyle, paddingLeft: 24 }}>
<p className="licensing-body">You do <strong className="licensing-strong">not</strong> need a paid license for:</p>
<ul className="licensing-body licensing-list">
<li>Personal, hobby, student, academic, or nonprofit projects</li>
<li>Public demos and tutorials</li>
<li>Open-source applications released under an OSI-approved license</li>
Expand All @@ -161,14 +72,14 @@ export default function LicensingPage() {

<Section surface="canvas">
<Container>
<div style={{ maxWidth: 760 }}>
<h2 style={headingStyle}>Install your license</h2>
<p style={bodyStyle}>
<div className="licensing-section-inner">
<h2 className="licensing-h2">Install your license</h2>
<p className="licensing-body">
After purchase, Threadplane emails a signed license token to the address on your receipt. Paste it
into your app's <code style={{ fontFamily: tokens.typography.fontMono }}>provideChat()</code>{' '}
into your app's <code className="licensing-code-inline">provideChat()</code>{' '}
configuration:
</p>
<pre style={codeBlockStyle}>{`// app.config.ts
<pre className="licensing-code-block">{`// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideChat } from '@threadplane/chat';

Expand All @@ -179,13 +90,13 @@ export const appConfig: ApplicationConfig = {
}),
],
};`}</pre>
<p style={bodyStyle}>
<p className="licensing-body">
The library verifies the token's Ed25519 signature on boot. The check is{' '}
<strong style={{ color: tokens.colors.textPrimary }}>advisory-only</strong>: a missing, expired, or
tampered token logs a <code style={{ fontFamily: tokens.typography.fontMono }}>console.warn</code> but
<strong className="licensing-strong">advisory-only</strong>: a missing, expired, or
tampered token logs a <code className="licensing-code-inline">console.warn</code> but
never blocks rendering. Verification is fully offline; no calls leave your app at runtime.
</p>
<p style={bodyStyle}>
<p className="licensing-body">
The token is safe to commit to a private repository, or to read from a build-time environment variable
for public repos. Public-repo demos are exempt from the commercial-use definition, but if your
public repo backs a commercial product, the deployed bundle does need a license.
Expand All @@ -196,41 +107,41 @@ export const appConfig: ApplicationConfig = {

<Section surface="canvas">
<Container>
<div style={{ maxWidth: 900 }}>
<h2 style={headingStyle}>Tier scoping</h2>
<p style={bodyStyle}>
<div className="licensing-section-inner licensing-section-inner-wide">
<h2 className="licensing-h2">Tier scoping</h2>
<p className="licensing-body">
Pick the tier that matches how you'll deploy. All paid tiers grant the same{' '}
Threadplane Commercial license; the difference is the scope of use and the number of seats.
</p>
<div style={{ overflow: 'auto' }}>
<table style={tableStyle}>
<div className="licensing-table-scroll">
<table className="licensing-table">
<thead>
<tr>
<th style={headerCellStyle}>Tier</th>
<th style={headerCellStyle}>Developers</th>
<th style={headerCellStyle}>Best for</th>
<th className="licensing-cell licensing-header-cell">Tier</th>
<th className="licensing-cell licensing-header-cell">Developers</th>
<th className="licensing-cell licensing-header-cell">Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td style={cellStyle}><strong style={{ color: tokens.colors.textPrimary }}>Developer Seat</strong> — $29/dev/mo or $299/dev/yr</td>
<td style={cellStyle}>Per seat</td>
<td style={cellStyle}>Solo devs, growing teams</td>
<td className="licensing-cell"><strong className="licensing-strong">Developer Seat</strong> — $29/dev/mo or $299/dev/yr</td>
<td className="licensing-cell">Per seat</td>
<td className="licensing-cell">Solo devs, growing teams</td>
</tr>
<tr>
<td style={cellStyle}><strong style={{ color: tokens.colors.textPrimary }}>Team</strong> — $149/mo or $1,495/yr</td>
<td style={cellStyle}>5 seats included</td>
<td style={cellStyle}>Small teams that want a single SKU and renewal</td>
<td className="licensing-cell"><strong className="licensing-strong">Team</strong> — $149/mo or $1,495/yr</td>
<td className="licensing-cell">5 seats included</td>
<td className="licensing-cell">Small teams that want a single SKU and renewal</td>
</tr>
<tr>
<td style={cellStyle}><strong style={{ color: tokens.colors.textPrimary }}>Enterprise</strong> — from $4,000/mo</td>
<td style={cellStyle}>Custom</td>
<td style={cellStyle}>SLA, security review, Pilot-to-Prod engagement, Slack Connect</td>
<td className="licensing-cell"><strong className="licensing-strong">Enterprise</strong> — from $4,000/mo</td>
<td className="licensing-cell">Custom</td>
<td className="licensing-cell">SLA, security review, Pilot-to-Prod engagement, Slack Connect</td>
</tr>
</tbody>
</table>
</div>
<p style={{ ...bodyStyle, fontSize: 13, color: tokens.colors.textMuted }}>
<p className="licensing-body licensing-footnote">
Paid tiers are recurring subscriptions. Annual saves ~15% vs monthly. Cancel anytime — the license
stays valid through the end of the current paid period.
</p>
Expand All @@ -240,47 +151,36 @@ export const appConfig: ApplicationConfig = {

<Section surface="canvas">
<Container>
<div style={{ maxWidth: 760 }}>
<h2 style={headingStyle}>Evaluation</h2>
<p style={bodyStyle}>
You may use <code style={{ fontFamily: tokens.typography.fontMono }}>@threadplane/chat</code> commercially
for <strong style={{ color: tokens.colors.textPrimary }}>30 calendar days</strong> from your first
<div className="licensing-section-inner">
<h2 className="licensing-h2">Evaluation</h2>
<p className="licensing-body">
You may use <code className="licensing-code-inline">@threadplane/chat</code> commercially
for <strong className="licensing-strong">30 calendar days</strong> from your first
commercial use as a good-faith evaluation. There is no telemetry, no registration, no email check —
we trust you to count the days. After 30 days you must either purchase a license or stop the
commercial use.
</p>

<h2 style={{ ...headingStyle, marginTop: 32 }}>Refunds</h2>
<p style={bodyStyle}>
<h2 className="licensing-h2 licensing-h2-spaced">Refunds</h2>
<p className="licensing-body">
If you refund a license through Stripe, the token is revoked automatically and we email a confirmation.
The verification check warns on boot. There's no clawback of the source code you already have —
everything is source-available under PolyForm Noncommercial by default.
</p>

<h2 style={{ ...headingStyle, marginTop: 32 }}>Questions</h2>
<p style={bodyStyle}>
<h2 className="licensing-h2 licensing-h2-spaced">Questions</h2>
<p className="licensing-body">
Volume pricing, multi-app licensing, audit clauses, custom terms — any of those, reach out and we'll
work it out.
</p>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 16 }}>
<div className="licensing-cta-row">
<Button variant="primary" size="md" href="/pricing">
See pricing
</Button>
<Button variant="ghost" size="md" href="/contact">
Contact us
</Button>
<Link
href="/pricing#faq"
style={{
display: 'inline-flex',
alignItems: 'center',
fontFamily: tokens.typography.body.family,
fontSize: 14,
color: tokens.colors.accent,
textDecoration: 'none',
padding: '8px 12px',
}}
>
<Link href="/pricing#faq" className="licensing-faq-link">
Pricing FAQ →
</Link>
</div>
Expand Down
Loading
Loading