nextworks 0.2.0-alpha.2 → 0.2.0-alpha.20

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.
Files changed (145) hide show
  1. package/README.md +280 -228
  2. package/dist/cli_manifests/blocks_manifest.json +193 -194
  3. package/dist/commands/blocks.d.ts +3 -0
  4. package/dist/commands/blocks.d.ts.map +1 -1
  5. package/dist/commands/blocks.js +128 -19
  6. package/dist/commands/blocks.js.map +1 -1
  7. package/dist/commands/doctor.d.ts +136 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +696 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/remove-blocks.d.ts +4 -1
  12. package/dist/commands/remove-blocks.d.ts.map +1 -1
  13. package/dist/commands/remove-blocks.js +24 -6
  14. package/dist/commands/remove-blocks.js.map +1 -1
  15. package/dist/index.js +125 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +104 -194
  18. package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +115 -75
  19. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +269 -251
  20. package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
  21. package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -36
  22. package/dist/kits/blocks/app/templates/digitalagency/components/About.tsx +71 -99
  23. package/dist/kits/blocks/app/templates/digitalagency/components/CTA.tsx +40 -74
  24. package/dist/kits/blocks/app/templates/digitalagency/components/Contact.tsx +123 -227
  25. package/dist/kits/blocks/app/templates/digitalagency/components/Footer.tsx +89 -89
  26. package/dist/kits/blocks/app/templates/digitalagency/components/Hero.tsx +83 -90
  27. package/dist/kits/blocks/app/templates/digitalagency/components/Navbar.tsx +121 -168
  28. package/dist/kits/blocks/app/templates/digitalagency/components/NetworkPattern.tsx +288 -297
  29. package/dist/kits/blocks/app/templates/digitalagency/components/Portfolio.tsx +157 -157
  30. package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
  31. package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
  32. package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
  33. package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
  34. package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
  35. package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
  36. package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +130 -85
  37. package/dist/kits/blocks/app/templates/gallery/page.tsx +357 -303
  38. package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -74
  39. package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -55
  40. package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -178
  41. package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -93
  42. package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
  43. package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -93
  44. package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -84
  45. package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
  46. package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -89
  47. package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -162
  48. package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
  49. package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
  50. package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
  51. package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
  52. package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -89
  53. package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
  54. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
  55. package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
  56. package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -38
  57. package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -176
  58. package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
  59. package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
  60. package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -91
  61. package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
  62. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -105
  63. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -127
  64. package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -159
  65. package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
  66. package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -97
  67. package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
  68. package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
  69. package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
  70. package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -183
  71. package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
  72. package/dist/kits/blocks/components/sections/About.tsx +291 -291
  73. package/dist/kits/blocks/components/sections/CTA.tsx +257 -258
  74. package/dist/kits/blocks/components/sections/CommandShowcase.tsx +517 -0
  75. package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
  76. package/dist/kits/blocks/components/sections/FAQ.tsx +214 -225
  77. package/dist/kits/blocks/components/sections/FeaturedProjectShowcase.tsx +687 -0
  78. package/dist/kits/blocks/components/sections/Features.tsx +268 -270
  79. package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
  80. package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
  81. package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
  82. package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +246 -0
  83. package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
  84. package/dist/kits/blocks/components/sections/HeroWithVideo.tsx +495 -0
  85. package/dist/kits/blocks/components/sections/Navbar.tsx +352 -353
  86. package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -550
  87. package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
  88. package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
  89. package/dist/kits/blocks/components/sections/ProjectDeepDive.tsx +805 -0
  90. package/dist/kits/blocks/components/sections/SelectedWorkRail.tsx +485 -0
  91. package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
  92. package/dist/kits/blocks/components/sections/Team.tsx +309 -309
  93. package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
  94. package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
  95. package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +128 -0
  96. package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +519 -0
  97. package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +140 -0
  98. package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +130 -0
  99. package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +430 -0
  100. package/dist/kits/blocks/components/sections/product-demo/TaskListPanel.tsx +302 -0
  101. package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +744 -0
  102. package/dist/kits/blocks/components/sections/product-demo/types.ts +262 -0
  103. package/dist/kits/blocks/components/theme-provider.tsx +1 -34
  104. package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
  105. package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
  106. package/dist/kits/blocks/components/ui/button.tsx +122 -122
  107. package/dist/kits/blocks/components/ui/card.tsx +95 -95
  108. package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
  109. package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
  110. package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
  111. package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
  112. package/dist/kits/blocks/components/ui/input.tsx +27 -27
  113. package/dist/kits/blocks/components/ui/label.tsx +29 -29
  114. package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
  115. package/dist/kits/blocks/components/ui/select.tsx +25 -25
  116. package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
  117. package/dist/kits/blocks/components/ui/table.tsx +98 -98
  118. package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
  119. package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
  120. package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -247
  121. package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
  122. package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
  123. package/dist/kits/blocks/lib/themes.ts +400 -400
  124. package/dist/kits/blocks/lib/utils.ts +6 -9
  125. package/dist/kits/blocks/package-deps.json +40 -28
  126. package/dist/kits/blocks/tsconfig.json +11 -0
  127. package/dist/kits/blocks/tsconfig.tsbuildinfo +1 -0
  128. package/dist/utils/file-operations.d.ts +3 -1
  129. package/dist/utils/file-operations.d.ts.map +1 -1
  130. package/dist/utils/file-operations.js +84 -12
  131. package/dist/utils/file-operations.js.map +1 -1
  132. package/dist/utils/installation-tracker.d.ts +2 -2
  133. package/dist/utils/installation-tracker.d.ts.map +1 -1
  134. package/dist/utils/installation-tracker.js +11 -11
  135. package/dist/utils/installation-tracker.js.map +1 -1
  136. package/dist/utils/package-manager.d.ts +6 -0
  137. package/dist/utils/package-manager.d.ts.map +1 -0
  138. package/dist/utils/package-manager.js +58 -0
  139. package/dist/utils/package-manager.js.map +1 -0
  140. package/dist/utils/yarn-pnp.d.ts +6 -0
  141. package/dist/utils/yarn-pnp.d.ts.map +1 -0
  142. package/dist/utils/yarn-pnp.js +39 -0
  143. package/dist/utils/yarn-pnp.js.map +1 -0
  144. package/package.json +5 -2
  145. package/dist/kits/blocks/notes/THEME_GUIDE.md +0 -29
