blocks-dusted 0.1.11 → 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 (161) 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/src/installers/patchRenderBlocks.js +1 -1
  6. package/src/registry/validateTemplateManifest.js +2 -1
  7. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  8. package/templates/blocks/BannerDD790432/config.ts +54 -0
  9. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  10. package/templates/blocks/Button47/Component.tsx +124 -0
  11. package/templates/blocks/Button47/config.ts +75 -0
  12. package/templates/blocks/Button47/link.ts +22 -0
  13. package/templates/blocks/Button47/manifest.json +105 -0
  14. package/templates/blocks/Button47/styles.module.scss +75 -0
  15. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  16. package/templates/blocks/DD-Accordion/config.ts +71 -59
  17. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  18. package/templates/blocks/DD-AnimText/config.ts +296 -295
  19. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  20. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  21. package/templates/blocks/DD-BookCall/Component.tsx +10 -9
  22. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  23. package/templates/blocks/DD-Card/Component.tsx +4 -4
  24. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -5
  25. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  28. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  29. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  30. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  31. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  32. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  33. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  34. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  35. package/templates/blocks/DD-Contact/Component.tsx +433 -434
  36. package/templates/blocks/DD-Counter/Component.tsx +18 -13
  37. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  38. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  39. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  40. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  41. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  42. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  43. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  44. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  45. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  46. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  47. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  48. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  49. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  50. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  51. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  52. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  53. package/templates/blocks/DD-Hero/Component.tsx +16 -40
  54. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  55. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  56. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  57. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  58. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -5
  59. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  60. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  61. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  62. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  63. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  64. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  65. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  66. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -547
  67. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  68. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  69. package/templates/blocks/DD-Masonry/config.ts +475 -475
  70. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  71. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  72. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  73. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  74. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  75. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  76. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  77. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  78. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  79. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  80. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  81. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  82. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  83. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  84. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  85. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  86. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  87. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  88. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  89. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  90. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  91. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  92. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  95. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  96. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  97. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  98. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  99. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  100. package/templates/blocks/DD-Pricing/Component.tsx +368 -372
  101. package/templates/blocks/DD-Pricing/config.ts +215 -215
  102. package/templates/blocks/DD-Process/Component.tsx +147 -149
  103. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  104. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  105. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  106. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  107. package/templates/blocks/DD-Section/Component.tsx +265 -266
  108. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  109. package/templates/blocks/DD-Services/Component.tsx +176 -176
  110. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  111. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  112. package/templates/blocks/DD-StackCards/Component.tsx +474 -473
  113. package/templates/blocks/DD-Team/Component.tsx +247 -247
  114. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  115. package/templates/blocks/DD-TechStack/Component.tsx +161 -168
  116. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  117. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  118. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  119. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  120. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  121. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  122. package/templates/blocks/DD-Work/Component.tsx +315 -315
  123. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  124. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  125. package/templates/blocks/DDHG-HighlightArc/Component.tsx +246 -243
  126. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
  127. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  128. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
  129. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  130. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  131. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
  132. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  133. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  134. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  135. package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
  136. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  137. package/templates/blocks/Form/Component.tsx +163 -0
  138. package/templates/blocks/Form/Country/index.tsx +65 -0
  139. package/templates/blocks/Form/Country/options.ts +982 -0
  140. package/templates/blocks/Form/Email/index.tsx +38 -0
  141. package/templates/blocks/Form/Error/index.tsx +15 -0
  142. package/templates/blocks/Form/Message/index.tsx +13 -0
  143. package/templates/blocks/Form/Number/index.tsx +36 -0
  144. package/templates/blocks/Form/Select/index.tsx +63 -0
  145. package/templates/blocks/Form/State/index.tsx +64 -0
  146. package/templates/blocks/Form/State/options.ts +52 -0
  147. package/templates/blocks/Form/Text/index.tsx +32 -0
  148. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  149. package/templates/blocks/Form/Width/index.tsx +13 -0
  150. package/templates/blocks/Form/config.ts +51 -0
  151. package/templates/blocks/Form/fields.tsx +21 -0
  152. package/templates/blocks/Form/manifest.json +168 -0
  153. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  154. package/templates/blocks/MediaBlock/config.ts +14 -0
  155. package/templates/blocks/MediaBlock/manifest.json +92 -0
  156. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  157. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  158. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  159. package/templates/blocks/rawHtml/Component.tsx +25 -0
  160. package/templates/blocks/rawHtml/config.ts +56 -0
  161. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -1,302 +1,302 @@
