blocks-dusted 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +967 -3
- package/package.json +40 -40
- package/src/commands/add.js +143 -45
- package/src/commands/list.js +2 -2
- package/src/installers/checkRequirements.js +1 -0
- package/src/installers/copyTemplateFiles.js +23 -2
- package/src/installers/patchPayloadConfigCollections.js +92 -0
- package/src/installers/writeInstalledBlockReadme.js +17 -4
- package/src/registry/listTemplates.js +16 -7
- package/src/registry/loadTemplateManifest.js +20 -11
- package/src/registry/validateTemplateManifest.js +6 -5
- package/src/utils/paths.js +7 -0
- package/templates/blocks/DD-BookCall/Component.tsx +535 -573
- package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
- package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
- package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
- package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
- package/templates/blocks/DD-Contact/Component.tsx +434 -439
- package/templates/blocks/DD-Contact/config.ts +8 -3
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
- package/templates/blocks/DD-FeatCat/config.ts +201 -204
- package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
- package/templates/blocks/DD-Hero/Component.tsx +297 -317
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
- package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
- package/templates/blocks/DD-Pricing/Component.tsx +372 -380
- package/templates/blocks/DD-Process/Component.tsx +149 -155
- package/templates/blocks/DD-Section/Component.tsx +266 -327
- package/templates/blocks/DD-Services/Component.tsx +176 -188
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
- package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
- package/templates/blocks/DD-StackCards/Component.tsx +137 -160
- package/templates/blocks/DD-Team/Component.tsx +247 -265
- package/templates/blocks/DD-TechStack/Component.tsx +57 -72
- package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
- package/templates/blocks/DD-Testimonials/config.ts +66 -66
- package/templates/blocks/DD-Work/Component.tsx +315 -328
- package/templates/blocks/DD-Work-B/Component.tsx +294 -320
- package/templates/collections/Testimonials/README.md +11 -0
- package/templates/collections/Testimonials/Testimonials.ts +161 -0
- package/templates/collections/Testimonials/manifest.json +55 -0
- package/templates/components/RichText/README.md +13 -0
- package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
- package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
- package/templates/components/RichText/converter/index.tsx +18 -0
- package/templates/components/RichText/converter/internalLinks.tsx +45 -0
- package/templates/components/RichText/converter/textConverter.tsx +27 -0
- package/templates/components/RichText/index.tsx +35 -0
- package/templates/components/RichText/manifest.json +80 -0
|
@@ -1,317 +1,297 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import React, { useState } from
|
|
4
|
-
import Image from
|
|
5
|
-
import { motion } from
|
|
6
|
-
import { TextFade } from
|
|
7
|
-
import * as LucideIcons from
|
|
8
|
-
|
|
9
|
-
export type DDHeroProps = Record<string, any
|
|
10
|
-
type Media = { url?: string | null; alt?: string | null; [key: string]: any }
|
|
11
|
-
type ButtonItem = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
/>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<span className="
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
{
|
|
233
|
-
<
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
className=
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
<div className="
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
))}
|
|
301
|
-
</div>
|
|
302
|
-
)}
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
|
-
{/* Scroll indicator */}
|
|
307
|
-
{showScrollIndicator && (
|
|
308
|
-
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-bounce">
|
|
309
|
-
<div className="w-5 h-8 border border-white/30 rounded-full flex items-start justify-center pt-1.5">
|
|
310
|
-
<div className="w-1 h-2 bg-stone-50/50 rounded-full" />
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
)}
|
|
314
|
-
</div>
|
|
315
|
-
</>
|
|
316
|
-
);
|
|
317
|
-
};
|
|
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
|
+
}
|