blocks-dusted 0.1.0

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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/bin/index.js +8 -0
  4. package/package.json +40 -0
  5. package/src/cli.js +79 -0
  6. package/src/commands/add.js +327 -0
  7. package/src/commands/doctor.js +21 -0
  8. package/src/commands/list.js +18 -0
  9. package/src/installers/checkRequirements.js +101 -0
  10. package/src/installers/copyTemplateFiles.js +79 -0
  11. package/src/installers/installDependencies.js +69 -0
  12. package/src/installers/patchPayloadBlocksArray.js +122 -0
  13. package/src/installers/patchRenderBlocks.js +110 -0
  14. package/src/installers/runGenerators.js +3 -0
  15. package/src/installers/writeInstalledBlockReadme.js +117 -0
  16. package/src/registry/listTemplates.js +17 -0
  17. package/src/registry/loadTemplateManifest.js +20 -0
  18. package/src/registry/validateTemplateManifest.js +69 -0
  19. package/src/utils/logger.js +8 -0
  20. package/src/utils/packageManager.js +42 -0
  21. package/src/utils/paths.js +7 -0
  22. package/src/utils/project.js +122 -0
  23. package/src/utils/strings.js +7 -0
  24. package/templates/blocks/DD-BookCall/Component.tsx +573 -0
  25. package/templates/blocks/DD-BookCall/config.ts +239 -0
  26. package/templates/blocks/DD-BookCall/manifest.json +112 -0
  27. package/templates/blocks/DD-CardTemplate01/Component.tsx +144 -0
  28. package/templates/blocks/DD-CardTemplate01/config.ts +106 -0
  29. package/templates/blocks/DD-CardTemplate01/manifest.json +84 -0
  30. package/templates/blocks/DD-Carousel-A/Component.tsx +266 -0
  31. package/templates/blocks/DD-Carousel-A/config.ts +163 -0
  32. package/templates/blocks/DD-Carousel-A/manifest.json +86 -0
  33. package/templates/blocks/DD-Carousel-B/Component.tsx +432 -0
  34. package/templates/blocks/DD-Carousel-B/config.ts +176 -0
  35. package/templates/blocks/DD-Carousel-B/manifest.json +86 -0
  36. package/templates/blocks/DD-ComparisonTable/Component.tsx +272 -0
  37. package/templates/blocks/DD-ComparisonTable/config.ts +105 -0
  38. package/templates/blocks/DD-ComparisonTable/manifest.json +78 -0
  39. package/templates/blocks/DD-Contact/Component.tsx +439 -0
  40. package/templates/blocks/DD-Contact/config.ts +217 -0
  41. package/templates/blocks/DD-Contact/manifest.json +107 -0
  42. package/templates/blocks/DD-FeatCat/Component.tsx +361 -0
  43. package/templates/blocks/DD-FeatCat/config.ts +204 -0
  44. package/templates/blocks/DD-FeatCat/manifest.json +87 -0
  45. package/templates/blocks/DD-FeatStrip/Component.tsx +615 -0
  46. package/templates/blocks/DD-FeatStrip/config.ts +134 -0
  47. package/templates/blocks/DD-FeatStrip/manifest.json +82 -0
  48. package/templates/blocks/DD-Hero/Component.tsx +317 -0
  49. package/templates/blocks/DD-Hero/config.ts +267 -0
  50. package/templates/blocks/DD-Hero/manifest.json +92 -0
  51. package/templates/blocks/DD-HorizontalScroll/Component.tsx +995 -0
  52. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -0
  53. package/templates/blocks/DD-HorizontalScroll/manifest.json +85 -0
  54. package/templates/blocks/DD-MasonaryMedia/Component.tsx +571 -0
  55. package/templates/blocks/DD-MasonaryMedia/config.ts +390 -0
  56. package/templates/blocks/DD-MasonaryMedia/manifest.json +101 -0
  57. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -0
  58. package/templates/blocks/DD-Pricing/Component.tsx +380 -0
  59. package/templates/blocks/DD-Pricing/config.ts +215 -0
  60. package/templates/blocks/DD-Pricing/manifest.json +78 -0
  61. package/templates/blocks/DD-Process/Component.tsx +155 -0
  62. package/templates/blocks/DD-Process/config.ts +150 -0
  63. package/templates/blocks/DD-Process/manifest.json +82 -0
  64. package/templates/blocks/DD-Section/Component.tsx +327 -0
  65. package/templates/blocks/DD-Section/config.ts +214 -0
  66. package/templates/blocks/DD-Section/manifest.json +85 -0
  67. package/templates/blocks/DD-Services/Component.tsx +188 -0
  68. package/templates/blocks/DD-Services/config.ts +149 -0
  69. package/templates/blocks/DD-Services/manifest.json +95 -0
  70. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +317 -0
  71. package/templates/blocks/DD-ShowcaseGrid/config.ts +197 -0
  72. package/templates/blocks/DD-ShowcaseGrid/manifest.json +80 -0
  73. package/templates/blocks/DD-Slider-A/Component.tsx +252 -0
  74. package/templates/blocks/DD-Slider-A/config.ts +118 -0
  75. package/templates/blocks/DD-Slider-A/manifest.json +86 -0
  76. package/templates/blocks/DD-StackCards/Component.tsx +496 -0
  77. package/templates/blocks/DD-StackCards/config.ts +399 -0
  78. package/templates/blocks/DD-StackCards/manifest.json +87 -0
  79. package/templates/blocks/DD-Team/Component.tsx +265 -0
  80. package/templates/blocks/DD-Team/config.ts +173 -0
  81. package/templates/blocks/DD-Team/manifest.json +82 -0
  82. package/templates/blocks/DD-TechStack/Component.tsx +189 -0
  83. package/templates/blocks/DD-TechStack/config.ts +153 -0
  84. package/templates/blocks/DD-TechStack/manifest.json +118 -0
  85. package/templates/blocks/DD-Testimonials/Component.tsx +147 -0
  86. package/templates/blocks/DD-Testimonials/config.ts +120 -0
  87. package/templates/blocks/DD-Testimonials/manifest.json +87 -0
  88. package/templates/blocks/DD-Work/Component.tsx +328 -0
  89. package/templates/blocks/DD-Work/config.ts +165 -0
  90. package/templates/blocks/DD-Work/manifest.json +87 -0
  91. package/templates/blocks/DD-Work-B/Component.tsx +320 -0
  92. package/templates/blocks/DD-Work-B/config.ts +165 -0
  93. package/templates/blocks/DD-Work-B/manifest.json +75 -0
  94. package/templates/shared/components/ui/anims/fade.tsx +83 -0
  95. package/templates/shared/components/ui/button.tsx +233 -0
  96. package/templates/shared/components/ui/input.tsx +41 -0
  97. package/templates/shared/fields/lucide-icon-picker/Cell.tsx +24 -0
  98. package/templates/shared/fields/lucide-icon-picker/README.md +160 -0
  99. package/templates/shared/fields/lucide-icon-picker/field.ts +40 -0
  100. package/templates/shared/fields/lucide-icon-picker/index.tsx +203 -0
  101. package/templates/shared/utilities/canUseDOM.ts +1 -0
  102. package/templates/shared/utilities/getURL.ts +31 -0
