nextworks 0.2.0-alpha.13 → 0.2.0-alpha.14
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.
- package/README.md +3 -1
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +2 -0
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +2 -0
- package/dist/kits/blocks/app/templates/aiworkflow/PresetThemeVars.tsx +1 -58
- package/dist/kits/blocks/app/templates/aiworkflow/README.md +2 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/CTA.tsx +9 -9
- package/dist/kits/blocks/app/templates/aiworkflow/components/Contact.tsx +12 -13
- package/dist/kits/blocks/app/templates/aiworkflow/components/FAQ.tsx +22 -19
- package/dist/kits/blocks/app/templates/aiworkflow/components/FeatureMockups.tsx +562 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Features.tsx +18 -16
- package/dist/kits/blocks/app/templates/aiworkflow/components/Footer.tsx +13 -9
- package/dist/kits/blocks/app/templates/aiworkflow/components/Hero.tsx +883 -636
- package/dist/kits/blocks/app/templates/aiworkflow/components/Navbar.tsx +14 -15
- package/dist/kits/blocks/app/templates/aiworkflow/components/Pricing.tsx +27 -22
- package/dist/kits/blocks/app/templates/aiworkflow/components/ProcessTimeline.tsx +20 -21
- package/dist/kits/blocks/app/templates/aiworkflow/components/Testimonials.tsx +17 -13
- package/dist/kits/blocks/app/templates/aiworkflow/components/TrustBadges.tsx +15 -12
- package/dist/kits/blocks/app/templates/aiworkflow/themes/animation.tsx +151 -0
- package/dist/kits/blocks/app/templates/aiworkflow/themes/default.tsx +158 -0
- package/dist/kits/blocks/app/templates/aiworkflow/themes/test.tsx +163 -0
- package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +46 -0
- package/dist/kits/blocks/app/templates/gallery/page.tsx +550 -161
- package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +74 -64
- package/dist/kits/blocks/components/sections/Navbar.tsx +2 -0
- package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +16 -13
- package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +283 -162
- package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +65 -53
- package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +20 -17
- package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +208 -127
- package/dist/kits/blocks/components/sections/product-demo/TaskListPanel.tsx +95 -0
- package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +714 -161
- package/dist/kits/blocks/components/sections/product-demo/types.ts +69 -0
- package/dist/kits/blocks/components/ui/theme-selector.tsx +1 -1
- package/dist/kits/blocks/package-deps.json +3 -3
- package/dist/kits/blocks/public/placeholders/aiworkflow/live.svg +92 -0
- package/dist/kits/blocks/public/placeholders/aiworkflow/review.svg +80 -0
- package/dist/kits/blocks/public/placeholders/aiworkflow/task.svg +71 -0
- package/dist/kits/blocks/tsconfig.json +13 -0
- package/dist/utils/file-operations.d.ts.map +1 -1
- package/dist/utils/file-operations.js +6 -1
- package/dist/utils/file-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -15,17 +15,20 @@ export interface DemoWindowProps {
|
|
|
15
15
|
enableMotion?: boolean;
|
|
16
16
|
showControls?: boolean;
|
|
17
17
|
showResizeHandle?: boolean;
|
|
18
|
+
showHeader?: boolean;
|
|
18
19
|
children?: React.ReactNode;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
|
|
22
|
-
neutral:
|
|
23
|
-
|
|
23
|
+
neutral:
|
|
24
|
+
"border-[var(--demo-border)] bg-[var(--demo-panel-bg)] text-[var(--demo-muted-fg)]",
|
|
25
|
+
info: "border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] text-[var(--demo-info)]",
|
|
24
26
|
success:
|
|
25
|
-
"border-
|
|
27
|
+
"border-[var(--demo-success-border)] bg-[var(--demo-success-soft-bg)] text-[var(--demo-success)]",
|
|
26
28
|
warning:
|
|
27
|
-
"border-
|
|
28
|
-
danger:
|
|
29
|
+
"border-[var(--demo-warning-border)] bg-[var(--demo-warning-soft-bg)] text-[var(--demo-warning)]",
|
|
30
|
+
danger:
|
|
31
|
+
"border-[var(--demo-danger-border)] bg-[var(--demo-danger-soft-bg)] text-[var(--demo-danger)]",
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
export function DemoWindow({
|
|
@@ -39,6 +42,7 @@ export function DemoWindow({
|
|
|
39
42
|
enableMotion = true,
|
|
40
43
|
showControls = true,
|
|
41
44
|
showResizeHandle = true,
|
|
45
|
+
showHeader = true,
|
|
42
46
|
children,
|
|
43
47
|
}: DemoWindowProps) {
|
|
44
48
|
const statusTone = window.status?.tone ?? "neutral";
|
|
@@ -46,11 +50,14 @@ export function DemoWindow({
|
|
|
46
50
|
return (
|
|
47
51
|
<section
|
|
48
52
|
className={cn(
|
|
49
|
-
"group relative flex h-full min-h-[14rem] flex-col overflow-hidden
|
|
53
|
+
"group relative flex h-full min-h-[14rem] flex-col overflow-hidden border border-[var(--demo-border)] bg-[var(--demo-shell-bg)] text-[var(--demo-fg)] shadow-none [font-synthesis:none] antialiased",
|
|
54
|
+
|
|
55
|
+
"before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-[linear-gradient(90deg,transparent,rgba(255,255,255,0.68),transparent)] before:opacity-70 dark:before:bg-[linear-gradient(90deg,transparent,rgba(255,255,255,0.16),transparent)] dark:before:opacity-100",
|
|
56
|
+
"after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-8 after:bg-[linear-gradient(180deg,transparent,rgba(15,23,42,0.03))] dark:after:bg-[linear-gradient(180deg,transparent,rgba(255,255,255,0.02))]",
|
|
50
57
|
enableMotion &&
|
|
51
|
-
"transition-[
|
|
52
|
-
active &&
|
|
53
|
-
|
|
58
|
+
"transition-[opacity,border-color,background-color] duration-500 ease-out motion-reduce:transition-none",
|
|
59
|
+
active && "border-[var(--demo-border-strong)]",
|
|
60
|
+
|
|
54
61
|
dimmed && "opacity-90",
|
|
55
62
|
className,
|
|
56
63
|
)}
|
|
@@ -58,55 +65,60 @@ export function DemoWindow({
|
|
|
58
65
|
data-active={active ? "true" : "false"}
|
|
59
66
|
aria-label={window.title}
|
|
60
67
|
>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<div className="flex items-start gap-3">
|
|
68
|
-
{showControls && (
|
|
69
|
-
<div className="mt-1 flex items-center gap-1.5 opacity-75 transition-opacity duration-200 group-hover:opacity-100">
|
|
70
|
-
<span className="h-2.5 w-2.5 rounded-full bg-rose-400/90" />
|
|
71
|
-
<span className="h-2.5 w-2.5 rounded-full bg-amber-400/90" />
|
|
72
|
-
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400/90" />
|
|
73
|
-
</div>
|
|
68
|
+
{showHeader ? (
|
|
69
|
+
<header
|
|
70
|
+
className={cn(
|
|
71
|
+
"relative flex min-h-[3.25rem] items-center justify-between gap-3 border-b border-[var(--demo-border)] bg-[var(--demo-shell-muted-bg)] px-4 py-2.5 [font-synthesis:none] antialiased sm:px-5",
|
|
72
|
+
|
|
73
|
+
chromeClassName,
|
|
74
74
|
)}
|
|
75
|
+
>
|
|
76
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
77
|
+
{showControls && (
|
|
78
|
+
<div className="flex items-center gap-1.5 opacity-75 transition-opacity duration-200 group-hover:opacity-100">
|
|
79
|
+
<span className="h-2.5 w-2.5 rounded-full bg-rose-400/90" />
|
|
80
|
+
<span className="h-2.5 w-2.5 rounded-full bg-amber-400/90" />
|
|
81
|
+
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400/90" />
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
<div className="min-w-0 flex items-center gap-2 overflow-hidden">
|
|
86
|
+
<div className="flex min-w-0 items-center gap-2 whitespace-nowrap">
|
|
87
|
+
<h3 className="shrink-0 text-sm font-semibold tracking-[-0.02em] text-[var(--demo-fg)] sm:text-[0.95rem]">
|
|
88
|
+
{window.title}
|
|
89
|
+
</h3>
|
|
90
|
+
{window.badge && (
|
|
91
|
+
<span className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.16em] text-[var(--demo-muted-fg)]">
|
|
92
|
+
{window.badge}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
{window.subtitle && (
|
|
96
|
+
<p className="min-w-0 truncate text-[11px] leading-5 tracking-[0.005em] text-[var(--demo-muted-fg)] sm:text-[0.8rem]">
|
|
97
|
+
{window.subtitle}
|
|
98
|
+
</p>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
86
101
|
</div>
|
|
87
|
-
{window.subtitle && (
|
|
88
|
-
<p className="mt-1 truncate text-xs text-muted-foreground sm:text-[0.8rem]">
|
|
89
|
-
{window.subtitle}
|
|
90
|
-
</p>
|
|
91
|
-
)}
|
|
92
102
|
</div>
|
|
93
|
-
</div>
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
{window.status?.label && (
|
|
105
|
+
<span
|
|
106
|
+
className={cn(
|
|
107
|
+
"shrink-0 rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.16em]",
|
|
108
|
+
STATUS_TONE_CLASSES[statusTone],
|
|
109
|
+
"hidden sm:inline-flex",
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{window.status.label}
|
|
113
|
+
</span>
|
|
114
|
+
)}
|
|
115
|
+
</header>
|
|
116
|
+
) : null}
|
|
106
117
|
|
|
107
118
|
<div
|
|
108
119
|
className={cn(
|
|
109
|
-
"relative flex-1 px-4 py-4 sm:px-5 sm:py-5",
|
|
120
|
+
"relative flex-1 min-h-0 px-4 py-4 sm:px-5 sm:py-5",
|
|
121
|
+
|
|
110
122
|
bodyClassName,
|
|
111
123
|
)}
|
|
112
124
|
>
|
|
@@ -117,9 +129,9 @@ export function DemoWindow({
|
|
|
117
129
|
aria-hidden="true"
|
|
118
130
|
className="pointer-events-none absolute bottom-3 right-3 h-4 w-4 opacity-0 transition-opacity duration-200 group-hover:opacity-70"
|
|
119
131
|
>
|
|
120
|
-
<span className="absolute bottom-0 right-0 h-px w-3 rotate-45 bg-border
|
|
121
|
-
<span className="absolute bottom-1 right-0 h-px w-2 rotate-45 bg-border
|
|
122
|
-
<span className="absolute bottom-0 right-1 h-px w-2 rotate-45 bg-border
|
|
132
|
+
<span className="absolute bottom-0 right-0 h-px w-3 rotate-45 bg-[var(--demo-border-strong)]" />
|
|
133
|
+
<span className="absolute bottom-1 right-0 h-px w-2 rotate-45 bg-[var(--demo-border)]" />
|
|
134
|
+
<span className="absolute bottom-0 right-1 h-px w-2 rotate-45 bg-[var(--demo-border)]" />
|
|
123
135
|
</div>
|
|
124
136
|
)}
|
|
125
137
|
</div>
|
|
@@ -10,13 +10,15 @@ export interface KnowledgePanelProps {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
|
|
13
|
-
neutral:
|
|
14
|
-
|
|
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)]",
|
|
15
16
|
success:
|
|
16
|
-
"border-
|
|
17
|
+
"border-[var(--demo-success-border)] bg-[var(--demo-success-soft-bg)] text-[var(--demo-success)]",
|
|
17
18
|
warning:
|
|
18
|
-
"border-
|
|
19
|
-
danger:
|
|
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)]",
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
function getStatusClass(tone: ProductDemoStatusTone = "neutral") {
|
|
@@ -25,28 +27,28 @@ function getStatusClass(tone: ProductDemoStatusTone = "neutral") {
|
|
|
25
27
|
|
|
26
28
|
export function KnowledgePanel({ state }: KnowledgePanelProps) {
|
|
27
29
|
return (
|
|
28
|
-
<div className="flex h-full flex-col gap-4">
|
|
30
|
+
<div className="flex h-full flex-col gap-4 text-[var(--demo-fg)]">
|
|
29
31
|
<div className="space-y-1.5">
|
|
30
32
|
{state.title && (
|
|
31
|
-
<h4 className="text-sm font-semibold text-
|
|
33
|
+
<h4 className="text-sm font-semibold text-[var(--demo-fg)]">
|
|
32
34
|
{state.title}
|
|
33
35
|
</h4>
|
|
34
36
|
)}
|
|
35
37
|
{state.subtitle && (
|
|
36
|
-
<p className="text-xs leading-relaxed text-muted-
|
|
38
|
+
<p className="text-xs leading-relaxed text-[var(--demo-muted-fg)]">
|
|
37
39
|
{state.subtitle}
|
|
38
40
|
</p>
|
|
39
41
|
)}
|
|
40
42
|
</div>
|
|
41
43
|
|
|
42
44
|
{state.query && (
|
|
43
|
-
<div className="rounded-xl border border-
|
|
45
|
+
<div className="rounded-xl border border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] px-3 py-2 text-xs text-[var(--demo-info)]">
|
|
44
46
|
{state.query}
|
|
45
47
|
</div>
|
|
46
48
|
)}
|
|
47
49
|
|
|
48
50
|
{state.summary && (
|
|
49
|
-
<p className="text-xs leading-relaxed text-muted-
|
|
51
|
+
<p className="text-xs leading-relaxed text-[var(--demo-muted-fg)]">
|
|
50
52
|
{state.summary}
|
|
51
53
|
</p>
|
|
52
54
|
)}
|
|
@@ -80,17 +82,18 @@ export function KnowledgePanel({ state }: KnowledgePanelProps) {
|
|
|
80
82
|
<div
|
|
81
83
|
key={snippet.id}
|
|
82
84
|
className={cn(
|
|
83
|
-
"rounded-2xl border border-border
|
|
84
|
-
isActive &&
|
|
85
|
+
"rounded-2xl border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] p-3",
|
|
86
|
+
isActive &&
|
|
87
|
+
"border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] shadow-sm",
|
|
85
88
|
)}
|
|
86
89
|
>
|
|
87
90
|
<div className="flex items-start justify-between gap-3">
|
|
88
91
|
<div>
|
|
89
|
-
<div className="text-sm font-semibold text-
|
|
92
|
+
<div className="text-sm font-semibold text-[var(--demo-fg)]">
|
|
90
93
|
{snippet.title}
|
|
91
94
|
</div>
|
|
92
95
|
{(snippet.excerptLabel || source?.label) && (
|
|
93
|
-
<div className="mt-1 text-[11px] text-muted-
|
|
96
|
+
<div className="mt-1 text-[11px] text-[var(--demo-muted-fg)]">
|
|
94
97
|
{[snippet.excerptLabel, source?.label]
|
|
95
98
|
.filter(Boolean)
|
|
96
99
|
.join(" • ")}
|
|
@@ -98,12 +101,12 @@ export function KnowledgePanel({ state }: KnowledgePanelProps) {
|
|
|
98
101
|
)}
|
|
99
102
|
</div>
|
|
100
103
|
{snippet.confidence && (
|
|
101
|
-
<span className="rounded-full border border-border
|
|
104
|
+
<span className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-muted-bg)] px-2 py-1 text-[10px] font-medium uppercase tracking-[0.14em] text-[var(--demo-muted-fg)]">
|
|
102
105
|
{snippet.confidence}
|
|
103
106
|
</span>
|
|
104
107
|
)}
|
|
105
108
|
</div>
|
|
106
|
-
<p className="mt-2 text-xs leading-relaxed text-muted-
|
|
109
|
+
<p className="mt-2 text-xs leading-relaxed text-[var(--demo-muted-fg)]">
|
|
107
110
|
{snippet.content}
|
|
108
111
|
</p>
|
|
109
112
|
{snippet.tags?.length ? (
|
|
@@ -111,7 +114,7 @@ export function KnowledgePanel({ state }: KnowledgePanelProps) {
|
|
|
111
114
|
{snippet.tags.map((tag) => (
|
|
112
115
|
<span
|
|
113
116
|
key={tag}
|
|
114
|
-
className="rounded-full border border-border
|
|
117
|
+
className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2 py-0.5 text-[10px] text-[var(--demo-muted-fg)]"
|
|
115
118
|
>
|
|
116
119
|
{tag}
|
|
117
120
|
</span>
|
|
@@ -1,149 +1,230 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
|
-
import type {
|
|
4
|
-
ProductDemoRunConsoleState,
|
|
5
|
-
ProductDemoStatusTone,
|
|
6
|
-
} from "./types";
|
|
3
|
+
import type { ProductDemoRunConsoleState } from "./types";
|
|
7
4
|
|
|
8
5
|
export interface RunConsolePanelProps {
|
|
9
6
|
state: ProductDemoRunConsoleState;
|
|
10
7
|
}
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
10
|
+
const playbackMs = state.playbackMs ?? 1800;
|
|
11
|
+
const playbackStepEntryIndices = state.playbackStepEntryIndices ?? [];
|
|
12
|
+
const playbackStepVisibleLineCounts =
|
|
13
|
+
state.playbackStepVisibleLineCounts ?? [];
|
|
14
|
+
const [activeIndex, setActiveIndex] = React.useState(
|
|
15
|
+
Math.max(
|
|
16
|
+
0,
|
|
17
|
+
state.entries.findIndex(
|
|
18
|
+
(entry) => entry.id === state.activeEntryId || entry.highlighted,
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
const preferredIndex = state.entries.findIndex(
|
|
25
|
+
(entry) => entry.id === state.activeEntryId || entry.highlighted,
|
|
26
|
+
);
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
if (typeof state.playbackStep === "number") {
|
|
29
|
+
const mappedIndex = playbackStepEntryIndices[state.playbackStep - 1];
|
|
30
|
+
const syncedIndex = Math.min(
|
|
31
|
+
Math.max(
|
|
32
|
+
typeof mappedIndex === "number"
|
|
33
|
+
? mappedIndex
|
|
34
|
+
: state.playbackStep - 2,
|
|
35
|
+
0,
|
|
36
|
+
),
|
|
37
|
+
Math.max(state.entries.length - 1, 0),
|
|
38
|
+
);
|
|
39
|
+
setActiveIndex(syncedIndex);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
setActiveIndex(Math.max(0, preferredIndex));
|
|
44
|
+
|
|
45
|
+
if (state.entries.length <= 1) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const interval = window.setInterval(() => {
|
|
50
|
+
setActiveIndex((current) => (current + 1) % state.entries.length);
|
|
51
|
+
}, playbackMs);
|
|
52
|
+
|
|
53
|
+
return () => window.clearInterval(interval);
|
|
54
|
+
}, [
|
|
55
|
+
playbackMs,
|
|
56
|
+
playbackStepEntryIndices,
|
|
57
|
+
state.activeEntryId,
|
|
58
|
+
state.entries,
|
|
59
|
+
state.title,
|
|
60
|
+
state.playbackStep,
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const fallbackCodeEntry =
|
|
64
|
+
state.entries.find((entry) => (entry.code?.length ?? 0) > 0) ??
|
|
65
|
+
state.entries[0];
|
|
66
|
+
const activeEntry = state.entries[activeIndex] ?? state.entries[0];
|
|
67
|
+
const displayEntry =
|
|
68
|
+
(activeEntry?.code?.length ?? 0) > 0 ? activeEntry : fallbackCodeEntry;
|
|
69
|
+
const activeCode = displayEntry?.code ?? [];
|
|
70
|
+
const startLine = Number(
|
|
71
|
+
displayEntry?.lineNumber ?? activeEntry?.lineNumber ?? 24,
|
|
72
|
+
);
|
|
73
|
+
const activeLineCount = Math.max(1, activeCode.length || 1);
|
|
74
|
+
const [visibleLineCount, setVisibleLineCount] = React.useState(
|
|
75
|
+
Math.max(1, Math.min(2, activeCode.length || 1)),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
if (typeof state.playbackStep === "number") {
|
|
80
|
+
const mappedVisibleCount =
|
|
81
|
+
playbackStepVisibleLineCounts[state.playbackStep - 1];
|
|
82
|
+
const revealFromStep = Math.max(state.playbackStep - 1, 1);
|
|
83
|
+
const syncedVisibleCount = Math.min(
|
|
84
|
+
activeCode.length || 1,
|
|
85
|
+
Math.max(
|
|
86
|
+
1,
|
|
87
|
+
typeof mappedVisibleCount === "number"
|
|
88
|
+
? mappedVisibleCount
|
|
89
|
+
: revealFromStep * 2,
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
setVisibleLineCount(syncedVisibleCount);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setVisibleLineCount(Math.max(1, Math.min(2, activeCode.length || 1)));
|
|
97
|
+
|
|
98
|
+
if (activeCode.length <= 2) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const interval = window.setInterval(
|
|
103
|
+
() => {
|
|
104
|
+
setVisibleLineCount((current) => {
|
|
105
|
+
if (current >= activeCode.length) {
|
|
106
|
+
return Math.max(1, Math.min(2, activeCode.length));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return current + 1;
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
Math.max(
|
|
113
|
+
520,
|
|
114
|
+
Math.round(playbackMs / Math.max(activeCode.length - 1, 1)),
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return () => window.clearInterval(interval);
|
|
119
|
+
}, [
|
|
120
|
+
activeCode,
|
|
121
|
+
playbackMs,
|
|
122
|
+
playbackStepVisibleLineCounts,
|
|
123
|
+
activeEntry?.id,
|
|
124
|
+
state.playbackStep,
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const visibleCode = activeCode.slice(0, visibleLineCount);
|
|
36
128
|
|
|
37
129
|
return (
|
|
38
|
-
<div className="flex h-full flex-col
|
|
39
|
-
<div className="flex flex-
|
|
40
|
-
<div className="
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{state.subtitle && (
|
|
47
|
-
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
48
|
-
{state.subtitle}
|
|
49
|
-
</p>
|
|
50
|
-
)}
|
|
51
|
-
</div>
|
|
130
|
+
<div className="flex h-full min-h-0 flex-col text-[var(--demo-fg)] [font-synthesis:none] antialiased">
|
|
131
|
+
<div className="flex min-h-0 flex-1 overflow-hidden rounded-none border border-[var(--demo-border)] bg-[var(--demo-code-bg)] shadow-none">
|
|
132
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
133
|
+
<div className="grid min-h-0 flex-1 grid-cols-[3.5rem_minmax(0,1fr)] bg-[var(--demo-code-bg)]">
|
|
134
|
+
<div className="border-r border-[var(--demo-border)] bg-[var(--demo-code-gutter-bg)] px-2 py-3 font-mono text-[11px] leading-7 text-[var(--demo-subtle-fg)]">
|
|
135
|
+
{visibleCode.map((line, index) => {
|
|
136
|
+
const isAdded = line.trimStart().startsWith("+");
|
|
137
|
+
const isRemoved = line.trimStart().startsWith("-");
|
|
52
138
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</div>
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
key={`${startLine + index}`}
|
|
142
|
+
className={cn(
|
|
143
|
+
"text-right",
|
|
144
|
+
isAdded && "text-[var(--demo-info)]",
|
|
145
|
+
isRemoved && "text-[var(--demo-danger)]",
|
|
146
|
+
)}
|
|
147
|
+
>
|
|
148
|
+
{startLine + index}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
68
153
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
154
|
+
<div className="relative flex h-full min-h-0 flex-col overflow-hidden bg-[var(--demo-code-bg)] px-3 py-3 font-mono text-[12px] leading-7 text-[var(--demo-fg)]">
|
|
155
|
+
<div>
|
|
156
|
+
{visibleCode.map((line, index) => {
|
|
157
|
+
const isAdded = line.trimStart().startsWith("+");
|
|
158
|
+
const isRemoved = line.trimStart().startsWith("-");
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div
|
|
162
|
+
key={`${line}-${index}`}
|
|
163
|
+
className={cn(
|
|
164
|
+
"flex border-l border-transparent pl-3 transition-colors duration-300",
|
|
165
|
+
isAdded &&
|
|
166
|
+
"border-[var(--demo-info-border)] bg-[var(--demo-info-soft-bg)] text-[var(--demo-fg)]",
|
|
167
|
+
isRemoved &&
|
|
168
|
+
"border-[var(--demo-danger-border)] bg-[var(--demo-danger-soft-bg)] text-[var(--demo-fg)]",
|
|
169
|
+
!isAdded && !isRemoved && "text-[var(--demo-muted-fg)]",
|
|
170
|
+
|
|
171
|
+
displayEntry?.highlighted &&
|
|
172
|
+
index === Math.min(1, visibleCode.length - 1) &&
|
|
173
|
+
"animate-pulse",
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
<span
|
|
177
|
+
className={cn(
|
|
178
|
+
"mr-3 w-3 shrink-0 text-center text-[11px]",
|
|
179
|
+
isAdded
|
|
180
|
+
? "text-[var(--demo-info)]"
|
|
181
|
+
: isRemoved
|
|
182
|
+
? "text-[var(--demo-danger)]"
|
|
183
|
+
: "text-[var(--demo-subtle-fg)]",
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{isAdded ? "+" : isRemoved ? "-" : " "}
|
|
187
|
+
</span>
|
|
188
|
+
<span className="min-w-0 flex-1 whitespace-pre-wrap break-words">
|
|
189
|
+
{isAdded || isRemoved
|
|
190
|
+
? line.slice(1).trimStart()
|
|
191
|
+
: line}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
})}
|
|
196
|
+
|
|
197
|
+
{displayEntry?.highlighted &&
|
|
198
|
+
visibleLineCount < activeLineCount ? (
|
|
199
|
+
<div className="mt-2 flex items-center gap-2 pl-3 text-[11px] text-[var(--demo-subtle-fg)]">
|
|
200
|
+
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-[var(--demo-accent)]" />
|
|
201
|
+
Applying change...
|
|
202
|
+
</div>
|
|
203
|
+
) : null}
|
|
95
204
|
</div>
|
|
96
|
-
|
|
205
|
+
|
|
206
|
+
<div className="flex-1" />
|
|
97
207
|
</div>
|
|
98
|
-
|
|
208
|
+
</div>
|
|
99
209
|
</div>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
)}
|
|
114
|
-
>
|
|
115
|
-
<div className="flex items-start justify-between gap-3">
|
|
116
|
-
<div className="min-w-0 flex-1">
|
|
117
|
-
<div className="text-sm text-card-foreground">
|
|
118
|
-
{entry.message}
|
|
119
|
-
</div>
|
|
120
|
-
{(entry.source || entry.timestamp) && (
|
|
121
|
-
<div className="mt-1 text-[11px] text-muted-foreground">
|
|
122
|
-
{[entry.source, entry.timestamp]
|
|
123
|
-
.filter(Boolean)
|
|
124
|
-
.join(" • ")}
|
|
125
|
-
</div>
|
|
126
|
-
)}
|
|
127
|
-
{entry.detail && (
|
|
128
|
-
<p className="mt-2 text-xs leading-relaxed text-muted-foreground">
|
|
129
|
-
{entry.detail}
|
|
130
|
-
</p>
|
|
131
|
-
)}
|
|
210
|
+
|
|
211
|
+
{state.metrics?.length ? (
|
|
212
|
+
<div className="hidden w-[6.75rem] shrink-0 border-l border-[var(--demo-border)] bg-[var(--demo-panel-muted-bg)] p-2 lg:flex lg:flex-col lg:gap-2">
|
|
213
|
+
{state.metrics.map((metric) => (
|
|
214
|
+
<div
|
|
215
|
+
key={metric.id}
|
|
216
|
+
className="rounded-md border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2 py-2 text-center"
|
|
217
|
+
>
|
|
218
|
+
<div className="text-[10px] uppercase tracking-[0.16em] text-[var(--demo-subtle-fg)]">
|
|
219
|
+
{metric.label}
|
|
220
|
+
</div>
|
|
221
|
+
<div className="mt-1 font-mono text-[12px] text-[var(--demo-fg)]">
|
|
222
|
+
{metric.value}
|
|
132
223
|
</div>
|
|
133
|
-
{entry.status && (
|
|
134
|
-
<span
|
|
135
|
-
className={cn(
|
|
136
|
-
"rounded-full border px-2 py-1 text-[10px] font-medium uppercase tracking-[0.14em]",
|
|
137
|
-
getStatusClass(entry.status),
|
|
138
|
-
)}
|
|
139
|
-
>
|
|
140
|
-
{entry.status}
|
|
141
|
-
</span>
|
|
142
|
-
)}
|
|
143
224
|
</div>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
) : null}
|
|
147
228
|
</div>
|
|
148
229
|
</div>
|
|
149
230
|
);
|