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
20 changes: 20 additions & 0 deletions apps/website/src/app/pilot-to-prod/page.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import PilotToProdPage from './page';

vi.mock('../../lib/analytics/client', () => ({
track: vi.fn(),
trackCtaClick: vi.fn(),
trackExternalLinkClick: vi.fn(),
trackWhitepaperDownloadClick: vi.fn(),
}));

describe('PilotToProdPage', () => {
it('renders rail-kicked section headers and the outcomes ledger', () => {
const { container } = render(<PilotToProdPage />);
expect(container.querySelectorAll('.pilot-rail2')).toHaveLength(1);
expect(container.querySelectorAll('.pilot-outcome-row')).toHaveLength(4);
expect(container.querySelectorAll('.pilot-outcomes-grid')).toHaveLength(0);
expect(screen.getByRole('heading', { level: 1 })).toBeTruthy();
});
});
28 changes: 13 additions & 15 deletions apps/website/src/app/pilot-to-prod/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Section } from '../../components/ui/Section';
import { Eyebrow } from '../../components/ui/Eyebrow';
import { Button } from '../../components/ui/Button';
import { Pill } from '../../components/ui/Pill';
import { Card } from '../../components/ui/Card';
import { FeatureBlock } from '../../components/landing/FeatureBlock';
import { BrowserFrame } from '../../components/ui/BrowserFrame';
import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock';
Expand Down Expand Up @@ -147,26 +146,25 @@ export default function PilotToProdPage() {
<Section surface="tinted" ariaLabelledBy="outcomes-heading">
<Container>
<div className="pilot-section-header">
<Eyebrow tone="accent" className="pilot-eyebrow-spaced">What you walk away with</Eyebrow>
<div className="pilot-rail2">
<Eyebrow tone="accent" className="pilot-eyebrow-tight">What you walk away with</Eyebrow>
<span className="pilot-rail2-line" aria-hidden="true" />
</div>
<h2 id="outcomes-heading" className="pilot-h2">
A working agent. A trained team. A runbook.
</h2>
</div>
<div className="pilot-outcomes-grid">
<div className="pilot-outcome-rows">
{[
{ t: 'Working demo', d: 'Live on your data, in your app — not in a sandbox.' },
{ t: 'Hardened patterns', d: 'Error/fallback/observability built in from the start.' },
{ t: 'Deploy-ready', d: 'Integrated with your CI/CD, your auth, your data.' },
{ t: 'Trained team', d: 'Your engineers own the framework and the runbook.' },
{ claim: 'Live on your data, in your app — not in a sandbox.', tail: 'working demo' },
{ claim: 'Error, fallback, and observability built in from the start.', tail: 'hardened patterns' },
{ claim: 'Integrated with your CI/CD, your auth, your data.', tail: 'deploy-ready' },
{ claim: 'Your engineers own the framework and the runbook.', tail: 'trained team' },
].map((o) => (
<Card key={o.t} padding="lg">
<h3 className="pilot-outcome-h3">
{o.t}
</h3>
<p className="pilot-outcome-body">
{o.d}
</p>
</Card>
<div className="pilot-outcome-row" key={o.tail}>
<p className="pilot-outcome-claim">{o.claim}</p>
<p className="pilot-outcome-tail">{o.tail}</p>
</div>
))}
</div>
</Container>
Expand Down
31 changes: 31 additions & 0 deletions apps/website/src/app/solutions/[slug]/page.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import SolutionPage from './page';
import { getAllSolutionSlugs } from '../../../lib/solutions-data';

vi.mock('../../../lib/analytics/client', () => ({
track: vi.fn(),
trackCtaClick: vi.fn(),
trackExternalLinkClick: vi.fn(),
}));

vi.mock('../../../components/solutions/SolutionCodeBlock', () => ({
SolutionCodeBlock: () => null,
}));

vi.mock('../../../components/solutions/SolutionDemoBlock', () => ({
SolutionDemoBlock: () => null,
}));

describe('SolutionPage', () => {
it('renders navy rails with no per-solution accent theming', async () => {
const slug = getAllSolutionSlugs()[0];
const ui = await SolutionPage({ params: Promise.resolve({ slug }) });
const { container } = render(ui);
expect(container.querySelectorAll('[data-accent-text]')).toHaveLength(0);
expect(container.querySelectorAll('[style*="--accent"]')).toHaveLength(0);
expect(container.querySelectorAll('.sol-page-rail').length).toBeGreaterThanOrEqual(3);
expect(container.querySelector('.sol-page-metric')).toBeTruthy();
expect(screen.getByRole('heading', { level: 1 })).toBeTruthy();
});
});
35 changes: 21 additions & 14 deletions apps/website/src/app/solutions/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ export async function generateMetadata({ params }: PageProps) {
};
}

