nextworks 0.2.0-alpha.17 → 0.2.0-alpha.18

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.
@@ -48,18 +48,22 @@
48
48
  "components/sections/About.tsx",
49
49
  "components/sections/Contact.tsx",
50
50
  "components/sections/CTA.tsx",
51
+ "components/sections/CommandShowcase.tsx",
51
52
  "components/sections/FAQ.tsx",
52
53
  "components/sections/Features.tsx",
53
54
  "components/sections/Footer.tsx",
55
+ "components/sections/FeaturedProjectShowcase.tsx",
54
56
  "components/sections/HeroMotion.tsx",
55
57
  "components/sections/HeroOverlay.tsx",
56
58
  "components/sections/HeroProductDemo.tsx",
57
59
  "components/sections/HeroSplit.tsx",
60
+ "components/sections/HeroWithVideo.tsx",
58
61
  "components/sections/Navbar.tsx",
59
62
  "components/sections/product-demo/ApprovalInboxPanel.tsx",
60
- "components/sections/product-demo/DemoStage.tsx",
63
+ "components/sections/product-demo/DemoStage.tsx",
61
64
  "components/sections/product-demo/DemoWindow.tsx",
62
65
  "components/sections/product-demo/KnowledgePanel.tsx",
66
+
63
67
  "components/sections/product-demo/RunConsolePanel.tsx",
64
68
  "components/sections/product-demo/types.ts",
65
69
  "components/sections/product-demo/TaskListPanel.tsx",
@@ -68,12 +72,16 @@
68
72
  "components/sections/PortfolioSimple.tsx",
69
73
  "components/sections/Pricing.tsx",
70
74
  "components/sections/ProcessTimeline.tsx",
75
+ "components/sections/ProjectDeepDive.tsx",
76
+ "components/sections/SelectedWorkRail.tsx",
71
77
  "components/sections/ServicesGrid.tsx",
72
- "components/sections/Team.tsx",
78
+ "components/sections/Team.tsx",
73
79
  "components/sections/Testimonials.tsx",
74
80
  "components/sections/TrustBadges.tsx"
75
81
  ]
76
82
  },
