blocks-dusted 0.1.12 → 0.1.13

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 (148) hide show
  1. package/package.json +1 -1
  2. package/src/installers/patchRenderBlocks.js +1 -1
  3. package/src/registry/validateTemplateManifest.js +2 -1
  4. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  5. package/templates/blocks/BannerDD790432/config.ts +54 -0
  6. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  7. package/templates/blocks/Button47/Component.tsx +124 -0
  8. package/templates/blocks/Button47/config.ts +75 -0
  9. package/templates/blocks/Button47/link.ts +22 -0
  10. package/templates/blocks/Button47/manifest.json +105 -0
  11. package/templates/blocks/Button47/styles.module.scss +75 -0
  12. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  13. package/templates/blocks/DD-Accordion/config.ts +71 -59
  14. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  15. package/templates/blocks/DD-AnimText/config.ts +296 -295
  16. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  17. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  18. package/templates/blocks/DD-BookCall/Component.tsx +535 -535
  19. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  20. package/templates/blocks/DD-Card/Component.tsx +4 -4
  21. package/templates/blocks/DD-CardTemplate01/Component.tsx +139 -140
  22. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  23. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  24. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  25. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  28. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  29. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  30. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  31. package/templates/blocks/DD-Contact/Component.tsx +433 -435
  32. package/templates/blocks/DD-Counter/Component.tsx +11 -6
  33. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  34. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  35. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  36. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  37. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  38. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  39. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  40. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  41. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  42. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  43. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  44. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  45. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  46. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  47. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  48. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  49. package/templates/blocks/DD-Hero/Component.tsx +296 -297
  50. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  51. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  52. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  53. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  54. package/templates/blocks/DD-HorizontalScroll/Component.tsx +937 -936
  55. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  56. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  57. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  58. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  59. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  60. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  61. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -545
  62. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  63. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  64. package/templates/blocks/DD-Masonry/config.ts +475 -475
  65. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  66. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  67. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  68. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  69. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  70. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  71. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  72. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  73. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  74. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  75. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  76. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  77. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  78. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  79. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  80. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  81. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  82. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  83. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  84. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  85. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  86. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  87. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  88. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  89. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  90. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  91. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  92. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  95. package/templates/blocks/DD-Pricing/Component.tsx +2 -3
  96. package/templates/blocks/DD-Pricing/config.ts +215 -215
  97. package/templates/blocks/DD-Process/Component.tsx +2 -2
  98. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  99. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  100. package/templates/blocks/DD-Section/Component.tsx +265 -266
  101. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  102. package/templates/blocks/DD-Services/Component.tsx +176 -176
  103. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  104. package/templates/blocks/DD-Slider-A/Component.tsx +2 -2
  105. package/templates/blocks/DD-StackCards/Component.tsx +172 -171
  106. package/templates/blocks/DD-Team/Component.tsx +247 -247
  107. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  108. package/templates/blocks/DD-TechStack/Component.tsx +173 -174
  109. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  110. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  111. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  112. package/templates/blocks/DD-Testimonials/Component.tsx +139 -139
  113. package/templates/blocks/DD-Thanks/Component.tsx +1 -1
  114. package/templates/blocks/DD-Work/Component.tsx +318 -318
  115. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  116. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  117. package/templates/blocks/DDHG-HighlightArc/Component.tsx +6 -3
  118. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +45 -8
  119. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +1 -3
  120. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +9 -22
  121. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  122. package/templates/blocks/DDHG-TeamCards/Component.tsx +6 -35
  123. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  124. package/templates/blocks/Form/Component.tsx +163 -0
  125. package/templates/blocks/Form/Country/index.tsx +65 -0
  126. package/templates/blocks/Form/Country/options.ts +982 -0
  127. package/templates/blocks/Form/Email/index.tsx +38 -0
  128. package/templates/blocks/Form/Error/index.tsx +15 -0
  129. package/templates/blocks/Form/Message/index.tsx +13 -0
  130. package/templates/blocks/Form/Number/index.tsx +36 -0
  131. package/templates/blocks/Form/Select/index.tsx +63 -0
  132. package/templates/blocks/Form/State/index.tsx +64 -0
  133. package/templates/blocks/Form/State/options.ts +52 -0
  134. package/templates/blocks/Form/Text/index.tsx +32 -0
  135. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  136. package/templates/blocks/Form/Width/index.tsx +13 -0
  137. package/templates/blocks/Form/config.ts +51 -0
  138. package/templates/blocks/Form/fields.tsx +21 -0
  139. package/templates/blocks/Form/manifest.json +168 -0
  140. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  141. package/templates/blocks/MediaBlock/config.ts +14 -0
  142. package/templates/blocks/MediaBlock/manifest.json +92 -0
  143. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  144. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  145. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  146. package/templates/blocks/rawHtml/Component.tsx +25 -0
  147. package/templates/blocks/rawHtml/config.ts +56 -0
  148. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -1,297 +1,296 @@
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
- }
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
+ <div className="max-w-2xl">
147
+ {/* Badge */}
148
+ {/* {badge?.show && badge.text && (
149
+ <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">
150
+ <span className="w-1.5 h-1.5 rounded-full bg-lime-400 animate-pulse" />
151
+ <span className="text-stone-50/80 text-xs font-medium tracking-wide">
152
+ {badge.text}
153
+ </span>
154
+ </div>
155
+ )} */}
156
+
157
+ {/* Badge - to convert to dynamic */}
158
+ {badge?.show && badge.text && (
159
+ <motion.div
160
+ variants={{
161
+ hidden: { opacity: 0, y: -20, filter: 'blur(6px)' },
162
+ visible: { opacity: 1, y: 0, filter: 'none' },
163
+ }}
164
+ transition={{ duration: 0.5, ease: 'easeOut' }}
165
+ >
166
+ <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">
167
+ {/* TODO: Reconnect Badge/AnimatedShinyText if this project provides them. */}
168
+ <ChipIcon className="mr-2 h-3.5 w-3.5" />
169
+ {badge.text}
170
+ </span>
171
+ </motion.div>
172
+ )}
173
+
174
+ {/* Headline */}
175
+ {/* <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">
176
+ {headlineMain}
177
+ <br />
178
+ <span className="text-stone-400">{headlineAccent}</span>
179
+ </h1> */}
180
+
181
+ {/* Headline */}
182
+ <motion.div
183
+ className="relative mt-4 z-50 headline-main"
184
+ variants={{
185
+ hidden: { opacity: 0, y: 40 },
186
+ visible: { opacity: 1, y: 0 },
187
+ }}
188
+ viewport={{ once: false, amount: 0.3 }}
189
+ transition={{ duration: 0.6, ease: 'easeOut' }}
190
+ style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
191
+ >
192
+ <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">
193
+ {/* TODO: Reconnect SplittingText animation if this project provides it. */}
194
+ {headlineMain}
195
+ </span>
196
+ </motion.div>
197
+ <motion.div
198
+ className="relative z-50 headline-accent"
199
+ variants={{
200
+ hidden: { opacity: 0, y: 40 },
201
+ visible: { opacity: 1, y: 0 },
202
+ }}
203
+ viewport={{ once: false, amount: 0.3 }}
204
+ transition={{ duration: 0.6, ease: 'easeOut' }}
205
+ style={{ filter: 'drop-shadow(3px 3px 1px #151515)' }}
206
+ >
207
+ <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">
208
+ {/* TODO: Reconnect SplittingText animation if this project provides it. */}
209
+ {headlineAccent}
210
+ </span>
211
+ </motion.div>
212
+
213
+ {/* Description */}
214
+ <TextFade direction="down" className="mt-10" delay={0.5}>
215
+ {description && (
216
+ <p className="text-stone-50/65 leading-relaxed mt-4 mb-0 max-w-xl">{description}</p>
217
+ )}
218
+ </TextFade>
219
+
220
+ {/* CTA Buttons */}
221
+ {/* <div className="flex flex-col sm:flex-row gap-3 mx-4">
222
+ {primaryCta?.label && (
223
+ <a
224
+ href={primaryCta.href ?? '#'}
225
+ 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"
226
+ >
227
+ <DynamicIcon className="shrink-0" name={primaryCta.iconName} size={16} />
228
+ {primaryCta.label}
229
+ </a>
230
+ )}
231
+ {secondaryCta?.label && (
232
+ <a
233
+ href={secondaryCta.href ?? '#'}
234
+ 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"
235
+ >
236
+ <DynamicIcon className="shrink-0" name={secondaryCta.iconName} size={16} />
237
+ {secondaryCta.label}
238
+ </a>
239
+ )}
240
+ </div> */}
241
+
242
+ {/* Dusted CTA Buttons */}
243
+ {showButtons && buttonItems.length > 0 && (
244
+ <motion.div
245
+ className="dusted-hero-cta-col mt-8 relative flex col-start-2 col-span-2 gap-4"
246
+ variants={{
247
+ hidden: { opacity: 0, y: 20 },
248
+ visible: { opacity: 1, y: 0 },
249
+ }}
250
+ transition={{ duration: 0.6, ease: 'easeOut' }}
251
+ >
252
+ {buttonItems.map((button, index) => (
253
+ <a
254
+ key={index}
255
+ href={(button as any).href ?? '#'}
256
+ className={
257
+ button.class ??
258
+ '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'
259
+ }
260
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
261
+ >
262
+ {/* TODO: Reconnect ButtonDustedFluid if this project provides it. */}
263
+ {button.label || 'Button'}
264
+ </a>
265
+ ))}
266
+ </motion.div>
267
+ )}
268
+
269
+ {/* Stats */}
270
+ {statItems.length > 0 && (
271
+ <div className="flex items-center gap-8 mt-24 pt-8 border-t border-white/10">
272
+ {statItems.map((stat, index) => (
273
+ <div key={stat.label ?? index}>
274
+ <div className="text-4xl font-semibold text-stone-100/50 dark:text-stone-50">
275
+ {stat.value}
276
+ </div>
277
+ <div className="text-xs text-stone-50/75 mt-0.5">{stat.label}</div>
278
+ </div>
279
+ ))}
280
+ </div>
281
+ )}
282
+ </div>
283
+ </div>
284
+
285
+ {/* Scroll indicator */}
286
+ {showScrollIndicator && (
287
+ <div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-bounce">
288
+ <div className="w-5 h-8 border border-white/30 rounded-full flex items-start justify-center pt-1.5">
289
+ <div className="w-1 h-2 bg-stone-50/50 rounded-full" />
290
+ </div>
291
+ </div>
292
+ )}
293
+ </div>
294
+ </>
295
+ )
296
+ }