1
- 'use client'
2
- import React, { useRef, useState } from 'react'
3
- import Link from 'next/link'
4
- import Image from 'next/image'
5
- import { ArrowRight } from 'lucide-react'
6
- import * as LucideIcons from 'lucide-react'
7
- import { motion, type Variants } from 'framer-motion'
8
- import { cn } from '@/utilities/ui'
9
- export type DDFeatCatProps = Record<string, any>
10
- type Media = { url?: string | null }
11
- type CategoryItem = {
12
- id?: string | null
13
- title?: string | null
14
- description?: string | null
15
- link?: string | null
16
- linkLabel?: string | null
17
- iconName?: string | null
18
- iconImage?: Media | string | null
19
- iconColorClass?: string | null
20
- iconBgClass?: string | null
21
- count?: string | null
22
- image?: Media | string | null
23
- imageFallbackUrl?: string | null
24
- }
25
-
26
- function isLocalImageSrc(src?: string | null): src is string {
27
- return typeof src === 'string' && src.startsWith('/api/media/file/')
28
- }
29
-
30
- function CategoryPlaceholder({ title }: { title: string }) {
31
- return (
32
- <div
33
- className="w-full h-48 overflow-hidden rounded-t-2xl relative bg-[#12151f]"
34
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
35
- >
36
- <svg
37
- aria-hidden="true"
38
- className="absolute inset-0 h-full w-full"
39
- viewBox="0 0 800 500"
40
- preserveAspectRatio="none"
41
- >
42
- <defs>
43
- <linearGradient id="dd-featcat-placeholder" x1="0" y1="0" x2="1" y2="1">
44
- <stop offset="0%" stopColor="#1f2937" />
45
- <stop offset="48%" stopColor="#251644" />
46
- <stop offset="100%" stopColor="#0f172a" />
47
- </linearGradient>
48
- <pattern id="dd-featcat-grid" width="64" height="64" patternUnits="userSpaceOnUse">
49
- <path
50
- d="M 64 0 L 0 0 0 64"
51
- fill="none"
52
- stroke="#ffffff"
53
- strokeOpacity="0.08"
54
- strokeWidth="1"
55
- />
56
- </pattern>
57
- </defs>
58
- <rect width="800" height="500" fill="url(#dd-featcat-placeholder)" />
59
- <rect width="800" height="500" fill="url(#dd-featcat-grid)" />
60
- <rect x="90" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.1" />
61
- <rect x="310" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.16" />
62
- <rect x="530" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.1" />
63
- <rect x="160" y="260" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.14" />
64
- <rect x="450" y="260" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.12" />
65
- </svg>
66
- <div className="absolute inset-x-4 bottom-4 text-xs font-medium uppercase tracking-widest text-stone-300/70">
67
- {title || 'Category'}
68
- </div>
69
- </div>
70
- )
71
- }
72
-
73
- // ─── SpotlightCard ────────────────────────────────────────────────────────────
74
- interface SpotlightCardProps {
75
- children: React.ReactNode
76
- className?: string
77
- spotlightColor?: string
78
- style?: React.CSSProperties
79
- }
80
-
81
- function SpotlightCard({
82
- children,
83
- className,
84
- spotlightColor = 'rgba(214,211,209,0.08)',
85
- style,
86
- }: SpotlightCardProps) {
87
- const cardRef = useRef<HTMLDivElement>(null)
88
- const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
89
-
90
- const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
91
- if (!cardRef.current) return
92
- const rect = cardRef.current.getBoundingClientRect()
93
- setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
94
- }
95
-
96
- const handleMouseLeave = () => setPos(null)
97
-
98
- return (
99
- <div
100
- ref={cardRef}
101
- className={cn('relative overflow-hidden', className)}
102
- onMouseMove={handleMouseMove}
103
- onMouseLeave={handleMouseLeave}
104
- style={style}
105
- >
106
- {pos && (
107
- <div
108
- className="pointer-events-none absolute inset-0 z-0 transition-opacity duration-300"
109
- style={{
110
- background: `radial-gradient(400px circle at ${pos.x}px ${pos.y}px, ${spotlightColor}, transparent 60%)`,
111
- }}
112
- />
113
- )}
114
- <div className="relative z-10 h-[57%]">{children}</div>
115
- </div>
116
- )
117
- }
118
-
119
- // ─── Dynamic Icon ─────────────────────────────────────────────────────────────
120
- function DynamicIcon({
121
- name,
122
- ...props
123
- }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
124
- if (!name) return null
125
- const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
126
- if (!Icon) return null
127
- return <Icon {...props} />
128
- }
129
-
130
- // ─── Animation variants ───────────────────────────────────────────────────────
131
- const containerVariants: Variants = {
132
- hidden: {},
133
- show: { transition: { staggerChildren: 0.1, delayChildren: 0.05 } },
134
- }
135
-
136
- const itemVariants: Variants = {
137
- hidden: { opacity: 0, y: 24 },
138
- show: { opacity: 1, y: 0, transition: { duration: 0.45, ease: 'easeOut' } },
139
- }
140
-
141
- // ─── Component ────────────────────────────────────────────────────────────────
142
- export const DDFeatCat: React.FC<DDFeatCatProps> = ({
143
- eyebrow = 'What we offer',
144
- headlineMain = 'Everything you need',
145
- headlineAccent = 'to ship faster',
146
- subtext = 'From individual UI blocks to complete website systems — and the studio team to build it all for you.',
147
- categories = [],
148
- sectionId = 'featured-categories',
149
- customCSS,
150
- }) => {
151
- const categoryItems = categories as CategoryItem[]
152
-
153
- return (
154
- <>
155
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
156
-
157
- <div id={sectionId} className="py-24">
158
- <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
159
- {/* Header */}
160
- <motion.div
161
- variants={containerVariants}
162
- initial="hidden"
163
- whileInView="show"
164
- viewport={{ once: false, amount: 0.2 }}
165
- className="flex flex-col lg:flex-row lg:items-end justify-between gap-6 mb-14 px-0 md:px-4 overflow-hidden"
166
- >
167
- <motion.div variants={itemVariants}>
168
- {eyebrow && (
169
- <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
170
- {eyebrow}
171
- </p>
172
- )}
173
- <h2 className="text-3d text-4xl md:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
174
- {headlineMain}
175
- {headlineAccent && (
176
- <>
177
- <br />
178
- <span className="text-stone-400">{headlineAccent}</span>
179
- </>
180
- )}
181
- </h2>
182
- </motion.div>
183
-
184
- {subtext && (
185
- <motion.p
186
- variants={itemVariants}
187
- className="text-stone-700 dark:text-stone-300 text-base leading-relaxed max-w-sm lg:text-right"
188
- >
189
- {subtext}
190
- </motion.p>
191
- )}
192
- </motion.div>
193
- {/* Cards */}
194
- <motion.div
195
- variants={containerVariants}
196
- initial="hidden"
197
- whileInView="show"
198
- viewport={{ once: false, amount: 0.1 }}
199
- className="grid grid-cols-1 md:grid-cols-3 gap-5"
200
- >
201
- {categoryItems.map((cat, index) => {
202
- const title = cat.title ?? ''
203
- const description = cat.description ?? ''
204
- const link = cat.link ?? '#'
205
- const linkLabel = cat.linkLabel ?? ''
206
- const imgSrc =
207
- cat.image && typeof cat.image === 'object' && cat.image.url
208
- ? cat.image.url
209
- : (cat.imageFallbackUrl ?? '')
210
- const safeImgSrc = isLocalImageSrc(imgSrc) ? imgSrc : null
211
- const iconSrc =
212
- cat.iconImage &&
213
- typeof cat.iconImage === 'object' &&
214
- isLocalImageSrc((cat.iconImage as { url?: string }).url)
215
- ? (cat.iconImage as { url: string }).url
216
- : null
217
-
218
- return (
219
- <motion.div key={cat.id ?? index} variants={itemVariants}>
220
- <SpotlightCard
221
- spotlightColor="rgba(214,211,209,0.08)"
222
- className="border border-stone-800 bg-stone-900 group transition-colors duration-300 h-full rounded-2xl hover:border-lime-500 corner-squircle overflow-hidden shadow-md outline-none hover:outline-stone-200/10 hover:outline-4 hover:shadow-2xl"
223
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
224
- >
225
- {/* Image */}
226
- {safeImgSrc ? (
227
- <div className="w-full h-48 overflow-hidden relative">
228
- <Image
229
- src={safeImgSrc}
230
- alt={title}
231
- fill
232
- className="object-cover object-top group-hover:scale-110 transition-transform duration-500 studio-card-image rounded-2xl corner-squircle overflow-hidden scale-105"
233
- sizes="(max-width: 768px) 100vw, 33vw"
234
- priority={false}
235
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
236
- />
237
- </div>
238
- ) : (
239
- <CategoryPlaceholder title={title} />
240
- )}
241
- {/* Content */}
242
- <div
243
- className="card-content bg-stone-100/90 dark:bg-stone-950/90 backdrop-blur-md -mt-6 mx-2 mb-2 p-4 relative z-5 drop-shadow-md rounded-2xl h-full corner-squircle group-hover:-translate-y-1 group-hover:shadow-2xl group-hover:brightness-110 transition-all duration-300"
244
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
245
- >
246
- <div className="flex items-center gap-3 mb-4">
247
- <div
248
- className={cn(
249
- 'w-9 h-9 flex items-center justify-center rounded-lg shrink-0',
250
- cat.iconBgClass ?? 'bg-stone-700',
251
- )}
252
- >
253
- {iconSrc ? (
254
- <Image
255
- src={iconSrc}
256
- alt={title}
257
- width={16}
258
- height={16}
259
- className={cn('w-4 h-4 object-contain', cat.iconColorClass)}
260
- />
261
- ) : (
262
- <DynamicIcon
263
- name={cat.iconName ?? ''}
264
- size={16}
265
- className={cat.iconColorClass ?? 'text-stone-300'}
266
- />
267
- )}
268
- </div>
269
- {cat.count && (
270
- <span className="text-xs font-medium text-stone-700 dark:text-stone-400">
271
- {cat.count}
272
- </span>
273
- )}
274
- </div>
275
-
276
- <h3 className="text-lg font-semibold text-stone-900 dark:text-stone-50 mb-2">
277
- {title}
278
- </h3>
279
- <p className="text-sm text-stone-600 dark:text-stone-400 leading-relaxed mb-5">
280
- {description}
281
- </p>
282
-
283
- <Link
284
- href={link}
285
- className="inline-flex items-center gap-1.5 text-sm font-semibold text-stone-700 dark:text-stone-300 hover:text-indigo-950 dark:hover:text-stone-50 hover:gap-2.5 transition-all duration-200 cursor-pointer"
286
- >
287
- {linkLabel}
288
- <ArrowRight className="w-4 h-4" />
289
- </Link>
290
- </div>
291
- </SpotlightCard>
292
- </motion.div>
293
- )
294
- })}
295
- </motion.div>
296
- </div>
297
- </div>
298
- </>
299
- )
300
- }
301
-
302
- export default DDFeatCat
1
+ 'use client'
2
+ import React, { useRef, useState } from 'react'
3
+ import Link from 'next/link'
4
+ import Image from 'next/image'
5
+ import { ArrowRight } from 'lucide-react'
6
+ import * as LucideIcons from 'lucide-react'
7
+ import { motion, type Variants } from 'framer-motion'
8
+ import { cn } from '@/utilities/ui'
9
+ export type DDFeatCatProps = Record<string, any>
10
+ type Media = { url?: string | null }
11
+ type CategoryItem = {
12
+ id?: string | null
13
+ title?: string | null
14
+ description?: string | null
15
+ link?: string | null
16
+ linkLabel?: string | null
17
+ iconName?: string | null
18
+ iconImage?: Media | string | null
19
+ iconColorClass?: string | null
20
+ iconBgClass?: string | null
21
+ count?: string | null
22
+ image?: Media | string | null
23
+ imageFallbackUrl?: string | null
24
+ }
25
+
26
+ function isLocalImageSrc(src?: string | null): src is string {
27
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
28
+ }
29
+
30
+ function CategoryPlaceholder({ title }: { title: string }) {
31
+ return (
32
+ <div
33
+ className="w-full h-48 overflow-hidden rounded-t-2xl relative bg-[#12151f]"
34
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
35
+ >
36
+ <svg
37
+ aria-hidden="true"
38
+ className="absolute inset-0 h-full w-full"
39
+ viewBox="0 0 800 500"
40
+ preserveAspectRatio="none"
41
+ >
42
+ <defs>
43
+ <linearGradient id="dd-featcat-placeholder" x1="0" y1="0" x2="1" y2="1">
44
+ <stop offset="0%" stopColor="#1f2937" />
45
+ <stop offset="48%" stopColor="#251644" />
46
+ <stop offset="100%" stopColor="#0f172a" />
47
+ </linearGradient>
48
+ <pattern id="dd-featcat-grid" width="64" height="64" patternUnits="userSpaceOnUse">
49
+ <path
50
+ d="M 64 0 L 0 0 0 64"
51
+ fill="none"
52
+ stroke="#ffffff"
53
+ strokeOpacity="0.08"
54
+ strokeWidth="1"
55
+ />
56
+ </pattern>
57
+ </defs>
58
+ <rect width="800" height="500" fill="url(#dd-featcat-placeholder)" />
59
+ <rect width="800" height="500" fill="url(#dd-featcat-grid)" />
60
+ <rect x="90" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.1" />
61
+ <rect x="310" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.16" />
62
+ <rect x="530" y="105" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.1" />
63
+ <rect x="160" y="260" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.14" />
64
+ <rect x="450" y="260" width="190" height="120" rx="18" fill="#ffffff" fillOpacity="0.12" />
65
+ </svg>
66
+ <div className="absolute inset-x-4 bottom-4 text-xs font-medium uppercase tracking-widest text-stone-300/70">
67
+ {title || 'Category'}
68
+ </div>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ // ─── SpotlightCard ────────────────────────────────────────────────────────────
74
+ interface SpotlightCardProps {
75
+ children: React.ReactNode
76
+ className?: string
77
+ spotlightColor?: string
78
+ style?: React.CSSProperties
79
+ }
80
+
81
+ function SpotlightCard({
82
+ children,
83
+ className,
84
+ spotlightColor = 'rgba(214,211,209,0.08)',
85
+ style,
86
+ }: SpotlightCardProps) {
87
+ const cardRef = useRef<HTMLDivElement>(null)
88
+ const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
89
+
90
+ const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
91
+ if (!cardRef.current) return
92
+ const rect = cardRef.current.getBoundingClientRect()
93
+ setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
94
+ }
95
+
96
+ const handleMouseLeave = () => setPos(null)
97
+
98
+ return (
99
+ <div
100
+ ref={cardRef}
101
+ className={cn('relative overflow-hidden', className)}
102
+ onMouseMove={handleMouseMove}
103
+ onMouseLeave={handleMouseLeave}
104
+ style={style}
105
+ >
106
+ {pos && (
107
+ <div
108
+ className="pointer-events-none absolute inset-0 z-0 transition-opacity duration-300"
109
+ style={{
110
+ background: `radial-gradient(400px circle at ${pos.x}px ${pos.y}px, ${spotlightColor}, transparent 60%)`,
111
+ }}
112
+ />
113
+ )}
114
+ <div className="relative z-10 h-[57%]">{children}</div>
115
+ </div>
116
+ )
117
+ }
118
+
119
+ // ─── Dynamic Icon ─────────────────────────────────────────────────────────────
120
+ function DynamicIcon({
121
+ name,
122
+ ...props
123
+ }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
124
+ if (!name) return null
125
+ const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
126
+ if (!Icon) return null
127
+ return <Icon {...props} />
128
+ }
129
+
130
+ // ─── Animation variants ───────────────────────────────────────────────────────
131
+ const containerVariants: Variants = {
132
+ hidden: {},
133
+ show: { transition: { staggerChildren: 0.1, delayChildren: 0.05 } },
134
+ }
135
+
136
+ const itemVariants: Variants = {
137
+ hidden: { opacity: 0, y: 24 },
138
+ show: { opacity: 1, y: 0, transition: { duration: 0.45, ease: 'easeOut' } },
139
+ }
140
+
141
+ // ─── Component ────────────────────────────────────────────────────────────────
142
+ export const DDFeatCat: React.FC<DDFeatCatProps> = ({
143
+ eyebrow = 'What we offer',
144
+ headlineMain = 'Everything you need',
145
+ headlineAccent = 'to ship faster',
146
+ subtext = 'From individual UI blocks to complete website systems — and the studio team to build it all for you.',
147
+ categories = [],
148
+ sectionId = 'featured-categories',
149
+ customCSS,
150
+ }) => {
151
+ const categoryItems = categories as CategoryItem[]
152
+
153
+ return (
154
+ <>
155
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
156
+
157
+ <div id={sectionId} className="py-4">
158
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
159
+ {/* Header */}
160
+ <motion.div
161
+ variants={containerVariants}
162
+ initial="hidden"
163
+ whileInView="show"
164
+ viewport={{ once: false, amount: 0.2 }}
165
+ className="flex flex-col lg:flex-row lg:items-end justify-between gap-6 mb-14 px-0 md:px-4 overflow-hidden"
166
+ >
167
+ <motion.div variants={itemVariants}>
168
+ {eyebrow && (
169
+ <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
170
+ {eyebrow}
171
+ </p>
172
+ )}
173
+ <h2 className="text-3d text-4xl md:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
174
+ {headlineMain}
175
+ {headlineAccent && (
176
+ <>
177
+ <br />
178
+ <span className="text-stone-400">{headlineAccent}</span>
179
+ </>
180
+ )}
181
+ </h2>
182
+ </motion.div>
183
+
184
+ {subtext && (
185
+ <motion.p
186
+ variants={itemVariants}
187
+ className="text-stone-700 dark:text-stone-300 text-base leading-relaxed max-w-sm lg:text-right"
188
+ >
189
+ {subtext}
190
+ </motion.p>
191
+ )}
192
+ </motion.div>
193
+ {/* Cards */}
194
+ <motion.div
195
+ variants={containerVariants}
196
+ initial="hidden"
197
+ whileInView="show"
198
+ viewport={{ once: false, amount: 0.1 }}
199
+ className="grid grid-cols-1 md:grid-cols-3 gap-5"
200
+ >
201
+ {categoryItems.map((cat, index) => {
202
+ const title = cat.title ?? ''
203
+ const description = cat.description ?? ''
204
+ const link = cat.link ?? '#'
205
+ const linkLabel = cat.linkLabel ?? ''
206
+ const imgSrc =
207
+ cat.image && typeof cat.image === 'object' && cat.image.url
208
+ ? cat.image.url
209
+ : (cat.imageFallbackUrl ?? '')
210
+ const safeImgSrc = isLocalImageSrc(imgSrc) ? imgSrc : null
211
+ const iconSrc =
212
+ cat.iconImage &&
213
+ typeof cat.iconImage === 'object' &&
214
+ isLocalImageSrc((cat.iconImage as { url?: string }).url)
215
+ ? (cat.iconImage as { url: string }).url
216
+ : null
217
+
218
+ return (
219
+ <motion.div key={cat.id ?? index} variants={itemVariants}>
220
+ <SpotlightCard
221
+ spotlightColor="rgba(214,211,209,0.08)"
222
+ className="border border-stone-800 bg-stone-900 group transition-colors duration-300 h-full rounded-2xl hover:border-lime-500 corner-squircle overflow-hidden shadow-md outline-none hover:outline-stone-200/10 hover:outline-4 hover:shadow-2xl"
223
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
224
+ >
225
+ {/* Image */}
226
+ {safeImgSrc ? (
227
+ <div className="w-full h-48 overflow-hidden relative">
228
+ <Image
229
+ src={safeImgSrc}
230
+ alt={title}
231
+ fill
232
+ className="object-cover object-top group-hover:scale-110 transition-transform duration-500 studio-card-image rounded-2xl corner-squircle overflow-hidden scale-105"
233
+ sizes="(max-width: 768px) 100vw, 33vw"
234
+ priority={false}
235
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
236
+ />
237
+ </div>
238
+ ) : (
239
+ <CategoryPlaceholder title={title} />
240
+ )}
241
+ {/* Content */}
242
+ <div
243
+ className="card-content bg-stone-100/90 dark:bg-stone-950/90 backdrop-blur-md -mt-6 mx-2 mb-2 p-4 relative z-5 drop-shadow-md rounded-2xl h-full corner-squircle group-hover:-translate-y-1 group-hover:shadow-2xl group-hover:brightness-110 transition-all duration-300"
244
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
245
+ >
246
+ <div className="flex items-center gap-3 mb-4">
247
+ <div
248
+ className={cn(
249
+ 'w-9 h-9 flex items-center justify-center rounded-lg shrink-0',
250
+ cat.iconBgClass ?? 'bg-stone-700',
251
+ )}
252
+ >
253
+ {iconSrc ? (
254
+ <Image
255
+ src={iconSrc}
256
+ alt={title}
257
+ width={16}
258
+ height={16}
259
+ className={cn('w-4 h-4 object-contain', cat.iconColorClass)}
260
+ />
261
+ ) : (
262
+ <DynamicIcon
263
+ name={cat.iconName ?? ''}
264
+ size={16}
265
+ className={cat.iconColorClass ?? 'text-stone-300'}
266
+ />
267
+ )}
268
+ </div>
269
+ {cat.count && (
270
+ <span className="text-xs font-medium text-stone-700 dark:text-stone-400">
271
+ {cat.count}
272
+ </span>
273
+ )}
274
+ </div>
275
+
276
+ <h3 className="text-lg font-semibold text-stone-900 dark:text-stone-50 mb-2">
277
+ {title}
278
+ </h3>
279
+ <p className="text-sm text-stone-600 dark:text-stone-400 leading-relaxed mb-5">
280
+ {description}
281
+ </p>
282
+
283
+ <Link
284
+ href={link}
285
+ className="inline-flex items-center gap-1.5 text-sm font-semibold text-stone-700 dark:text-stone-300 hover:text-indigo-950 dark:hover:text-stone-50 hover:gap-2.5 transition-all duration-200 cursor-pointer"
286
+ >
287
+ {linkLabel}
288
+ <ArrowRight className="w-4 h-4" />
289
+ </Link>
290
+ </div>
291
+ </SpotlightCard>
292
+ </motion.div>
293
+ )
294
+ })}
295
+ </motion.div>
296
+ </div>
297
+ </div>
298
+ </>
299
+ )
300
+ }
301
+
302
+ export default DDFeatCat