@open-aippt/cli 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3406 @@
1
+ import type { DesignSystem, Page, SlideMeta } from '@open-aippt/core';
2
+
3
+ import claudeLogo from './assets/claude.svg';
4
+ import cloudflareLogo from './assets/cloudflare.svg';
5
+ import geminiLogo from './assets/gemini.svg';
6
+ import codexLogo from './assets/openai.svg';
7
+ import opencodeLogo from './assets/opencode.svg';
8
+ import vercelLogo from './assets/vercel.svg';
9
+ import zeaburLogo from './assets/zeabur.svg';
10
+
11
+ // ─── Panel-tweakable design tokens ────────────────────────────────────────────
12
+ // Edit live from the Design panel; consumed via `var(--osd-*)` in inline styles.
13
+ export const design: DesignSystem = {
14
+ palette: {
15
+ bg: '#08090a',
16
+ text: '#f7f8f8',
17
+ accent: '#7170ff',
18
+ },
19
+ fonts: {
20
+ display: '"Inter", "SF Pro Display", system-ui, -apple-system, sans-serif',
21
+ body: '"Inter", "SF Pro Display", system-ui, -apple-system, sans-serif',
22
+ },
23
+ typeScale: {
24
+ hero: 168,
25
+ body: 36,
26
+ },
27
+ radius: 16,
28
+ };
29
+
30
+ // ─── Local (non-tweakable) constants ──────────────────────────────────────────
31
+ // Mirrors of the panel tokens for template-string / arithmetic use, plus the
32
+ // secondary palette and the mono font (kept hard-coded — outside what the
33
+ // Design panel currently exposes).
34
+ const palette = {
35
+ bg: design.palette.bg,
36
+ text: design.palette.text,
37
+ accent: design.palette.accent,
38
+ surface: '#0e0f12',
39
+ surfaceHi: '#14161a',
40
+ surfaceMax: '#1a1c21',
41
+ textSoft: '#c7c9d1',
42
+ muted: '#6f727c',
43
+ dim: '#3e4048',
44
+ border: 'rgba(255,255,255,0.07)',
45
+ borderBright: 'rgba(255,255,255,0.14)',
46
+ accentSoft: '#a3a0ff',
47
+ accent2: '#5e6ad2',
48
+ mint: '#68cc9a',
49
+ amber: '#e0b25c',
50
+ inspect: '#3b82f6',
51
+ inspectFill: 'rgba(59,130,246,0.10)',
52
+ };
53
+
54
+ const font = {
55
+ sans: design.fonts.body,
56
+ display: design.fonts.display,
57
+ mono: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace',
58
+ };
59
+
60
+ const fill = {
61
+ width: '100%',
62
+ height: '100%',
63
+ background: 'var(--osd-bg)',
64
+ color: 'var(--osd-text)',
65
+ fontFamily: 'var(--osd-font-body)',
66
+ letterSpacing: '-0.015em',
67
+ overflow: 'hidden',
68
+ position: 'relative' as const,
69
+ };
70
+
71
+ // ─── Shared animations (injected per slide so direct-nav also works) ──────────
72
+ const styles = `
73
+ @keyframes es-fadeUp {
74
+ from { opacity: 0; transform: translateY(18px); }
75
+ to { opacity: 1; transform: translateY(0); }
76
+ }
77
+ @keyframes es-fadeIn {
78
+ from { opacity: 0; }
79
+ to { opacity: 1; }
80
+ }
81
+ @keyframes es-blink {
82
+ 0%, 49% { opacity: 1; }
83
+ 50%, 100% { opacity: 0; }
84
+ }
85
+ @keyframes gs-type {
86
+ from { width: 0; }
87
+ to { width: 100%; }
88
+ }
89
+ @keyframes gs-thumbIn {
90
+ from { opacity: 0; transform: translateY(24px) scale(.96); }
91
+ to { opacity: 1; transform: translateY(0) scale(1); }
92
+ }
93
+ @keyframes gs-canvasSwap {
94
+ 0% { opacity: 0; transform: scale(.985); filter: blur(6px); }
95
+ 60% { opacity: 1; transform: scale(1); filter: blur(0); }
96
+ 100% { opacity: 1; transform: scale(1); filter: blur(0); }
97
+ }
98
+ @keyframes gs-crosshair {
99
+ 0% { transform: translate(-60px, 40px); }
100
+ 55% { transform: translate(0, 0); }
101
+ 100% { transform: translate(0, 0); }
102
+ }
103
+ @keyframes gs-outline {
104
+ 0%, 40% { opacity: 0; transform: scale(1.02); }
105
+ 60% { opacity: 1; transform: scale(1); }
106
+ 100% { opacity: 1; transform: scale(1); }
107
+ }
108
+ @keyframes gs-popover {
109
+ 0%, 60% { opacity: 0; transform: translateY(6px) scale(.96); }
110
+ 80% { opacity: 1; transform: translateY(0) scale(1); }
111
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
112
+ }
113
+ @keyframes gs-morph {
114
+ 0%, 30% { color: ${palette.text}; text-shadow: 0 0 0 transparent; }
115
+ 55% { color: ${palette.accent}; text-shadow: 0 0 28px ${palette.accent}55; }
116
+ 100% { color: ${palette.accent}; text-shadow: 0 0 0 transparent; }
117
+ }
118
+ @keyframes gs-strike {
119
+ from { background-size: 0 1px; }
120
+ to { background-size: 100% 1px; }
121
+ }
122
+ @keyframes gs-pulse {
123
+ 0%, 100% { box-shadow: 0 0 0 0 ${palette.inspect}00; }
124
+ 50% { box-shadow: 0 0 0 8px ${palette.inspect}22; }
125
+ }
126
+ .es-fadeUp { opacity: 0; animation: es-fadeUp 0.9s cubic-bezier(.2,.7,.2,1) forwards; }
127
+ .es-fadeIn { opacity: 0; animation: es-fadeIn 1.2s ease forwards; }
128
+ .es-caret::after {
129
+ content: '';
130
+ display: inline-block;
131
+ width: 0.06em;
132
+ height: 0.9em;
133
+ background: currentColor;
134
+ margin-left: 0.08em;
135
+ vertical-align: baseline;
136
+ animation: es-blink 1.05s steps(1) infinite;
137
+ }
138
+ .gs-type {
139
+ display: inline-block;
140
+ overflow: hidden;
141
+ white-space: nowrap;
142
+ width: 0;
143
+ animation: gs-type 1.6s steps(40, end) forwards;
144
+ }
145
+ .gs-stream { opacity: 0; animation: es-fadeIn .45s ease forwards; }
146
+ .gs-thumbIn { opacity: 0; animation: gs-thumbIn .75s cubic-bezier(.2,.7,.2,1) forwards; }
147
+ .gs-canvasSwap { opacity: 0; animation: gs-canvasSwap 1.1s cubic-bezier(.2,.7,.2,1) forwards; }
148
+ .gs-crosshair { animation: gs-crosshair 1.6s cubic-bezier(.2,.7,.2,1) forwards; }
149
+ .gs-outline { opacity: 0; animation: gs-outline 1.9s cubic-bezier(.2,.7,.2,1) forwards; }
150
+ .gs-popover { opacity: 0; animation: gs-popover 2.3s cubic-bezier(.2,.7,.2,1) forwards; }
151
+ .gs-morph { animation: gs-morph 2.4s cubic-bezier(.2,.7,.2,1) forwards; }
152
+ .gs-strike {
153
+ background-image: linear-gradient(${palette.muted}, ${palette.muted});
154
+ background-repeat: no-repeat;
155
+ background-position: left center;
156
+ background-size: 0 1px;
157
+ animation: gs-strike 1s ease forwards;
158
+ }
159
+ .gs-pulse { animation: gs-pulse 2s ease-in-out infinite; }
160
+ `;
161
+
162
+ const Styles = () => <style>{styles}</style>;
163
+
164
+ // ─── Shared chrome ────────────────────────────────────────────────────────────
165
+ const GridBg = () => (
166
+ <div
167
+ style={{
168
+ position: 'absolute',
169
+ inset: 0,
170
+ backgroundImage:
171
+ 'linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px)',
172
+ backgroundSize: '96px 96px',
173
+ maskImage: 'radial-gradient(ellipse at center, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0) 70%)',
174
+ WebkitMaskImage: 'radial-gradient(ellipse at center, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0) 70%)',
175
+ }}
176
+ />
177
+ );
178
+
179
+ const Eyebrow = ({
180
+ children,
181
+ style,
182
+ className,
183
+ }: {
184
+ children: React.ReactNode;
185
+ style?: React.CSSProperties;
186
+ className?: string;
187
+ }) => (
188
+ <div
189
+ className={className}
190
+ style={{
191
+ fontFamily: font.mono,
192
+ fontSize: 22,
193
+ letterSpacing: '0.18em',
194
+ textTransform: 'uppercase',
195
+ color: palette.muted,
196
+ ...style,
197
+ }}
198
+ >
199
+ {children}
200
+ </div>
201
+ );
202
+
203
+ const TrafficLights = () => (
204
+ <div style={{ display: 'flex', gap: 10 }}>
205
+ {['#ff5f56', '#ffbd2e', '#27c93f'].map((c) => (
206
+ <span
207
+ key={c}
208
+ style={{
209
+ width: 14,
210
+ height: 14,
211
+ borderRadius: '50%',
212
+ background: c,
213
+ boxShadow: `inset 0 0 0 1px rgba(0,0,0,0.25)`,
214
+ }}
215
+ />
216
+ ))}
217
+ </div>
218
+ );
219
+
220
+ const WindowShell = ({
221
+ title,
222
+ badge,
223
+ children,
224
+ style,
225
+ }: {
226
+ title: string;
227
+ badge?: React.ReactNode;
228
+ children: React.ReactNode;
229
+ style?: React.CSSProperties;
230
+ }) => (
231
+ <div
232
+ style={{
233
+ background: palette.surface,
234
+ border: `1px solid ${palette.border}`,
235
+ borderRadius: 'var(--osd-radius)',
236
+ boxShadow: '0 40px 80px -30px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.02)',
237
+ overflow: 'hidden',
238
+ display: 'flex',
239
+ flexDirection: 'column',
240
+ ...style,
241
+ }}
242
+ >
243
+ <div
244
+ style={{
245
+ height: 52,
246
+ padding: '0 20px',
247
+ display: 'flex',
248
+ alignItems: 'center',
249
+ gap: 16,
250
+ background: palette.surfaceHi,
251
+ borderBottom: `1px solid ${palette.border}`,
252
+ flexShrink: 0,
253
+ }}
254
+ >
255
+ <TrafficLights />
256
+ <div
257
+ style={{
258
+ flex: 1,
259
+ textAlign: 'center',
260
+ fontFamily: font.mono,
261
+ fontSize: 20,
262
+ color: palette.muted,
263
+ letterSpacing: '0.02em',
264
+ }}
265
+ >
266
+ {title}
267
+ </div>
268
+ <div style={{ minWidth: 40, display: 'flex', justifyContent: 'flex-end' }}>{badge}</div>
269
+ </div>
270
+ {children}
271
+ </div>
272
+ );
273
+
274
+ const SlashCmd = ({ name, color = palette.accent }: { name: string; color?: string }) => (
275
+ <span
276
+ style={{
277
+ fontFamily: font.mono,
278
+ color,
279
+ background: `${color}16`,
280
+ border: `1px solid ${color}40`,
281
+ padding: '2px 10px',
282
+ borderRadius: 6,
283
+ fontWeight: 500,
284
+ }}
285
+ >
286
+ /{name}
287
+ </span>
288
+ );
289
+
290
+ const AgentLine = ({
291
+ speaker,
292
+ children,
293
+ delay,
294
+ }: {
295
+ speaker: 'user' | 'assistant' | 'tool';
296
+ children: React.ReactNode;
297
+ delay: number;
298
+ }) => {
299
+ const label = speaker === 'user' ? 'you' : speaker === 'assistant' ? 'agent' : 'tool';
300
+ const color =
301
+ speaker === 'user'
302
+ ? palette.mint
303
+ : speaker === 'assistant'
304
+ ? palette.accentSoft
305
+ : palette.amber;
306
+ return (
307
+ <div
308
+ className="gs-stream"
309
+ style={{
310
+ animationDelay: `${delay}s`,
311
+ display: 'flex',
312
+ gap: 18,
313
+ alignItems: 'flex-start',
314
+ padding: '12px 0',
315
+ }}
316
+ >
317
+ <span
318
+ style={{
319
+ flex: '0 0 110px',
320
+ fontFamily: font.mono,
321
+ fontSize: 20,
322
+ letterSpacing: '0.12em',
323
+ textTransform: 'uppercase',
324
+ color,
325
+ paddingTop: 6,
326
+ }}
327
+ >
328
+ {label}
329
+ </span>
330
+ <div
331
+ style={{
332
+ flex: 1,
333
+ fontFamily: font.mono,
334
+ fontSize: 26,
335
+ color: palette.textSoft,
336
+ lineHeight: 1.45,
337
+ }}
338
+ >
339
+ {children}
340
+ </div>
341
+ </div>
342
+ );
343
+ };
344
+
345
+ const LogoCard = ({
346
+ src,
347
+ name,
348
+ delay = 0,
349
+ logoHeight = 72,
350
+ }: {
351
+ src: string;
352
+ name: string;
353
+ delay?: number;
354
+ logoHeight?: number;
355
+ }) => (
356
+ <div
357
+ className="es-fadeUp"
358
+ style={{
359
+ animationDelay: `${delay}s`,
360
+ flex: 1,
361
+ background: palette.surface,
362
+ border: `1px solid ${palette.border}`,
363
+ borderRadius: 'var(--osd-radius)',
364
+ padding: '40px 28px 32px',
365
+ display: 'flex',
366
+ flexDirection: 'column',
367
+ alignItems: 'center',
368
+ justifyContent: 'center',
369
+ gap: 28,
370
+ minHeight: 0,
371
+ }}
372
+ >
373
+ <div
374
+ style={{
375
+ height: logoHeight,
376
+ display: 'flex',
377
+ alignItems: 'center',
378
+ justifyContent: 'center',
379
+ }}
380
+ >
381
+ <img
382
+ src={src}
383
+ alt={name}
384
+ style={{ height: logoHeight, width: 'auto', objectFit: 'contain' }}
385
+ />
386
+ </div>
387
+ <div
388
+ style={{
389
+ fontFamily: font.mono,
390
+ fontSize: 22,
391
+ color: palette.textSoft,
392
+ letterSpacing: '0.02em',
393
+ }}
394
+ >
395
+ {name}
396
+ </div>
397
+ </div>
398
+ );
399
+
400
+ // ─── Slide 1: Cover ──────────────────────────────────────────────────────────
401
+ const Cover: Page = () => (
402
+ <div style={fill}>
403
+ <Styles />
404
+ <GridBg />
405
+ <div
406
+ style={{
407
+ position: 'absolute',
408
+ inset: 0,
409
+ padding: '140px 140px',
410
+ display: 'flex',
411
+ flexDirection: 'column',
412
+ justifyContent: 'space-between',
413
+ }}
414
+ >
415
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
416
+ <Eyebrow className="es-fadeUp" style={{ animationDelay: '0.05s' }}>
417
+ open-aippt · getting started
418
+ </Eyebrow>
419
+ <div
420
+ className="es-fadeUp"
421
+ style={{
422
+ animationDelay: '0.05s',
423
+ fontFamily: font.mono,
424
+ fontSize: 20,
425
+ color: palette.muted,
426
+ border: `1px solid ${palette.border}`,
427
+ padding: '8px 16px',
428
+ borderRadius: 999,
429
+ }}
430
+ >
431
+ v1
432
+ </div>
433
+ </div>
434
+
435
+ <div>
436
+ <h1
437
+ className="es-fadeUp"
438
+ style={{
439
+ fontFamily: 'var(--osd-font-display)',
440
+ fontSize: 'var(--osd-size-hero)',
441
+ lineHeight: 0.98,
442
+ fontWeight: 600,
443
+ margin: 0,
444
+ letterSpacing: '-0.045em',
445
+ animationDelay: '0.15s',
446
+ }}
447
+ >
448
+ Author slides
449
+ <br />
450
+ <span
451
+ style={{
452
+ background: `linear-gradient(90deg, ${palette.accentSoft}, var(--osd-accent))`,
453
+ WebkitBackgroundClip: 'text',
454
+ backgroundClip: 'text',
455
+ color: 'transparent',
456
+ }}
457
+ >
458
+ with your agent.
459
+ </span>
460
+ </h1>
461
+ <p
462
+ className="es-fadeUp"
463
+ style={{
464
+ marginTop: 48,
465
+ maxWidth: 1100,
466
+ fontSize: 'var(--osd-size-body)',
467
+ lineHeight: 1.35,
468
+ color: palette.textSoft,
469
+ animationDelay: '0.35s',
470
+ }}
471
+ >
472
+ Three steps from empty folder to a live, editable slide.
473
+ </p>
474
+ </div>
475
+
476
+ <div
477
+ className="es-fadeUp"
478
+ style={{
479
+ animationDelay: '0.55s',
480
+ display: 'flex',
481
+ gap: 48,
482
+ fontFamily: font.mono,
483
+ fontSize: 22,
484
+ color: palette.muted,
485
+ }}
486
+ >
487
+ <span>
488
+ <span style={{ color: palette.accentSoft }}>01</span> init
489
+ </span>
490
+ <span>
491
+ <span style={{ color: palette.accentSoft }}>02</span> prompt
492
+ </span>
493
+ <span>
494
+ <span style={{ color: palette.accentSoft }}>03</span> edit
495
+ </span>
496
+ <span>
497
+ <span style={{ color: palette.accentSoft }}>04</span> assets
498
+ </span>
499
+ <span>
500
+ <span style={{ color: palette.accentSoft }}>05</span> comment
501
+ </span>
502
+ </div>
503
+ </div>
504
+ </div>
505
+ );
506
+
507
+ // ─── Slide 2: Init in a terminal ─────────────────────────────────────────────
508
+ type InitLine =
509
+ | { kind: 'blank' }
510
+ | { kind: 'success'; text: string; dim?: string }
511
+ | { kind: 'bold'; text: string }
512
+ | { kind: 'cmd'; text: string }
513
+ | { kind: 'dim'; text: string };
514
+
515
+ const Init: Page = () => {
516
+ const stream: InitLine[] = [
517
+ { kind: 'blank' },
518
+ {
519
+ kind: 'success',
520
+ text: 'Created open-aippt workspace',
521
+ dim: 'in /Users/you/my-slide',
522
+ },
523
+ { kind: 'blank' },
524
+ { kind: 'bold', text: 'Installing dependencies with pnpm…' },
525
+ { kind: 'blank' },
526
+ { kind: 'success', text: 'Initialized git repository with first commit.' },
527
+ { kind: 'blank' },
528
+ { kind: 'bold', text: 'Next steps:' },
529
+ { kind: 'cmd', text: 'cd my-slide' },
530
+ { kind: 'cmd', text: 'pnpm dev' },
531
+ { kind: 'blank' },
532
+ {
533
+ kind: 'dim',
534
+ text: 'Then open the dev server and start authoring in slides/<your-slide>/.',
535
+ },
536
+ ];
537
+
538
+ const renderLine = (line: InitLine): React.ReactNode => {
539
+ switch (line.kind) {
540
+ case 'blank':
541
+ return ' ';
542
+ case 'success':
543
+ return (
544
+ <>
545
+ <span style={{ color: palette.mint }}>✔</span>{' '}
546
+ <span style={{ color: palette.text }}>{line.text}</span>
547
+ {line.dim && <span style={{ color: palette.muted }}> {line.dim}</span>}
548
+ </>
549
+ );
550
+ case 'bold':
551
+ return <span style={{ color: palette.text, fontWeight: 600 }}>{line.text}</span>;
552
+ case 'cmd':
553
+ return (
554
+ <>
555
+ {' '}
556
+ <span style={{ color: palette.accentSoft }}>{line.text}</span>
557
+ </>
558
+ );
559
+ case 'dim':
560
+ return <span style={{ color: palette.muted }}>{line.text}</span>;
561
+ }
562
+ };
563
+
564
+ return (
565
+ <div style={fill}>
566
+ <Styles />
567
+ <GridBg />
568
+ <div
569
+ style={{
570
+ position: 'absolute',
571
+ inset: 0,
572
+ padding: '100px 140px',
573
+ display: 'flex',
574
+ flexDirection: 'column',
575
+ gap: 48,
576
+ }}
577
+ >
578
+ <div className="es-fadeUp">
579
+ <Eyebrow>01 / Initialize</Eyebrow>
580
+ <h2
581
+ style={{
582
+ marginTop: 20,
583
+ marginBottom: 0,
584
+ fontFamily: 'var(--osd-font-display)',
585
+ fontSize: 88,
586
+ fontWeight: 600,
587
+ letterSpacing: '-0.035em',
588
+ lineHeight: 1.02,
589
+ }}
590
+ >
591
+ One command to scaffold.
592
+ </h2>
593
+ <p
594
+ style={{
595
+ marginTop: 20,
596
+ fontSize: 28,
597
+ color: palette.textSoft,
598
+ letterSpacing: '-0.01em',
599
+ }}
600
+ >
601
+ Installs deps, inits git, drops you at the dev server. No global installs, no Vite
602
+ config to touch.
603
+ </p>
604
+ </div>
605
+
606
+ <WindowShell title="~/code — zsh" style={{ flex: 1 }}>
607
+ <div
608
+ style={{
609
+ flex: 1,
610
+ padding: '32px 44px',
611
+ fontFamily: font.mono,
612
+ fontSize: 24,
613
+ lineHeight: 1.45,
614
+ color: palette.textSoft,
615
+ background: palette.surface,
616
+ overflow: 'hidden',
617
+ }}
618
+ >
619
+ <div style={{ display: 'flex', gap: 16 }}>
620
+ <span style={{ color: palette.mint }}>$</span>
621
+ <span className="gs-type" style={{ color: palette.text }}>
622
+ npx @open-aippt/cli init my-slide
623
+ </span>
624
+ </div>
625
+ <div style={{ height: 16 }} />
626
+ {stream.map((line, i) => (
627
+ <div
628
+ key={i}
629
+ className="gs-stream"
630
+ style={{
631
+ minHeight: 34,
632
+ animationDelay: `${1.8 + i * 0.1}s`,
633
+ whiteSpace: 'pre',
634
+ }}
635
+ >
636
+ {renderLine(line)}
637
+ </div>
638
+ ))}
639
+ <div
640
+ className="gs-stream"
641
+ style={{
642
+ marginTop: 14,
643
+ animationDelay: `${1.8 + stream.length * 0.1 + 0.1}s`,
644
+ display: 'flex',
645
+ gap: 16,
646
+ }}
647
+ >
648
+ <span style={{ color: palette.mint }}>$</span>
649
+ <span className="es-caret" style={{ color: palette.text }} />
650
+ </div>
651
+ </div>
652
+ </WindowShell>
653
+ </div>
654
+ </div>
655
+ );
656
+ };
657
+
658
+ // ─── Slide 3: Prompt → create-slide → pages appear ───────────────────────────
659
+ const Prompt: Page = () => {
660
+ const thumbs = ['Cover', 'Agenda', 'Problem', 'Solution', 'Metrics', 'Next'];
661
+ return (
662
+ <div style={fill}>
663
+ <Styles />
664
+ <GridBg />
665
+ <div
666
+ style={{
667
+ position: 'absolute',
668
+ inset: 0,
669
+ padding: '90px 120px 100px',
670
+ display: 'flex',
671
+ flexDirection: 'column',
672
+ gap: 36,
673
+ }}
674
+ >
675
+ <div className="es-fadeUp">
676
+ <Eyebrow>02 / Prompt the agent</Eyebrow>
677
+ <h2
678
+ style={{
679
+ marginTop: 20,
680
+ marginBottom: 0,
681
+ fontFamily: 'var(--osd-font-display)',
682
+ fontSize: 88,
683
+ fontWeight: 600,
684
+ letterSpacing: '-0.035em',
685
+ lineHeight: 1.02,
686
+ }}
687
+ >
688
+ Ask. Watch slides appear.
689
+ </h2>
690
+ </div>
691
+
692
+ <div
693
+ style={{
694
+ flex: 1,
695
+ display: 'grid',
696
+ gridTemplateColumns: '1fr 1.15fr',
697
+ gap: 40,
698
+ minHeight: 0,
699
+ }}
700
+ >
701
+ {/* LEFT — agent CLI */}
702
+ <WindowShell title="claude · ~/my-slide">
703
+ <div
704
+ style={{
705
+ flex: 1,
706
+ padding: '28px 36px',
707
+ background: palette.surface,
708
+ display: 'flex',
709
+ flexDirection: 'column',
710
+ }}
711
+ >
712
+ <AgentLine speaker="user" delay={0.3}>
713
+ <div>
714
+ <SlashCmd name="create-slide" />
715
+ </div>
716
+ <div style={{ marginTop: 10 }}>
717
+ <span className="gs-type" style={{ maxWidth: '100%', color: palette.text }}>
718
+ slides about the Q2 launch
719
+ </span>
720
+ </div>
721
+ </AgentLine>
722
+ <div style={{ height: 1, background: palette.border, margin: '8px 0' }} />
723
+ <AgentLine speaker="assistant" delay={2.0}>
724
+ Drafting 6 pages…
725
+ </AgentLine>
726
+ <AgentLine speaker="tool" delay={2.7}>
727
+ <div style={{ color: palette.muted }}>
728
+ write <span style={{ color: palette.text }}>slides/q2-launch/index.tsx</span>
729
+ </div>
730
+ </AgentLine>
731
+ <AgentLine speaker="tool" delay={3.4}>
732
+ <div style={{ color: palette.muted }}>
733
+ hmr <span style={{ color: palette.mint }}>✓</span> localhost:5173 updated
734
+ </div>
735
+ </AgentLine>
736
+ <div style={{ flex: 1 }} />
737
+ <div
738
+ className="gs-stream"
739
+ style={{
740
+ animationDelay: '4.1s',
741
+ display: 'flex',
742
+ gap: 16,
743
+ fontFamily: font.mono,
744
+ fontSize: 26,
745
+ color: palette.muted,
746
+ }}
747
+ >
748
+ <span style={{ color: palette.accentSoft }}>{'>'}</span>
749
+ <span className="es-caret" />
750
+ </div>
751
+ </div>
752
+ </WindowShell>
753
+
754
+ {/* RIGHT — browser preview */}
755
+ <WindowShell title="localhost:5173/s/q2-launch">
756
+ <div
757
+ style={{
758
+ flex: 1,
759
+ display: 'flex',
760
+ background: palette.surface,
761
+ minHeight: 0,
762
+ }}
763
+ >
764
+ {/* Thumbnail rail */}
765
+ <div
766
+ style={{
767
+ width: 220,
768
+ padding: '20px 14px',
769
+ borderRight: `1px solid ${palette.border}`,
770
+ background: palette.surfaceHi,
771
+ display: 'flex',
772
+ flexDirection: 'column',
773
+ gap: 10,
774
+ overflow: 'hidden',
775
+ }}
776
+ >
777
+ {thumbs.map((label, i) => (
778
+ <div
779
+ key={label}
780
+ className="gs-thumbIn"
781
+ style={{
782
+ animationDelay: `${1.2 + i * 0.25}s`,
783
+ display: 'flex',
784
+ alignItems: 'center',
785
+ gap: 10,
786
+ padding: 8,
787
+ borderRadius: 10,
788
+ border: `1px solid ${i === 0 ? palette.accent : palette.border}`,
789
+ background: i === 0 ? `${palette.accent}12` : palette.surface,
790
+ }}
791
+ >
792
+ <span
793
+ style={{
794
+ fontFamily: font.mono,
795
+ fontSize: 16,
796
+ color: palette.muted,
797
+ width: 22,
798
+ }}
799
+ >
800
+ {String(i + 1).padStart(2, '0')}
801
+ </span>
802
+ <div
803
+ style={{
804
+ flex: 1,
805
+ height: 66,
806
+ borderRadius: 6,
807
+ background: `linear-gradient(135deg, ${palette.surfaceMax}, ${palette.bg})`,
808
+ border: `1px solid ${palette.border}`,
809
+ padding: 8,
810
+ display: 'flex',
811
+ flexDirection: 'column',
812
+ justifyContent: 'space-between',
813
+ }}
814
+ >
815
+ <div
816
+ style={{
817
+ height: 6,
818
+ width: '60%',
819
+ background: palette.textSoft,
820
+ opacity: 0.55,
821
+ borderRadius: 2,
822
+ }}
823
+ />
824
+ <div
825
+ style={{
826
+ height: 4,
827
+ width: '40%',
828
+ background: palette.muted,
829
+ borderRadius: 2,
830
+ }}
831
+ />
832
+ </div>
833
+ </div>
834
+ ))}
835
+ </div>
836
+
837
+ {/* Canvas */}
838
+ <div
839
+ style={{
840
+ flex: 1,
841
+ position: 'relative',
842
+ display: 'flex',
843
+ alignItems: 'center',
844
+ justifyContent: 'center',
845
+ padding: 40,
846
+ }}
847
+ >
848
+ <div
849
+ className="gs-canvasSwap"
850
+ style={{
851
+ animationDelay: `${1.2 + thumbs.length * 0.25 + 0.2}s`,
852
+ width: '100%',
853
+ height: '100%',
854
+ borderRadius: 14,
855
+ border: `1px solid ${palette.border}`,
856
+ background: `radial-gradient(ellipse at 30% 30%, ${palette.accent2}22, transparent 60%), ${palette.bg}`,
857
+ padding: 48,
858
+ display: 'flex',
859
+ flexDirection: 'column',
860
+ justifyContent: 'space-between',
861
+ boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.02)',
862
+ }}
863
+ >
864
+ <Eyebrow style={{ fontSize: 14 }}>cover</Eyebrow>
865
+ <div>
866
+ <div
867
+ style={{
868
+ fontSize: 64,
869
+ fontWeight: 600,
870
+ letterSpacing: '-0.035em',
871
+ lineHeight: 1.02,
872
+ }}
873
+ >
874
+ Q2 Launch
875
+ </div>
876
+ <div
877
+ style={{
878
+ marginTop: 16,
879
+ fontSize: 22,
880
+ color: palette.textSoft,
881
+ maxWidth: 560,
882
+ }}
883
+ >
884
+ What we're shipping, why it matters, and how we'll measure success.
885
+ </div>
886
+ </div>
887
+ <div
888
+ style={{
889
+ display: 'flex',
890
+ justifyContent: 'space-between',
891
+ fontFamily: font.mono,
892
+ fontSize: 14,
893
+ color: palette.muted,
894
+ }}
895
+ >
896
+ <span>acme · product</span>
897
+ <span>01 / 06</span>
898
+ </div>
899
+ </div>
900
+ </div>
901
+ </div>
902
+ </WindowShell>
903
+ </div>
904
+ </div>
905
+ </div>
906
+ );
907
+ };
908
+
909
+ // ─── Slide: Visual editor (click → tweak → save) ─────────────────────────────
910
+ const VisualEdit: Page = () => {
911
+ return (
912
+ <div style={fill}>
913
+ <Styles />
914
+ <GridBg />
915
+ <style>{`
916
+ @keyframes ve-saveSwap {
917
+ 0%, 88% { opacity: 1; transform: translateY(0); }
918
+ 92% { opacity: 0; transform: translateY(-4px); }
919
+ 100% { opacity: 0; transform: translateY(-4px); }
920
+ }
921
+ @keyframes ve-savedIn {
922
+ 0%, 88% { opacity: 0; transform: translateY(4px); }
923
+ 100% { opacity: 1; transform: translateY(0); }
924
+ }
925
+ @keyframes ve-swatchPulse {
926
+ 0%, 100% { box-shadow: 0 0 0 0 ${palette.accent}00; transform: scale(1); }
927
+ 50% { box-shadow: 0 0 0 6px ${palette.accent}33; transform: scale(1.06); }
928
+ }
929
+ .ve-saveSwap { animation: ve-saveSwap 3.6s ease forwards; }
930
+ .ve-savedIn { animation: ve-savedIn 3.6s ease forwards; }
931
+ .ve-swatchPulse { animation: ve-swatchPulse 1.6s ease-in-out 1.0s 1 both; }
932
+ `}</style>
933
+ <div
934
+ style={{
935
+ position: 'absolute',
936
+ inset: 0,
937
+ padding: '90px 120px 100px',
938
+ display: 'flex',
939
+ flexDirection: 'column',
940
+ gap: 36,
941
+ }}
942
+ >
943
+ <div className="es-fadeUp">
944
+ <Eyebrow>03 / Edit visually</Eyebrow>
945
+ <h2
946
+ style={{
947
+ marginTop: 20,
948
+ marginBottom: 0,
949
+ fontFamily: 'var(--osd-font-display)',
950
+ fontSize: 88,
951
+ fontWeight: 600,
952
+ letterSpacing: '-0.035em',
953
+ lineHeight: 1.02,
954
+ }}
955
+ >
956
+ Click. Tweak. Save.
957
+ </h2>
958
+ <p
959
+ style={{
960
+ marginTop: 20,
961
+ fontSize: 28,
962
+ color: palette.textSoft,
963
+ maxWidth: 1280,
964
+ }}
965
+ >
966
+ Pick any element. Change text, font, color, or swap an image — right on the canvas.
967
+ Edits buffer until you hit{' '}
968
+ <span style={{ fontFamily: font.mono, color: palette.accentSoft }}>Save</span>.
969
+ </p>
970
+ </div>
971
+
972
+ <WindowShell
973
+ title="localhost:5173/s/q2-launch"
974
+ badge={
975
+ <span
976
+ className="gs-pulse"
977
+ style={{
978
+ display: 'inline-flex',
979
+ alignItems: 'center',
980
+ gap: 8,
981
+ padding: '6px 14px',
982
+ background: `${palette.inspect}22`,
983
+ border: `1px solid ${palette.inspect}`,
984
+ borderRadius: 8,
985
+ fontFamily: font.mono,
986
+ fontSize: 20,
987
+ color: palette.inspect,
988
+ }}
989
+ >
990
+ <span
991
+ style={{
992
+ width: 10,
993
+ height: 10,
994
+ borderRadius: '50%',
995
+ background: palette.inspect,
996
+ }}
997
+ />
998
+ Inspect on
999
+ </span>
1000
+ }
1001
+ style={{ flex: 1, minHeight: 0 }}
1002
+ >
1003
+ <div
1004
+ style={{
1005
+ flex: 1,
1006
+ display: 'grid',
1007
+ gridTemplateColumns: '1fr 360px',
1008
+ background: palette.surface,
1009
+ minHeight: 0,
1010
+ }}
1011
+ >
1012
+ {/* LEFT — canvas with selection + SaveBar */}
1013
+ <div
1014
+ style={{
1015
+ position: 'relative',
1016
+ display: 'flex',
1017
+ alignItems: 'center',
1018
+ justifyContent: 'center',
1019
+ padding: 40,
1020
+ borderRight: `1px solid ${palette.border}`,
1021
+ minHeight: 0,
1022
+ }}
1023
+ >
1024
+ <div
1025
+ style={{
1026
+ width: '100%',
1027
+ height: '100%',
1028
+ borderRadius: 14,
1029
+ border: `1px solid ${palette.border}`,
1030
+ background: `radial-gradient(ellipse at 30% 30%, ${palette.accent2}22, transparent 60%), ${palette.bg}`,
1031
+ padding: 56,
1032
+ position: 'relative',
1033
+ display: 'flex',
1034
+ flexDirection: 'column',
1035
+ justifyContent: 'center',
1036
+ }}
1037
+ >
1038
+ <Eyebrow style={{ fontSize: 14 }}>cover</Eyebrow>
1039
+ <div
1040
+ style={{
1041
+ position: 'relative',
1042
+ marginTop: 20,
1043
+ display: 'inline-block',
1044
+ width: 'fit-content',
1045
+ }}
1046
+ >
1047
+ <div
1048
+ className="gs-outline"
1049
+ style={{
1050
+ position: 'absolute',
1051
+ inset: -10,
1052
+ border: `2px solid ${palette.inspect}`,
1053
+ background: palette.inspectFill,
1054
+ borderRadius: 6,
1055
+ pointerEvents: 'none',
1056
+ animationDelay: '0.6s',
1057
+ }}
1058
+ />
1059
+ <div
1060
+ className="gs-morph"
1061
+ style={{
1062
+ animationDelay: '1.4s',
1063
+ fontSize: 88,
1064
+ fontWeight: 600,
1065
+ letterSpacing: '-0.035em',
1066
+ lineHeight: 1.02,
1067
+ color: palette.text,
1068
+ position: 'relative',
1069
+ }}
1070
+ >
1071
+ Q2 Launch
1072
+ </div>
1073
+ </div>
1074
+ <div
1075
+ style={{
1076
+ marginTop: 18,
1077
+ fontSize: 24,
1078
+ color: palette.textSoft,
1079
+ maxWidth: 620,
1080
+ }}
1081
+ >
1082
+ What we're shipping, why it matters, and how we'll measure success.
1083
+ </div>
1084
+
1085
+ {/* Crosshair cursor */}
1086
+ <div
1087
+ className="gs-crosshair"
1088
+ style={{
1089
+ position: 'absolute',
1090
+ left: 220,
1091
+ top: 200,
1092
+ width: 28,
1093
+ height: 28,
1094
+ pointerEvents: 'none',
1095
+ animationDelay: '0.2s',
1096
+ }}
1097
+ >
1098
+ <div
1099
+ style={{
1100
+ position: 'absolute',
1101
+ left: 0,
1102
+ top: '50%',
1103
+ width: '100%',
1104
+ height: 2,
1105
+ background: palette.inspect,
1106
+ }}
1107
+ />
1108
+ <div
1109
+ style={{
1110
+ position: 'absolute',
1111
+ top: 0,
1112
+ left: '50%',
1113
+ width: 2,
1114
+ height: '100%',
1115
+ background: palette.inspect,
1116
+ }}
1117
+ />
1118
+ <div
1119
+ style={{
1120
+ position: 'absolute',
1121
+ inset: '25%',
1122
+ border: `2px solid ${palette.inspect}`,
1123
+ borderRadius: '50%',
1124
+ background: 'transparent',
1125
+ }}
1126
+ />
1127
+ </div>
1128
+
1129
+ {/* SaveBar pill */}
1130
+ <div
1131
+ className="es-fadeUp"
1132
+ style={{
1133
+ animationDelay: '2.0s',
1134
+ position: 'absolute',
1135
+ left: '50%',
1136
+ bottom: 28,
1137
+ transform: 'translateX(-50%)',
1138
+ }}
1139
+ >
1140
+ <div
1141
+ style={{
1142
+ position: 'relative',
1143
+ display: 'inline-flex',
1144
+ alignItems: 'center',
1145
+ gap: 10,
1146
+ padding: '6px 6px 6px 16px',
1147
+ borderRadius: 999,
1148
+ background: `${palette.surfaceHi}f0`,
1149
+ border: `1px solid ${palette.borderBright}`,
1150
+ boxShadow: '0 24px 48px -16px rgba(0,0,0,0.6)',
1151
+ backdropFilter: 'blur(8px)',
1152
+ fontFamily: font.sans,
1153
+ fontSize: 18,
1154
+ color: palette.text,
1155
+ minHeight: 40,
1156
+ }}
1157
+ >
1158
+ <span
1159
+ className="ve-saveSwap"
1160
+ style={{
1161
+ display: 'inline-flex',
1162
+ alignItems: 'center',
1163
+ gap: 12,
1164
+ }}
1165
+ >
1166
+ <span style={{ fontWeight: 500 }}>1 unsaved change</span>
1167
+ <span
1168
+ style={{
1169
+ fontFamily: font.mono,
1170
+ fontSize: 15,
1171
+ color: palette.muted,
1172
+ padding: '6px 12px',
1173
+ borderRadius: 999,
1174
+ }}
1175
+ >
1176
+ ↺ Discard
1177
+ </span>
1178
+ <span
1179
+ style={{
1180
+ fontFamily: font.sans,
1181
+ fontSize: 15,
1182
+ fontWeight: 500,
1183
+ color: palette.text,
1184
+ background: palette.accent,
1185
+ padding: '6px 14px',
1186
+ borderRadius: 999,
1187
+ }}
1188
+ >
1189
+ ⤓ Save
1190
+ </span>
1191
+ </span>
1192
+ <span
1193
+ className="ve-savedIn"
1194
+ style={{
1195
+ position: 'absolute',
1196
+ inset: 0,
1197
+ display: 'inline-flex',
1198
+ alignItems: 'center',
1199
+ justifyContent: 'center',
1200
+ gap: 8,
1201
+ color: palette.text,
1202
+ fontWeight: 500,
1203
+ }}
1204
+ >
1205
+ <span style={{ color: palette.mint, fontSize: 18 }}>✓</span>
1206
+ Saved
1207
+ </span>
1208
+ </div>
1209
+ </div>
1210
+ </div>
1211
+ </div>
1212
+
1213
+ {/* RIGHT — inspector property panel mock */}
1214
+ <div
1215
+ style={{
1216
+ background: palette.surfaceHi,
1217
+ display: 'flex',
1218
+ flexDirection: 'column',
1219
+ minHeight: 0,
1220
+ overflow: 'hidden',
1221
+ }}
1222
+ >
1223
+ <div
1224
+ style={{
1225
+ padding: '20px 22px 14px',
1226
+ borderBottom: `1px solid ${palette.border}`,
1227
+ display: 'flex',
1228
+ alignItems: 'center',
1229
+ justifyContent: 'space-between',
1230
+ fontFamily: font.mono,
1231
+ fontSize: 18,
1232
+ color: palette.muted,
1233
+ }}
1234
+ >
1235
+ <span style={{ color: palette.textSoft, letterSpacing: '0.02em' }}>
1236
+ &lt;h1&gt; · line 58
1237
+ </span>
1238
+ <span style={{ color: palette.dim }}>✕</span>
1239
+ </div>
1240
+
1241
+ <PanelSection title="Content">
1242
+ <div
1243
+ style={{
1244
+ background: palette.surface,
1245
+ border: `1px solid ${palette.border}`,
1246
+ borderRadius: 8,
1247
+ padding: '12px 14px',
1248
+ fontFamily: font.sans,
1249
+ fontSize: 18,
1250
+ color: palette.text,
1251
+ minHeight: 64,
1252
+ }}
1253
+ >
1254
+ Q2 Launch
1255
+ </div>
1256
+ </PanelSection>
1257
+
1258
+ <PanelDivider />
1259
+
1260
+ <PanelSection title="Typography">
1261
+ <PanelRow label="Size">
1262
+ <div
1263
+ style={{
1264
+ flex: 1,
1265
+ height: 6,
1266
+ borderRadius: 3,
1267
+ background: palette.surfaceMax,
1268
+ position: 'relative',
1269
+ }}
1270
+ >
1271
+ <div
1272
+ style={{
1273
+ position: 'absolute',
1274
+ left: 0,
1275
+ top: 0,
1276
+ bottom: 0,
1277
+ width: '38%',
1278
+ background: palette.accent,
1279
+ borderRadius: 3,
1280
+ }}
1281
+ />
1282
+ <div
1283
+ style={{
1284
+ position: 'absolute',
1285
+ left: '38%',
1286
+ top: '50%',
1287
+ width: 14,
1288
+ height: 14,
1289
+ marginLeft: -7,
1290
+ marginTop: -7,
1291
+ borderRadius: '50%',
1292
+ background: palette.text,
1293
+ border: `2px solid ${palette.accent}`,
1294
+ }}
1295
+ />
1296
+ </div>
1297
+ <PanelInput value="88px" />
1298
+ </PanelRow>
1299
+ <PanelRow label="Weight">
1300
+ <PanelSelect value="Semibold · 600" />
1301
+ </PanelRow>
1302
+ </PanelSection>
1303
+
1304
+ <PanelDivider />
1305
+
1306
+ <PanelSection title="Color">
1307
+ <PanelRow label="Color">
1308
+ <div
1309
+ className="ve-swatchPulse"
1310
+ style={{
1311
+ width: 28,
1312
+ height: 28,
1313
+ borderRadius: 6,
1314
+ background: palette.accent,
1315
+ border: `1px solid ${palette.borderBright}`,
1316
+ }}
1317
+ />
1318
+ <PanelInput value={palette.accent} />
1319
+ </PanelRow>
1320
+ <PanelRow label="Background">
1321
+ <div
1322
+ style={{
1323
+ width: 28,
1324
+ height: 28,
1325
+ borderRadius: 6,
1326
+ background: 'transparent',
1327
+ border: `1px dashed ${palette.dim}`,
1328
+ }}
1329
+ />
1330
+ <PanelInput value="—" dim />
1331
+ </PanelRow>
1332
+ </PanelSection>
1333
+
1334
+ <PanelDivider />
1335
+
1336
+ <PanelSection title="Image">
1337
+ <div
1338
+ style={{
1339
+ fontFamily: font.mono,
1340
+ fontSize: 15,
1341
+ color: palette.dim,
1342
+ }}
1343
+ >
1344
+ No image on this element.
1345
+ </div>
1346
+ </PanelSection>
1347
+ </div>
1348
+ </div>
1349
+ </WindowShell>
1350
+ </div>
1351
+ </div>
1352
+ );
1353
+ };
1354
+
1355
+ // ─── Slide: Assets manager ───────────────────────────────────────────────────
1356
+ const AssetsManager: Page = () => {
1357
+ const cards: { name: string; size: string; src: string }[] = [
1358
+ { name: 'claude.svg', size: '3.4 KB', src: claudeLogo },
1359
+ { name: 'openai.svg', size: '2.1 KB', src: codexLogo },
1360
+ { name: 'gemini.svg', size: '4.0 KB', src: geminiLogo },
1361
+ { name: 'opencode.svg', size: '5.2 KB', src: opencodeLogo },
1362
+ { name: 'cloudflare.svg', size: '6.8 KB', src: cloudflareLogo },
1363
+ { name: 'zeabur.svg', size: '4.7 KB', src: zeaburLogo },
1364
+ ];
1365
+
1366
+ const svglResults: { name: string; src: string }[] = [
1367
+ { name: 'Vercel', src: vercelLogo },
1368
+ { name: 'Cloudflare', src: cloudflareLogo },
1369
+ { name: 'Zeabur', src: zeaburLogo },
1370
+ ];
1371
+
1372
+ return (
1373
+ <div style={fill}>
1374
+ <Styles />
1375
+ <GridBg />
1376
+ <style>{`
1377
+ @keyframes am-marchingAnts {
1378
+ to { background-position: 16px 0, -16px 0, 0 16px, 0 -16px; }
1379
+ }
1380
+ @keyframes am-overlayLoop {
1381
+ 0%, 8% { opacity: 0; }
1382
+ 14%, 38% { opacity: 1; }
1383
+ 44%, 100% { opacity: 0; }
1384
+ }
1385
+ @keyframes am-newCardIn {
1386
+ 0%, 50% { opacity: 0; transform: translateY(12px) scale(.96); }
1387
+ 70% { opacity: 1; transform: translateY(0) scale(1); }
1388
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
1389
+ }
1390
+ .am-overlay {
1391
+ background-image:
1392
+ linear-gradient(90deg, ${palette.borderBright} 50%, transparent 0),
1393
+ linear-gradient(90deg, ${palette.borderBright} 50%, transparent 0),
1394
+ linear-gradient(0deg, ${palette.borderBright} 50%, transparent 0),
1395
+ linear-gradient(0deg, ${palette.borderBright} 50%, transparent 0);
1396
+ background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
1397
+ background-size: 16px 1px, 16px 1px, 1px 16px, 1px 16px;
1398
+ background-position: 0 0, 0 100%, 0 0, 100% 0;
1399
+ animation: am-marchingAnts 0.9s linear infinite, am-overlayLoop 4.5s ease-in-out 1.6s infinite;
1400
+ }
1401
+ .am-overlayPill { animation: am-overlayLoop 4.5s ease-in-out 1.6s infinite; }
1402
+ .am-newCard { opacity: 0; animation: am-newCardIn 4.5s ease-in-out 1.6s infinite; }
1403
+ `}</style>
1404
+ <div
1405
+ style={{
1406
+ position: 'absolute',
1407
+ inset: 0,
1408
+ padding: '90px 120px 100px',
1409
+ display: 'flex',
1410
+ flexDirection: 'column',
1411
+ gap: 36,
1412
+ }}
1413
+ >
1414
+ <div className="es-fadeUp">
1415
+ <Eyebrow>04 / Manage assets</Eyebrow>
1416
+ <h2
1417
+ style={{
1418
+ marginTop: 20,
1419
+ marginBottom: 0,
1420
+ fontFamily: 'var(--osd-font-display)',
1421
+ fontSize: 88,
1422
+ fontWeight: 600,
1423
+ letterSpacing: '-0.035em',
1424
+ lineHeight: 1.02,
1425
+ }}
1426
+ >
1427
+ Drop images. Pull in logos.
1428
+ </h2>
1429
+ <p
1430
+ style={{
1431
+ marginTop: 20,
1432
+ fontSize: 28,
1433
+ color: palette.textSoft,
1434
+ maxWidth: 1280,
1435
+ }}
1436
+ >
1437
+ Drag files into the deck — or search{' '}
1438
+ <span style={{ fontFamily: font.mono, color: palette.accentSoft }}>svgl</span> for a
1439
+ brand logo. Rename, replace, or delete without leaving the editor.
1440
+ </p>
1441
+ </div>
1442
+
1443
+ <WindowShell title="localhost:5173/s/q2-launch · assets" style={{ flex: 1, minHeight: 0 }}>
1444
+ <div
1445
+ style={{
1446
+ flex: 1,
1447
+ background: palette.surface,
1448
+ display: 'flex',
1449
+ flexDirection: 'column',
1450
+ minHeight: 0,
1451
+ position: 'relative',
1452
+ }}
1453
+ >
1454
+ {/* Toolbar: Slides/Assets switcher + Upload */}
1455
+ <div
1456
+ style={{
1457
+ padding: '20px 28px',
1458
+ borderBottom: `1px solid ${palette.border}`,
1459
+ display: 'flex',
1460
+ alignItems: 'center',
1461
+ justifyContent: 'space-between',
1462
+ gap: 16,
1463
+ }}
1464
+ >
1465
+ <div
1466
+ style={{
1467
+ display: 'inline-flex',
1468
+ background: palette.surfaceHi,
1469
+ border: `1px solid ${palette.border}`,
1470
+ borderRadius: 999,
1471
+ padding: 4,
1472
+ position: 'relative',
1473
+ }}
1474
+ >
1475
+ <div
1476
+ style={{
1477
+ position: 'absolute',
1478
+ top: 4,
1479
+ left: 'calc(50% + 0px)',
1480
+ width: 'calc(50% - 4px)',
1481
+ bottom: 4,
1482
+ background: `${palette.accent}22`,
1483
+ border: `1px solid ${palette.accent}`,
1484
+ borderRadius: 999,
1485
+ transition: 'left 200ms ease',
1486
+ }}
1487
+ />
1488
+ <span
1489
+ style={{
1490
+ position: 'relative',
1491
+ padding: '8px 22px',
1492
+ fontFamily: font.mono,
1493
+ fontSize: 18,
1494
+ color: palette.muted,
1495
+ }}
1496
+ >
1497
+ Slides
1498
+ </span>
1499
+ <span
1500
+ style={{
1501
+ position: 'relative',
1502
+ padding: '8px 22px',
1503
+ fontFamily: font.mono,
1504
+ fontSize: 18,
1505
+ color: palette.accentSoft,
1506
+ }}
1507
+ >
1508
+ Assets
1509
+ </span>
1510
+ </div>
1511
+ <div
1512
+ style={{
1513
+ display: 'inline-flex',
1514
+ alignItems: 'center',
1515
+ gap: 10,
1516
+ padding: '8px 18px',
1517
+ background: palette.surfaceHi,
1518
+ border: `1px solid ${palette.borderBright}`,
1519
+ borderRadius: 10,
1520
+ fontFamily: font.sans,
1521
+ fontSize: 18,
1522
+ color: palette.text,
1523
+ }}
1524
+ >
1525
+ <span style={{ color: palette.accentSoft }}>↑</span>
1526
+ Upload
1527
+ </div>
1528
+ </div>
1529
+
1530
+ {/* Grid */}
1531
+ <div
1532
+ style={{
1533
+ flex: 1,
1534
+ padding: '28px 32px',
1535
+ display: 'grid',
1536
+ gridTemplateColumns: 'repeat(4, 1fr)',
1537
+ gridAutoRows: 'min-content',
1538
+ gap: 22,
1539
+ minHeight: 0,
1540
+ alignContent: 'start',
1541
+ }}
1542
+ >
1543
+ {cards.map((c, i) => (
1544
+ <AssetCardMock
1545
+ key={c.name}
1546
+ name={c.name}
1547
+ size={c.size}
1548
+ src={c.src}
1549
+ className="gs-thumbIn"
1550
+ delay={0.3 + i * 0.08}
1551
+ />
1552
+ ))}
1553
+ <AssetCardMock
1554
+ key="vercel-new"
1555
+ name="vercel.svg"
1556
+ size="3.2 KB"
1557
+ src={vercelLogo}
1558
+ className="am-newCard"
1559
+ accent
1560
+ />
1561
+ </div>
1562
+
1563
+ {/* Drag-drop overlay (loops) */}
1564
+ <div
1565
+ className="am-overlay"
1566
+ style={{
1567
+ position: 'absolute',
1568
+ inset: 12,
1569
+ pointerEvents: 'none',
1570
+ borderRadius: 14,
1571
+ background: `${palette.bg}26`,
1572
+ }}
1573
+ />
1574
+ <div
1575
+ className="am-overlayPill"
1576
+ style={{
1577
+ position: 'absolute',
1578
+ left: '50%',
1579
+ bottom: 36,
1580
+ transform: 'translateX(-50%)',
1581
+ pointerEvents: 'none',
1582
+ }}
1583
+ >
1584
+ <div
1585
+ style={{
1586
+ display: 'inline-flex',
1587
+ alignItems: 'center',
1588
+ gap: 10,
1589
+ padding: '10px 20px',
1590
+ borderRadius: 999,
1591
+ background: `${palette.surfaceHi}f0`,
1592
+ border: `1px solid ${palette.borderBright}`,
1593
+ boxShadow: '0 18px 36px -12px rgba(0,0,0,0.5)',
1594
+ backdropFilter: 'blur(8px)',
1595
+ fontFamily: font.sans,
1596
+ fontSize: 18,
1597
+ color: palette.textSoft,
1598
+ }}
1599
+ >
1600
+ <span style={{ color: palette.accentSoft }}>↓</span>
1601
+ Drop to upload
1602
+ </div>
1603
+ </div>
1604
+
1605
+ {/* svgl Logo Search dialog (popovers in late) */}
1606
+ <div
1607
+ className="gs-popover"
1608
+ style={{
1609
+ position: 'absolute',
1610
+ right: 36,
1611
+ bottom: 36,
1612
+ width: 420,
1613
+ background: palette.surfaceHi,
1614
+ border: `1px solid ${palette.borderBright}`,
1615
+ borderRadius: 14,
1616
+ padding: 18,
1617
+ boxShadow: '0 40px 80px -24px rgba(0,0,0,0.7)',
1618
+ animationDelay: '3.4s',
1619
+ }}
1620
+ >
1621
+ <div
1622
+ style={{
1623
+ display: 'flex',
1624
+ alignItems: 'center',
1625
+ justifyContent: 'space-between',
1626
+ fontFamily: font.mono,
1627
+ fontSize: 15,
1628
+ color: palette.muted,
1629
+ marginBottom: 10,
1630
+ }}
1631
+ >
1632
+ <span>Search svgl</span>
1633
+ <span style={{ color: palette.dim }}>✕</span>
1634
+ </div>
1635
+ <div
1636
+ style={{
1637
+ display: 'flex',
1638
+ alignItems: 'center',
1639
+ gap: 10,
1640
+ background: palette.surface,
1641
+ border: `1px solid ${palette.border}`,
1642
+ borderRadius: 8,
1643
+ padding: '10px 12px',
1644
+ fontFamily: font.mono,
1645
+ fontSize: 17,
1646
+ color: palette.text,
1647
+ marginBottom: 14,
1648
+ }}
1649
+ >
1650
+ <span style={{ color: palette.muted }}>⌕</span>
1651
+ vercel
1652
+ <span className="es-caret" style={{ color: palette.text }} />
1653
+ </div>
1654
+ <div
1655
+ style={{
1656
+ display: 'grid',
1657
+ gridTemplateColumns: 'repeat(3, 1fr)',
1658
+ gap: 10,
1659
+ }}
1660
+ >
1661
+ {svglResults.map((r, i) => (
1662
+ <div
1663
+ key={r.name}
1664
+ style={{
1665
+ background: palette.surface,
1666
+ border: `1px solid ${i === 0 ? palette.accent : palette.border}`,
1667
+ borderRadius: 10,
1668
+ padding: '14px 8px 10px',
1669
+ display: 'flex',
1670
+ flexDirection: 'column',
1671
+ alignItems: 'center',
1672
+ gap: 8,
1673
+ }}
1674
+ >
1675
+ <div
1676
+ style={{
1677
+ height: 40,
1678
+ display: 'flex',
1679
+ alignItems: 'center',
1680
+ justifyContent: 'center',
1681
+ }}
1682
+ >
1683
+ <img
1684
+ src={r.src}
1685
+ alt={r.name}
1686
+ style={{ height: 32, width: 'auto', objectFit: 'contain' }}
1687
+ />
1688
+ </div>
1689
+ <div
1690
+ style={{
1691
+ fontFamily: font.mono,
1692
+ fontSize: 13,
1693
+ color: palette.textSoft,
1694
+ }}
1695
+ >
1696
+ {r.name}
1697
+ </div>
1698
+ </div>
1699
+ ))}
1700
+ </div>
1701
+ </div>
1702
+ </div>
1703
+ </WindowShell>
1704
+ </div>
1705
+ </div>
1706
+ );
1707
+ };
1708
+
1709
+ // ─── Inspector panel mock helpers ────────────────────────────────────────────
1710
+ const PanelSection = ({ title, children }: { title: string; children: React.ReactNode }) => (
1711
+ <div style={{ padding: '16px 22px' }}>
1712
+ <div
1713
+ style={{
1714
+ marginBottom: 12,
1715
+ fontFamily: font.mono,
1716
+ fontSize: 12,
1717
+ letterSpacing: '0.08em',
1718
+ textTransform: 'uppercase',
1719
+ color: palette.muted,
1720
+ }}
1721
+ >
1722
+ {title}
1723
+ </div>
1724
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>{children}</div>
1725
+ </div>
1726
+ );
1727
+
1728
+ const PanelDivider = () => <div style={{ height: 1, background: palette.border }} />;
1729
+
1730
+ const PanelRow = ({ label, children }: { label: string; children: React.ReactNode }) => (
1731
+ <div
1732
+ style={{
1733
+ display: 'grid',
1734
+ gridTemplateColumns: '80px 1fr',
1735
+ alignItems: 'center',
1736
+ gap: 12,
1737
+ }}
1738
+ >
1739
+ <span
1740
+ style={{
1741
+ fontFamily: font.sans,
1742
+ fontSize: 14,
1743
+ color: palette.muted,
1744
+ }}
1745
+ >
1746
+ {label}
1747
+ </span>
1748
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>{children}</div>
1749
+ </div>
1750
+ );
1751
+
1752
+ const PanelInput = ({ value, dim = false }: { value: string; dim?: boolean }) => (
1753
+ <div
1754
+ style={{
1755
+ flex: 1,
1756
+ background: palette.surface,
1757
+ border: `1px solid ${palette.border}`,
1758
+ borderRadius: 6,
1759
+ padding: '6px 10px',
1760
+ fontFamily: font.mono,
1761
+ fontSize: 14,
1762
+ color: dim ? palette.dim : palette.text,
1763
+ minHeight: 28,
1764
+ }}
1765
+ >
1766
+ {value}
1767
+ </div>
1768
+ );
1769
+
1770
+ const PanelSelect = ({ value }: { value: string }) => (
1771
+ <div
1772
+ style={{
1773
+ flex: 1,
1774
+ display: 'flex',
1775
+ alignItems: 'center',
1776
+ justifyContent: 'space-between',
1777
+ background: palette.surface,
1778
+ border: `1px solid ${palette.border}`,
1779
+ borderRadius: 6,
1780
+ padding: '6px 10px',
1781
+ fontFamily: font.sans,
1782
+ fontSize: 14,
1783
+ color: palette.text,
1784
+ minHeight: 28,
1785
+ }}
1786
+ >
1787
+ <span>{value}</span>
1788
+ <span style={{ color: palette.muted, fontSize: 12 }}>▾</span>
1789
+ </div>
1790
+ );
1791
+
1792
+ // ─── Asset card mock ─────────────────────────────────────────────────────────
1793
+ const AssetCardMock = ({
1794
+ name,
1795
+ size,
1796
+ src,
1797
+ className,
1798
+ delay = 0,
1799
+ accent = false,
1800
+ }: {
1801
+ name: string;
1802
+ size: string;
1803
+ src: string;
1804
+ className?: string;
1805
+ delay?: number;
1806
+ accent?: boolean;
1807
+ }) => (
1808
+ <div
1809
+ className={className}
1810
+ style={{
1811
+ animationDelay: `${delay}s`,
1812
+ borderRadius: 12,
1813
+ border: `1px solid ${accent ? palette.accent : palette.border}`,
1814
+ background: palette.surfaceHi,
1815
+ overflow: 'hidden',
1816
+ display: 'flex',
1817
+ flexDirection: 'column',
1818
+ boxShadow: accent ? `0 0 0 3px ${palette.accent}22` : 'none',
1819
+ }}
1820
+ >
1821
+ <div
1822
+ style={{
1823
+ height: 130,
1824
+ display: 'flex',
1825
+ alignItems: 'center',
1826
+ justifyContent: 'center',
1827
+ background:
1828
+ 'repeating-conic-gradient(#1a1c2155 0deg 90deg, transparent 90deg 180deg) 0 0 / 16px 16px',
1829
+ }}
1830
+ >
1831
+ <img src={src} alt="" style={{ height: 64, width: 'auto', objectFit: 'contain' }} />
1832
+ </div>
1833
+ <div
1834
+ style={{
1835
+ padding: '10px 14px',
1836
+ borderTop: `1px solid ${palette.border}`,
1837
+ background: palette.surfaceHi,
1838
+ }}
1839
+ >
1840
+ <div
1841
+ style={{
1842
+ fontFamily: font.sans,
1843
+ fontSize: 16,
1844
+ color: palette.text,
1845
+ letterSpacing: '-0.005em',
1846
+ whiteSpace: 'nowrap',
1847
+ overflow: 'hidden',
1848
+ textOverflow: 'ellipsis',
1849
+ }}
1850
+ >
1851
+ {name}
1852
+ </div>
1853
+ <div
1854
+ style={{
1855
+ fontFamily: font.mono,
1856
+ fontSize: 12,
1857
+ color: palette.muted,
1858
+ marginTop: 2,
1859
+ }}
1860
+ >
1861
+ {size}
1862
+ </div>
1863
+ </div>
1864
+ </div>
1865
+ );
1866
+
1867
+ // ─── Slide: Inspect a block ──────────────────────────────────────────────────
1868
+ const Inspect: Page = () => (
1869
+ <div style={fill}>
1870
+ <Styles />
1871
+ <GridBg />
1872
+ <div
1873
+ style={{
1874
+ position: 'absolute',
1875
+ inset: 0,
1876
+ padding: '90px 120px 100px',
1877
+ display: 'flex',
1878
+ flexDirection: 'column',
1879
+ gap: 36,
1880
+ }}
1881
+ >
1882
+ <div className="es-fadeUp">
1883
+ <Eyebrow>05 / Inspect &amp; comment</Eyebrow>
1884
+ <h2
1885
+ style={{
1886
+ marginTop: 20,
1887
+ marginBottom: 0,
1888
+ fontFamily: 'var(--osd-font-display)',
1889
+ fontSize: 88,
1890
+ fontWeight: 600,
1891
+ letterSpacing: '-0.035em',
1892
+ lineHeight: 1.02,
1893
+ }}
1894
+ >
1895
+ Point at what's wrong.
1896
+ </h2>
1897
+ <p
1898
+ style={{
1899
+ marginTop: 20,
1900
+ fontSize: 28,
1901
+ color: palette.textSoft,
1902
+ }}
1903
+ >
1904
+ Toggle inspect, click a block, leave a note. The tool drops a{' '}
1905
+ <span style={{ fontFamily: font.mono, color: palette.accentSoft }}>@slide-comment</span>{' '}
1906
+ marker in your source.
1907
+ </p>
1908
+ </div>
1909
+
1910
+ <WindowShell
1911
+ title="localhost:5173/s/q2-launch"
1912
+ badge={
1913
+ <span
1914
+ className="gs-pulse"
1915
+ style={{
1916
+ display: 'inline-flex',
1917
+ alignItems: 'center',
1918
+ gap: 8,
1919
+ padding: '6px 14px',
1920
+ background: `${palette.inspect}22`,
1921
+ border: `1px solid ${palette.inspect}`,
1922
+ borderRadius: 8,
1923
+ fontFamily: font.mono,
1924
+ fontSize: 20,
1925
+ color: palette.inspect,
1926
+ }}
1927
+ >
1928
+ <span
1929
+ style={{
1930
+ width: 10,
1931
+ height: 10,
1932
+ borderRadius: '50%',
1933
+ background: palette.inspect,
1934
+ }}
1935
+ />
1936
+ Inspect on
1937
+ </span>
1938
+ }
1939
+ style={{ flex: 1, minHeight: 0 }}
1940
+ >
1941
+ <div
1942
+ style={{
1943
+ flex: 1,
1944
+ display: 'flex',
1945
+ background: palette.surface,
1946
+ position: 'relative',
1947
+ minHeight: 0,
1948
+ }}
1949
+ >
1950
+ {/* Thumbnail rail (static) */}
1951
+ <div
1952
+ style={{
1953
+ width: 200,
1954
+ padding: '20px 14px',
1955
+ borderRight: `1px solid ${palette.border}`,
1956
+ background: palette.surfaceHi,
1957
+ display: 'flex',
1958
+ flexDirection: 'column',
1959
+ gap: 10,
1960
+ }}
1961
+ >
1962
+ {Array.from({ length: 6 }).map((_, i) => (
1963
+ <div
1964
+ key={i}
1965
+ style={{
1966
+ height: 80,
1967
+ borderRadius: 8,
1968
+ border: `1px solid ${i === 0 ? palette.accent : palette.border}`,
1969
+ background: i === 0 ? `${palette.accent}10` : palette.surface,
1970
+ }}
1971
+ />
1972
+ ))}
1973
+ </div>
1974
+
1975
+ {/* Canvas with inspect overlay */}
1976
+ <div
1977
+ style={{
1978
+ flex: 1,
1979
+ position: 'relative',
1980
+ display: 'flex',
1981
+ alignItems: 'center',
1982
+ justifyContent: 'center',
1983
+ padding: 60,
1984
+ }}
1985
+ >
1986
+ <div
1987
+ style={{
1988
+ width: '100%',
1989
+ height: '100%',
1990
+ borderRadius: 14,
1991
+ border: `1px solid ${palette.border}`,
1992
+ background: `radial-gradient(ellipse at 30% 30%, ${palette.accent2}22, transparent 60%), ${palette.bg}`,
1993
+ padding: 56,
1994
+ position: 'relative',
1995
+ display: 'flex',
1996
+ flexDirection: 'column',
1997
+ justifyContent: 'center',
1998
+ }}
1999
+ >
2000
+ <Eyebrow style={{ fontSize: 14 }}>cover</Eyebrow>
2001
+ <div
2002
+ style={{
2003
+ position: 'relative',
2004
+ marginTop: 20,
2005
+ display: 'inline-block',
2006
+ width: 'fit-content',
2007
+ }}
2008
+ >
2009
+ {/* Inspect outline */}
2010
+ <div
2011
+ className="gs-outline"
2012
+ style={{
2013
+ position: 'absolute',
2014
+ inset: -10,
2015
+ border: `2px solid ${palette.inspect}`,
2016
+ background: palette.inspectFill,
2017
+ borderRadius: 6,
2018
+ pointerEvents: 'none',
2019
+ }}
2020
+ />
2021
+ <div
2022
+ style={{
2023
+ fontSize: 72,
2024
+ fontWeight: 600,
2025
+ letterSpacing: '-0.035em',
2026
+ lineHeight: 1.02,
2027
+ color: palette.text,
2028
+ position: 'relative',
2029
+ }}
2030
+ >
2031
+ Q2 Launch
2032
+ </div>
2033
+ </div>
2034
+ <div
2035
+ style={{
2036
+ marginTop: 18,
2037
+ fontSize: 24,
2038
+ color: palette.textSoft,
2039
+ maxWidth: 620,
2040
+ }}
2041
+ >
2042
+ What we're shipping, why it matters, and how we'll measure success.
2043
+ </div>
2044
+
2045
+ {/* Crosshair cursor (approaches target) */}
2046
+ <div
2047
+ className="gs-crosshair"
2048
+ style={{
2049
+ position: 'absolute',
2050
+ left: 240,
2051
+ top: 220,
2052
+ width: 28,
2053
+ height: 28,
2054
+ pointerEvents: 'none',
2055
+ }}
2056
+ >
2057
+ <div
2058
+ style={{
2059
+ position: 'absolute',
2060
+ left: 0,
2061
+ top: '50%',
2062
+ width: '100%',
2063
+ height: 2,
2064
+ background: palette.inspect,
2065
+ }}
2066
+ />
2067
+ <div
2068
+ style={{
2069
+ position: 'absolute',
2070
+ top: 0,
2071
+ left: '50%',
2072
+ width: 2,
2073
+ height: '100%',
2074
+ background: palette.inspect,
2075
+ }}
2076
+ />
2077
+ <div
2078
+ style={{
2079
+ position: 'absolute',
2080
+ inset: '25%',
2081
+ border: `2px solid ${palette.inspect}`,
2082
+ borderRadius: '50%',
2083
+ background: 'transparent',
2084
+ }}
2085
+ />
2086
+ </div>
2087
+
2088
+ {/* Comment popover */}
2089
+ <div
2090
+ className="gs-popover"
2091
+ style={{
2092
+ position: 'absolute',
2093
+ left: 320,
2094
+ top: 240,
2095
+ width: 380,
2096
+ background: palette.surfaceHi,
2097
+ border: `1px solid ${palette.borderBright}`,
2098
+ borderRadius: 12,
2099
+ padding: 18,
2100
+ boxShadow: '0 30px 60px -20px rgba(0,0,0,0.6)',
2101
+ transformOrigin: 'top left',
2102
+ }}
2103
+ >
2104
+ <div
2105
+ style={{
2106
+ display: 'flex',
2107
+ justifyContent: 'space-between',
2108
+ alignItems: 'center',
2109
+ fontFamily: font.mono,
2110
+ fontSize: 15,
2111
+ color: palette.muted,
2112
+ marginBottom: 12,
2113
+ }}
2114
+ >
2115
+ <span>Line 58 · Comment</span>
2116
+ <span style={{ color: palette.dim }}>✕</span>
2117
+ </div>
2118
+ <div
2119
+ style={{
2120
+ background: palette.surface,
2121
+ border: `1px solid ${palette.border}`,
2122
+ borderRadius: 8,
2123
+ padding: '14px 14px',
2124
+ fontSize: 20,
2125
+ color: palette.text,
2126
+ minHeight: 78,
2127
+ lineHeight: 1.4,
2128
+ }}
2129
+ >
2130
+ <span className="gs-type" style={{ maxWidth: '100%' }}>
2131
+ use the accent color on this title
2132
+ </span>
2133
+ <span className="es-caret" style={{ color: palette.text }} />
2134
+ </div>
2135
+ <div
2136
+ style={{
2137
+ marginTop: 12,
2138
+ fontFamily: font.mono,
2139
+ fontSize: 13,
2140
+ color: palette.muted,
2141
+ textAlign: 'right',
2142
+ }}
2143
+ >
2144
+ ⌘ / Ctrl + Enter to submit
2145
+ </div>
2146
+ </div>
2147
+ </div>
2148
+ </div>
2149
+ </div>
2150
+ </WindowShell>
2151
+ </div>
2152
+ </div>
2153
+ );
2154
+
2155
+ // ─── Slide 5: Apply comments ─────────────────────────────────────────────────
2156
+ const Apply: Page = () => (
2157
+ <div style={fill}>
2158
+ <Styles />
2159
+ <GridBg />
2160
+ <div
2161
+ style={{
2162
+ position: 'absolute',
2163
+ inset: 0,
2164
+ padding: '90px 120px 100px',
2165
+ display: 'flex',
2166
+ flexDirection: 'column',
2167
+ gap: 36,
2168
+ }}
2169
+ >
2170
+ <div className="es-fadeUp">
2171
+ <Eyebrow>06 / Apply comments</Eyebrow>
2172
+ <h2
2173
+ style={{
2174
+ marginTop: 20,
2175
+ marginBottom: 0,
2176
+ fontFamily: 'var(--osd-font-display)',
2177
+ fontSize: 88,
2178
+ fontWeight: 600,
2179
+ letterSpacing: '-0.035em',
2180
+ lineHeight: 1.02,
2181
+ }}
2182
+ >
2183
+ Agent reads markers. Edits apply live.
2184
+ </h2>
2185
+ </div>
2186
+
2187
+ <div
2188
+ style={{
2189
+ flex: 1,
2190
+ display: 'grid',
2191
+ gridTemplateColumns: '1.1fr 1fr',
2192
+ gap: 40,
2193
+ minHeight: 0,
2194
+ }}
2195
+ >
2196
+ {/* LEFT — agent CLI + code */}
2197
+ <WindowShell title="claude · ~/my-slide">
2198
+ <div
2199
+ style={{
2200
+ flex: 1,
2201
+ background: palette.surface,
2202
+ padding: '28px 36px',
2203
+ display: 'flex',
2204
+ flexDirection: 'column',
2205
+ gap: 6,
2206
+ minHeight: 0,
2207
+ overflow: 'hidden',
2208
+ }}
2209
+ >
2210
+ <AgentLine speaker="user" delay={0.2}>
2211
+ <SlashCmd name="apply-comments" color={palette.amber} />
2212
+ </AgentLine>
2213
+ <AgentLine speaker="assistant" delay={1.0}>
2214
+ 1 marker found. Applying…
2215
+ </AgentLine>
2216
+
2217
+ {/* Code snippet with marker being struck-through */}
2218
+ <div
2219
+ className="gs-stream"
2220
+ style={{
2221
+ animationDelay: '1.8s',
2222
+ marginTop: 8,
2223
+ background: palette.bg,
2224
+ border: `1px solid ${palette.border}`,
2225
+ borderRadius: 10,
2226
+ padding: '18px 22px',
2227
+ fontFamily: font.mono,
2228
+ fontSize: 18,
2229
+ lineHeight: 1.55,
2230
+ color: palette.textSoft,
2231
+ overflow: 'hidden',
2232
+ }}
2233
+ >
2234
+ <div style={{ color: palette.muted }}>
2235
+ <span style={{ color: palette.dim, marginRight: 14 }}>57</span>
2236
+ &lt;section&gt;
2237
+ </div>
2238
+ <div
2239
+ className="gs-strike"
2240
+ style={{
2241
+ color: palette.muted,
2242
+ animationDelay: '2.8s',
2243
+ whiteSpace: 'nowrap',
2244
+ overflow: 'hidden',
2245
+ textOverflow: 'ellipsis',
2246
+ paddingRight: 8,
2247
+ }}
2248
+ >
2249
+ <span style={{ color: palette.dim, marginRight: 14 }}>58</span>
2250
+ {'{/* '}
2251
+ <span style={{ color: palette.accentSoft }}>@slide-comment</span> id=
2252
+ <span style={{ color: palette.mint }}>"c-a1b2c3d4"</span> ts=
2253
+ <span style={{ color: palette.mint }}>"2026-04-20T10:15:00.000Z"</span> text=
2254
+ <span style={{ color: palette.mint }}>"eyJub3RlIjoi…"</span> {'*/}'}
2255
+ </div>
2256
+ <div>
2257
+ <span style={{ color: palette.dim, marginRight: 14 }}>59</span>
2258
+ &lt;h1 style={'{{'} color:{' '}
2259
+ <span
2260
+ className="gs-morph"
2261
+ style={{
2262
+ animationDelay: '3.2s',
2263
+ color: palette.text,
2264
+ }}
2265
+ >
2266
+ '{palette.accent}'
2267
+ </span>
2268
+ {' }}'}&gt;Q2 Launch&lt;/h1&gt;
2269
+ </div>
2270
+ <div style={{ color: palette.muted }}>
2271
+ <span style={{ color: palette.dim, marginRight: 14 }}>60</span>
2272
+ &lt;/section&gt;
2273
+ </div>
2274
+ </div>
2275
+
2276
+ <AgentLine speaker="tool" delay={3.8}>
2277
+ <div style={{ color: palette.muted }}>
2278
+ edit <span style={{ color: palette.text }}>slides/q2-launch/index.tsx</span>{' '}
2279
+ <span style={{ color: palette.mint }}>✓ 1 comment applied</span>
2280
+ </div>
2281
+ </AgentLine>
2282
+ <div style={{ flex: 1 }} />
2283
+ </div>
2284
+ </WindowShell>
2285
+
2286
+ {/* RIGHT — browser canvas morphs */}
2287
+ <WindowShell title="localhost:5173/s/q2-launch">
2288
+ <div
2289
+ style={{
2290
+ flex: 1,
2291
+ background: palette.surface,
2292
+ display: 'flex',
2293
+ padding: 40,
2294
+ minHeight: 0,
2295
+ }}
2296
+ >
2297
+ <div
2298
+ style={{
2299
+ flex: 1,
2300
+ borderRadius: 14,
2301
+ border: `1px solid ${palette.border}`,
2302
+ background: `radial-gradient(ellipse at 30% 30%, ${palette.accent2}22, transparent 60%), ${palette.bg}`,
2303
+ padding: 56,
2304
+ display: 'flex',
2305
+ flexDirection: 'column',
2306
+ justifyContent: 'center',
2307
+ }}
2308
+ >
2309
+ <Eyebrow style={{ fontSize: 14 }}>cover</Eyebrow>
2310
+ <div
2311
+ className="gs-morph"
2312
+ style={{
2313
+ animationDelay: '3.2s',
2314
+ marginTop: 20,
2315
+ fontSize: 84,
2316
+ fontWeight: 600,
2317
+ letterSpacing: '-0.035em',
2318
+ lineHeight: 1.02,
2319
+ color: palette.text,
2320
+ }}
2321
+ >
2322
+ Q2 Launch
2323
+ </div>
2324
+ <div
2325
+ style={{
2326
+ marginTop: 18,
2327
+ fontSize: 24,
2328
+ color: palette.textSoft,
2329
+ maxWidth: 620,
2330
+ }}
2331
+ >
2332
+ What we're shipping, why it matters, and how we'll measure success.
2333
+ </div>
2334
+ <div
2335
+ className="gs-stream"
2336
+ style={{
2337
+ animationDelay: '3.6s',
2338
+ marginTop: 40,
2339
+ display: 'inline-flex',
2340
+ alignItems: 'center',
2341
+ gap: 10,
2342
+ padding: '6px 12px',
2343
+ borderRadius: 999,
2344
+ background: `${palette.mint}18`,
2345
+ border: `1px solid ${palette.mint}55`,
2346
+ color: palette.mint,
2347
+ fontFamily: font.mono,
2348
+ fontSize: 16,
2349
+ width: 'fit-content',
2350
+ }}
2351
+ >
2352
+ <span
2353
+ style={{
2354
+ width: 8,
2355
+ height: 8,
2356
+ borderRadius: '50%',
2357
+ background: palette.mint,
2358
+ }}
2359
+ />
2360
+ hmr · updated
2361
+ </div>
2362
+ </div>
2363
+ </div>
2364
+ </WindowShell>
2365
+ </div>
2366
+ </div>
2367
+ </div>
2368
+ );
2369
+
2370
+ // ─── Slide: Themes ───────────────────────────────────────────────────────────
2371
+ const Themes: Page = () => {
2372
+ const themes = [
2373
+ {
2374
+ id: 'editorial-noir',
2375
+ mode: 'dark · serif',
2376
+ bg: '#0b0d10',
2377
+ text: '#f4ecdc',
2378
+ accent: '#d6a64b',
2379
+ muted: '#7a7468',
2380
+ titleFont: "'Georgia', 'Source Serif Pro', serif",
2381
+ sample: 'A quiet year.',
2382
+ },
2383
+ {
2384
+ id: 'paper-press',
2385
+ mode: 'light · serif',
2386
+ bg: '#f6f1e7',
2387
+ text: '#141210',
2388
+ accent: '#c43a1d',
2389
+ muted: '#8a8276',
2390
+ titleFont: "'Times New Roman', serif",
2391
+ sample: 'Field notes.',
2392
+ },
2393
+ {
2394
+ id: 'neon-terminal',
2395
+ mode: 'dark · mono',
2396
+ bg: '#05070a',
2397
+ text: '#e6edf3',
2398
+ accent: '#39ff88',
2399
+ muted: '#4a5560',
2400
+ titleFont: font.mono,
2401
+ sample: '$ boot.',
2402
+ },
2403
+ ];
2404
+
2405
+ return (
2406
+ <div style={fill}>
2407
+ <Styles />
2408
+ <GridBg />
2409
+ <div
2410
+ style={{
2411
+ position: 'absolute',
2412
+ inset: 0,
2413
+ padding: '140px 140px',
2414
+ display: 'flex',
2415
+ flexDirection: 'column',
2416
+ justifyContent: 'space-between',
2417
+ }}
2418
+ >
2419
+ <Eyebrow className="es-fadeUp">themes · /create-theme</Eyebrow>
2420
+
2421
+ <div className="es-fadeUp" style={{ animationDelay: '0.15s' }}>
2422
+ <h2
2423
+ style={{
2424
+ fontSize: 132,
2425
+ fontWeight: 600,
2426
+ letterSpacing: '-0.04em',
2427
+ lineHeight: 0.98,
2428
+ margin: 0,
2429
+ maxWidth: 1500,
2430
+ }}
2431
+ >
2432
+ Pin your look once,{' '}
2433
+ <span
2434
+ style={{
2435
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
2436
+ WebkitBackgroundClip: 'text',
2437
+ backgroundClip: 'text',
2438
+ color: 'transparent',
2439
+ }}
2440
+ >
2441
+ reuse it everywhere.
2442
+ </span>
2443
+ </h2>
2444
+ <p
2445
+ style={{
2446
+ marginTop: 28,
2447
+ fontSize: 28,
2448
+ lineHeight: 1.45,
2449
+ color: palette.textSoft,
2450
+ maxWidth: 1380,
2451
+ }}
2452
+ >
2453
+ Each{' '}
2454
+ <code style={{ fontFamily: font.mono, color: palette.accentSoft }}>
2455
+ themes/&lt;id&gt;.md
2456
+ </code>{' '}
2457
+ describes one visual identity — palette, typography, fixed Title and Footer.{' '}
2458
+ <code style={{ fontFamily: font.mono, color: palette.accentSoft }}>/create-slide</code>{' '}
2459
+ picks one before authoring; every page in the deck stays consistent.
2460
+ </p>
2461
+ </div>
2462
+
2463
+ <div
2464
+ style={{
2465
+ display: 'grid',
2466
+ gridTemplateColumns: 'repeat(3, 1fr)',
2467
+ gap: 28,
2468
+ }}
2469
+ >
2470
+ {themes.map((t, i) => (
2471
+ <div
2472
+ key={t.id}
2473
+ className="es-fadeUp"
2474
+ style={{
2475
+ animationDelay: `${0.35 + i * 0.12}s`,
2476
+ borderRadius: 18,
2477
+ border: `1px solid ${palette.border}`,
2478
+ background: t.bg,
2479
+ padding: '32px 32px 28px',
2480
+ display: 'flex',
2481
+ flexDirection: 'column',
2482
+ gap: 22,
2483
+ minHeight: 320,
2484
+ position: 'relative',
2485
+ overflow: 'hidden',
2486
+ }}
2487
+ >
2488
+ <div
2489
+ style={{
2490
+ display: 'flex',
2491
+ justifyContent: 'space-between',
2492
+ alignItems: 'baseline',
2493
+ fontFamily: font.mono,
2494
+ fontSize: 18,
2495
+ letterSpacing: '0.12em',
2496
+ textTransform: 'uppercase',
2497
+ }}
2498
+ >
2499
+ <span style={{ color: t.accent }}>{t.id}</span>
2500
+ <span style={{ color: t.muted }}>{t.mode}</span>
2501
+ </div>
2502
+
2503
+ <div
2504
+ style={{
2505
+ fontFamily: t.titleFont,
2506
+ fontSize: 64,
2507
+ fontWeight: 700,
2508
+ lineHeight: 1.04,
2509
+ color: t.text,
2510
+ letterSpacing: '-0.015em',
2511
+ }}
2512
+ >
2513
+ {t.sample}
2514
+ </div>
2515
+
2516
+ <div style={{ flex: 1 }} />
2517
+
2518
+ <div style={{ display: 'flex', gap: 8 }}>
2519
+ {[t.bg, t.text, t.accent, t.muted].map((c) => (
2520
+ <span
2521
+ key={c}
2522
+ style={{
2523
+ width: 36,
2524
+ height: 36,
2525
+ borderRadius: 8,
2526
+ background: c,
2527
+ border: '1px solid rgba(255,255,255,0.10)',
2528
+ }}
2529
+ />
2530
+ ))}
2531
+ </div>
2532
+ </div>
2533
+ ))}
2534
+ </div>
2535
+
2536
+ <div
2537
+ className="es-fadeUp"
2538
+ style={{
2539
+ animationDelay: '0.85s',
2540
+ display: 'flex',
2541
+ justifyContent: 'space-between',
2542
+ alignItems: 'center',
2543
+ fontFamily: font.mono,
2544
+ fontSize: 22,
2545
+ color: palette.muted,
2546
+ }}
2547
+ >
2548
+ <span>
2549
+ extract from a slide, image, or prose —{' '}
2550
+ <span style={{ color: palette.text }}>/create-theme</span>
2551
+ </span>
2552
+ <span>themes/*.md</span>
2553
+ </div>
2554
+ </div>
2555
+ </div>
2556
+ );
2557
+ };
2558
+
2559
+ // ─── Slide 6: Recap ──────────────────────────────────────────────────────────
2560
+ const Recap: Page = () => {
2561
+ const steps = [
2562
+ { n: '01', title: 'init', caption: 'npx @open-aippt/cli init' },
2563
+ { n: '02', title: 'prompt', caption: 'create-slide' },
2564
+ { n: '03', title: 'edit', caption: 'click → save' },
2565
+ { n: '04', title: 'assets', caption: 'drag · drop · svgl' },
2566
+ { n: '05', title: 'comment', caption: 'apply-comments' },
2567
+ ];
2568
+ return (
2569
+ <div style={fill}>
2570
+ <Styles />
2571
+ <GridBg />
2572
+ <div
2573
+ style={{
2574
+ position: 'absolute',
2575
+ inset: 0,
2576
+ padding: '140px 140px',
2577
+ display: 'flex',
2578
+ flexDirection: 'column',
2579
+ justifyContent: 'space-between',
2580
+ }}
2581
+ >
2582
+ <Eyebrow className="es-fadeUp">recap</Eyebrow>
2583
+
2584
+ <div className="es-fadeUp" style={{ animationDelay: '0.15s' }}>
2585
+ <h2
2586
+ style={{
2587
+ fontFamily: 'var(--osd-font-display)',
2588
+ fontSize: 160,
2589
+ fontWeight: 600,
2590
+ letterSpacing: '-0.045em',
2591
+ lineHeight: 0.98,
2592
+ margin: 0,
2593
+ }}
2594
+ >
2595
+ That's the
2596
+ <br />
2597
+ <span
2598
+ style={{
2599
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
2600
+ WebkitBackgroundClip: 'text',
2601
+ backgroundClip: 'text',
2602
+ color: 'transparent',
2603
+ }}
2604
+ >
2605
+ whole loop.
2606
+ </span>
2607
+ </h2>
2608
+ </div>
2609
+
2610
+ <div
2611
+ style={{
2612
+ display: 'grid',
2613
+ gridTemplateColumns: 'repeat(5, 1fr)',
2614
+ gap: 20,
2615
+ }}
2616
+ >
2617
+ {steps.map((s, i) => (
2618
+ <div
2619
+ key={s.n}
2620
+ className="es-fadeUp"
2621
+ style={{
2622
+ animationDelay: `${0.35 + i * 0.1}s`,
2623
+ padding: '24px 24px',
2624
+ border: `1px solid ${palette.border}`,
2625
+ borderRadius: 14,
2626
+ background: palette.surface,
2627
+ display: 'flex',
2628
+ flexDirection: 'column',
2629
+ gap: 14,
2630
+ }}
2631
+ >
2632
+ <div
2633
+ style={{
2634
+ fontFamily: font.mono,
2635
+ fontSize: 20,
2636
+ color: palette.accentSoft,
2637
+ letterSpacing: '0.12em',
2638
+ }}
2639
+ >
2640
+ {s.n}
2641
+ </div>
2642
+ <div
2643
+ style={{
2644
+ fontSize: 40,
2645
+ fontWeight: 600,
2646
+ letterSpacing: '-0.03em',
2647
+ }}
2648
+ >
2649
+ {s.title}
2650
+ </div>
2651
+ <div
2652
+ style={{
2653
+ fontFamily: font.mono,
2654
+ fontSize: 18,
2655
+ color: palette.muted,
2656
+ }}
2657
+ >
2658
+ {s.caption}
2659
+ </div>
2660
+ </div>
2661
+ ))}
2662
+ </div>
2663
+
2664
+ <div
2665
+ className="es-fadeUp"
2666
+ style={{
2667
+ animationDelay: '0.75s',
2668
+ display: 'flex',
2669
+ justifyContent: 'space-between',
2670
+ alignItems: 'center',
2671
+ fontFamily: font.mono,
2672
+ fontSize: 22,
2673
+ color: palette.muted,
2674
+ }}
2675
+ >
2676
+ <span>
2677
+ edit <span style={{ color: palette.text }}>slides/&lt;your-slide&gt;/index.tsx</span> —
2678
+ HMR does the rest
2679
+ </span>
2680
+ <span>open-aippt</span>
2681
+ </div>
2682
+ </div>
2683
+ </div>
2684
+ );
2685
+ };
2686
+
2687
+ // ─── Slide: Agent agnostic ───────────────────────────────────────────────────
2688
+ const AgentAgnostic: Page = () => {
2689
+ const agents = [
2690
+ { name: 'Claude Code', src: claudeLogo },
2691
+ { name: 'Codex', src: codexLogo },
2692
+ { name: 'Gemini CLI', src: geminiLogo },
2693
+ { name: 'opencode', src: opencodeLogo },
2694
+ ];
2695
+ return (
2696
+ <div style={fill}>
2697
+ <Styles />
2698
+ <GridBg />
2699
+ <div
2700
+ style={{
2701
+ position: 'absolute',
2702
+ inset: 0,
2703
+ padding: '110px 140px',
2704
+ display: 'flex',
2705
+ flexDirection: 'column',
2706
+ gap: 56,
2707
+ }}
2708
+ >
2709
+ <div className="es-fadeUp">
2710
+ <Eyebrow>why open-aippt · 01</Eyebrow>
2711
+ <h2
2712
+ style={{
2713
+ marginTop: 24,
2714
+ marginBottom: 0,
2715
+ fontFamily: 'var(--osd-font-display)',
2716
+ fontSize: 120,
2717
+ fontWeight: 600,
2718
+ letterSpacing: '-0.04em',
2719
+ lineHeight: 1.0,
2720
+ }}
2721
+ >
2722
+ Bring your{' '}
2723
+ <span
2724
+ style={{
2725
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
2726
+ WebkitBackgroundClip: 'text',
2727
+ backgroundClip: 'text',
2728
+ color: 'transparent',
2729
+ }}
2730
+ >
2731
+ favorite agent.
2732
+ </span>
2733
+ </h2>
2734
+ <p
2735
+ style={{
2736
+ marginTop: 28,
2737
+ maxWidth: 1280,
2738
+ fontSize: 32,
2739
+ lineHeight: 1.4,
2740
+ color: palette.textSoft,
2741
+ letterSpacing: '-0.01em',
2742
+ }}
2743
+ >
2744
+ open-aippt speaks plain React and a file-convention protocol. Any agent can author and
2745
+ edit slides — no lock-in, no bespoke SDK.
2746
+ </p>
2747
+ </div>
2748
+
2749
+ <div
2750
+ style={{
2751
+ flex: 1,
2752
+ display: 'flex',
2753
+ gap: 28,
2754
+ minHeight: 0,
2755
+ }}
2756
+ >
2757
+ {agents.map((a, i) => (
2758
+ <LogoCard
2759
+ key={a.name}
2760
+ src={a.src}
2761
+ name={a.name}
2762
+ delay={0.25 + i * 0.1}
2763
+ logoHeight={96}
2764
+ />
2765
+ ))}
2766
+ </div>
2767
+
2768
+ <div
2769
+ className="es-fadeUp"
2770
+ style={{
2771
+ animationDelay: '0.8s',
2772
+ fontFamily: font.mono,
2773
+ fontSize: 22,
2774
+ color: palette.muted,
2775
+ textAlign: 'center',
2776
+ }}
2777
+ >
2778
+ …and anything else that can write files.
2779
+ </div>
2780
+ </div>
2781
+ </div>
2782
+ );
2783
+ };
2784
+
2785
+ // ─── Slide: Free layout ──────────────────────────────────────────────────────
2786
+ const FreeLayout: Page = () => {
2787
+ const mockSlide = (
2788
+ kind: 'hero' | 'split' | 'bleed' | 'grid' | 'quote' | 'bullets',
2789
+ ): React.ReactNode => {
2790
+ const base: React.CSSProperties = {
2791
+ width: '100%',
2792
+ height: '100%',
2793
+ borderRadius: 12,
2794
+ border: `1px solid ${palette.border}`,
2795
+ background: `radial-gradient(ellipse at 30% 30%, ${palette.accent2}1f, transparent 60%), ${palette.bg}`,
2796
+ padding: 26,
2797
+ display: 'flex',
2798
+ overflow: 'hidden',
2799
+ position: 'relative',
2800
+ };
2801
+ if (kind === 'hero') {
2802
+ return (
2803
+ <div style={{ ...base, flexDirection: 'column', justifyContent: 'center' }}>
2804
+ <div
2805
+ style={{
2806
+ fontSize: 44,
2807
+ fontWeight: 600,
2808
+ letterSpacing: '-0.03em',
2809
+ lineHeight: 1,
2810
+ color: palette.text,
2811
+ }}
2812
+ >
2813
+ Ship
2814
+ <br />
2815
+ <span style={{ color: palette.accentSoft }}>louder.</span>
2816
+ </div>
2817
+ </div>
2818
+ );
2819
+ }
2820
+ if (kind === 'split') {
2821
+ return (
2822
+ <div style={{ ...base, padding: 0 }}>
2823
+ <div
2824
+ style={{
2825
+ flex: 1,
2826
+ padding: 22,
2827
+ display: 'flex',
2828
+ flexDirection: 'column',
2829
+ justifyContent: 'center',
2830
+ gap: 8,
2831
+ }}
2832
+ >
2833
+ <div
2834
+ style={{
2835
+ height: 8,
2836
+ width: '70%',
2837
+ background: palette.textSoft,
2838
+ opacity: 0.7,
2839
+ borderRadius: 2,
2840
+ }}
2841
+ />
2842
+ <div style={{ height: 6, width: '55%', background: palette.muted, borderRadius: 2 }} />
2843
+ <div style={{ height: 6, width: '60%', background: palette.muted, borderRadius: 2 }} />
2844
+ <div style={{ height: 6, width: '40%', background: palette.muted, borderRadius: 2 }} />
2845
+ </div>
2846
+ <div
2847
+ style={{
2848
+ flex: 1,
2849
+ background: `linear-gradient(135deg, ${palette.accent}55, ${palette.accent2}33)`,
2850
+ }}
2851
+ />
2852
+ </div>
2853
+ );
2854
+ }
2855
+ if (kind === 'bleed') {
2856
+ return (
2857
+ <div
2858
+ style={{
2859
+ ...base,
2860
+ padding: 0,
2861
+ background: `linear-gradient(160deg, ${palette.accentSoft}55, ${palette.accent2}33 40%, ${palette.bg} 100%)`,
2862
+ }}
2863
+ >
2864
+ <div
2865
+ style={{
2866
+ position: 'absolute',
2867
+ left: 22,
2868
+ bottom: 22,
2869
+ right: 22,
2870
+ display: 'flex',
2871
+ flexDirection: 'column',
2872
+ gap: 6,
2873
+ }}
2874
+ >
2875
+ <div
2876
+ style={{
2877
+ height: 7,
2878
+ width: '45%',
2879
+ background: palette.text,
2880
+ opacity: 0.95,
2881
+ borderRadius: 2,
2882
+ }}
2883
+ />
2884
+ <div
2885
+ style={{
2886
+ height: 5,
2887
+ width: '65%',
2888
+ background: palette.textSoft,
2889
+ opacity: 0.75,
2890
+ borderRadius: 2,
2891
+ }}
2892
+ />
2893
+ </div>
2894
+ </div>
2895
+ );
2896
+ }
2897
+ if (kind === 'grid') {
2898
+ return (
2899
+ <div
2900
+ style={{
2901
+ ...base,
2902
+ padding: 22,
2903
+ display: 'grid',
2904
+ gridTemplateColumns: 'repeat(3, 1fr)',
2905
+ gridTemplateRows: 'repeat(3, 1fr)',
2906
+ gap: 8,
2907
+ }}
2908
+ >
2909
+ {Array.from({ length: 9 }).map((_, i) => (
2910
+ <div
2911
+ key={i}
2912
+ style={{
2913
+ borderRadius: 4,
2914
+ background: i % 4 === 0 ? `${palette.accent}44` : palette.surfaceMax,
2915
+ border: `1px solid ${palette.border}`,
2916
+ }}
2917
+ />
2918
+ ))}
2919
+ </div>
2920
+ );
2921
+ }
2922
+ if (kind === 'quote') {
2923
+ return (
2924
+ <div
2925
+ style={{
2926
+ ...base,
2927
+ flexDirection: 'column',
2928
+ justifyContent: 'center',
2929
+ alignItems: 'center',
2930
+ textAlign: 'center',
2931
+ padding: 30,
2932
+ }}
2933
+ >
2934
+ <div
2935
+ style={{
2936
+ fontSize: 40,
2937
+ color: palette.muted,
2938
+ lineHeight: 1,
2939
+ marginBottom: 6,
2940
+ fontFamily: font.mono,
2941
+ }}
2942
+ >
2943
+
2944
+ </div>
2945
+ <div
2946
+ style={{
2947
+ fontSize: 22,
2948
+ fontWeight: 500,
2949
+ letterSpacing: '-0.02em',
2950
+ lineHeight: 1.2,
2951
+ color: palette.textSoft,
2952
+ }}
2953
+ >
2954
+ Any layout.
2955
+ <br />
2956
+ Any time.
2957
+ </div>
2958
+ </div>
2959
+ );
2960
+ }
2961
+ return (
2962
+ <div
2963
+ style={{
2964
+ ...base,
2965
+ flexDirection: 'column',
2966
+ justifyContent: 'center',
2967
+ gap: 12,
2968
+ paddingLeft: 32,
2969
+ }}
2970
+ >
2971
+ {['— prompt. write. ship.', '— no templates.', '— no themes.', '— zero friction.'].map(
2972
+ (t, i) => (
2973
+ <div
2974
+ key={i}
2975
+ style={{
2976
+ fontFamily: font.mono,
2977
+ fontSize: 18,
2978
+ color: i === 0 ? palette.accentSoft : palette.textSoft,
2979
+ letterSpacing: '-0.01em',
2980
+ }}
2981
+ >
2982
+ {t}
2983
+ </div>
2984
+ ),
2985
+ )}
2986
+ </div>
2987
+ );
2988
+ };
2989
+
2990
+ const kinds: Array<'hero' | 'split' | 'bleed' | 'grid' | 'quote' | 'bullets'> = [
2991
+ 'hero',
2992
+ 'split',
2993
+ 'bleed',
2994
+ 'grid',
2995
+ 'quote',
2996
+ 'bullets',
2997
+ ];
2998
+
2999
+ return (
3000
+ <div style={fill}>
3001
+ <Styles />
3002
+ <GridBg />
3003
+ <div
3004
+ style={{
3005
+ position: 'absolute',
3006
+ inset: 0,
3007
+ padding: '110px 140px',
3008
+ display: 'flex',
3009
+ flexDirection: 'column',
3010
+ gap: 48,
3011
+ }}
3012
+ >
3013
+ <div className="es-fadeUp">
3014
+ <Eyebrow>why open-aippt · 02</Eyebrow>
3015
+ <h2
3016
+ style={{
3017
+ marginTop: 24,
3018
+ marginBottom: 0,
3019
+ fontFamily: 'var(--osd-font-display)',
3020
+ fontSize: 120,
3021
+ fontWeight: 600,
3022
+ letterSpacing: '-0.04em',
3023
+ lineHeight: 1.0,
3024
+ }}
3025
+ >
3026
+ No templates.{' '}
3027
+ <span
3028
+ style={{
3029
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
3030
+ WebkitBackgroundClip: 'text',
3031
+ backgroundClip: 'text',
3032
+ color: 'transparent',
3033
+ }}
3034
+ >
3035
+ No opinions.
3036
+ </span>
3037
+ </h2>
3038
+ <p
3039
+ style={{
3040
+ marginTop: 28,
3041
+ maxWidth: 1280,
3042
+ fontSize: 32,
3043
+ lineHeight: 1.4,
3044
+ color: palette.textSoft,
3045
+ letterSpacing: '-0.01em',
3046
+ }}
3047
+ >
3048
+ Zero layouts. Zero slide types. Zero "themes". Each page is just a React component on a
3049
+ 1920×1080 canvas — the agent decides everything.
3050
+ </p>
3051
+ </div>
3052
+
3053
+ <div
3054
+ style={{
3055
+ flex: 1,
3056
+ display: 'grid',
3057
+ gridTemplateColumns: 'repeat(3, 1fr)',
3058
+ gridTemplateRows: 'repeat(2, 1fr)',
3059
+ gap: 28,
3060
+ minHeight: 0,
3061
+ }}
3062
+ >
3063
+ {kinds.map((k, i) => (
3064
+ <div key={k} className="gs-thumbIn" style={{ animationDelay: `${0.25 + i * 0.09}s` }}>
3065
+ {mockSlide(k)}
3066
+ </div>
3067
+ ))}
3068
+ </div>
3069
+ </div>
3070
+ </div>
3071
+ );
3072
+ };
3073
+
3074
+ // ─── Slide: Git-tracked, yours forever ───────────────────────────────────────
3075
+ const GitTracked: Page = () => {
3076
+ const commits = [
3077
+ {
3078
+ hash: 'a1b2c3d',
3079
+ head: '(HEAD -> main)',
3080
+ msg: 'refine cover typography',
3081
+ },
3082
+ {
3083
+ hash: '9f8e7d6',
3084
+ head: '',
3085
+ msg: 'revise Q2 metrics from finance review',
3086
+ },
3087
+ {
3088
+ hash: '6a5b4c3',
3089
+ head: '',
3090
+ msg: 'initial draft of Q2 launch deck',
3091
+ },
3092
+ ];
3093
+
3094
+ const properties = [
3095
+ { label: 'plain .tsx files', caption: 'no proprietary format' },
3096
+ { label: 'diffable in any tool', caption: 'review like any other PR' },
3097
+ { label: 'branch · merge · revert', caption: 'the tools you already know' },
3098
+ ];
3099
+
3100
+ return (
3101
+ <div style={fill}>
3102
+ <Styles />
3103
+ <GridBg />
3104
+ <div
3105
+ style={{
3106
+ position: 'absolute',
3107
+ inset: 0,
3108
+ padding: '90px 120px 100px',
3109
+ display: 'flex',
3110
+ flexDirection: 'column',
3111
+ gap: 40,
3112
+ }}
3113
+ >
3114
+ <div className="es-fadeUp">
3115
+ <Eyebrow>why open-aippt · 03</Eyebrow>
3116
+ <h2
3117
+ style={{
3118
+ marginTop: 20,
3119
+ marginBottom: 0,
3120
+ fontFamily: 'var(--osd-font-display)',
3121
+ fontSize: 104,
3122
+ fontWeight: 600,
3123
+ letterSpacing: '-0.04em',
3124
+ lineHeight: 1.0,
3125
+ }}
3126
+ >
3127
+ Your slides are{' '}
3128
+ <span
3129
+ style={{
3130
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
3131
+ WebkitBackgroundClip: 'text',
3132
+ backgroundClip: 'text',
3133
+ color: 'transparent',
3134
+ }}
3135
+ >
3136
+ yours. Forever.
3137
+ </span>
3138
+ </h2>
3139
+ <p
3140
+ style={{
3141
+ marginTop: 20,
3142
+ fontSize: 28,
3143
+ color: palette.textSoft,
3144
+ maxWidth: 1280,
3145
+ letterSpacing: '-0.01em',
3146
+ }}
3147
+ >
3148
+ Every slide is a file in your repo. No proprietary database. No SaaS lock-in. No
3149
+ export-to-PDF-and-pray.
3150
+ </p>
3151
+ </div>
3152
+
3153
+ <div
3154
+ style={{
3155
+ flex: 1,
3156
+ display: 'grid',
3157
+ gridTemplateColumns: '1.25fr 1fr',
3158
+ gap: 36,
3159
+ minHeight: 0,
3160
+ }}
3161
+ >
3162
+ <WindowShell title="~/my-slide — git log">
3163
+ <div
3164
+ style={{
3165
+ flex: 1,
3166
+ background: palette.surface,
3167
+ padding: '32px 40px',
3168
+ fontFamily: font.mono,
3169
+ fontSize: 22,
3170
+ lineHeight: 1.65,
3171
+ color: palette.textSoft,
3172
+ overflow: 'hidden',
3173
+ }}
3174
+ >
3175
+ <div style={{ color: palette.muted, marginBottom: 14 }}>
3176
+ <span style={{ color: palette.mint }}>$</span> git log --oneline slides/q2-launch/
3177
+ </div>
3178
+ {commits.map((c, i) => (
3179
+ <div
3180
+ key={c.hash}
3181
+ className="gs-stream"
3182
+ style={{
3183
+ animationDelay: `${0.4 + i * 0.25}s`,
3184
+ display: 'flex',
3185
+ gap: 14,
3186
+ padding: '6px 0',
3187
+ }}
3188
+ >
3189
+ <span style={{ color: palette.amber }}>*</span>
3190
+ <span style={{ color: palette.accentSoft }}>{c.hash}</span>
3191
+ {c.head && <span style={{ color: palette.mint }}>{c.head}</span>}
3192
+ <span style={{ color: palette.text }}>{c.msg}</span>
3193
+ </div>
3194
+ ))}
3195
+ <div
3196
+ className="gs-stream"
3197
+ style={{
3198
+ animationDelay: '1.25s',
3199
+ marginTop: 22,
3200
+ display: 'flex',
3201
+ gap: 14,
3202
+ color: palette.muted,
3203
+ }}
3204
+ >
3205
+ <span style={{ color: palette.mint }}>$</span>
3206
+ <span className="es-caret" style={{ color: palette.text }} />
3207
+ </div>
3208
+ </div>
3209
+ </WindowShell>
3210
+
3211
+ <div
3212
+ style={{
3213
+ display: 'flex',
3214
+ flexDirection: 'column',
3215
+ gap: 20,
3216
+ minHeight: 0,
3217
+ }}
3218
+ >
3219
+ {properties.map((p, i) => (
3220
+ <div
3221
+ key={p.label}
3222
+ className="es-fadeUp"
3223
+ style={{
3224
+ animationDelay: `${0.5 + i * 0.12}s`,
3225
+ flex: 1,
3226
+ padding: '28px 32px',
3227
+ borderRadius: 14,
3228
+ border: `1px solid ${palette.border}`,
3229
+ background: palette.surface,
3230
+ display: 'flex',
3231
+ flexDirection: 'column',
3232
+ justifyContent: 'center',
3233
+ gap: 10,
3234
+ }}
3235
+ >
3236
+ <div
3237
+ style={{
3238
+ display: 'flex',
3239
+ alignItems: 'center',
3240
+ gap: 14,
3241
+ }}
3242
+ >
3243
+ <span
3244
+ style={{
3245
+ width: 10,
3246
+ height: 10,
3247
+ borderRadius: '50%',
3248
+ background: palette.accent,
3249
+ boxShadow: `0 0 16px ${palette.accent}`,
3250
+ }}
3251
+ />
3252
+ <span
3253
+ style={{
3254
+ fontFamily: font.mono,
3255
+ fontSize: 28,
3256
+ color: palette.text,
3257
+ letterSpacing: '-0.01em',
3258
+ }}
3259
+ >
3260
+ {p.label}
3261
+ </span>
3262
+ </div>
3263
+ <div
3264
+ style={{
3265
+ paddingLeft: 24,
3266
+ fontSize: 22,
3267
+ color: palette.muted,
3268
+ }}
3269
+ >
3270
+ {p.caption}
3271
+ </div>
3272
+ </div>
3273
+ ))}
3274
+ </div>
3275
+ </div>
3276
+ </div>
3277
+ </div>
3278
+ );
3279
+ };
3280
+
3281
+ // ─── Slide: Deploy anywhere ──────────────────────────────────────────────────
3282
+ const DeployAnywhere: Page = () => {
3283
+ const hosts = [
3284
+ { name: 'Vercel', src: vercelLogo },
3285
+ { name: 'Cloudflare', src: cloudflareLogo },
3286
+ { name: 'Zeabur', src: zeaburLogo },
3287
+ ];
3288
+ return (
3289
+ <div style={fill}>
3290
+ <Styles />
3291
+ <GridBg />
3292
+ <div
3293
+ style={{
3294
+ position: 'absolute',
3295
+ inset: 0,
3296
+ padding: '110px 140px',
3297
+ display: 'flex',
3298
+ flexDirection: 'column',
3299
+ gap: 48,
3300
+ }}
3301
+ >
3302
+ <div className="es-fadeUp">
3303
+ <Eyebrow>why open-aippt · 04</Eyebrow>
3304
+ <h2
3305
+ style={{
3306
+ marginTop: 24,
3307
+ marginBottom: 0,
3308
+ fontFamily: 'var(--osd-font-display)',
3309
+ fontSize: 104,
3310
+ fontWeight: 600,
3311
+ letterSpacing: '-0.04em',
3312
+ lineHeight: 1.0,
3313
+ }}
3314
+ >
3315
+ Ship it{' '}
3316
+ <span
3317
+ style={{
3318
+ background: `linear-gradient(90deg, ${palette.accentSoft}, ${palette.accent})`,
3319
+ WebkitBackgroundClip: 'text',
3320
+ backgroundClip: 'text',
3321
+ color: 'transparent',
3322
+ }}
3323
+ >
3324
+ anywhere.
3325
+ </span>
3326
+ </h2>
3327
+ <p
3328
+ style={{
3329
+ marginTop: 24,
3330
+ maxWidth: 1280,
3331
+ fontSize: 32,
3332
+ lineHeight: 1.4,
3333
+ color: palette.textSoft,
3334
+ letterSpacing: '-0.01em',
3335
+ }}
3336
+ >
3337
+ open-aippt builds to plain static assets. Drop them on Vercel, Cloudflare, Zeabur — or
3338
+ any server that serves HTML.
3339
+ </p>
3340
+ </div>
3341
+
3342
+ <div
3343
+ style={{
3344
+ flex: 1,
3345
+ display: 'flex',
3346
+ gap: 32,
3347
+ minHeight: 0,
3348
+ }}
3349
+ >
3350
+ {hosts.map((h, i) => (
3351
+ <LogoCard
3352
+ key={h.name}
3353
+ src={h.src}
3354
+ name={h.name}
3355
+ delay={0.25 + i * 0.12}
3356
+ logoHeight={104}
3357
+ />
3358
+ ))}
3359
+ </div>
3360
+
3361
+ <div
3362
+ className="es-fadeUp"
3363
+ style={{
3364
+ animationDelay: '0.75s',
3365
+ alignSelf: 'center',
3366
+ padding: '18px 28px',
3367
+ borderRadius: 12,
3368
+ border: `1px solid ${palette.border}`,
3369
+ background: palette.surface,
3370
+ fontFamily: font.mono,
3371
+ fontSize: 26,
3372
+ color: palette.textSoft,
3373
+ display: 'flex',
3374
+ gap: 16,
3375
+ alignItems: 'center',
3376
+ }}
3377
+ >
3378
+ <span style={{ color: palette.mint }}>$</span>
3379
+ <span style={{ color: palette.text }}>pnpm build</span>
3380
+ <span style={{ color: palette.muted }}>→ dist/</span>
3381
+ </div>
3382
+ </div>
3383
+ </div>
3384
+ );
3385
+ };
3386
+
3387
+ // ─── Slide export ────────────────────────────────────────────────────────────
3388
+ export const meta: SlideMeta = {
3389
+ title: 'Getting started with open-aippt',
3390
+ };
3391
+
3392
+ export default [
3393
+ Cover,
3394
+ AgentAgnostic,
3395
+ FreeLayout,
3396
+ Init,
3397
+ Prompt,
3398
+ VisualEdit,
3399
+ AssetsManager,
3400
+ Inspect,
3401
+ Apply,
3402
+ Themes,
3403
+ GitTracked,
3404
+ DeployAnywhere,
3405
+ Recap,
3406
+ ] satisfies Page[];