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
53 changes: 12 additions & 41 deletions apps/website/src/components/landing/DemoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// apps/website/src/components/landing/DemoModal.tsx
'use client';
import { useEffect, useRef } from 'react';
import { tokens } from '@threadplane/design-tokens';
import { trackExternalLinkClick } from '../../lib/analytics/client';

type TabKey = 'langgraph' | 'ag-ui';
Expand Down Expand Up @@ -62,67 +61,39 @@ export function DemoModal({ open, onClose, tabs, active, onActive }: DemoModalPr
aria-modal="true"
aria-label="Live demo"
onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
style={{
position: 'fixed', inset: 0, zIndex: 100,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'rgba(16,18,32,.55)',
animation: 'demoModalBackdrop .16s ease-out',
}}
className="demo-modal"
>
<style>{`
@keyframes demoModalBackdrop { from { opacity: 0 } to { opacity: 1 } }
@keyframes demoModalFrame { from { transform: scale(.96); opacity:.6 } to { transform: scale(1); opacity:1 } }
.demo-modal__frame {
width: min(96vw, calc(90vh * 16 / 10));
background: ${tokens.surfaces.surface};
border-radius: ${tokens.radius.lg};
box-shadow: 0 24px 60px rgba(0,0,0,.45);
overflow: hidden;
display: flex; flex-direction: column;
animation: demoModalFrame .16s ease-out;
}
.demo-modal__body { width: 100%; aspect-ratio: 16 / 10; background: #15161f; }
@media (max-width: 640px) {
.demo-modal__frame { width: 100vw; height: 100dvh; border-radius: 0; }
.demo-modal__body { aspect-ratio: auto; flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
[role="dialog"], .demo-modal__frame { animation: none !important; }
}
`}</style>

<div ref={frameRef} className="demo-modal__frame">
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: tokens.surfaces.surfaceTinted, borderBottom: `1px solid ${tokens.surfaces.border}` }}>
<div style={{ display: 'flex', gap: 5 }} aria-hidden="true">
{[0, 1, 2].map((i) => <span key={i} style={{ width: 8, height: 8, borderRadius: '50%', background: '#cdd2df' }} />)}
<div className="demo-modal__titlebar">
<div className="demo-modal__dots" aria-hidden="true">
{[0, 1, 2].map((i) => <span key={i} className="demo-modal__dot" />)}
</div>
<div role="tablist" aria-label="Demo backend" style={{ display: 'flex', gap: 5 }}>
<div role="tablist" aria-label="Demo backend" className="demo-modal__tabs">
{tabs.map((t) => {
const on = t.key === active;
return (
<button key={t.key} role="tab" aria-selected={on} onClick={() => onActive(t.key)}
style={{ fontFamily: 'Inter, sans-serif', fontSize: 12, fontWeight: 600, padding: '6px 12px', borderRadius: 7, border: 'none', cursor: 'pointer',
background: on ? tokens.colors.accent : tokens.colors.accentSurface, color: on ? tokens.colors.textInverted : tokens.colors.textMuted }}>
className="demo-modal__tab">
{t.tabLabel}
</button>
);
})}
</div>
<span style={{ flex: 1, textAlign: 'center', fontFamily: tokens.typography.fontMono, fontSize: 12, fontWeight: 600, color: tokens.colors.textMuted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{tab.url}</span>
<span className="demo-modal__url">{tab.url}</span>
<button ref={closeBtnRef} onClick={onClose} aria-label="Close demo"
style={{ width: 28, height: 28, borderRadius: 7, border: 'none', cursor: 'pointer', background: tokens.colors.accentSurface, color: tokens.colors.textSecondary, fontSize: 16, lineHeight: 1 }}>&#215;</button>
className="demo-modal__close">&#215;</button>
</div>

<div className="demo-modal__body">
<iframe src={tab.href} title={`${tab.tabLabel} live demo`}
style={{ width: '100%', height: '100%', border: 'none', display: 'block' }} />
className="demo-modal__iframe" />
</div>

<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 14px', borderTop: `1px solid ${tokens.surfaces.border}`, background: tokens.surfaces.surface }}>
<span style={{ fontFamily: 'Inter, sans-serif', fontSize: 12, color: tokens.colors.textMuted }}>Esc or click outside to close &middot; no signup</span>
<div className="demo-modal__footer">
<span className="demo-modal__hint">Esc or click outside to close &middot; no signup</span>
<a href={tab.href} target="_blank" rel="noopener noreferrer"
onClick={() => trackExternalLinkClick(tab.href, { surface: 'home_demo', cta_id: `home_demo_full_${tab.key.replace(/-/g, '_')}`, cta_text: 'Open the full demo' })}
style={{ fontFamily: 'Inter, sans-serif', fontSize: 12, fontWeight: 600, color: tokens.colors.accent, textDecoration: 'none' }}>Open the full demo &#8599;</a>
className="demo-modal__open-link">Open the full demo &#8599;</a>
</div>
</div>
</div>
Expand Down
20 changes: 9 additions & 11 deletions apps/website/src/components/landing/DemoShowcase.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';
import { useState } from 'react';
import { tokens } from '@threadplane/design-tokens';
import { ClipPlayer } from '../ui/ClipPlayer';
import { TabGroup } from '../ui/TabGroup';
import { Button } from '../ui/Button';
Expand Down Expand Up @@ -35,12 +34,12 @@ export function DemoShowcase() {
};

return (
<div style={{ maxWidth: 760, margin: '0 auto', textAlign: 'center' }}>
<p style={{ fontFamily: tokens.typography.fontMono, fontSize: 12, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: tokens.colors.accent, margin: 0 }}>See it running</p>
<h2 style={{ fontFamily: tokens.typography.h2.family, fontSize: tokens.typography.h2.size, lineHeight: tokens.typography.h2.line, fontWeight: 700, color: tokens.colors.textPrimary, margin: '10px 0 8px', letterSpacing: '-0.015em' }}>
<div className="demo-showcase">
<p className="demo-showcase__eyebrow">See it running</p>
<h2 className="demo-showcase__heading">
One chat UI. Two runtimes. Same code.
</h2>
<p style={{ fontFamily: tokens.typography.bodyLg.family, fontSize: tokens.typography.bodyLg.size, lineHeight: tokens.typography.bodyLg.line, color: tokens.colors.textSecondary, maxWidth: 560, margin: '0 auto 20px' }}>
<p className="demo-showcase__subhead">
The identical Threadplane chat surface, running live against a LangGraph backend and an AG-UI backend. Switch tabs to compare — the front end never changes.
</p>

Expand All @@ -62,10 +61,9 @@ export function DemoShowcase() {
clip={m}
overlay={
<button onClick={() => launch(m)} aria-label={`Launch ${m.tabLabel} live demo`}
style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10,
background: 'linear-gradient(180deg, rgba(16,18,32,.15), rgba(16,18,32,.45))', border: 'none', cursor: 'pointer' }}>
<span style={{ width: 56, height: 56, borderRadius: '50%', background: 'rgba(255,255,255,.95)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#15161f', fontSize: 22 }}>&#9654;</span>
<span style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600, fontSize: 13, color: '#fff', background: 'rgba(0,0,0,.5)', padding: '8px 14px', borderRadius: 8 }}>Launch live demo</span>
className="demo-showcase__launch-btn">
<span className="demo-showcase__launch-icon">&#9654;</span>
<span className="demo-showcase__launch-label">Launch live demo</span>
</button>
}
/>
Expand All @@ -74,13 +72,13 @@ export function DemoShowcase() {
onSelect={(pane) => setActive(pane.id as TabKey)}
/>

<div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap', marginTop: 18 }}>
<div className="demo-showcase__cta-row">
<DemoCtaPair surface="home_demo" size="lg" />
<Button variant="ghost" size="lg" href="https://cockpit.threadplane.ai" target="_blank" rel="noopener noreferrer">
See each feature in action →
</Button>
</div>
<p style={{ fontFamily: tokens.typography.caption.family, fontSize: tokens.typography.caption.size, color: tokens.colors.textMuted, margin: '14px 0 0' }}>
<p className="demo-showcase__caption">
Video loops instantly · click Launch to open the live, interactive demo · no signup
</p>
<DemoModal
Expand Down
123 changes: 15 additions & 108 deletions apps/website/src/components/landing/Differentiator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function CheckIcon() {
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
style={{ flexShrink: 0, marginTop: 4 }}
className="differentiator-check-icon"
>
<path d="M3 8.5l3.5 3.5L13 5" />
</svg>
Expand All @@ -89,125 +89,41 @@ export function Differentiator() {
<Section
surface="canvas"
ariaLabelledBy="differentiator-heading"
style={{ paddingTop: 72 }}
className="differentiator-section"
>
<Container>
<div style={{ maxWidth: 1020, margin: '0 auto 44px', textAlign: 'center' }}>
<Eyebrow tone="accent" style={{ marginBottom: 16 }}>Why this exists</Eyebrow>
<h2
id="differentiator-heading"
style={{
fontFamily: tokens.typography.h2.family,
fontSize: tokens.typography.h2.size,
lineHeight: tokens.typography.h2.line,
fontWeight: 700,
color: tokens.colors.textPrimary,
margin: 0,
marginBottom: 20,
letterSpacing: '-0.015em',
}}
>
<div className="differentiator-intro">
<Eyebrow tone="accent" className="differentiator-eyebrow">Why this exists</Eyebrow>
<h2 id="differentiator-heading" className="differentiator-heading">
Everything an Angular agent needs once the demo works.
</h2>
<p
style={{
fontFamily: tokens.typography.bodyLg.family,
fontSize: tokens.typography.bodyLg.size,
lineHeight: tokens.typography.bodyLg.line,
color: tokens.colors.textSecondary,
margin: '0 auto',
maxWidth: 760,
}}
>
<p className="differentiator-subhead">
A streaming chat tutorial takes an hour. Shipping a real agent — durable, interruptible, observable, on your design system — takes most teams six months. Threadplane gives the Angular surface that the rest of the stack assumes you&apos;ve already built.
</p>
</div>

<ul
style={{
listStyle: 'none',
padding: 0,
margin: '0 auto',
maxWidth: 980,
borderTop: `1px solid ${tokens.surfaces.border}`,
}}
>
<ul className="differentiator-list">
{PRODUCTION_ROWS.map((row) => (
<li
key={row.need}
className="why-row"
style={{
display: 'flex',
alignItems: 'flex-start',
gap: 16,
padding: '18px 8px',
borderBottom: `1px solid ${tokens.surfaces.border}`,
}}
>
<li key={row.need} className="why-row">
<CheckIcon />
<div
className="why-row__body"
style={{
flex: 1,
display: 'flex',
gap: 16,
alignItems: 'baseline',
flexWrap: 'wrap',
}}
>
<div style={{ flex: '1 1 200px', minWidth: 0 }}>
<span
style={{
fontFamily: tokens.typography.body.family,
fontSize: tokens.typography.body.size,
lineHeight: tokens.typography.body.line,
fontWeight: 600,
color: tokens.colors.textPrimary,
marginRight: 8,
}}
>
<div className="why-row__body">
<div className="why-row__text">
<span className="why-row__need">
{row.need}
</span>
<span
style={{
fontFamily: tokens.typography.body.family,
fontSize: tokens.typography.body.size,
lineHeight: tokens.typography.body.line,
color: tokens.colors.textSecondary,
}}
>
<span className="why-row__description">
{row.description}
</span>
</div>
<code
style={{
fontFamily: tokens.typography.fontMono,
fontSize: 13,
color: tokens.colors.textMuted,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%',
}}
>
<code className="why-row__primitive">
{row.primitive}
</code>
</div>
</li>
))}
</ul>

<p
style={{
margin: '24px auto 0',
maxWidth: 980,
textAlign: 'center',
fontFamily: tokens.typography.body.family,
fontSize: tokens.typography.body.size,
lineHeight: tokens.typography.body.line,
color: tokens.colors.textMuted,
}}
>
<p className="differentiator-footer">
Want help walking these on your codebase?{' '}
<a
href="/pilot-to-prod"
Expand All @@ -219,20 +135,11 @@ export function Differentiator() {
cta_text: 'Pilot to Prod',
})
}
style={{ color: tokens.colors.accent, textDecoration: 'none', fontWeight: 600 }}
className="differentiator-footer-link"
>
Pilot to Prod →
</a>
</p>

<style>{`
@media (max-width: 640px) {
.why-row__body {
flex-direction: column !important;
gap: 6px !important;
}
}
`}</style>
</Container>
</Section>
);
Expand Down
Loading
Loading