|
1 | 1 | 'use client'; |
2 | 2 | import { useState } from 'react'; |
3 | 3 | import { tokens } from '@threadplane/design-tokens'; |
4 | | -import { BrowserFrame } from '../ui/BrowserFrame'; |
| 4 | +import { ClipPlayer } from '../ui/ClipPlayer'; |
5 | 5 | import { TabGroup } from '../ui/TabGroup'; |
6 | 6 | import { Button } from '../ui/Button'; |
7 | 7 | import { DemoCtaPair } from './DemoCtaPair'; |
8 | 8 | import { DemoModal } from './DemoModal'; |
9 | 9 | import { trackCtaClick } from '../../lib/analytics/client'; |
10 | 10 | import { DEMOS } from '../../lib/demos'; |
11 | | -import { DEMO_CDN } from '../../lib/demo-media'; |
| 11 | +import { AG_UI_CLIP, LANGGRAPH_CLIP, type DemoClip } from '../../lib/demo-media'; |
12 | 12 |
|
13 | 13 | type TabKey = (typeof DEMOS)[number]['key']; |
14 | 14 |
|
15 | | -interface DemoMedia { |
| 15 | +/** A runtime tab: the shared clip plus how this section labels and links it. */ |
| 16 | +interface DemoMedia extends DemoClip { |
16 | 17 | key: TabKey; |
17 | 18 | tabLabel: string; |
18 | | - url: string; |
19 | | - videoMp4: string; |
20 | | - videoWebm: string; |
21 | | - poster: string; |
22 | 19 | href: string; |
23 | 20 | } |
24 | 21 |
|
25 | 22 |
|
26 | 23 | const MEDIA: DemoMedia[] = [ |
27 | | - { 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 }, |
28 | | - { 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 }, |
| 24 | + { ...LANGGRAPH_CLIP, key: 'langgraph', tabLabel: 'LangGraph', href: DEMOS.find((d) => d.key === 'langgraph')!.href }, |
| 25 | + { ...AG_UI_CLIP, key: 'ag-ui', tabLabel: 'AG-UI', href: DEMOS.find((d) => d.key === 'ag-ui')!.href }, |
29 | 26 | ]; |
30 | 27 |
|
31 | 28 | export function DemoShowcase() { |
@@ -61,21 +58,17 @@ export function DemoShowcase() { |
61 | 58 | id: m.key, |
62 | 59 | label: m.tabLabel, |
63 | 60 | content: ( |
64 | | - <BrowserFrame url={m.url} elevation="lg"> |
65 | | - <div style={{ position: 'relative', width: '100%', aspectRatio: '16 / 10', background: '#15161f' }}> |
66 | | - <video autoPlay muted loop playsInline poster={m.poster} |
67 | | - style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}> |
68 | | - <source src={m.videoWebm} type="video/webm" /> |
69 | | - <source src={m.videoMp4} type="video/mp4" /> |
70 | | - </video> |
| 61 | + <ClipPlayer |
| 62 | + clip={m} |
| 63 | + overlay={ |
71 | 64 | <button onClick={() => launch(m)} aria-label={`Launch ${m.tabLabel} live demo`} |
72 | 65 | style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, |
73 | 66 | background: 'linear-gradient(180deg, rgba(16,18,32,.15), rgba(16,18,32,.45))', border: 'none', cursor: 'pointer' }}> |
74 | 67 | <span style={{ width: 56, height: 56, borderRadius: '50%', background: 'rgba(255,255,255,.95)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#15161f', fontSize: 22 }}>▶</span> |
75 | 68 | <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> |
76 | 69 | </button> |
77 | | - </div> |
78 | | - </BrowserFrame> |
| 70 | + } |
| 71 | + /> |
79 | 72 | ), |
80 | 73 | }))} |
81 | 74 | onSelect={(pane) => setActive(pane.id as TabKey)} |
|
0 commit comments