83
+
84
+
77
85
  "templates": {
78
86
  "description": "Full page templates (Product Launch, SaaS Dashboard, Digital Agency, Gallery)",
79
87
  "files": [
@@ -89,6 +89,9 @@ Supporting template components live alongside the template:
89
89
 
90
90
  - Customize a reusable section (Navbar/Hero/etc.):
91
91
  - edit `components/sections/<Section>.tsx`
92
+ - use `components/sections/HeroWithVideo.tsx` for a centered dark hero with CTAs, command pill, and video preview
93
+ - use `components/sections/FeaturedProjectShowcase.tsx` for a flagship project block with tags, feature bullets, CTAs, and optional media
94
+ - use `components/sections/CommandShowcase.tsx` for a terminal-style command/setup flow with optional output and notes
92
95
 
93
96
  - Customize theme/palette:
94
97
  - see `.nextworks/docs/THEME_GUIDE.md`
@@ -14,7 +14,8 @@ If you’re reading this inside your app repo, you’ll also find companion docs
14
14
  Depending on install flags, Blocks adds:
15
15
 
16
16
  - **UI primitives** under `components/ui/**` (Button, Input, Card, Select, Checkbox, Switch, Toaster, etc.)
17
- - **Reusable marketing sections** under `components/sections/**` (Navbar, Hero variants, Features, Pricing, Testimonials, FAQ, Contact, Footer, …)
17
+ - **Reusable marketing sections** under `components/sections/**` (Navbar, Hero variants including `HeroWithVideo`, `FeaturedProjectShowcase`, `CommandShowcase`, `ProjectDeepDive`, `SelectedWorkRail`, Features, Pricing, Testimonials, FAQ, Contact, Footer, …)
18
+
18
19
  - **Full page templates** under a router-native path (see below)
19
20
  - **Theme + provider utilities** under `components/**` and `lib/**`
20
21
  - **Global styles**: `app/globals.css` and `app/tw-animate.css`
@@ -22,6 +23,7 @@ Depending on install flags, Blocks adds:
22
23
 
23
24
  ---
24
25
 
26
+
25
27
  ## Install commands / options
26
28
 
27
29
  > ```bash
@@ -102,6 +104,14 @@ Notes:
102
104
 
103
105
  - Want to customize a section (Navbar/Hero/etc.)?
104
106
  - edit files directly under `components/sections/**`
107
+ - for a centered cinematic hero with a video preview, start with `components/sections/HeroWithVideo.tsx`
108
+ - for a deeper flagship project block with tags, feature bullets, CTAs, and media, use `components/sections/FeaturedProjectShowcase.tsx`
109
+ - for a terminal-style command/setup flow, use `components/sections/CommandShowcase.tsx`
110
+ - for a premium visual showcase of templates, launches, or selected projects, use `components/sections/SelectedWorkRail.tsx`
111
+ - for a reusable case-study block covering problem, solution, technical highlights, and result, use `components/sections/ProjectDeepDive.tsx`
112
+
105
113
 
106
114
  - Want to customize a template page?
115
+
107
116
  - edit the template route file (see paths above) and its local components.
117
+
@@ -0,0 +1,517 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import Link from "next/link";
5
+ import { Button } from "@/components/ui/button";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ type ButtonVariant =
9
+ | "default"
10
+ | "destructive"
11
+ | "outline"
12
+ | "secondary"
13
+ | "ghost"
14
+ | "link";
15
+
16
+ type ButtonSize = "default" | "sm" | "lg" | "icon";
17
+
18
+ export interface CommandShowcaseCta {
19
+ label?: string;
20
+ href?: string;
21
+ ariaLabel?: string;
22
+ variant?: ButtonVariant;
23
+ size?: ButtonSize;
24
+ className?: string;
25
+ unstyled?: boolean;
26
+ style?: React.CSSProperties;
27
+ target?: string;
28
+ rel?: string;
29
+ }
30
+
31
+ export interface CommandShowcaseLine {
32
+ text: React.ReactNode;
33
+ className?: string;
34
+ }
35
+
36
+ export interface CommandShowcaseCommandLine extends CommandShowcaseLine {
37
+ command?: string;
38
+ }
39
+
40
+ export interface CommandShowcaseNote {
41
+ title?: React.ReactNode;
42
+ description: React.ReactNode;
43
+ className?: string;
44
+ }
45
+
46
+ export interface CommandShowcaseClassNames {
47
+ section?: string;
48
+ backgroundGlow?: string;
49
+ container?: string;
50
+ header?: string;
51
+ eyebrow?: string;
52
+ title?: string;
53
+ description?: string;
54
+ contentGrid?: string;
55
+ terminalOuter?: string;
56
+ terminalCard?: string;
57
+ terminalChrome?: string;
58
+ terminalChromeDot?: string;
59
+ terminalTitle?: string;
60
+ copyButton?: string;
61
+ commandList?: string;
62
+ commandLine?: string;
63
+ prompt?: string;
64
+ commandText?: string;
65
+ outputList?: string;
66
+ outputLine?: string;
67
+ notes?: string;
68
+ note?: string;
69
+ noteMarker?: string;
70
+ noteTitle?: string;
71
+ noteDescription?: string;
72
+ buttons?: string;
73
+ cta?: string;
74
+ }
75
+
76
+ export interface CommandShowcaseProps {
77
+ id?: string;
78
+ className?: string;
79
+ eyebrow?: React.ReactNode;
80
+ title?: React.ReactNode;
81
+ description?: React.ReactNode;
82
+ commands?: Array<string | CommandShowcaseCommandLine>;
83
+ output?: Array<string | CommandShowcaseLine> | false;
84
+ notes?: Array<string | CommandShowcaseNote> | false;
85
+ bullets?: Array<string | CommandShowcaseNote> | false;
86
+ cta?: CommandShowcaseCta;
87
+ showCopyButton?: boolean;
88
+ copyLabel?: string;
89
+ copiedLabel?: string;
90
+ copyValue?: string;
91
+ terminalLabel?: string;
92
+ terminalTitle?: React.ReactNode;
93
+ ariaLabel?: string;
94
+ titleId?: string;
95
+ enableMotion?: boolean;
96
+ classNames?: CommandShowcaseClassNames;
97
+ section?: { className?: string };
98
+ container?: { className?: string };
99
+ header?: { className?: string };
100
+ terminal?: { className?: string };
101
+ notesSlot?: { className?: string };
102
+ }
103
+
104
+ const defaultCommands = [
105
+ "npm create example-app@latest",
106
+ "cd example-app",
107
+ "npm install",
108
+ "npm run dev",
109
+ ];
110
+
111
+ const defaultOutput = [
112
+ "✓ Project created",
113
+ "✓ Dependencies installed",
114
+ "✓ Configuration checked",
115
+ "✓ Development server ready",
116
+ ];
117
+
118
+ const defaultNotes: CommandShowcaseNote[] = [
119
+ {
120
+ title: "Step-by-step",
121
+ description: "Show the exact commands someone can follow from start to finish.",
122
+ },
123
+ {
124
+ title: "Copy-friendly",
125
+ description: "Keep setup details close to the product story without running anything live.",
126
+ },
127
+ {
128
+ title: "Developer-focused",
129
+ description: "Useful for CLIs, SDKs, starter kits, integration guides, and internal tools.",
130
+ },
131
+ ];
132
+
133
+ function normalizeCommandLine(
134
+ line: string | CommandShowcaseCommandLine,
135
+ ): CommandShowcaseCommandLine {
136
+ return typeof line === "string" ? { text: line, command: line } : line;
137
+ }
138
+
139
+ function normalizeLine(line: string | CommandShowcaseLine): CommandShowcaseLine {
140
+ return typeof line === "string" ? { text: line } : line;
141
+ }
142
+
143
+ function normalizeNote(note: string | CommandShowcaseNote): CommandShowcaseNote {
144
+ return typeof note === "string" ? { description: note } : note;
145
+ }
146
+
147
+ function getLineText(line: string | CommandShowcaseCommandLine) {
148
+ if (typeof line === "string") {
149
+ return line;
150
+ }
151
+
152
+ if (line.command) {
153
+ return line.command;
154
+ }
155
+
156
+ return typeof line.text === "string" ? line.text : "";
157
+ }
158
+
159
+ function renderCta(cta: CommandShowcaseCta | undefined) {
160
+ if (!cta?.label) {
161
+ return null;
162
+ }
163
+
164
+ return (
165
+ <Button
166
+ asChild
167
+ variant={cta.variant}
168
+ size={cta.size}
169
+ className={cta.className}
170
+ unstyled={cta.unstyled}
171
+ style={cta.style}
172
+ >
173
+ <Link
174
+ href={cta.href || "#"}
175
+ aria-label={cta.ariaLabel ?? cta.label}
176
+ target={cta.target}
177
+ rel={cta.rel ?? (cta.target === "_blank" ? "noreferrer" : undefined)}
178
+ >
179
+ {cta.label}
180
+ </Link>
181
+ </Button>
182
+ );
183
+ }
184
+
185
+ export function CommandShowcase({
186
+ id,
187
+ className,
188
+ eyebrow = "Command showcase",
189
+ title = "Show a setup flow without leaving the page.",
190
+ description =
191
+ "Use a terminal-style section to explain commands, install steps, or developer workflow output.",
192
+ commands = defaultCommands,
193
+ output = defaultOutput,
194
+ notes,
195
+ bullets,
196
+ cta,
197
+ showCopyButton = false,
198
+ copyLabel = "Copy commands",
199
+ copiedLabel = "Copied",
200
+ copyValue,
201
+ terminalLabel = "Terminal preview showing setup commands and output",
202
+ terminalTitle = "setup.sh",
203
+ ariaLabel = "Command showcase section",
204
+ titleId,
205
+ enableMotion = true,
206
+ classNames,
207
+ section,
208
+ container,
209
+ header,
210
+ terminal,
211
+ notesSlot,
212
+ }: CommandShowcaseProps) {
213
+ const [copyStatus, setCopyStatus] = React.useState<"idle" | "copied">("idle");
214
+
215
+ const normalizedCommands = commands.map(normalizeCommandLine);
216
+ const normalizedOutput = output === false ? [] : output.map(normalizeLine);
217
+ const resolvedNotes = notes ?? bullets ?? defaultNotes;
218
+ const normalizedNotes =
219
+ resolvedNotes === false ? [] : resolvedNotes.map(normalizeNote);
220
+
221
+ const resolvedCopyValue =
222
+ copyValue ?? commands.map(getLineText).filter(Boolean).join("\n");
223
+
224
+ const motionClasses = enableMotion
225
+ ? "transition-all duration-200 hover:-translate-y-0.5"
226
+ : "transition-none hover:!translate-y-0";
227
+
228
+ const handleCopy = async () => {
229
+ if (!resolvedCopyValue || typeof navigator === "undefined") {
230
+ return;
231
+ }
232
+
233
+ try {
234
+ await navigator.clipboard.writeText(resolvedCopyValue);
235
+ setCopyStatus("copied");
236
+ window.setTimeout(() => setCopyStatus("idle"), 1600);
237
+ } catch {
238
+ setCopyStatus("idle");
239
+ }
240
+ };
241
+
242
+ const resolvedCta: CommandShowcaseCta | undefined = cta?.label
243
+ ? {
244
+ href: "#",
245
+ variant: "outline",
246
+ size: "lg",
247
+ unstyled: true,
248
+ ...cta,
249
+ className: cn(
250
+ "h-11 rounded-full border border-border bg-background/80 px-6 text-sm font-medium text-foreground hover:bg-muted",
251
+
252
+ motionClasses,
253
+ classNames?.cta,
254
+ cta.className,
255
+ ),
256
+ }
257
+ : undefined;
258
+
259
+ return (
260
+ <section
261
+ id={id}
262
+ className={cn(
263
+ "relative overflow-hidden bg-background px-4 py-20 text-foreground sm:px-6 lg:px-8 lg:py-24",
264
+
265
+ section?.className,
266
+ classNames?.section,
267
+ className,
268
+ )}
269
+ aria-label={ariaLabel}
270
+ aria-labelledby={titleId}
271
+ >
272
+ <div
273
+ className={cn(
274
+ "pointer-events-none absolute left-1/2 top-28 h-[34rem] w-[48rem] -translate-x-1/2 rounded-full bg-[radial-gradient(circle_at_center,rgba(0,0,0,0.08),rgba(0,0,0,0.03)_38%,transparent_72%)] blur-3xl dark:bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.11),rgba(255,255,255,0.04)_38%,transparent_72%)]",
275
+
276
+ classNames?.backgroundGlow,
277
+ )}
278
+ aria-hidden="true"
279
+ />
280
+
281
+ <div
282
+ className={cn(
283
+ "relative z-10 mx-auto max-w-7xl",
284
+ container?.className,
285
+ classNames?.container,
286
+ )}
287
+ >
288
+ <div
289
+ className={cn(
290
+ "mx-auto max-w-3xl text-center",
291
+ header?.className,
292
+ classNames?.header,
293
+ )}
294
+ >
295
+ {eyebrow ? (
296
+ <p
297
+ className={cn(
298
+ "text-xs font-medium uppercase tracking-[0.28em] text-muted-foreground",
299
+
300
+ classNames?.eyebrow,
301
+ )}
302
+ >
303
+ {eyebrow}
304
+ </p>
305
+ ) : null}
306
+
307
+ <h2
308
+ id={titleId}
309
+ className={cn(
310
+ "mt-5 text-balance text-4xl font-semibold tracking-[-0.055em] text-foreground sm:text-5xl lg:text-6xl",
311
+
312
+ classNames?.title,
313
+ )}
314
+ >
315
+ {title}
316
+ </h2>
317
+
318
+ {description ? (
319
+ <p
320
+ className={cn(
321
+ "mx-auto mt-5 max-w-2xl text-pretty text-base leading-7 text-muted-foreground sm:text-lg",
322
+
323
+ classNames?.description,
324
+ )}
325
+ >
326
+ {description}
327
+ </p>
328
+ ) : null}
329
+ </div>
330
+
331
+ <div
332
+ className={cn(
333
+ "mt-12 grid gap-6 lg:grid-cols-[minmax(0,1.1fr)_minmax(20rem,0.9fr)] lg:items-stretch",
334
+ classNames?.contentGrid,
335
+ )}
336
+ >
337
+ <div
338
+ className={cn(
339
+ "relative overflow-hidden rounded-[2rem] border border-border bg-card/80 p-2 shadow-[0_24px_90px_rgba(0,0,0,0.12)] backdrop-blur dark:shadow-[0_24px_90px_rgba(0,0,0,0.62)]",
340
+
341
+ enableMotion && "transition-transform duration-300 hover:-translate-y-1",
342
+ !enableMotion && "transition-none hover:!translate-y-0",
343
+ terminal?.className,
344
+ classNames?.terminalOuter,
345
+ )}
346
+ >
347
+ <div
348
+ className={cn(
349
+ "overflow-hidden rounded-[1.55rem] border border-border bg-card",
350
+
351
+ classNames?.terminalCard,
352
+ )}
353
+ role="region"
354
+ aria-label={terminalLabel}
355
+ >
356
+ <div
357
+ className={cn(
358
+ "flex h-11 items-center gap-2 border-b border-border px-4",
359
+
360
+ classNames?.terminalChrome,
361
+ )}
362
+ >
363
+ <span
364
+ className={cn("size-2.5 rounded-full bg-foreground/24", classNames?.terminalChromeDot)}
365
+
366
+ aria-hidden="true"
367
+ />
368
+ <span
369
+ className={cn("size-2.5 rounded-full bg-foreground/16", classNames?.terminalChromeDot)}
370
+
371
+ aria-hidden="true"
372
+ />
373
+ <span
374
+ className={cn("size-2.5 rounded-full bg-foreground/10", classNames?.terminalChromeDot)}
375
+
376
+ aria-hidden="true"
377
+ />
378
+ <span
379
+ className={cn(
380
+ "ml-3 min-w-0 flex-1 truncate font-mono text-xs text-muted-foreground",
381
+
382
+ classNames?.terminalTitle,
383
+ )}
384
+ >
385
+ {terminalTitle}
386
+ </span>
387
+ {showCopyButton ? (
388
+ <button
389
+ type="button"
390
+ onClick={handleCopy}
391
+ className={cn(
392
+ "rounded-full border border-border bg-muted/60 px-3 py-1 font-sans text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
393
+
394
+ classNames?.copyButton,
395
+ )}
396
+ aria-label={copyStatus === "copied" ? copiedLabel : copyLabel}
397
+ >
398
+ {copyStatus === "copied" ? copiedLabel : copyLabel}
399
+ </button>
400
+ ) : null}
401
+ </div>
402
+
403
+ <div className="p-5 font-mono text-sm sm:p-6">
404
+ <div className={cn("space-y-3", classNames?.commandList)}>
405
+ {normalizedCommands.map((line, index) => (
406
+ <p
407
+ key={`${getLineText(commands[index])}-${index}`}
408
+ className={cn(
409
+ "flex gap-3 break-all leading-6 text-foreground/80",
410
+
411
+ classNames?.commandLine,
412
+ line.className,
413
+ )}
414
+ >
415
+ <span
416
+ className={cn("shrink-0 text-muted-foreground/60", classNames?.prompt)}
417
+
418
+ aria-hidden="true"
419
+ >
420
+ $
421
+ </span>
422
+ <span className={classNames?.commandText}>{line.text}</span>
423
+ </p>
424
+ ))}
425
+ </div>
426
+
427
+ {normalizedOutput.length > 0 ? (
428
+ <div
429
+ className={cn(
430
+ "mt-7 space-y-2 border-t border-border pt-5",
431
+
432
+ classNames?.outputList,
433
+ )}
434
+ aria-label="Command output"
435
+ >
436
+ {normalizedOutput.map((line, index) => (
437
+ <p
438
+ key={`${String(line.text)}-${index}`}
439
+ className={cn(
440
+ "break-all leading-6 text-muted-foreground",
441
+
442
+ classNames?.outputLine,
443
+ line.className,
444
+ )}
445
+ >
446
+ {line.text}
447
+ </p>
448
+ ))}
449
+ </div>
450
+ ) : null}
451
+ </div>
452
+ </div>
453
+ </div>
454
+
455
+ {normalizedNotes.length > 0 || resolvedCta ? (
456
+ <div
457
+ className={cn(
458
+ "flex flex-col justify-between rounded-[2rem] border border-border bg-card/80 p-6 shadow-[0_24px_90px_rgba(0,0,0,0.12)] backdrop-blur dark:shadow-[0_24px_90px_rgba(0,0,0,0.48)] sm:p-8",
459
+
460
+ notesSlot?.className,
461
+ classNames?.notes,
462
+ )}
463
+ >
464
+ {normalizedNotes.length > 0 ? (
465
+ <ul className="space-y-5">
466
+ {normalizedNotes.map((note, index) => (
467
+ <li
468
+ key={`${String(note.title ?? note.description)}-${index}`}
469
+ className={cn("flex gap-4", classNames?.note, note.className)}
470
+ >
471
+ <span
472
+ className={cn(
473
+ "mt-2 size-1.5 shrink-0 rounded-full bg-foreground/50",
474
+
475
+ classNames?.noteMarker,
476
+ )}
477
+ aria-hidden="true"
478
+ />
479
+ <span>
480
+ {note.title ? (
481
+ <span
482
+ className={cn(
483
+ "block text-sm font-medium text-foreground",
484
+
485
+ classNames?.noteTitle,
486
+ )}
487
+ >
488
+ {note.title}
489
+ </span>
490
+ ) : null}
491
+ <span
492
+ className={cn(
493
+ "mt-1 block text-sm leading-6 text-muted-foreground",
494
+
495
+ classNames?.noteDescription,
496
+ )}
497
+ >
498
+ {note.description}
499
+ </span>
500
+ </span>
501
+ </li>
502
+ ))}
503
+ </ul>
504
+ ) : null}
505
+
506
+ {resolvedCta ? (
507
+ <div className={cn("mt-8 flex", classNames?.buttons)}>
508
+ {renderCta(resolvedCta)}
509
+ </div>
510
+ ) : null}
511
+ </div>
512
+ ) : null}
513
+ </div>
514
+ </div>
515
+ </section>
516
+ );
517
+ }