blocks-dusted 0.1.11 → 0.1.12

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 (46) hide show
  1. package/package.json +5 -2
  2. package/src/cli.js +39 -3
  3. package/src/commands/shop.js +59 -0
  4. package/src/commands/warehouse.js +73 -0
  5. package/templates/blocks/DD-BookCall/Component.tsx +535 -534
  6. package/templates/blocks/DD-CardTemplate01/Component.tsx +140 -139
  7. package/templates/blocks/DD-Carousel-A/Component.tsx +1 -1
  8. package/templates/blocks/DD-Carousel-B/Component.tsx +1 -1
  9. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  10. package/templates/blocks/DD-ComparisonTable/Component.tsx +1 -1
  11. package/templates/blocks/DD-Contact/Component.tsx +8 -7
  12. package/templates/blocks/DD-Counter/Component.tsx +10 -10
  13. package/templates/blocks/DD-FeatCat/Component.tsx +1 -1
  14. package/templates/blocks/DD-FeatStrip/Component.tsx +1 -1
  15. package/templates/blocks/DD-Hero/Component.tsx +297 -320
  16. package/templates/blocks/DD-HorizontalScroll/Component.tsx +936 -938
  17. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  18. package/templates/blocks/DD-MasonaryMedia/Component.tsx +8 -10
  19. package/templates/blocks/DD-Pricing/Component.tsx +369 -372
  20. package/templates/blocks/DD-Process/Component.tsx +147 -149
  21. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  22. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  23. package/templates/blocks/DD-Section/Component.tsx +1 -1
  24. package/templates/blocks/DD-Services/Component.tsx +1 -1
  25. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +13 -13
  26. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  27. package/templates/blocks/DD-StackCards/Component.tsx +473 -473
  28. package/templates/blocks/DD-Team/Component.tsx +1 -1
  29. package/templates/blocks/DD-TechStack/Component.tsx +22 -28
  30. package/templates/blocks/DD-Testimonials/Component.tsx +35 -43
  31. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  32. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  33. package/templates/blocks/DD-Work/Component.tsx +4 -4
  34. package/templates/blocks/DD-Work-B/Component.tsx +1 -1
  35. package/templates/blocks/DDHG-CTASection2/Component.tsx +6 -6
  36. package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -243
  37. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +143 -93
  38. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  39. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +227 -213
  40. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  41. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  42. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +24 -28
  43. package/templates/blocks/DDHG-ServiceHero/Component.tsx +1 -1
  44. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  45. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  46. package/templates/blocks/DDHG-TeamCards/Component.tsx +169 -131