@@ -0,0 +1,107 @@
1
+ {
2
+ "type": "block",
3
+ "name": "DD-Contact",
4
+ "label": "Studio Contact Block",
5
+ "sourceName": "StudioContactBlock",
6
+ "portableName": "DD-Contact",
7
+ "slug": "DD-Contact",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/DesignsDustedv3",
11
+ "originalPath": "src/blocks/StudioContactBlock",
12
+ "originalSlug": "studioContactBlock",
13
+ "originalConfigExport": "StudioContactBlock",
14
+ "originalComponentExport": "StudioContactBlock",
15
+ "originalInterfaceName": "StudioContactBlock"
16
+ },
17
+ "files": [
18
+ {
19
+ "from": "Component.tsx",
20
+ "to": "src/blocks/DD-Contact/Component.tsx"
21
+ },
22
+ {
23
+ "from": "config.ts",
24
+ "to": "src/blocks/DD-Contact/config.ts"
25
+ }
26
+ ],
27
+ "dependencies": [
28
+ {
29
+ "name": "lucide-react"
30
+ }
31
+ ],
32
+ "sharedFiles": [
33
+ {
34
+ "from": "templates/shared/components/ui/input.tsx",
35
+ "to": "src/components/ui/input.tsx",
36
+ "required": true,
37
+ "safeToCopy": true
38
+ },
39
+ {
40
+ "from": "templates/shared/utilities/getURL.ts",
41
+ "to": "src/utilities/getURL.ts",
42
+ "required": true,
43
+ "safeToCopy": true
44
+ },
45
+ {
46
+ "from": "templates/shared/utilities/canUseDOM.ts",
47
+ "to": "src/utilities/canUseDOM.ts",
48
+ "required": true,
49
+ "safeToCopy": true
50
+ }
51
+ ],
52
+ "requiredProjectFiles": [
53
+ {
54
+ "path": "src/utilities/ui.ts",
55
+ "reason": "Shared Input component uses the cn utility. If missing, add cn or replace the Input component classes."
56
+ }
57
+ ],
58
+ "optionalProjectFiles": [],
59
+ "collectionRegistration": {
60
+ "enabled": true,
61
+ "collections": [
62
+ "Pages",
63
+ "Posts"
64
+ ]
65
+ },
66
+ "renderBlocksRegistration": {
67
+ "enabled": true
68
+ },
69
+ "renderBlocksBehavior": {
70
+ "wrapperless": false
71
+ },
72
+ "pageRouteIntegration": {
73
+ "required": false,
74
+ "manualSteps": []
75
+ },
76
+ "manual": [
77
+ "Uncomment and reconnect the `forms` relationship only after Payload Form Builder or a compatible forms collection exists.",
78
+ "Create/select a Payload form with fields named name, email, company, service, budget, timeline, and message.",
79
+ "Register DDContact in Pages/Posts blocks arrays if automatic registration was skipped.",
80
+ "Register DD-Contact in RenderBlocks if automatic registration was skipped.",
81
+ "Run payload generate:types after schema changes.",
82
+ "Run payload generate:importmap if admin/component imports changed."
83
+ ],
84
+ "manualSteps": [
85
+ "Uncomment and reconnect the `forms` relationship only after Payload Form Builder or a compatible forms collection exists.",
86
+ "Create/select a Payload form with fields named name, email, company, service, budget, timeline, and message.",
87
+ "Register DDContact in Pages/Posts blocks arrays if automatic registration was skipped.",
88
+ "Register DD-Contact in RenderBlocks if automatic registration was skipped.",
89
+ "Run payload generate:types after schema changes.",
90
+ "Run payload generate:importmap if admin/component imports changed."
91
+ ],
92
+ "patchTargets": {
93
+ "blockConfigRegistration": {
94
+ "description": "Installer can add import { DDContact } from '@/blocks/DD-Contact/config' and add DDContact to clear Pages/Posts blocks arrays."
95
+ },
96
+ "renderBlocksRegistration": {
97
+ "description": "Installer can register DD-Contact in src/blocks/**/RenderBlocks.tsx when the blockComponents map is clear."
98
+ }
99
+ },
100
+ "notes": [
101
+ "Converted from DesignsDustedv3 StudioContactBlock.",
102
+ "Installer must not overwrite existing target files.",
103
+ "The generated Payload type import was replaced with a local portable props type so the block can be copied before payload generate:types."
104
+ ],
105
+ "exportName": "DDContact",
106
+ "interfaceName": "DDContact"
107
+ }
@@ -0,0 +1,361 @@
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
44
+ id="dd-featcat-placeholder"
45
+ x1="0"
46
+ y1="0"
47
+ x2="1"
48
+ y2="1"
49
+ >
50
+ <stop offset="0%" stopColor="#1f2937" />
51
+ <stop offset="48%" stopColor="#251644" />
52
+ <stop offset="100%" stopColor="#0f172a" />
53
+ </linearGradient>
54
+ <pattern
55
+ id="dd-featcat-grid"
56
+ width="64"
57
+ height="64"
58
+ patternUnits="userSpaceOnUse"
59
+ >
60
+ <path
61
+ d="M 64 0 L 0 0 0 64"
62
+ fill="none"
63
+ stroke="#ffffff"
64
+ strokeOpacity="0.08"
65
+ strokeWidth="1"
66
+ />
67
+ </pattern>
68
+ </defs>
69
+ <rect width="800" height="500" fill="url(#dd-featcat-placeholder)" />
70
+ <rect width="800" height="500" fill="url(#dd-featcat-grid)" />
71
+ <rect
72
+ x="90"
73
+ y="105"
74
+ width="190"
75
+ height="120"
76
+ rx="18"
77
+ fill="#ffffff"
78
+ fillOpacity="0.1"
79
+ />
80
+ <rect
81
+ x="310"
82
+ y="105"
83
+ width="190"
84
+ height="120"
85
+ rx="18"
86
+ fill="#ffffff"
87
+ fillOpacity="0.16"
88
+ />
89
+ <rect
90
+ x="530"
91
+ y="105"
92
+ width="190"
93
+ height="120"
94
+ rx="18"
95
+ fill="#ffffff"
96
+ fillOpacity="0.1"
97
+ />
98
+ <rect
99
+ x="160"
100
+ y="260"
101
+ width="190"
102
+ height="120"
103
+ rx="18"
104
+ fill="#ffffff"
105
+ fillOpacity="0.14"
106
+ />
107
+ <rect
108
+ x="450"
109
+ y="260"
110
+ width="190"
111
+ height="120"
112
+ rx="18"
113
+ fill="#ffffff"
114
+ fillOpacity="0.12"
115
+ />
116
+ </svg>
117
+ <div className="absolute inset-x-4 bottom-4 text-xs font-medium uppercase tracking-widest text-stone-300/70">
118
+ {title || "Category"}
119
+ </div>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ // ─── SpotlightCard ────────────────────────────────────────────────────────────
125
+ interface SpotlightCardProps {
126
+ children: React.ReactNode;
127
+ className?: string;
128
+ spotlightColor?: string;
129
+ style?: React.CSSProperties;
130
+ }
131
+
132
+ function SpotlightCard({
133
+ children,
134
+ className,
135
+ spotlightColor = "rgba(214,211,209,0.08)",
136
+ style,
137
+ }: SpotlightCardProps) {
138
+ const cardRef = useRef<HTMLDivElement>(null);
139
+ const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
140
+
141
+ const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
142
+ if (!cardRef.current) return;
143
+ const rect = cardRef.current.getBoundingClientRect();
144
+ setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
145
+ };
146
+
147
+ const handleMouseLeave = () => setPos(null);
148
+
149
+ return (
150
+ <div
151
+ ref={cardRef}
152
+ className={cn("relative overflow-hidden", className)}
153
+ onMouseMove={handleMouseMove}
154
+ onMouseLeave={handleMouseLeave}
155
+ style={style}
156
+ >
157
+ {pos && (
158
+ <div
159
+ className="pointer-events-none absolute inset-0 z-0 transition-opacity duration-300"
160
+ style={{
161
+ background: `radial-gradient(400px circle at ${pos.x}px ${pos.y}px, ${spotlightColor}, transparent 60%)`,
162
+ }}
163
+ />
164
+ )}
165
+ <div className="relative z-10 h-[57%]">{children}</div>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ // ─── Dynamic Icon ─────────────────────────────────────────────────────────────
171
+ function DynamicIcon({
172
+ name,
173
+ ...props
174
+ }: { name?: string | null } & Omit<LucideIcons.LucideProps, "name">) {
175
+ if (!name) return null;
176
+ const Icon = (
177
+ LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>
178
+ )[name];
179
+ if (!Icon) return null;
180
+ return <Icon {...props} />;
181
+ }
182
+
183
+ // ─── Animation variants ───────────────────────────────────────────────────────
184
+ const containerVariants: Variants = {
185
+ hidden: {},
186
+ show: { transition: { staggerChildren: 0.1, delayChildren: 0.05 } },
187
+ };
188
+
189
+ const itemVariants: Variants = {
190
+ hidden: { opacity: 0, y: 24 },
191
+ show: { opacity: 1, y: 0, transition: { duration: 0.45, ease: "easeOut" } },
192
+ };
193
+
194
+ // ─── Component ────────────────────────────────────────────────────────────────
195
+ export const DDFeatCat: React.FC<DDFeatCatProps> = ({
196
+ eyebrow = "What we offer",
197
+ headlineMain = "Everything you need",
198
+ headlineAccent = "to ship faster",
199
+ subtext = "From individual UI blocks to complete website systems — and the studio team to build it all for you.",
200
+ categories = [],
201
+ sectionId = "featured-categories",
202
+ customCSS,
203
+ }) => {
204
+ const categoryItems = categories as CategoryItem[];
205
+
206
+ return (
207
+ <>
208
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
209
+
210
+ <div id={sectionId} className="py-24">
211
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
212
+ {/* Header */}
213
+ <motion.div
214
+ variants={containerVariants}
215
+ initial="hidden"
216
+ whileInView="show"
217
+ viewport={{ once: false, amount: 0.2 }}
218
+ className="flex flex-col lg:flex-row lg:items-end justify-between gap-6 mb-14 px-0 md:px-4 overflow-hidden"
219
+ >
220
+ <motion.div variants={itemVariants}>
221
+ {eyebrow && (
222
+ <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
223
+ {eyebrow}
224
+ </p>
225
+ )}
226
+ <h2 className="text-3d text-4xl md:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
227
+ {headlineMain}
228
+ {headlineAccent && (
229
+ <>
230
+ <br />
231
+ <span className="text-stone-400">{headlineAccent}</span>
232
+ </>
233
+ )}
234
+ </h2>
235
+ </motion.div>
236
+
237
+ {subtext && (
238
+ <motion.p
239
+ variants={itemVariants}
240
+ className="text-stone-700 dark:text-stone-300 text-base leading-relaxed max-w-sm lg:text-right"
241
+ >
242
+ {subtext}
243
+ </motion.p>
244
+ )}
245
+ </motion.div>
246
+
247
+ {/* Cards */}
248
+ <motion.div
249
+ variants={containerVariants}
250
+ initial="hidden"
251
+ whileInView="show"
252
+ viewport={{ once: false, amount: 0.1 }}
253
+ className="grid grid-cols-1 md:grid-cols-3 gap-5"
254
+ >
255
+ {categoryItems.map((cat, index) => {
256
+ const title = cat.title ?? "";
257
+ const description = cat.description ?? "";
258
+ const link = cat.link ?? "#";
259
+ const linkLabel = cat.linkLabel ?? "";
260
+ const imgSrc =
261
+ cat.image && typeof cat.image === "object" && cat.image.url
262
+ ? cat.image.url
263
+ : (cat.imageFallbackUrl ?? "");
264
+ const safeImgSrc = isLocalImageSrc(imgSrc) ? imgSrc : null;
265
+ const iconSrc =
266
+ cat.iconImage &&
267
+ typeof cat.iconImage === "object" &&
268
+ isLocalImageSrc((cat.iconImage as { url?: string }).url)
269
+ ? (cat.iconImage as { url: string }).url
270
+ : null;
271
+
272
+ return (
273
+ <motion.div key={cat.id ?? index} variants={itemVariants}>
274
+ <SpotlightCard
275
+ spotlightColor="rgba(214,211,209,0.08)"
276
+ 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"
277
+ style={{ cornerShape: "squircle" } as React.CSSProperties}
278
+ >
279
+ {/* Image */}
280
+ {safeImgSrc ? (
281
+ <div className="w-full h-48 overflow-hidden relative">
282
+ <Image
283
+ src={safeImgSrc}
284
+ alt={title}
285
+ fill
286
+ className="object-cover object-top group-hover:scale-110 transition-transform duration-500 studio-card-image rounded-2xl corner-squircle overflow-hidden scale-105"
287
+ sizes="(max-width: 768px) 100vw, 33vw"
288
+ priority={false}
289
+ style={
290
+ { cornerShape: "squircle" } as React.CSSProperties
291
+ }
292
+ />
293
+ </div>
294
+ ) : (
295
+ <CategoryPlaceholder title={title} />
296
+ )}
297
+ {/* Content */}
298
+ <div
299
+ 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"
300
+ style={{ cornerShape: "squircle" } as React.CSSProperties}
301
+ >
302
+ <div className="flex items-center gap-3 mb-4">
303
+ <div
304
+ className={cn(
305
+ "w-9 h-9 flex items-center justify-center rounded-lg shrink-0",
306
+ cat.iconBgClass ?? "bg-stone-700",
307
+ )}
308
+ >
309
+ {iconSrc ? (
310
+ <Image
311
+ src={iconSrc}
312
+ alt={title}
313
+ width={16}
314
+ height={16}
315
+ className={cn(
316
+ "w-4 h-4 object-contain",
317
+ cat.iconColorClass,
318
+ )}
319
+ />
320
+ ) : (
321
+ <DynamicIcon
322
+ name={cat.iconName ?? ""}
323
+ size={16}
324
+ className={cat.iconColorClass ?? "text-stone-300"}
325
+ />
326
+ )}
327
+ </div>
328
+ {cat.count && (
329
+ <span className="text-xs font-medium text-stone-700 dark:text-stone-400">
330
+ {cat.count}
331
+ </span>
332
+ )}
333
+ </div>
334
+
335
+ <h3 className="text-lg font-semibold text-stone-900 dark:text-stone-50 mb-2">
336
+ {title}
337
+ </h3>
338
+ <p className="text-sm text-stone-600 dark:text-stone-400 leading-relaxed mb-5">
339
+ {description}
340
+ </p>
341
+
342
+ <Link
343
+ href={link}
344
+ 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"
345
+ >
346
+ {linkLabel}
347
+ <ArrowRight className="w-4 h-4" />
348
+ </Link>
349
+ </div>
350
+ </SpotlightCard>
351
+ </motion.div>
352
+ );
353
+ })}
354
+ </motion.div>
355
+ </div>
356
+ </div>
357
+ </>
358
+ );
359
+ };
360
+
361
+ export default DDFeatCat;
@@ -0,0 +1,204 @@
1
+ import type { Block } from 'payload'
2
+
3
+ export const DDFeatCat: Block = {
4
+ slug: 'DD-FeatCat',
5
+ interfaceName: 'DDFeatCat',
6
+ labels: {
7
+ singular: 'Featured Categories',
8
+ plural: 'Featured Categories'
9
+ },
10
+ fields: [
11
+ {
12
+ type: 'tabs',
13
+ tabs: [
14
+ {
15
+ label: 'Header',
16
+ fields: [
17
+ {
18
+ name: 'eyebrow',
19
+ type: 'text',
20
+ label: 'Eyebrow Label',
21
+ defaultValue: 'What we offer'
22
+ },
23
+ {
24
+ name: 'headlineMain',
25
+ type: 'text',
26
+ label: 'Headline (Main)',
27
+ required: true,
28
+ defaultValue: 'Everything you need'
29
+ },
30
+ {
31
+ name: 'headlineAccent',
32
+ type: 'text',
33
+ label: 'Headline (Accent)',
34
+ defaultValue: 'to ship faster'
35
+ },
36
+ {
37
+ name: 'subtext',
38
+ type: 'textarea',
39
+ label: 'Subtext',
40
+ defaultValue:
41
+ 'From individual UI blocks to complete website systems — and the studio team to build it all for you.'
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ label: 'Categories',
47
+ fields: [
48
+ {
49
+ name: 'categories',
50
+ type: 'array',
51
+ label: 'Category Cards',
52
+ minRows: 1,
53
+ maxRows: 6,
54
+ defaultValue: [
55
+ {
56
+ title: 'UI Components',
57
+ description:
58
+ 'Production-ready blocks for hero sections, navigation, pricing, forms, and more. Drop into any Next.js project.',
59
+ link: '/components',
60
+ linkLabel: 'Browse Components',
61
+ iconName: 'LayoutGrid',
62
+ count: '120+ blocks',
63
+ imageFallbackUrl:
64
+ 'https://readdy.ai/api/search-image?query=collection%20of%20UI%20component%20blocks%20showing%20hero%2C%20pricing%2C%20navigation%2C%20and%20form%20elements%20arranged%20in%20a%20clean%20grid%20layout%20on%20dark%20background%2C%20modern%20developer%20tool%20aesthetic%2C%20dark%20theme&width=800&height=500&seq=cat1&orientation=landscape',
65
+ iconColorClass: 'text-amber-400',
66
+ iconBgClass: 'bg-amber-500/20'
67
+ },
68
+ {
69
+ title: 'Website Templates',
70
+ description:
71
+ 'Full-site starters for SaaS, agencies, blogs, and e-commerce. Powered by Next.js and Payload CMS.',
72
+ link: '/templates',
73
+ linkLabel: 'Explore Templates',
74
+ iconName: 'FileStack',
75
+ count: '24+ templates',
76
+ imageFallbackUrl:
77
+ 'https://readdy.ai/api/search-image?query=website%20template%20previews%20showing%20SaaS%20landing%20page%2C%20agency%20portfolio%2C%20and%20blog%20layouts%20in%20a%20clean%20mockup%20display%20on%20dark%20background%2C%20professional%20web%20design%20showcase%2C%20dark%20theme&width=800&height=500&seq=cat2&orientation=landscape',
78
+ iconColorClass: 'text-stone-300',
79
+ iconBgClass: 'bg-stone-600/40'
80
+ },
81
+ {
82
+ title: 'Studio Services',
83
+ description:
84
+ 'Custom UX/UI design, web development, CMS architecture, and digital product consulting from our expert team.',
85
+ link: '/studio',
86
+ linkLabel: 'Hire the Studio',
87
+ iconName: 'Users',
88
+ count: 'Custom projects',
89
+ imageFallbackUrl:
90
+ 'https://readdy.ai/api/search-image?query=design%20studio%20workspace%20with%20wireframes%2C%20design%20mockups%2C%20and%20development%20tools%20on%20a%20clean%20desk%2C%20professional%20creative%20agency%20environment%2C%20dark%20minimal%20aesthetic&width=800&height=500&seq=cat3&orientation=landscape',
91
+ iconColorClass: 'text-emerald-400',
92
+ iconBgClass: 'bg-emerald-500/20'
93
+ }
94
+ ],
95
+ fields: [
96
+ {
97
+ name: 'title',
98
+ type: 'text',
99
+ label: 'Title',
100
+ required: true
101
+ },
102
+ {
103
+ name: 'description',
104
+ type: 'textarea',
105
+ label: 'Description',
106
+ required: true
107
+ },
108
+ {
109
+ name: 'link',
110
+ type: 'text',
111
+ label: 'Link URL',
112
+ required: true
113
+ },
114
+ {
115
+ name: 'linkLabel',
116
+ type: 'text',
117
+ label: 'Link Label',
118
+ required: true
119
+ },
120
+ {
121
+ name: 'iconName',
122
+ type: 'text',
123
+ label: 'Lucide Icon Name',
124
+ admin: {
125
+ description:
126
+ 'Lucide icon name e.g. LayoutGrid, FileStack, Users, Zap. Ignored if Icon Image is set.'
127
+ }
128
+ },
129
+ {
130
+ name: 'iconImage',
131
+ type: 'upload',
132
+ label: 'Icon Image (SVG / PNG)',
133
+ relationTo: 'media',
134
+ admin: {
135
+ description:
136
+ 'Upload a custom SVG or image to use as the icon. Overrides Lucide Icon Name.'
137
+ }
138
+ },
139
+ {
140
+ name: 'count',
141
+ type: 'text',
142
+ label: 'Count / Badge Label',
143
+ admin: {
144
+ description: 'e.g. "120+ blocks", "Custom projects"'
145
+ }
146
+ },
147
+ {
148
+ name: 'image',
149
+ type: 'upload',
150
+ label: 'Card Image',
151
+ relationTo: 'media'
152
+ },
153
+ {
154
+ name: 'imageFallbackUrl',
155
+ type: 'text',
156
+ label: 'Image Fallback URL',
157
+ admin: {
158
+ placeholder: '/assets/placeholder/01.png',
159
+ description: 'Used if no uploaded image is set'
160
+ }
161
+ },
162
+ {
163
+ name: 'iconColorClass',
164
+ type: 'text',
165
+ label: 'Icon Color Class',
166
+ defaultValue: 'text-amber-400',
167
+ admin: {
168
+ description: 'Tailwind text-color class for the icon e.g. text-amber-400'
169
+ }
170
+ },
171
+ {
172
+ name: 'iconBgClass',
173
+ type: 'text',
174
+ label: 'Icon Background Class',
175
+ defaultValue: 'bg-amber-500/20',
176
+ admin: {
177
+ description: 'Tailwind bg class for the icon container e.g. bg-amber-500/20'
178
+ }
179
+ }
180
+ ]
181
+ }
182
+ ]
183
+ },
184
+ {
185
+ label: 'Settings',
186
+ fields: [
187
+ {
188
+ name: 'sectionId',
189
+ type: 'text',
190
+ label: 'Section ID',
191
+ defaultValue: 'featured-categories'
192
+ },
193
+ {
194
+ name: 'customCSS',
195
+ type: 'code',
196
+ label: 'Custom CSS',
197
+ admin: { language: 'css' }
198
+ }
199
+ ]
200
+ }
201
+ ]
202
+ }
203
+ ]
204
+ }