@@ -0,0 +1,128 @@
1
+ import React from "react";
2
+ import { cn } from "@/lib/utils";
3
+ import type {
4
+ ProductDemoApprovalInboxState,
5
+ ProductDemoStatusTone,
6
+ } from "./types";
7
+
8
+ export interface ApprovalInboxPanelProps {
9
+ state: ProductDemoApprovalInboxState;
10
+ }
11
+
12
+ const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
13
+ neutral:
14
+ "border-[var(--demo-border)] bg-[var(--demo-panel-bg)] text-[var(--demo-muted-fg)]",
15
+ info: "border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] text-[var(--demo-info)]",
16
+ success:
17
+ "border-[var(--demo-success-border)] bg-[var(--demo-success-soft-bg)] text-[var(--demo-success)]",
18
+ warning:
19
+ "border-[var(--demo-warning-border)] bg-[var(--demo-warning-soft-bg)] text-[var(--demo-warning)]",
20
+ danger:
21
+ "border-[var(--demo-danger-border)] bg-[var(--demo-danger-soft-bg)] text-[var(--demo-danger)]",
22
+ };
23
+
24
+ function getStatusClass(tone: ProductDemoStatusTone = "neutral") {
25
+ return STATUS_TONE_CLASSES[tone];
26
+ }
27
+
28
+ export function ApprovalInboxPanel({ state }: ApprovalInboxPanelProps) {
29
+ return (
30
+ <div className="flex h-full flex-col gap-4 text-[var(--demo-fg)]">
31
+ <div className="space-y-1.5">
32
+ {state.title && (
33
+ <h4 className="text-sm font-semibold text-[var(--demo-fg)]">
34
+ {state.title}
35
+ </h4>
36
+ )}
37
+ {state.subtitle && (
38
+ <p className="text-xs leading-relaxed text-[var(--demo-muted-fg)]">
39
+ {state.subtitle}
40
+ </p>
41
+ )}
42
+ </div>
43
+
44
+ {state.counts?.length ? (
45
+ <div className="flex flex-wrap gap-2">
46
+ {state.counts.map((count) => (
47
+ <div
48
+ key={count.id}
49
+ className={cn(
50
+ "rounded-xl border px-3 py-2",
51
+ getStatusClass(count.tone),
52
+ )}
53
+ >
54
+ <div className="text-[10px] font-medium uppercase tracking-[0.14em] opacity-80">
55
+ {count.label}
56
+ </div>
57
+ <div className="mt-1 text-sm font-semibold">{count.value}</div>
58
+ </div>
59
+ ))}
60
+ </div>
61
+ ) : null}
62
+
63
+ <div className="space-y-3">
64
+ {state.items.map((item) => {
65
+ const isActive = item.id === state.activeItemId || item.highlighted;
66
+
67
+ return (
68
+ <div
69
+ key={item.id}
70
+ className={cn(
71
+ "rounded-2xl border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] p-3",
72
+ isActive &&
73
+ "border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] shadow-sm",
74
+ )}
75
+ >
76
+ <div className="flex items-start justify-between gap-3">
77
+ <div>
78
+ <div className="text-sm font-semibold text-[var(--demo-fg)]">
79
+ {item.title}
80
+ </div>
81
+ {item.description && (
82
+ <p className="mt-1 text-xs leading-relaxed text-[var(--demo-muted-fg)]">
83
+ {item.description}
84
+ </p>
85
+ )}
86
+ </div>
87
+ {item.status && (
88
+ <span
89
+ className={cn(
90
+ "rounded-full border px-2 py-1 text-[10px] font-medium uppercase tracking-[0.14em]",
91
+ getStatusClass(item.status),
92
+ )}
93
+ >
94
+ {item.status}
95
+ </span>
96
+ )}
97
+ </div>
98
+
99
+ {(item.requester || item.priorityLabel || item.dueLabel) && (
100
+ <div className="mt-3 flex flex-wrap gap-2 text-[11px] text-[var(--demo-muted-fg)]">
101
+ {item.requester && <span>By {item.requester}</span>}
102
+ {item.priorityLabel && <span>{item.priorityLabel}</span>}
103
+ {item.dueLabel && <span>{item.dueLabel}</span>}
104
+ </div>
105
+ )}
106
+
107
+ {item.actions?.length ? (
108
+ <div className="mt-3 flex flex-wrap gap-2">
109
+ {item.actions.map((action) => (
110
+ <span
111
+ key={action.id}
112
+ className={cn(
113
+ "rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.14em]",
114
+ getStatusClass(action.tone),
115
+ )}
116
+ >
117
+ {action.label}
118
+ </span>
119
+ ))}
120
+ </div>
121
+ ) : null}
122
+ </div>
123
+ );
124
+ })}
125
+ </div>
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,519 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { cn } from "@/lib/utils";
5
+ import { DemoWindow } from "./DemoWindow";
6
+ import { KnowledgePanel } from "./KnowledgePanel";
7
+ import { RunConsolePanel } from "./RunConsolePanel";
8
+ import { TaskListPanel } from "./TaskListPanel";
9
+ import { WorkflowStudioPanel } from "./WorkflowStudioPanel";
10
+ import type {
11
+ ProductDemoHighlightTarget,
12
+ ProductDemoHighlightTone,
13
+ ProductDemoRunConsolePlaybackConfig,
14
+ ProductDemoScenario,
15
+ ProductDemoWindowKey,
16
+ ProductDemoWindowMeta,
17
+ ProductDemoWorkflowPlaybackConfig,
18
+ } from "./types";
19
+
20
+ export interface DemoStageProps {
21
+ scenarios?: ProductDemoScenario[];
22
+ initialScenarioIndex?: number;
23
+ activeScenarioKey?: string;
24
+ autoCycle?: boolean;
25
+ cycleIntervalMs?: number;
26
+ className?: string;
27
+ enableMotion?: boolean;
28
+ ariaLabel?: string;
29
+ }
30
+
31
+ type WindowRenderData = {
32
+ key: ProductDemoWindowKey;
33
+ meta: ProductDemoWindowMeta;
34
+ content: React.ReactNode;
35
+ };
36
+
37
+ const WINDOW_ORDER: ProductDemoWindowKey[] = [
38
+ "taskList",
39
+ "workflowStudio",
40
+ "runConsole",
41
+ "knowledgePanel",
42
+ ];
43
+
44
+ const HIGHLIGHT_TONE_CLASSES: Record<ProductDemoHighlightTone, string> = {
45
+ neutral: "border-border/60 bg-muted/60 text-muted-foreground",
46
+ info: "border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-300",
47
+ success:
48
+ "border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300",
49
+ warning:
50
+ "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
51
+ danger: "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-300",
52
+ accent: "border-primary/35 bg-primary/10 text-primary",
53
+ muted: "border-border/60 bg-background/70 text-muted-foreground",
54
+ };
55
+
56
+ function clampInitialScenarioIndex(index: number | undefined, count: number) {
57
+ if (count <= 0) {
58
+ return 0;
59
+ }
60
+
61
+ return Math.max(0, Math.min(index ?? 0, count - 1));
62
+ }
63
+
64
+ function getHighlightClass(tone: ProductDemoHighlightTone = "accent") {
65
+ return HIGHLIGHT_TONE_CLASSES[tone];
66
+ }
67
+
68
+ function useActiveScenario({
69
+ scenarios,
70
+ initialScenarioIndex,
71
+ activeScenarioKey,
72
+ }: {
73
+ scenarios: ProductDemoScenario[];
74
+ initialScenarioIndex?: number;
75
+ activeScenarioKey?: string;
76
+ }) {
77
+ const fallbackIndex = React.useMemo(
78
+ () => clampInitialScenarioIndex(initialScenarioIndex, scenarios.length),
79
+ [initialScenarioIndex, scenarios.length],
80
+ );
81
+
82
+ const keyedIndex = React.useMemo(() => {
83
+ if (!activeScenarioKey) {
84
+ return -1;
85
+ }
86
+
87
+ return scenarios.findIndex(
88
+ (scenario) => scenario.key === activeScenarioKey,
89
+ );
90
+ }, [activeScenarioKey, scenarios]);
91
+
92
+ const controlledIndex = keyedIndex >= 0 ? keyedIndex : fallbackIndex;
93
+ const [internalIndex, setInternalIndex] = React.useState(controlledIndex);
94
+ const activeIndex = activeScenarioKey ? controlledIndex : internalIndex;
95
+
96
+ React.useEffect(() => {
97
+ if (activeScenarioKey) {
98
+ setInternalIndex(controlledIndex);
99
+ }
100
+ }, [activeScenarioKey, controlledIndex]);
101
+
102
+ return {
103
+ activeScenario: scenarios[activeIndex] ?? scenarios[0],
104
+ activeIndex,
105
+ setActiveIndex: setInternalIndex,
106
+ };
107
+ }
108
+
109
+ type PlaybackTimelineConfig = {
110
+ stepCount: number;
111
+ playbackMs?: number;
112
+ playbackStepDurationsMs?: number[];
113
+ playbackResetDelayMs?: number;
114
+ scenarioKey: string;
115
+ };
116
+
117
+ function getWorkflowPlaybackConfig(
118
+ scenario: ProductDemoScenario,
119
+ ): ProductDemoWorkflowPlaybackConfig {
120
+ return {
121
+ playbackMs:
122
+ scenario.playback?.workflowStudio?.playbackMs ??
123
+ scenario.workflowStudio.playbackMs,
124
+ playbackStepDurationsMs:
125
+ scenario.playback?.workflowStudio?.playbackStepDurationsMs ??
126
+ scenario.workflowStudio.playbackStepDurationsMs,
127
+ playbackResetDelayMs:
128
+ scenario.playback?.workflowStudio?.playbackResetDelayMs ??
129
+ scenario.workflowStudio.playbackResetDelayMs,
130
+ };
131
+ }
132
+
133
+ function getRunConsolePlaybackConfig(
134
+ scenario: ProductDemoScenario,
135
+ ): ProductDemoRunConsolePlaybackConfig {
136
+ return {
137
+ playbackMs:
138
+ scenario.playback?.runConsole?.playbackMs ??
139
+ scenario.runConsole.playbackMs,
140
+ playbackStepDurationsMs:
141
+ scenario.playback?.runConsole?.playbackStepDurationsMs ??
142
+ scenario.runConsole.playbackStepDurationsMs,
143
+ playbackResetDelayMs:
144
+ scenario.playback?.runConsole?.playbackResetDelayMs ??
145
+ scenario.runConsole.playbackResetDelayMs,
146
+ playbackStepEntryIndices:
147
+ scenario.playback?.runConsole?.playbackStepEntryIndices ??
148
+ scenario.runConsole.playbackStepEntryIndices,
149
+ playbackStepVisibleLineCounts:
150
+ scenario.playback?.runConsole?.playbackStepVisibleLineCounts ??
151
+ scenario.runConsole.playbackStepVisibleLineCounts,
152
+ };
153
+ }
154
+
155
+ function useDeterministicPlaybackStep({
156
+ stepCount,
157
+ playbackMs,
158
+ playbackStepDurationsMs,
159
+ playbackResetDelayMs,
160
+ scenarioKey,
161
+ }: PlaybackTimelineConfig) {
162
+ const basePlaybackMs = playbackMs ?? 1800;
163
+ const stepDurations = playbackStepDurationsMs ?? [];
164
+ const resetDelayMs =
165
+ playbackResetDelayMs ?? Math.max(basePlaybackMs + 240, 1600);
166
+ const [playbackStep, setPlaybackStep] = React.useState(1);
167
+
168
+ React.useEffect(() => {
169
+ const initialStep = Math.max(1, Math.min(2, stepCount));
170
+ setPlaybackStep(initialStep);
171
+
172
+ if (stepCount <= 2) {
173
+ return;
174
+ }
175
+
176
+ let timeoutId = 0;
177
+ let cancelled = false;
178
+
179
+ const scheduleStep = (step: number) => {
180
+ const delay = Math.max(260, stepDurations[step - 3] ?? basePlaybackMs);
181
+
182
+ timeoutId = window.setTimeout(() => {
183
+ if (cancelled) {
184
+ return;
185
+ }
186
+
187
+ setPlaybackStep(step);
188
+
189
+ if (step >= stepCount) {
190
+ timeoutId = window.setTimeout(
191
+ () => {
192
+ if (cancelled) {
193
+ return;
194
+ }
195
+
196
+ setPlaybackStep(initialStep);
197
+ scheduleStep(initialStep + 1);
198
+ },
199
+ Math.max(600, resetDelayMs),
200
+ );
201
+
202
+ return;
203
+ }
204
+
205
+ scheduleStep(step + 1);
206
+ }, delay);
207
+ };
208
+
209
+ scheduleStep(initialStep + 1);
210
+
211
+ return () => {
212
+ cancelled = true;
213
+ window.clearTimeout(timeoutId);
214
+ };
215
+ }, [basePlaybackMs, stepCount, resetDelayMs, stepDurations, scenarioKey]);
216
+
217
+ return playbackStep;
218
+ }
219
+
220
+ function HighlightPills({
221
+ highlights,
222
+ }: {
223
+ highlights?: ProductDemoHighlightTarget[];
224
+ }) {
225
+ if (!highlights?.length) {
226
+ return null;
227
+ }
228
+
229
+ return (
230
+ <div className="flex flex-wrap gap-2">
231
+ {highlights.map((highlight) => (
232
+ <span
233
+ key={highlight.id}
234
+ className={cn(
235
+ "rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.16em]",
236
+ getHighlightClass(highlight.tone),
237
+ )}
238
+ >
239
+ {highlight.label ?? highlight.id}
240
+ </span>
241
+ ))}
242
+ </div>
243
+ );
244
+ }
245
+
246
+ function getWindowRenderData(
247
+ scenario: ProductDemoScenario,
248
+ onSelectTask: (taskId: string) => void,
249
+ ): WindowRenderData[] {
250
+ return [
251
+ {
252
+ key: "taskList",
253
+ meta: scenario.taskList.window,
254
+ content: (
255
+ <TaskListPanel state={scenario.taskList} onSelect={onSelectTask} />
256
+ ),
257
+ },
258
+ {
259
+ key: "workflowStudio",
260
+ meta: scenario.workflowStudio.window,
261
+ content: <WorkflowStudioPanel state={scenario.workflowStudio} />,
262
+ },
263
+ {
264
+ key: "runConsole",
265
+ meta: scenario.runConsole.window,
266
+ content: <RunConsolePanel state={scenario.runConsole} />,
267
+ },
268
+ {
269
+ key: "knowledgePanel",
270
+ meta: scenario.knowledgePanel.window,
271
+ content: (
272
+ <>
273
+ <HighlightPills highlights={scenario.knowledgePanel.highlights} />
274
+ <KnowledgePanel state={scenario.knowledgePanel} />
275
+ </>
276
+ ),
277
+ },
278
+ ];
279
+ }
280
+
281
+ function getWindowShellClass(key: ProductDemoWindowKey) {
282
+ switch (key) {
283
+ case "taskList":
284
+ return "lg:col-span-2";
285
+ case "workflowStudio":
286
+ return "lg:col-span-3";
287
+ case "runConsole":
288
+ return "lg:col-span-5";
289
+ case "knowledgePanel":
290
+ case "approvalInbox":
291
+ return "hidden";
292
+ default:
293
+ return "";
294
+ }
295
+ }
296
+
297
+ function getMobileWindowClass(key: ProductDemoWindowKey) {
298
+ switch (key) {
299
+ case "taskList":
300
+ return "h-[15rem] sm:h-[16rem] lg:h-full";
301
+ case "workflowStudio":
302
+ return "h-[18rem] sm:h-[19rem] lg:h-full";
303
+ case "runConsole":
304
+ return "h-[18rem] sm:h-[19rem] lg:h-full";
305
+ default:
306
+ return "lg:h-full";
307
+ }
308
+ }
309
+
310
+ export function DemoStage({
311
+ scenarios = [],
312
+ initialScenarioIndex,
313
+ activeScenarioKey,
314
+ className,
315
+ enableMotion = true,
316
+ ariaLabel = "Product demo stage",
317
+ }: DemoStageProps) {
318
+ const { activeScenario, activeIndex, setActiveIndex } = useActiveScenario({
319
+ scenarios,
320
+ initialScenarioIndex,
321
+ activeScenarioKey,
322
+ });
323
+
324
+ if (!activeScenario) {
325
+ return (
326
+ <div
327
+ data-product-demo-stage
328
+ className={cn(
329
+ "relative isolate min-h-[24rem] w-full overflow-hidden rounded-[2rem] border border-border/60 bg-gradient-to-br from-background via-background to-muted/40 shadow-2xl",
330
+ enableMotion &&
331
+ "transition-transform duration-300 hover:-translate-y-1 motion-reduce:transition-none",
332
+ className,
333
+ )}
334
+ aria-label={ariaLabel}
335
+ >
336
+ <div className="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(59,130,246,0.12),transparent_38%),linear-gradient(135deg,rgba(148,163,184,0.08),transparent_55%)]" />
337
+ <div className="absolute inset-4 rounded-[1.5rem] border border-dashed border-border/60 bg-background/70" />
338
+ </div>
339
+ );
340
+ }
341
+
342
+ const workflowPlayback = getWorkflowPlaybackConfig(activeScenario);
343
+ const runConsolePlayback = getRunConsolePlaybackConfig(activeScenario);
344
+
345
+ const workflowPlaybackStep = useDeterministicPlaybackStep({
346
+ stepCount: Math.max(
347
+ 1,
348
+ activeScenario.workflowStudio.transcript?.length ?? 1,
349
+ ),
350
+ playbackMs: workflowPlayback.playbackMs,
351
+ playbackStepDurationsMs: workflowPlayback.playbackStepDurationsMs,
352
+ playbackResetDelayMs: workflowPlayback.playbackResetDelayMs,
353
+ scenarioKey: `${activeScenario.key}-workflowStudio`,
354
+ });
355
+
356
+ const runConsolePlaybackStep = useDeterministicPlaybackStep({
357
+ stepCount: Math.max(
358
+ 1,
359
+ runConsolePlayback.playbackStepEntryIndices?.length ?? 0,
360
+ runConsolePlayback.playbackStepVisibleLineCounts?.length ?? 0,
361
+ activeScenario.runConsole.entries.length,
362
+ ),
363
+ playbackMs: runConsolePlayback.playbackMs,
364
+ playbackStepDurationsMs: runConsolePlayback.playbackStepDurationsMs,
365
+ playbackResetDelayMs: runConsolePlayback.playbackResetDelayMs,
366
+ scenarioKey: `${activeScenario.key}-runConsole`,
367
+ });
368
+
369
+ const scenarioWithPlayback: ProductDemoScenario = {
370
+ ...activeScenario,
371
+ workflowStudio: {
372
+ ...activeScenario.workflowStudio,
373
+ ...workflowPlayback,
374
+ playbackStep: workflowPlaybackStep,
375
+ },
376
+ runConsole: {
377
+ ...activeScenario.runConsole,
378
+ ...runConsolePlayback,
379
+ playbackStep: runConsolePlaybackStep,
380
+ },
381
+ };
382
+
383
+ const windows = getWindowRenderData(scenarioWithPlayback, (taskId) => {
384
+ const nextIndex = scenarios.findIndex(
385
+ (scenario) => scenario.key === taskId,
386
+ );
387
+
388
+ if (nextIndex >= 0) {
389
+ setActiveIndex(nextIndex);
390
+ }
391
+ }).sort((a, b) => WINDOW_ORDER.indexOf(a.key) - WINDOW_ORDER.indexOf(b.key));
392
+
393
+ return (
394
+ <div
395
+ data-product-demo-stage
396
+ data-enable-motion={enableMotion ? "true" : "false"}
397
+ data-scenario-count={scenarios.length}
398
+ data-active-scenario-key={activeScenario.key}
399
+ data-active-scenario-index={activeIndex}
400
+ className={cn(
401
+ "relative isolate min-h-[36rem] w-full overflow-visible rounded-none border-0 bg-transparent shadow-none lg:h-full lg:min-h-0",
402
+ className,
403
+ )}
404
+ aria-label={ariaLabel}
405
+ >
406
+ <div className="relative z-10 flex min-h-[36rem] flex-col gap-4 lg:h-full lg:min-h-0 lg:gap-0">
407
+ <div className="grid auto-rows-max gap-4 lg:hidden">
408
+ {windows.map((windowData) => {
409
+ if (getWindowShellClass(windowData.key) === "hidden") {
410
+ return null;
411
+ }
412
+
413
+ const activeWindow =
414
+ activeScenario.activeWindow === windowData.key ||
415
+ (!activeScenario.activeWindow &&
416
+ windowData.key === "workflowStudio");
417
+
418
+ return (
419
+ <DemoWindow
420
+ key={windowData.key}
421
+ window={windowData.meta}
422
+ active={activeWindow}
423
+ enableMotion={enableMotion}
424
+ showControls={false}
425
+ showResizeHandle={false}
426
+ showHeader={false}
427
+ className={getMobileWindowClass(windowData.key)}
428
+ bodyClassName="px-0 py-0 sm:px-0 sm:py-0"
429
+ >
430
+ {windowData.content}
431
+ </DemoWindow>
432
+ );
433
+ })}
434
+ </div>
435
+
436
+ <div className="hidden overflow-hidden rounded-[0.4rem] border border-[var(--demo-border)] bg-[var(--demo-shell-bg)] shadow-[var(--demo-shell-shadow)] ring-1 ring-[var(--demo-shell-ring)] lg:flex lg:h-full lg:min-h-0 lg:flex-col">
437
+ <div className="relative flex h-[3.2rem] items-center justify-between border-b border-[var(--demo-border)] bg-[var(--demo-shell-muted-bg)] px-4 py-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.72)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.05)] sm:px-5">
438
+ <div className="flex min-w-0 items-center gap-3.5">
439
+ <div className="flex h-8 w-8 items-center justify-center rounded-[0.4rem] border border-[var(--demo-border)] bg-[var(--demo-shell-strong-bg)] text-[var(--demo-fg)] shadow-[inset_0_1px_0_rgba(255,255,255,0.72)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]">
440
+ <span className="grid grid-cols-2 gap-[2px]">
441
+ <span className="h-[3px] w-[3px] rounded-[1px] bg-[var(--demo-fg)]" />
442
+ <span className="h-[3px] w-[3px] rounded-[1px] bg-[var(--demo-muted-fg)]" />
443
+ <span className="h-[3px] w-[3px] rounded-[1px] bg-[var(--demo-muted-fg)]" />
444
+ <span className="h-[3px] w-[3px] rounded-[1px] bg-[var(--demo-fg)]" />
445
+ </span>
446
+ </div>
447
+ <div className="min-w-0 space-y-0.5">
448
+ <div className="flex min-w-0 items-center gap-2">
449
+ <div className="truncate text-[12px] font-semibold tracking-[-0.02em] text-[var(--demo-fg)]">
450
+ Agent workspace
451
+ </div>
452
+ <span className="hidden rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2 py-0.5 text-[9px] font-medium uppercase tracking-[0.14em] text-[var(--demo-subtle-fg)] xl:inline-flex">
453
+ session 03
454
+ </span>
455
+ </div>
456
+ <div className="text-[10px] uppercase tracking-[0.18em] text-[var(--demo-subtle-fg)]">
457
+ Session · live repo
458
+ </div>
459
+ </div>
460
+ </div>
461
+ <div className="flex items-center gap-2.5 text-[10px] uppercase tracking-[0.16em] text-[var(--demo-subtle-fg)]">
462
+ <div className="hidden items-center gap-2 text-[10px] text-[var(--demo-subtle-fg)] xl:flex">
463
+ <span>repo</span>
464
+ <span className="font-mono text-[var(--demo-muted-fg)]">
465
+ apps/web
466
+ </span>
467
+ </div>
468
+ <span className="inline-flex items-center gap-1.5 rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2.5 py-1 text-[var(--demo-muted-fg)]">
469
+ <span className="h-1.5 w-1.5 rounded-full bg-[var(--demo-success)]" />
470
+ Active
471
+ </span>
472
+ </div>
473
+ </div>
474
+
475
+ <div className="grid min-h-0 flex-1 lg:grid-cols-10 lg:gap-0">
476
+ {windows.map((windowData) => {
477
+ const shellClass = getWindowShellClass(windowData.key);
478
+
479
+ if (shellClass === "hidden") {
480
+ return null;
481
+ }
482
+
483
+ const activeWindow =
484
+ activeScenario.activeWindow === windowData.key ||
485
+ (!activeScenario.activeWindow &&
486
+ windowData.key === "workflowStudio");
487
+
488
+ return (
489
+ <div key={windowData.key} className={cn("min-h-0", shellClass)}>
490
+ <DemoWindow
491
+ window={windowData.meta}
492
+ active={activeWindow}
493
+ dimmed={false}
494
+ enableMotion={enableMotion}
495
+ showControls={false}
496
+ showResizeHandle={false}
497
+ showHeader={false}
498
+ className={cn(
499
+ "h-full min-h-0 border-0 shadow-none",
500
+ windowData.key === "taskList" &&
501
+ "rounded-none border-r border-[var(--demo-border)]",
502
+ windowData.key === "workflowStudio" &&
503
+ "rounded-none border-r border-[var(--demo-border)]",
504
+
505
+ windowData.key === "runConsole" && "rounded-none",
506
+ )}
507
+ bodyClassName="px-0 py-0 sm:px-0 sm:py-0"
508
+ >
509
+ {windowData.content}
510
+ </DemoWindow>
511
+ </div>
512
+ );
513
+ })}
514
+ </div>
515
+ </div>
516
+ </div>
517
+ </div>
518
+ );
519
+ }