@@ -1,320 +1,297 @@
1
- 'use client'
2
-
3
- import React, { useState } from 'react'
4
- import Image from 'next/image'
5
- import { motion } from 'framer-motion'
6
- import { TextFade } from '@/components/ui/anims/fade'
7
- import * as LucideIcons from 'lucide-react'
8
-
9
- export type DDHeroProps = Record<string, any>
10
- type Media = { url?: string | null; alt?: string | null; [key: string]: any }
11
- type ButtonItem = { href?: string | null; class?: string | null; label?: string | null }
12
- type StatItem = { value?: string | null; label?: string | null }
13
-
14
- function isLocalAssetSrc(src?: string | null): src is string {
15
- return typeof src === 'string' && src.startsWith('/api/media/file/')
16
- }
17
-
18
- function HeroPlaceholder() {
19
- return (
20
- <svg
21
- aria-hidden="true"
22
- className="h-full w-full"
23
- viewBox="0 0 1600 900"
24
- preserveAspectRatio="none"
25
- >
26
- <defs>
27
- <linearGradient id="dd-hero-placeholder" x1="0" y1="0" x2="1" y2="1">
28
- <stop offset="0%" stopColor="#09090b" />
29
- <stop offset="45%" stopColor="#241244" />
30
- <stop offset="100%" stopColor="#111827" />
31
- </linearGradient>
32
- <pattern id="dd-hero-grid" width="80" height="80" patternUnits="userSpaceOnUse">
33
- <path
34
- d="M 80 0 L 0 0 0 80"
35
- fill="none"
36
- stroke="#ffffff"
37
- strokeOpacity="0.06"
38
- strokeWidth="1"
39
- />
40
- </pattern>
41
- </defs>
42
- <rect width="1600" height="900" fill="url(#dd-hero-placeholder)" />
43
- <rect width="1600" height="900" fill="url(#dd-hero-grid)" />
44
- <path
45
- d="M 1040 140 C 1260 220 1380 390 1320 570 C 1240 800 910 790 760 640 C 620 500 790 260 1040 140 Z"
46
- fill="#ffffff"
47
- fillOpacity="0.08"
48
- />
49
- <path
50
- d="M 180 620 C 360 470 570 490 690 640 C 810 790 600 900 360 840 C 210 800 90 740 180 620 Z"
51
- fill="#a3e635"
52
- fillOpacity="0.08"
53
- />
54
- </svg>
55
- )
56
- }
57
-
58
- function DynamicIcon({
59
- name,
60
- size = 16,
61
- ...props
62
- }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
63
- if (!name) return null
64
- const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
65
- if (!Icon) return null
66
- return <Icon size={size} {...props} />
67
- }
68
-
69
- export const DDHero: React.FC<DDHeroProps> = ({
70
- badge,
71
- eyebrow,
72
- headlineMain,
73
- headlineAccent,
74
- description,
75
- headlineText,
76
- accentText,
77
- subText,
78
- phase1,
79
- phase2,
80
- phase3,
81
- phase1Desc,
82
- phase2Desc,
83
- phase3Desc,
84
- // primaryCta,
85
- // secondaryCta,
86
- stats,
87
- backgroundImage,
88
- backgroundImageFallbackUrl,
89
- sectionId,
90
- showScrollIndicator,
91
- customCSS,
92
- showButtons = true,
93
- buttons = [
94
- { variant: 'v1', icon: 'Goal', label: 'Choose Lane' },
95
- { variant: 'v1', icon: 'Feather', label: 'How it works' },
96
- ],
97
- }) => {
98
- const bgMedia =
99
- backgroundImage && typeof backgroundImage === 'object' ? (backgroundImage as Media) : null
100
- const bgUrl = bgMedia?.url ?? backgroundImageFallbackUrl ?? undefined
101
- const safeBgUrl = isLocalAssetSrc(bgUrl) ? bgUrl : null
102
- const isVideo = Boolean(safeBgUrl && (bgMedia?.mimeType?.startsWith('video/') ?? false))
103
- const resolvedBadge = badge?.text ?? eyebrow
104
- const resolvedHeadlineMain = headlineMain ?? headlineText ?? 'Launch-ready digital products'
105
- const resolvedHeadlineAccent = headlineAccent ?? accentText ?? 'with sharper positioning'
106
- const resolvedDescription =
107
- description ??
108
- subText ??
109
- 'A hero section for selling a focused offer with clear messaging, visual depth, and direct action.'
110
- const phaseStats = [
111
- phase1 && { value: phase1, label: phase1Desc },
112
- phase2 && { value: phase2, label: phase2Desc },
113
- phase3 && { value: phase3, label: phase3Desc }
114
- ].filter(Boolean) as StatItem[]
115
- const buttonItems = buttons as ButtonItem[]
116
- const statItems = ((stats?.length ? stats : phaseStats) ?? []) as StatItem[]
117
-
118
- const [bgLoaded, setBgLoaded] = useState(false)
119
-
120
- // Get dynamic icon component
121
- const ChipIcon = LucideIcons.Sparkles
122
-
123
- return (
124
- <>
125
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
126
-
127
- <div
128
- id={sectionId ?? undefined}
129
- className="relative flex min-h-[72vh] w-full items-center overflow-hidden rounded-2xl"
130
- >
131
- {/* Background Image */}
132
- <div className="absolute inset-0">
133
- {safeBgUrl ? (
134
- <>
135
- {!bgLoaded && <div className="absolute inset-0 bg-stone-900 animate-pulse" />}
136
- {isVideo ? (
137
- <video
138
- src={safeBgUrl}
139
- autoPlay
140
- muted
141
- loop
142
- playsInline
143
- preload="none"
144
- className="w-full h-full object-cover object-top"
145
- onLoadedData={() => setBgLoaded(true)}
146
- />
147
- ) : (
148
- <Image
149
- src={safeBgUrl}
150
- alt="Studio hero background"
151
- fill
152
- priority
153
- loading="eager"
154
- className="object-cover object-top"
155
- onLoad={() => setBgLoaded(true)}
156
- />
157
- )}
158
- </>
159
- ) : (
160
- <HeroPlaceholder />
161
- )}
162
- <div className="absolute inset-0 bg-gradient-to-r from-stone-950/95 via-stone-950/80 to-stone-950/40" />
163
- <div className="absolute inset-0 bg-linear-to-t from-stone-950/60 via-transparent to-transparent" />
164
- </div>
165
-
166
- {/* Content */}
167
- <div className="relative z-10 mx-auto flex w-full max-w-6xl items-center px-6 py-20 md:px-10 lg:px-14">
168
- <div className="max-w-3xl">
169
- {/* Badge */}
170
- {/* {badge?.show && badge.text && (
171
- <div className="inline-flex items-center gap-2 bg-stone-50/10 border border-white/20 rounded-full px-4 py-1.5 mb-8">
172
- <span className="w-1.5 h-1.5 rounded-full bg-lime-400 animate-pulse" />
173
- <span className="text-stone-50/80 text-xs font-medium tracking-wide">
174
- {resolvedBadge}
175
- </span>
176
- </div>
177
- )} */}
178
-
179
- {/* Badge - to convert to dynamic */}
180
- {(badge?.show ?? Boolean(resolvedBadge)) && resolvedBadge && (
181
- <motion.div
182
- variants={{
183
- hidden: { opacity: 0, y: -20, filter: 'blur(6px)' },
184
- visible: { opacity: 1, y: 0, filter: 'none' },
185
- }}
186
- transition={{ duration: 0.5, ease: 'easeOut' }}
187
- >
188
- <span className="inline-flex items-center gap-2 rounded-full border border-lime-500 bg-black/70 px-4 py-1.5 text-xs text-lime-100">
189
- {/* TODO: Reconnect Badge/AnimatedShinyText if this project provides them. */}
190
- <ChipIcon className="mr-2 h-3.5 w-3.5" />
191
- {resolvedBadge}
192
- </span>
193
- </motion.div>
194
- )}
195
-
196
- {/* Headline */}
197
- {/* <h1 className="text-5xl md:text-6xl lg:text-7xl font-semibold text-stone-900 dark:text-stone-50 leading-[1.05] tracking-tight mb-6">
198
- {resolvedHeadlineMain}
199
- <br />
200
- <span className="text-stone-400">{resolvedHeadlineAccent}</span>
201
- </h1> */}
202
-
203
- {/* Headline */}
204
- <motion.div
205
- className="relative mt-4 z-50 headline-main"
206
- variants={{
207
- hidden: { opacity: 0, y: 40 },
208
- visible: { opacity: 1, y: 0 },
209
- }}
210
- viewport={{ once: false, amount: 0.3 }}
211
- transition={{ duration: 0.6, ease: 'easeOut' }}
212
- style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
213
- >
214
- <span className="block text-left text-5xl font-semibold tracking-tight text-lime-300 drop-shadow-lg sm:text-6xl md:text-7xl">
215
- {/* TODO: Reconnect SplittingText animation if this project provides it. */}
216
- {resolvedHeadlineMain}
217
- </span>
218
- </motion.div>
219
- <motion.div
220
- className="relative z-50 headline-accent"
221
- variants={{
222
- hidden: { opacity: 0, y: 40 },
223
- visible: { opacity: 1, y: 0 },
224
- }}
225
- viewport={{ once: false, amount: 0.3 }}
226
- transition={{ duration: 0.6, ease: 'easeOut' }}
227
- style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
228
- >
229
- <span className="block text-left text-5xl font-semibold tracking-tight text-stone-50 drop-shadow-lg sm:text-6xl md:text-7xl">
230
- {/* TODO: Reconnect SplittingText animation if this project provides it. */}
231
- {resolvedHeadlineAccent}
232
- </span>
233
- </motion.div>
234
-
235
- {/* Description */}
236
- <TextFade direction="down" className="mt-10" delay={0.5}>
237
- {resolvedDescription && (
238
- <p className="mt-4 mb-0 max-w-2xl text-lg leading-relaxed text-stone-50/75">
239
- {resolvedDescription}
240
- </p>
241
- )}
242
- </TextFade>
243
-
244
- {/* CTA Buttons */}
245
- {/* <div className="flex flex-col sm:flex-row gap-3 mx-4">
246
- {primaryCta?.label && (
247
- <a
248
- href={primaryCta.href ?? '#'}
249
- className="inline-flex items-center justify-center gap-2 bg-stone-50 text-stone-900 px-6 py-3 rounded-lg text-sm font-semibold hover:bg-stone-100 transition-colors cursor-pointer whitespace-nowrap"
250
- >
251
- <DynamicIcon className="shrink-0" name={primaryCta.iconName} size={16} />
252
- {primaryCta.label}
253
- </a>
254
- )}
255
- {secondaryCta?.label && (
256
- <a
257
- href={secondaryCta.href ?? '#'}
258
- className="inline-flex items-center justify-center gap-2 bg-stone-50/10 border border-white/20 text-stone-50 px-6 py-3 rounded-lg text-sm font-semibold hover:bg-stone-50/20 transition-colors cursor-pointer whitespace-nowrap"
259
- >
260
- <DynamicIcon className="shrink-0" name={secondaryCta.iconName} size={16} />
261
- {secondaryCta.label}
262
- </a>
263
- )}
264
- </div> */}
265
-
266
- {/* Dusted CTA Buttons */}
267
- {showButtons && buttonItems.length > 0 && (
268
- <motion.div
269
- className="dusted-hero-cta-col mt-8 relative flex col-start-2 col-span-2 gap-4"
270
- variants={{
271
- hidden: { opacity: 0, y: 20 },
272
- visible: { opacity: 1, y: 0 },
273
- }}
274
- transition={{ duration: 0.6, ease: 'easeOut' }}
275
- >
276
- {buttonItems.map((button, index) => (
277
- <a
278
- key={index}
279
- href={(button as any).href ?? '#'}
280
- className={
281
- button.class ??
282
- 'inline-flex items-center justify-center gap-2 rounded-full bg-lime-400 px-5 py-2.5 text-sm font-semibold text-stone-950 shadow-[0_0_28px_rgba(132,204,22,0.35)] transition-all duration-150 hover:bg-lime-300 hover:scale-[1.03] whitespace-nowrap'
283
- }
284
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
285
- >
286
- {/* TODO: Reconnect ButtonDustedFluid if this project provides it. */}
287
- {button.label || 'Button'}
288
- </a>
289
- ))}
290
- </motion.div>
291
- )}
292
-
293
- {/* Stats */}
294
- {statItems.length > 0 && (
295
- <div className="flex items-center gap-8 mt-24 pt-8 border-t border-white/10">
296
- {statItems.map((stat, index) => (
297
- <div key={stat.label ?? index}>
298
- <div className="text-4xl font-semibold text-stone-100/50 dark:text-stone-50">
299
- {stat.value}
300
- </div>
301
- <div className="text-xs text-stone-50/75 mt-0.5">{stat.label}</div>
302
- </div>
303
- ))}
304
- </div>
305
- )}
306
- </div>
307
- </div>
308
-
309
- {/* Scroll indicator */}
310
- {showScrollIndicator && (
311
- <div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-bounce">
312
- <div className="w-5 h-8 border border-white/30 rounded-full flex items-start justify-center pt-1.5">
313
- <div className="w-1 h-2 bg-stone-50/50 rounded-full" />
314
- </div>
315
- </div>
316
- )}
317
- </div>
318
- </>
319
- )
320
- }
1
+ 'use client'
2
+
3
+ import React, { useState } from 'react'
4
+ import Image from 'next/image'
5
+ import { motion } from 'framer-motion'
6
+ import { TextFade } from '@/components/ui/anims/fade'
7
+ import * as LucideIcons from 'lucide-react'
8
+
9
+ export type DDHeroProps = Record<string, any>
10
+ type Media = { url?: string | null; alt?: string | null; [key: string]: any }
11
+ type ButtonItem = { href?: string | null; class?: string | null; label?: string | null }
12
+ type StatItem = { value?: string | null; label?: string | null }
13
+
14
+ function isLocalAssetSrc(src?: string | null): src is string {
15
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
16
+ }
17
+
18
+ function HeroPlaceholder() {
19
+ return (
20
+ <svg
21
+ aria-hidden="true"
22
+ className="h-full w-full"
23
+ viewBox="0 0 1600 900"
24
+ preserveAspectRatio="none"
25
+ >
26
+ <defs>
27
+ <linearGradient id="dd-hero-placeholder" x1="0" y1="0" x2="1" y2="1">
28
+ <stop offset="0%" stopColor="#09090b" />
29
+ <stop offset="45%" stopColor="#241244" />
30
+ <stop offset="100%" stopColor="#111827" />
31
+ </linearGradient>
32
+ <pattern id="dd-hero-grid" width="80" height="80" patternUnits="userSpaceOnUse">
33
+ <path
34
+ d="M 80 0 L 0 0 0 80"
35
+ fill="none"
36
+ stroke="#ffffff"
37
+ strokeOpacity="0.06"
38
+ strokeWidth="1"
39
+ />
40
+ </pattern>
41
+ </defs>
42
+ <rect width="1600" height="900" fill="url(#dd-hero-placeholder)" />
43
+ <rect width="1600" height="900" fill="url(#dd-hero-grid)" />
44
+ <path
45
+ d="M 1040 140 C 1260 220 1380 390 1320 570 C 1240 800 910 790 760 640 C 620 500 790 260 1040 140 Z"
46
+ fill="#ffffff"
47
+ fillOpacity="0.08"
48
+ />
49
+ <path
50
+ d="M 180 620 C 360 470 570 490 690 640 C 810 790 600 900 360 840 C 210 800 90 740 180 620 Z"
51
+ fill="#a3e635"
52
+ fillOpacity="0.08"
53
+ />
54
+ </svg>
55
+ )
56
+ }
57
+
58
+ function DynamicIcon({
59
+ name,
60
+ size = 16,
61
+ ...props
62
+ }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
63
+ if (!name) return null
64
+ const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
65
+ if (!Icon) return null
66
+ return <Icon size={size} {...props} />
67
+ }
68
+
69
+ export const DDHero: React.FC<DDHeroProps> = ({
70
+ badge,
71
+ headlineMain,
72
+ headlineAccent,
73
+ description,
74
+ // primaryCta,
75
+ // secondaryCta,
76
+ stats,
77
+ backgroundImage,
78
+ backgroundImageFallbackUrl,
79
+ sectionId,
80
+ showScrollIndicator,
81
+ customCSS,
82
+ showButtons = true,
83
+ buttons = [
84
+ { variant: 'v1', icon: 'Goal', label: 'Choose Lane' },
85
+ { variant: 'v1', icon: 'Feather', label: 'How it works' },
86
+ ],
87
+ }) => {
88
+ const bgMedia =
89
+ backgroundImage && typeof backgroundImage === 'object' ? (backgroundImage as Media) : null
90
+ const bgUrl = bgMedia?.url ?? backgroundImageFallbackUrl ?? undefined
91
+ const safeBgUrl = isLocalAssetSrc(bgUrl) ? bgUrl : null
92
+ const isVideo = Boolean(safeBgUrl && (bgMedia?.mimeType?.startsWith('video/') ?? false))
93
+ const buttonItems = buttons as ButtonItem[]
94
+ const statItems = (stats ?? []) as StatItem[]
95
+
96
+ const [bgLoaded, setBgLoaded] = useState(false)
97
+
98
+ // Get dynamic icon component
99
+ const ChipIcon = LucideIcons.Sparkles
100
+
101
+ return (
102
+ <>
103
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
104
+
105
+ <div
106
+ id={sectionId ?? undefined}
107
+ className="relative min-h-screen flex items-center overflow-hidden"
108
+ >
109
+ {/* Background Image */}
110
+ <div className="absolute inset-0">
111
+ {safeBgUrl ? (
112
+ <>
113
+ {!bgLoaded && <div className="absolute inset-0 bg-stone-900 animate-pulse" />}
114
+ {isVideo ? (
115
+ <video
116
+ src={safeBgUrl}
117
+ autoPlay
118
+ muted
119
+ loop
120
+ playsInline
121
+ preload="none"
122
+ className="w-full h-full object-cover object-top"
123
+ onLoadedData={() => setBgLoaded(true)}
124
+ />
125
+ ) : (
126
+ <Image
127
+ src={safeBgUrl}
128
+ alt="Studio hero background"
129
+ fill
130
+ priority
131
+ loading="eager"
132
+ className="object-cover object-top"
133
+ onLoad={() => setBgLoaded(true)}
134
+ />
135
+ )}
136
+ </>
137
+ ) : (
138
+ <HeroPlaceholder />
139
+ )}
140
+ <div className="absolute inset-0 bg-gradient-to-r from-stone-950/95 via-stone-950/80 to-stone-950/40" />
141
+ <div className="absolute inset-0 bg-linear-to-t from-stone-950/60 via-transparent to-transparent" />
142
+ </div>
143
+
144
+ {/* Content */}
145
+ <div className="relative z-10 w-full max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6 pt-28 pb-24">
146
+ #BlockName-DDHero
147
+ <div className="max-w-2xl">
148
+ {/* Badge */}
149
+ {/* {badge?.show && badge.text && (
150
+ <div className="inline-flex items-center gap-2 bg-stone-50/10 border border-white/20 rounded-full px-4 py-1.5 mb-8">
151
+ <span className="w-1.5 h-1.5 rounded-full bg-lime-400 animate-pulse" />
152
+ <span className="text-stone-50/80 text-xs font-medium tracking-wide">
153
+ {badge.text}
154
+ </span>
155
+ </div>
156
+ )} */}
157
+
158
+ {/* Badge - to convert to dynamic */}
159
+ {badge?.show && badge.text && (
160
+ <motion.div
161
+ variants={{
162
+ hidden: { opacity: 0, y: -20, filter: 'blur(6px)' },
163
+ visible: { opacity: 1, y: 0, filter: 'none' },
164
+ }}
165
+ transition={{ duration: 0.5, ease: 'easeOut' }}
166
+ >
167
+ <span className="inline-flex items-center gap-2 rounded-full border border-lime-500 bg-black/70 px-4 py-1.5 text-xs text-lime-100">
168
+ {/* TODO: Reconnect Badge/AnimatedShinyText if this project provides them. */}
169
+ <ChipIcon className="mr-2 h-3.5 w-3.5" />
170
+ {badge.text}
171
+ </span>
172
+ </motion.div>
173
+ )}
174
+
175
+ {/* Headline */}
176
+ {/* <h1 className="text-5xl md:text-6xl lg:text-7xl font-semibold text-stone-900 dark:text-stone-50 leading-[1.05] tracking-tight mb-6">
177
+ {headlineMain}
178
+ <br />
179
+ <span className="text-stone-400">{headlineAccent}</span>
180
+ </h1> */}
181
+
182
+ {/* Headline */}
183
+ <motion.div
184
+ className="relative mt-4 z-50 headline-main"
185
+ variants={{
186
+ hidden: { opacity: 0, y: 40 },
187
+ visible: { opacity: 1, y: 0 },
188
+ }}
189
+ viewport={{ once: false, amount: 0.3 }}
190
+ transition={{ duration: 0.6, ease: 'easeOut' }}
191
+ style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
192
+ >
193
+ <span className="block text-6xl sm:text-6xl font-semibold text-left tracking-tight md:text-7xl text-purple-600 dark:text-violet-600 hyphens-auto">
194
+ {/* TODO: Reconnect SplittingText animation if this project provides it. */}
195
+ {headlineMain}
196
+ </span>
197
+ </motion.div>
198
+ <motion.div
199
+ className="relative z-50 headline-accent"
200
+ variants={{
201
+ hidden: { opacity: 0, y: 40 },
202
+ visible: { opacity: 1, y: 0 },
203
+ }}
204
+ viewport={{ once: false, amount: 0.3 }}
205
+ transition={{ duration: 0.6, ease: 'easeOut' }}
206
+ style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
207
+ >
208
+ <span className="block text-6xl sm:text-6xl font-semibold text-left tracking-tight md:text-7xl text-purple-900 dark:text-violet-900 hyphens-auto">
209
+ {/* TODO: Reconnect SplittingText animation if this project provides it. */}
210
+ {headlineAccent}
211
+ </span>
212
+ </motion.div>
213
+
214
+ {/* Description */}
215
+ <TextFade direction="down" className="mt-10" delay={0.5}>
216
+ {description && (
217
+ <p className="text-stone-50/65 leading-relaxed mt-4 mb-0 max-w-xl">{description}</p>
218
+ )}
219
+ </TextFade>
220
+
221
+ {/* CTA Buttons */}
222
+ {/* <div className="flex flex-col sm:flex-row gap-3 mx-4">
223
+ {primaryCta?.label && (
224
+ <a
225
+ href={primaryCta.href ?? '#'}
226
+ className="inline-flex items-center justify-center gap-2 bg-stone-50 text-stone-900 px-6 py-3 rounded-lg text-sm font-semibold hover:bg-stone-100 transition-colors cursor-pointer whitespace-nowrap"
227
+ >
228
+ <DynamicIcon className="shrink-0" name={primaryCta.iconName} size={16} />
229
+ {primaryCta.label}
230
+ </a>
231
+ )}
232
+ {secondaryCta?.label && (
233
+ <a
234
+ href={secondaryCta.href ?? '#'}
235
+ className="inline-flex items-center justify-center gap-2 bg-stone-50/10 border border-white/20 text-stone-50 px-6 py-3 rounded-lg text-sm font-semibold hover:bg-stone-50/20 transition-colors cursor-pointer whitespace-nowrap"
236
+ >
237
+ <DynamicIcon className="shrink-0" name={secondaryCta.iconName} size={16} />
238
+ {secondaryCta.label}
239
+ </a>
240
+ )}
241
+ </div> */}
242
+
243
+ {/* Dusted CTA Buttons */}
244
+ {showButtons && buttonItems.length > 0 && (
245
+ <motion.div
246
+ className="dusted-hero-cta-col mt-8 relative flex col-start-2 col-span-2 gap-4"
247
+ variants={{
248
+ hidden: { opacity: 0, y: 20 },
249
+ visible: { opacity: 1, y: 0 },
250
+ }}
251
+ transition={{ duration: 0.6, ease: 'easeOut' }}
252
+ >
253
+ {buttonItems.map((button, index) => (
254
+ <a
255
+ key={index}
256
+ href={(button as any).href ?? '#'}
257
+ className={
258
+ button.class ??
259
+ 'inline-flex items-center gap-1.5 bg-indigo-700 text-stone-50 text-xs font-semibold px-3 py-1.5 rounded-lg group-hover:bg-indigo-600 group-hover:scale-[1.05] group-hover:shadow-xl transition-all duration-150 whitespace-nowrap'
260
+ }
261
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
262
+ >
263
+ {/* TODO: Reconnect ButtonDustedFluid if this project provides it. */}
264
+ {button.label || 'Button'}
265
+ </a>
266
+ ))}
267
+ </motion.div>
268
+ )}
269
+
270
+ {/* Stats */}
271
+ {statItems.length > 0 && (
272
+ <div className="flex items-center gap-8 mt-24 pt-8 border-t border-white/10">
273
+ {statItems.map((stat, index) => (
274
+ <div key={stat.label ?? index}>
275
+ <div className="text-4xl font-semibold text-stone-100/50 dark:text-stone-50">
276
+ {stat.value}
277
+ </div>
278
+ <div className="text-xs text-stone-50/75 mt-0.5">{stat.label}</div>
279
+ </div>
280
+ ))}
281
+ </div>
282
+ )}
283
+ </div>
284
+ </div>
285
+
286
+ {/* Scroll indicator */}
287
+ {showScrollIndicator && (
288
+ <div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-bounce">
289
+ <div className="w-5 h-8 border border-white/30 rounded-full flex items-start justify-center pt-1.5">
290
+ <div className="w-1 h-2 bg-stone-50/50 rounded-full" />
291
+ </div>
292
+ </div>
293
+ )}
294
+ </div>
295
+ </>
296
+ )
297
+ }