diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index f3fe052bf..28cd0e628 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -3,6 +3,7 @@ import { EcosystemStrip } from '../components/landing/EcosystemStrip'; import { Differentiator } from '../components/landing/Differentiator'; import { FeatureBlock } from '../components/landing/FeatureBlock'; import { BrowserFrame } from '../components/ui/BrowserFrame'; +import { ClipPlayer } from '../components/ui/ClipPlayer'; import { DemoShowcase } from '../components/landing/DemoShowcase'; import { MediumSwitcher } from '../components/landing/MediumSwitcher'; import type { MediumPane } from '../components/landing/MediumSwitcher'; @@ -45,24 +46,7 @@ async function buildPanes(media: SectionMedia, clipUrl: string): Promise -
- -
- - ), + content: , }); } diff --git a/apps/website/src/components/landing/DemoShowcase.tsx b/apps/website/src/components/landing/DemoShowcase.tsx index 0c302052f..c7aaa6888 100644 --- a/apps/website/src/components/landing/DemoShowcase.tsx +++ b/apps/website/src/components/landing/DemoShowcase.tsx @@ -1,31 +1,28 @@ 'use client'; import { useState } from 'react'; import { tokens } from '@threadplane/design-tokens'; -import { BrowserFrame } from '../ui/BrowserFrame'; +import { ClipPlayer } from '../ui/ClipPlayer'; import { TabGroup } from '../ui/TabGroup'; import { Button } from '../ui/Button'; import { DemoCtaPair } from './DemoCtaPair'; import { DemoModal } from './DemoModal'; import { trackCtaClick } from '../../lib/analytics/client'; import { DEMOS } from '../../lib/demos'; -import { DEMO_CDN } from '../../lib/demo-media'; +import { AG_UI_CLIP, LANGGRAPH_CLIP, type DemoClip } from '../../lib/demo-media'; type TabKey = (typeof DEMOS)[number]['key']; -interface DemoMedia { +/** A runtime tab: the shared clip plus how this section labels and links it. */ +interface DemoMedia extends DemoClip { key: TabKey; tabLabel: string; - url: string; - videoMp4: string; - videoWebm: string; - poster: string; href: string; } const MEDIA: DemoMedia[] = [ - { key: 'langgraph', tabLabel: 'LangGraph', url: 'demo.threadplane.ai', videoMp4: `${DEMO_CDN}/langgraph-demo.mp4`, videoWebm: `${DEMO_CDN}/langgraph-demo.webm`, poster: `${DEMO_CDN}/langgraph-demo-poster.webp`, href: DEMOS.find((d) => d.key === 'langgraph')!.href }, - { key: 'ag-ui', tabLabel: 'AG-UI', url: 'ag-ui.threadplane.ai', videoMp4: `${DEMO_CDN}/ag-ui-demo.mp4`, videoWebm: `${DEMO_CDN}/ag-ui-demo.webm`, poster: `${DEMO_CDN}/ag-ui-demo-poster.webp`, href: DEMOS.find((d) => d.key === 'ag-ui')!.href }, + { ...LANGGRAPH_CLIP, key: 'langgraph', tabLabel: 'LangGraph', href: DEMOS.find((d) => d.key === 'langgraph')!.href }, + { ...AG_UI_CLIP, key: 'ag-ui', tabLabel: 'AG-UI', href: DEMOS.find((d) => d.key === 'ag-ui')!.href }, ]; export function DemoShowcase() { @@ -61,21 +58,17 @@ export function DemoShowcase() { id: m.key, label: m.tabLabel, content: ( - -
- + 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' }}> Launch live demo -
-
+ } + /> ), }))} onSelect={(pane) => setActive(pane.id as TabKey)} diff --git a/apps/website/src/components/solutions/SolutionDemoBlock.tsx b/apps/website/src/components/solutions/SolutionDemoBlock.tsx index 3fbf1fe2a..85c803e96 100644 --- a/apps/website/src/components/solutions/SolutionDemoBlock.tsx +++ b/apps/website/src/components/solutions/SolutionDemoBlock.tsx @@ -3,7 +3,7 @@ import { tokens } from '@threadplane/design-tokens'; import { Container } from '../ui/Container'; import { Section } from '../ui/Section'; import { Eyebrow } from '../ui/Eyebrow'; -import { BrowserFrame } from '../ui/BrowserFrame'; +import { ClipPlayer } from '../ui/ClipPlayer'; import type { DemoClip } from '../../lib/demo-media'; /** @@ -53,27 +53,7 @@ export function SolutionDemoBlock({ clip, accent }: { clip: DemoClip; accent: st {clip.caption}

- -
- {/* - Silent, decorative loop. `aria-label` rather than captions: there - is no audio track and no narration to caption, and the prose - above already states what the clip shows. - */} - -
-
+

+

+ + {overlay} +
+ + ); +} diff --git a/apps/website/src/lib/demo-media.ts b/apps/website/src/lib/demo-media.ts index 44fcd10a2..940aecc45 100644 --- a/apps/website/src/lib/demo-media.ts +++ b/apps/website/src/lib/demo-media.ts @@ -41,8 +41,8 @@ export const HITL_CLIP: DemoClip = { /** * The LangGraph streaming demo, recorded on the canonical demo shell. Exported - * so the section switcher does not add another hardcoded copy of these URLs. - * `DemoShowcase` still declares its own; consolidating it is tracked separately. + * Shared by the homepage showcase and the section switcher, so a recut or a + * store move changes one place rather than three. */ export const LANGGRAPH_CLIP: DemoClip = { caption: 'Tokens stream into the Angular surface as the agent produces them.', @@ -52,6 +52,18 @@ export const LANGGRAPH_CLIP: DemoClip = { poster: `${DEMO_CDN}/langgraph-demo-poster.webp`, }; +/** + * The AG-UI runtime demo. Paired with `LANGGRAPH_CLIP` so the homepage showcase + * can state "same front end, two runtimes" from one place. + */ +export const AG_UI_CLIP: DemoClip = { + caption: 'The same Threadplane chat surface, running against an AG-UI backend.', + url: 'ag-ui.threadplane.ai', + videoMp4: `${DEMO_CDN}/ag-ui-demo.mp4`, + videoWebm: `${DEMO_CDN}/ag-ui-demo.webm`, + poster: `${DEMO_CDN}/ag-ui-demo-poster.webp`, +}; + /** * Generative UI: the agent emits a json-render spec and the demo mounts it as * real Angular components. Recorded by