function PainPoints({ items, accent }: { items: SolutionPainPoint[]; accent: string }) {
function PainPoints({ items }: { items: SolutionPainPoint[] }) {
return (
<Section surface="canvas" ariaLabelledBy="problem-heading">
<Container>
<div className="sol-page-section-header">
<Eyebrow style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-page-eyebrow-tight">The problem</Eyebrow>
<div className="sol-page-rail">
<Eyebrow tone="accent" className="sol-page-eyebrow-tight">The problem</Eyebrow>
<span className="sol-page-rail-line" aria-hidden="true" />
</div>
<h2 id="problem-heading" className="sol-page-h2">
Why this is hard today.
</h2>
Expand All @@ -78,17 +81,18 @@ function PainPoints({ items, accent }: { items: SolutionPainPoint[]; accent: str
function Architecture({
intro,
layers,
accent,
}: {
intro: string;
layers: ArchitectureLayer[];
accent: string;
}) {
return (
<Section surface="tinted" ariaLabelledBy="arch-heading">
<Container>
<div className="sol-page-section-header">
<Eyebrow style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-page-eyebrow-tight">Architecture</Eyebrow>
<div className="sol-page-rail">
<Eyebrow tone="accent" className="sol-page-eyebrow-tight">Architecture</Eyebrow>
<span className="sol-page-rail-line" aria-hidden="true" />
</div>
<h2 id="arch-heading" className="sol-page-h2 sol-page-h2-spaced">
How the three libraries compose.
</h2>
Expand Down Expand Up @@ -131,20 +135,23 @@ function Architecture({
);
}

function Capabilities({ items, accent }: { items: ProofPoint[]; accent: string }) {
function Capabilities({ items }: { items: ProofPoint[] }) {
return (
<Section surface="canvas" ariaLabelledBy="capabilities-heading">
<Container>
<div className="sol-page-section-header">
<Eyebrow style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-page-eyebrow-tight">What you ship</Eyebrow>
<div className="sol-page-rail">
<Eyebrow tone="accent" className="sol-page-eyebrow-tight">What you ship</Eyebrow>
<span className="sol-page-rail-line" aria-hidden="true" />
</div>
<h2 id="capabilities-heading" className="sol-page-h2">
Capabilities the framework delivers.
</h2>
</div>
<div className="sol-page-grid-260">
{items.map((p) => (
<Card key={p.metric + p.label} padding="lg">
<div style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-page-metric">
<div className="sol-page-metric">
{p.metric}
</div>
<p className="sol-page-card-body">
Expand All @@ -169,7 +176,7 @@ export default async function SolutionPage({ params }: PageProps) {
<Section surface="canvas" ariaLabelledBy="solution-hero-heading">
<Container>
<div className="sol-page-hero-inner">
<Eyebrow style={{ '--accent': solution.color } as React.CSSProperties} data-accent-text className="sol-page-eyebrow-spaced">{solution.eyebrow}</Eyebrow>
<Eyebrow tone="accent" className="sol-page-eyebrow-spaced">{solution.eyebrow}</Eyebrow>
<h1 id="solution-hero-heading" className="sol-page-h1">
{solution.title}
</h1>
Expand All @@ -188,11 +195,11 @@ export default async function SolutionPage({ params }: PageProps) {
</Container>
</Section>

<PainPoints items={solution.painPoints} accent={solution.color} />
<Architecture intro={solution.architectureIntro} layers={solution.architectureLayers} accent={solution.color} />
<Capabilities items={solution.proofPoints} accent={solution.color} />
<SolutionCodeBlock code={solution.code} accent={solution.color} />
{solution.demo && <SolutionDemoBlock clip={solution.demo} accent={solution.color} />}
<PainPoints items={solution.painPoints} />
<Architecture intro={solution.architectureIntro} layers={solution.architectureLayers} />
<Capabilities items={solution.proofPoints} />
<SolutionCodeBlock code={solution.code} />
{solution.demo && <SolutionDemoBlock clip={solution.demo} />}
<WhitePaperBlock />
<FinalCTA
headline={solution.ctaHeadline}
Expand Down
5 changes: 4 additions & 1 deletion apps/website/src/app/solutions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export default function SolutionsIndexPage() {
<Section surface="canvas" ariaLabelledBy="solutions-grid-heading">
<Container>
<div className="sol-index-grid-header">
<Eyebrow className="sol-index-eyebrow-tight">By use case</Eyebrow>
<div className="sol-index-rail">
<Eyebrow className="sol-index-eyebrow-tight">By use case</Eyebrow>
<span className="sol-index-rail-line" aria-hidden="true" />
</div>
<h2 id="solutions-grid-heading" className="sol-index-h2">
Where agents earn their keep.
</h2>
Expand Down
4 changes: 2 additions & 2 deletions apps/website/src/components/solutions/SolutionCodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function highlight(block: SolutionCode) {
return codeToHtml(block.source, { lang: block.language, theme: 'tokyo-night' });
}

export async function SolutionCodeBlock({ code, accent }: { code: SolutionCodeBlocks; accent: string }) {
export async function SolutionCodeBlock({ code }: { code: SolutionCodeBlocks }) {
// Highlight every block up front: an async map inside JSX would give React
// promises to render rather than markup.
const rendered = await Promise.all(
Expand All @@ -31,7 +31,7 @@ export async function SolutionCodeBlock({ code, accent }: { code: SolutionCodeBl
<Section surface="canvas" ariaLabelledBy="solution-code-heading">
<Container>
<div className="sol-code-wrap">
<Eyebrow style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-code-eyebrow">In practice</Eyebrow>
<Eyebrow tone="accent" className="sol-code-eyebrow">In practice</Eyebrow>
<h2 id="solution-code-heading" className="sol-code-heading">
What it looks like in your codebase
</h2>
Expand Down
4 changes: 2 additions & 2 deletions apps/website/src/components/solutions/SolutionDemoBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import type { DemoClip } from '../../lib/demo-media';
* No client JS: `autoPlay muted loop playsInline` is the whole behaviour, so
* this stays a Server Component.
*/
export function SolutionDemoBlock({ clip, accent }: { clip: DemoClip; accent: string }) {
export function SolutionDemoBlock({ clip }: { clip: DemoClip }) {
return (
<Section surface="tinted" ariaLabelledBy="solution-demo-heading">
<Container>
<div className="sol-demo-wrap">
<Eyebrow style={{ '--accent': accent } as React.CSSProperties} data-accent-text className="sol-code-eyebrow">See it running</Eyebrow>
<Eyebrow tone="accent" className="sol-code-eyebrow">See it running</Eyebrow>
<h2 id="solution-demo-heading" className="sol-demo-heading">
The approval gate, in the product
</h2>
Expand Down
8 changes: 0 additions & 8 deletions apps/website/src/lib/solutions-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ export type SolutionCodeBlocks = readonly SolutionCode[];

export interface SolutionConfig {
slug: string;
color: string;
rgb: string;
eyebrow: string;
title: string;
subtitle: string;
Expand All @@ -97,8 +95,6 @@ export interface SolutionConfig {
export const SOLUTIONS: SolutionConfig[] = [
{
slug: 'compliance',
color: '#D4850F',
rgb: '212,133,15',
eyebrow: 'Compliance & Audit',
title: 'AI agents your compliance\nteam will actually approve',
subtitle: 'Human-in-the-loop approvals, auditable thread history, and deterministic testing — built into the framework, not bolted on.',
Expand Down Expand Up @@ -190,8 +186,6 @@ export const SOLUTIONS: SolutionConfig[] = [
},
{
slug: 'analytics',
color: '#0F7B8D',
rgb: '15,123,141',
eyebrow: 'Analytics & BI',
title: 'Natural language queries.\nReal-time dashboards.',
subtitle: 'Your users ask questions in plain English. The agent queries, visualizes, and streams results — all inside your Angular app.',
Expand Down Expand Up @@ -268,8 +262,6 @@ export class DashboardComponent {
},
{
slug: 'customer-support',
color: '#5B4FCF',
rgb: '91,79,207',
eyebrow: 'Customer Support',
title: 'AI agents that know when\nto escalate to a human',
subtitle: 'Resolve routine tickets autonomously, surface context instantly, and hand off to humans seamlessly — with full conversation history.',
Expand Down
Loading
Loading