blocks-dusted 0.1.1 → 0.1.3

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 (56) hide show
  1. package/README.md +967 -3
  2. package/package.json +2 -2
  3. package/src/commands/add.js +143 -45
  4. package/src/commands/list.js +2 -2
  5. package/src/installers/checkRequirements.js +1 -0
  6. package/src/installers/copyTemplateFiles.js +23 -2
  7. package/src/installers/patchPayloadConfigCollections.js +92 -0
  8. package/src/installers/writeInstalledBlockReadme.js +17 -4
  9. package/src/registry/listTemplates.js +16 -7
  10. package/src/registry/loadTemplateManifest.js +20 -11
  11. package/src/registry/validateTemplateManifest.js +6 -5
  12. package/src/utils/paths.js +7 -0
  13. package/templates/blocks/DD-BookCall/Component.tsx +535 -573
  14. package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
  15. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
  16. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
  17. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
  18. package/templates/blocks/DD-Contact/Component.tsx +434 -439
  19. package/templates/blocks/DD-Contact/config.ts +8 -3
  20. package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
  21. package/templates/blocks/DD-FeatCat/config.ts +201 -204
  22. package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
  23. package/templates/blocks/DD-Hero/Component.tsx +297 -317
  24. package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
  25. package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
  26. package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
  27. package/templates/blocks/DD-Pricing/Component.tsx +372 -380
  28. package/templates/blocks/DD-Process/Component.tsx +149 -155
  29. package/templates/blocks/DD-Section/Component.tsx +266 -327
  30. package/templates/blocks/DD-Services/Component.tsx +176 -188
  31. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
  32. package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
  33. package/templates/blocks/DD-StackCards/Component.tsx +137 -160
  34. package/templates/blocks/DD-Team/Component.tsx +247 -265
  35. package/templates/blocks/DD-TechStack/Component.tsx +57 -72
  36. package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
  37. package/templates/blocks/DD-Testimonials/config.ts +66 -66
  38. package/templates/blocks/DD-Work/Component.tsx +315 -328
  39. package/templates/blocks/DD-Work-B/Component.tsx +294 -320
  40. package/templates/collections/Testimonials/README.md +11 -0
  41. package/templates/collections/Testimonials/Testimonials.ts +161 -0
  42. package/templates/collections/Testimonials/manifest.json +55 -0
  43. package/templates/components/HeaderDD02/MobileBottomNav/index.tsx +545 -0
  44. package/templates/components/HeaderDD02/README.md +45 -0
  45. package/templates/components/HeaderDD02/RowLabel.tsx +33 -0
  46. package/templates/components/HeaderDD02/config.ts +302 -0
  47. package/templates/components/HeaderDD02/index.tsx +517 -0
  48. package/templates/components/HeaderDD02/manifest.json +123 -0
  49. package/templates/components/RichText/README.md +13 -0
  50. package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
  51. package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
  52. package/templates/components/RichText/converter/index.tsx +18 -0
  53. package/templates/components/RichText/converter/internalLinks.tsx +45 -0
  54. package/templates/components/RichText/converter/textConverter.tsx +27 -0
  55. package/templates/components/RichText/index.tsx +35 -0
  56. package/templates/components/RichText/manifest.json +80 -0
@@ -1,63 +1,60 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
- "use client";
2
+ 'use client'
3
3
 
4
- import { useState, useEffect } from "react";
5
- import Link from "next/link";
6
- import Image from "next/image";
7
- import dynamic from "next/dynamic";
8
- import { ArrowRight } from "lucide-react";
4
+ import { useState, useEffect } from 'react'
5
+ import Link from 'next/link'
6
+ import Image from 'next/image'
7
+ import dynamic from 'next/dynamic'
8
+ import { ArrowRight } from 'lucide-react'
9
9
 
10
10
  const DotLottieReact = dynamic(
11
- () => import("@lottiefiles/dotlottie-react").then((m) => m.DotLottieReact),
11
+ () => import('@lottiefiles/dotlottie-react').then((m) => m.DotLottieReact),
12
12
  { ssr: false },
13
- );
13
+ )
14
14
 
15
15
  /* ── Label color maps ─────────────────────────────────────────── */
16
16
 
17
17
  const labelColorMap: Record<string, string> = {
18
- indigo: "text-indigo-400",
19
- emerald: "text-emerald-400",
20
- amber: "text-amber-400",
21
- rose: "text-rose-400",
22
- violet: "text-violet-400",
23
- sky: "text-sky-400",
24
- };
25
-
26
- const exploreColorMap: Record<
27
- string,
28
- { base: string; hover: string; glow: string }
29
- > = {
18
+ indigo: 'text-indigo-400',
19
+ emerald: 'text-emerald-400',
20
+ amber: 'text-amber-400',
21
+ rose: 'text-rose-400',
22
+ violet: 'text-violet-400',
23
+ sky: 'text-sky-400',
24
+ }
25
+
26
+ const exploreColorMap: Record<string, { base: string; hover: string; glow: string }> = {
30
27
  indigo: {
31
- base: "text-indigo-300",
32
- hover: "group-hover:text-indigo-400",
33
- glow: "rgba(99,102,241,0.08)",
28
+ base: 'text-indigo-300',
29
+ hover: 'group-hover:text-indigo-400',
30
+ glow: 'rgba(99,102,241,0.08)',
34
31
  },
35
32
  emerald: {
36
- base: "text-emerald-300",
37
- hover: "group-hover:text-emerald-400",
38
- glow: "rgba(16,185,129,0.08)",
33
+ base: 'text-emerald-300',
34
+ hover: 'group-hover:text-emerald-400',
35
+ glow: 'rgba(16,185,129,0.08)',
39
36
  },
40
37
  amber: {
41
- base: "text-amber-300",
42
- hover: "group-hover:text-amber-400",
43
- glow: "rgba(245,158,11,0.08)",
38
+ base: 'text-amber-300',
39
+ hover: 'group-hover:text-amber-400',
40
+ glow: 'rgba(245,158,11,0.08)',
44
41
  },
45
42
  rose: {
46
- base: "text-rose-300",
47
- hover: "group-hover:text-rose-400",
48
- glow: "rgba(244,63,94,0.08)",
43
+ base: 'text-rose-300',
44
+ hover: 'group-hover:text-rose-400',
45
+ glow: 'rgba(244,63,94,0.08)',
49
46
  },
50
47
  violet: {
51
- base: "text-violet-300",
52
- hover: "group-hover:text-violet-400",
53
- glow: "rgba(167,139,250,0.08)",
48
+ base: 'text-violet-300',
49
+ hover: 'group-hover:text-violet-400',
50
+ glow: 'rgba(167,139,250,0.08)',
54
51
  },
55
52
  sky: {
56
- base: "text-sky-300",
57
- hover: "group-hover:text-sky-400",
58
- glow: "rgba(56,189,248,0.08)",
53
+ base: 'text-sky-300',
54
+ hover: 'group-hover:text-sky-400',
55
+ glow: 'rgba(56,189,248,0.08)',
59
56
  },
60
- };
57
+ }
61
58
 
62
59
  /* ── Isometric SVG Icons ─────────────────────────────────────── */
63
60
 
@@ -69,32 +66,32 @@ function UIBlocksIcon({ hovered }: { hovered: boolean }) {
69
66
  xmlns="http://www.w3.org/2000/svg"
70
67
  className="w-full h-full"
71
68
  style={{
72
- transition: "filter 0.4s ease",
73
- filter: hovered ? "drop-shadow(0 0 12px rgba(99,102,241,0.7))" : "none",
69
+ transition: 'filter 0.4s ease',
70
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(99,102,241,0.7))' : 'none',
74
71
  }}
75
72
  >
76
73
  <g
77
74
  style={{
78
- transform: hovered ? "translateY(-4px)" : "translateY(0)",
79
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
75
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
76
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
80
77
  }}
81
78
  >
82
79
  <polygon
83
80
  points="60,4 108,28 60,52 12,28"
84
- fill={hovered ? "#312e81" : "#1e1b4b"}
85
- stroke={hovered ? "#818cf8" : "#4f46e5"}
81
+ fill={hovered ? '#312e81' : '#1e1b4b'}
82
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
86
83
  strokeWidth="1.2"
87
84
  />
88
85
  <polygon
89
86
  points="12,28 12,58 60,82 60,52"
90
- fill={hovered ? "#1e1b4b" : "#161430"}
91
- stroke={hovered ? "#6366f1" : "#3730a3"}
87
+ fill={hovered ? '#1e1b4b' : '#161430'}
88
+ stroke={hovered ? '#6366f1' : '#3730a3'}
92
89
  strokeWidth="1.2"
93
90
  />
94
91
  <polygon
95
92
  points="108,28 108,58 60,82 60,52"
96
- fill={hovered ? "#2e2a6e" : "#1a1740"}
97
- stroke={hovered ? "#818cf8" : "#4338ca"}
93
+ fill={hovered ? '#2e2a6e' : '#1a1740'}
94
+ stroke={hovered ? '#818cf8' : '#4338ca'}
98
95
  strokeWidth="1.2"
99
96
  />
100
97
  <line
@@ -102,7 +99,7 @@ function UIBlocksIcon({ hovered }: { hovered: boolean }) {
102
99
  y1="4"
103
100
  x2="60"
104
101
  y2="52"
105
- stroke={hovered ? "#818cf8" : "#4f46e5"}
102
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
106
103
  strokeWidth="0.6"
107
104
  strokeDasharray="3 3"
108
105
  />
@@ -111,7 +108,7 @@ function UIBlocksIcon({ hovered }: { hovered: boolean }) {
111
108
  y1="16"
112
109
  x2="84"
113
110
  y2="40"
114
- stroke={hovered ? "#818cf8" : "#4f46e5"}
111
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
115
112
  strokeWidth="0.6"
116
113
  strokeDasharray="3 3"
117
114
  />
@@ -120,31 +117,31 @@ function UIBlocksIcon({ hovered }: { hovered: boolean }) {
120
117
  y1="16"
121
118
  x2="36"
122
119
  y2="40"
123
- stroke={hovered ? "#818cf8" : "#4f46e5"}
120
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
124
121
  strokeWidth="0.6"
125
122
  strokeDasharray="3 3"
126
123
  />
127
124
  <polygon
128
125
  points="60,14 78,23 60,32 42,23"
129
- fill={hovered ? "#4f46e5" : "#312e81"}
130
- stroke={hovered ? "#a5b4fc" : "#6366f1"}
126
+ fill={hovered ? '#4f46e5' : '#312e81'}
127
+ stroke={hovered ? '#a5b4fc' : '#6366f1'}
131
128
  strokeWidth="1"
132
129
  />
133
130
  <polygon
134
131
  points="42,23 42,33 60,42 60,32"
135
- fill={hovered ? "#3730a3" : "#1e1b4b"}
136
- stroke={hovered ? "#818cf8" : "#4338ca"}
132
+ fill={hovered ? '#3730a3' : '#1e1b4b'}
133
+ stroke={hovered ? '#818cf8' : '#4338ca'}
137
134
  strokeWidth="1"
138
135
  />
139
136
  <polygon
140
137
  points="78,23 78,33 60,42 60,32"
141
- fill={hovered ? "#4338ca" : "#272466"}
142
- stroke={hovered ? "#a5b4fc" : "#6366f1"}
138
+ fill={hovered ? '#4338ca' : '#272466'}
139
+ stroke={hovered ? '#a5b4fc' : '#6366f1'}
143
140
  strokeWidth="1"
144
141
  />
145
142
  </g>
146
143
  </svg>
147
- );
144
+ )
148
145
  }
149
146
 
150
147
  function TemplatesIcon({ hovered }: { hovered: boolean }) {
@@ -155,32 +152,32 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
155
152
  xmlns="http://www.w3.org/2000/svg"
156
153
  className="w-full h-full"
157
154
  style={{
158
- transition: "filter 0.4s ease",
159
- filter: hovered ? "drop-shadow(0 0 12px rgba(16,185,129,0.6))" : "none",
155
+ transition: 'filter 0.4s ease',
156
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(16,185,129,0.6))' : 'none',
160
157
  }}
161
158
  >
162
159
  <g
163
160
  style={{
164
- transform: hovered ? "translateY(-4px)" : "translateY(0)",
165
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
161
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
162
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
166
163
  }}
167
164
  >
168
165
  <polygon
169
166
  points="60,6 104,30 60,54 16,30"
170
- fill={hovered ? "#064e3b" : "#022c22"}
171
- stroke={hovered ? "#34d399" : "#059669"}
167
+ fill={hovered ? '#064e3b' : '#022c22'}
168
+ stroke={hovered ? '#34d399' : '#059669'}
172
169
  strokeWidth="1.2"
173
170
  />
174
171
  <polygon
175
172
  points="16,30 16,60 60,84 60,54"
176
- fill={hovered ? "#022c22" : "#011a15"}
177
- stroke={hovered ? "#10b981" : "#047857"}
173
+ fill={hovered ? '#022c22' : '#011a15'}
174
+ stroke={hovered ? '#10b981' : '#047857'}
178
175
  strokeWidth="1.2"
179
176
  />
180
177
  <polygon
181
178
  points="104,30 104,60 60,84 60,54"
182
- fill={hovered ? "#043927" : "#011a15"}
183
- stroke={hovered ? "#34d399" : "#059669"}
179
+ fill={hovered ? '#043927' : '#011a15'}
180
+ stroke={hovered ? '#34d399' : '#059669'}
184
181
  strokeWidth="1.2"
185
182
  />
186
183
  <line
@@ -188,7 +185,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
188
185
  y1="30"
189
186
  x2="60"
190
187
  y2="16"
191
- stroke={hovered ? "#6ee7b7" : "#34d399"}
188
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
192
189
  strokeWidth="1"
193
190
  opacity="0.7"
194
191
  />
@@ -197,7 +194,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
197
194
  y1="36"
198
195
  x2="60"
199
196
  y2="22"
200
- stroke={hovered ? "#6ee7b7" : "#34d399"}
197
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
201
198
  strokeWidth="0.8"
202
199
  opacity="0.5"
203
200
  />
@@ -206,7 +203,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
206
203
  y1="42"
207
204
  x2="52"
208
205
  y2="30"
209
- stroke={hovered ? "#6ee7b7" : "#34d399"}
206
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
210
207
  strokeWidth="0.8"
211
208
  opacity="0.4"
212
209
  />
@@ -215,7 +212,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
215
212
  y1="22"
216
213
  x2="96"
217
214
  y2="34"
218
- stroke={hovered ? "#6ee7b7" : "#34d399"}
215
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
219
216
  strokeWidth="1"
220
217
  opacity="0.7"
221
218
  />
@@ -224,7 +221,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
224
221
  y1="28"
225
222
  x2="90"
226
223
  y2="37"
227
- stroke={hovered ? "#6ee7b7" : "#34d399"}
224
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
228
225
  strokeWidth="0.8"
229
226
  opacity="0.5"
230
227
  />
@@ -233,7 +230,7 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
233
230
  y1="34"
234
231
  x2="84"
235
232
  y2="40"
236
- stroke={hovered ? "#6ee7b7" : "#34d399"}
233
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
237
234
  strokeWidth="0.8"
238
235
  opacity="0.4"
239
236
  />
@@ -242,13 +239,13 @@ function TemplatesIcon({ hovered }: { hovered: boolean }) {
242
239
  y1="12"
243
240
  x2="66"
244
241
  y2="50"
245
- stroke={hovered ? "#34d399" : "#059669"}
242
+ stroke={hovered ? '#34d399' : '#059669'}
246
243
  strokeWidth="0.8"
247
244
  strokeDasharray="2 2"
248
245
  />
249
246
  </g>
250
247
  </svg>
251
- );
248
+ )
252
249
  }
253
250
 
254
251
  function UIKitIcon({ hovered }: { hovered: boolean }) {
@@ -259,133 +256,109 @@ function UIKitIcon({ hovered }: { hovered: boolean }) {
259
256
  xmlns="http://www.w3.org/2000/svg"
260
257
  className="w-full h-full"
261
258
  style={{
262
- transition: "filter 0.4s ease",
263
- filter: hovered ? "drop-shadow(0 0 12px rgba(245,158,11,0.6))" : "none",
259
+ transition: 'filter 0.4s ease',
260
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(245,158,11,0.6))' : 'none',
264
261
  }}
265
262
  >
266
263
  <g
267
264
  style={{
268
- transform: hovered ? "translateY(-4px)" : "translateY(0)",
269
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
265
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
266
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
270
267
  }}
271
268
  >
272
269
  <polygon
273
270
  points="60,42 100,62 60,82 20,62"
274
- fill={hovered ? "#451a03" : "#27100a"}
275
- stroke={hovered ? "#f59e0b" : "#b45309"}
271
+ fill={hovered ? '#451a03' : '#27100a'}
272
+ stroke={hovered ? '#f59e0b' : '#b45309'}
276
273
  strokeWidth="1.2"
277
274
  />
278
275
  <polygon
279
276
  points="20,52 20,62 60,82 60,72"
280
- fill={hovered ? "#27100a" : "#1a0a05"}
281
- stroke={hovered ? "#d97706" : "#92400e"}
277
+ fill={hovered ? '#27100a' : '#1a0a05'}
278
+ stroke={hovered ? '#d97706' : '#92400e'}
282
279
  strokeWidth="1"
283
280
  />
284
281
  <polygon
285
282
  points="100,52 100,62 60,82 60,72"
286
- fill={hovered ? "#3b1a06" : "#1a0a05"}
287
- stroke={hovered ? "#f59e0b" : "#b45309"}
283
+ fill={hovered ? '#3b1a06' : '#1a0a05'}
284
+ stroke={hovered ? '#f59e0b' : '#b45309'}
288
285
  strokeWidth="1"
289
286
  />
290
287
  <polygon
291
288
  points="60,26 100,46 60,66 20,46"
292
- fill={hovered ? "#78350f" : "#451a03"}
293
- stroke={hovered ? "#fbbf24" : "#d97706"}
289
+ fill={hovered ? '#78350f' : '#451a03'}
290
+ stroke={hovered ? '#fbbf24' : '#d97706'}
294
291
  strokeWidth="1.2"
295
292
  />
296
293
  <polygon
297
294
  points="20,36 20,46 60,66 60,56"
298
- fill={hovered ? "#451a03" : "#27100a"}
299
- stroke={hovered ? "#f59e0b" : "#b45309"}
295
+ fill={hovered ? '#451a03' : '#27100a'}
296
+ stroke={hovered ? '#f59e0b' : '#b45309'}
300
297
  strokeWidth="1"
301
298
  />
302
299
  <polygon
303
300
  points="100,36 100,46 60,66 60,56"
304
- fill={hovered ? "#5c2a07" : "#2d1108"}
305
- stroke={hovered ? "#fbbf24" : "#d97706"}
301
+ fill={hovered ? '#5c2a07' : '#2d1108'}
302
+ stroke={hovered ? '#fbbf24' : '#d97706'}
306
303
  strokeWidth="1"
307
304
  />
308
305
  <polygon
309
306
  points="60,10 100,30 60,50 20,30"
310
- fill={hovered ? "#92400e" : "#78350f"}
311
- stroke={hovered ? "#fcd34d" : "#fbbf24"}
307
+ fill={hovered ? '#92400e' : '#78350f'}
308
+ stroke={hovered ? '#fcd34d' : '#fbbf24'}
312
309
  strokeWidth="1.2"
313
310
  />
314
311
  <polygon
315
312
  points="20,20 20,30 60,50 60,40"
316
- fill={hovered ? "#78350f" : "#451a03"}
317
- stroke={hovered ? "#fbbf24" : "#d97706"}
313
+ fill={hovered ? '#78350f' : '#451a03'}
314
+ stroke={hovered ? '#fbbf24' : '#d97706'}
318
315
  strokeWidth="1"
319
316
  />
320
317
  <polygon
321
318
  points="100,20 100,30 60,50 60,40"
322
- fill={hovered ? "#7c3a0e" : "#4a2008"}
323
- stroke={hovered ? "#fcd34d" : "#fbbf24"}
319
+ fill={hovered ? '#7c3a0e' : '#4a2008'}
320
+ stroke={hovered ? '#fcd34d' : '#fbbf24'}
324
321
  strokeWidth="1"
325
322
  />
326
- <circle
327
- cx="44"
328
- cy="28"
329
- r="2.5"
330
- fill={hovered ? "#fcd34d" : "#fbbf24"}
331
- opacity="0.9"
332
- />
333
- <circle
334
- cx="60"
335
- cy="20"
336
- r="2.5"
337
- fill={hovered ? "#fcd34d" : "#fbbf24"}
338
- opacity="0.9"
339
- />
340
- <circle
341
- cx="76"
342
- cy="28"
343
- r="2.5"
344
- fill={hovered ? "#fcd34d" : "#fbbf24"}
345
- opacity="0.9"
346
- />
323
+ <circle cx="44" cy="28" r="2.5" fill={hovered ? '#fcd34d' : '#fbbf24'} opacity="0.9" />
324
+ <circle cx="60" cy="20" r="2.5" fill={hovered ? '#fcd34d' : '#fbbf24'} opacity="0.9" />
325
+ <circle cx="76" cy="28" r="2.5" fill={hovered ? '#fcd34d' : '#fbbf24'} opacity="0.9" />
347
326
  </g>
348
327
  </svg>
349
- );
328
+ )
350
329
  }
351
330
 
352
331
  const iconMap: Record<string, React.FC<{ hovered: boolean }>> = {
353
332
  uiBlocks: UIBlocksIcon,
354
333
  templates: TemplatesIcon,
355
334
  uiKit: UIKitIcon,
356
- };
335
+ }
357
336
 
358
337
  /* ── Icon renderer ───────────────────────────────────────────── */
359
338
 
360
339
  type MediaLike = {
361
- url?: string | null;
362
- mimeType?: string | null;
363
- filename?: string | null;
364
- };
340
+ url?: string | null
341
+ mimeType?: string | null
342
+ filename?: string | null
343
+ }
365
344
 
366
345
  function isLocalMediaSrc(src?: string | null): src is string {
367
- return typeof src === "string" && src.startsWith("/api/media/file/");
346
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
368
347
  }
369
348
 
370
- function CardIconMedia({
371
- media,
372
- hovered,
373
- }: {
374
- media: MediaLike;
375
- hovered: boolean;
376
- }) {
377
- const url = media.url ?? "";
378
- const safeUrl = isLocalMediaSrc(url) ? url : "";
379
- const mime = media.mimeType ?? "";
380
- const filename = media.filename ?? "";
349
+ function CardIconMedia({ media, hovered }: { media: MediaLike; hovered: boolean }) {
350
+ const url = media.url ?? ''
351
+ const safeUrl = isLocalMediaSrc(url) ? url : ''
352
+ const mime = media.mimeType ?? ''
353
+ const filename = media.filename ?? ''
381
354
  const isLottie =
382
- mime === "application/json" ||
383
- filename.endsWith(".lottie") ||
384
- (mime === "application/octet-stream" && filename.endsWith(".lottie"));
385
- const isDotLottie = filename.endsWith(".lottie");
386
- const isJson = mime === "application/json" || filename.endsWith(".json");
355
+ mime === 'application/json' ||
356
+ filename.endsWith('.lottie') ||
357
+ (mime === 'application/octet-stream' && filename.endsWith('.lottie'))
358
+ const isDotLottie = filename.endsWith('.lottie')
359
+ const isJson = mime === 'application/json' || filename.endsWith('.json')
387
360
 
388
- if (!safeUrl) return null;
361
+ if (!safeUrl) return null
389
362
 
390
363
  if (isDotLottie || isJson) {
391
364
  return (
@@ -394,13 +367,13 @@ function CardIconMedia({
394
367
  loop
395
368
  autoplay
396
369
  style={{
397
- width: "100%",
398
- height: "100%",
399
- transform: hovered ? "scale(1.08)" : "scale(1)",
400
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
370
+ width: '100%',
371
+ height: '100%',
372
+ transform: hovered ? 'scale(1.08)' : 'scale(1)',
373
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
401
374
  }}
402
375
  />
403
- );
376
+ )
404
377
  }
405
378
 
406
379
  // SVG or raster image
@@ -411,16 +384,16 @@ function CardIconMedia({
411
384
  fill
412
385
  className="object-contain"
413
386
  style={{
414
- transform: hovered ? "scale(1.08)" : "scale(1)",
415
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
387
+ transform: hovered ? 'scale(1.08)' : 'scale(1)',
388
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
416
389
  }}
417
390
  />
418
- );
391
+ )
419
392
  }
420
393
 
421
394
  /* ── Card ────────────────────────────────────────────────────── */
422
395
 
423
- type CardData = { [key: string]: any };
396
+ type CardData = { [key: string]: any }
424
397
 
425
398
  function FeatureCard({
426
399
  card,
@@ -429,47 +402,43 @@ function FeatureCard({
429
402
  totalCards,
430
403
  isMobile,
431
404
  }: {
432
- card: CardData;
433
- index: number;
434
- isLast: boolean;
435
- totalCards: number;
436
- isMobile: boolean;
405
+ card: CardData
406
+ index: number
407
+ isLast: boolean
408
+ totalCards: number
409
+ isMobile: boolean
437
410
  }) {
438
- const [hovered, setHovered] = useState(false);
439
- const colorKey = card.labelColor || "indigo";
440
- const colors = exploreColorMap[colorKey] || exploreColorMap.indigo;
441
- const labelClass = labelColorMap[colorKey] || labelColorMap.indigo;
442
- const FallbackIcon = iconMap[card.iconType || "uiBlocks"] || UIBlocksIcon;
411
+ const [hovered, setHovered] = useState(false)
412
+ const colorKey = card.labelColor || 'indigo'
413
+ const colors = exploreColorMap[colorKey] || exploreColorMap.indigo
414
+ const labelClass = labelColorMap[colorKey] || labelColorMap.indigo
415
+ const FallbackIcon = iconMap[card.iconType || 'uiBlocks'] || UIBlocksIcon
443
416
  const iconMedia =
444
- card.iconMedia &&
445
- typeof card.iconMedia === "object" &&
446
- (card.iconMedia as MediaLike).url
417
+ card.iconMedia && typeof card.iconMedia === 'object' && (card.iconMedia as MediaLike).url
447
418
  ? (card.iconMedia as MediaLike)
448
- : null;
419
+ : null
449
420
 
450
421
  // On mobile, last card is full-width if total is odd
451
- const isFullWidthOnMobile = isMobile && totalCards % 2 !== 0 && isLast;
422
+ const isFullWidthOnMobile = isMobile && totalCards % 2 !== 0 && isLast
452
423
 
453
- let borderStyle: React.CSSProperties = {};
424
+ let borderStyle: React.CSSProperties = {}
454
425
  if (isMobile) {
455
- const isEvenPosition = index % 2 === 0;
456
- const isSecondRowOrLater = index >= 2;
426
+ const isEvenPosition = index % 2 === 0
427
+ const isSecondRowOrLater = index >= 2
457
428
  borderStyle = {
458
429
  borderRight:
459
430
  isEvenPosition && index + 1 < totalCards && !isFullWidthOnMobile
460
- ? "1px solid rgba(255,255,255,0.07)"
461
- : "none",
462
- borderTop: isSecondRowOrLater
463
- ? "1px solid rgba(255,255,255,0.07)"
464
- : "none",
465
- };
431
+ ? '1px solid rgba(255,255,255,0.07)'
432
+ : 'none',
433
+ borderTop: isSecondRowOrLater ? '1px solid rgba(255,255,255,0.07)' : 'none',
434
+ }
466
435
  } else {
467
436
  borderStyle = {
468
- borderRight: !isLast ? "1px solid rgba(255,255,255,0.07)" : "none",
469
- };
437
+ borderRight: !isLast ? '1px solid rgba(255,255,255,0.07)' : 'none',
438
+ }
470
439
  }
471
440
 
472
- const cardClass = `group relative flex flex-col sm:flex-row items-start gap-4 sm:gap-6 px-5 py-7 sm:px-8 sm:py-10${card.href ? " cursor-pointer" : ""}${isFullWidthOnMobile ? "col-span-1" : ""}`;
441
+ const cardClass = `group relative flex flex-col sm:flex-row items-start gap-4 sm:gap-6 px-5 py-7 sm:px-8 sm:py-10${card.href ? ' cursor-pointer' : ''}${isFullWidthOnMobile ? 'col-span-1' : ''}`
473
442
 
474
443
  const innerContent = (
475
444
  <>
@@ -487,8 +456,8 @@ function FeatureCard({
487
456
  style={
488
457
  !iconMedia
489
458
  ? {
490
- transform: hovered ? "scale(1.08)" : "scale(1)",
491
- transition: "transform 0.4s cubic-bezier(0.34,1.56,0.64,1)",
459
+ transform: hovered ? 'scale(1.08)' : 'scale(1)',
460
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
492
461
  }
493
462
  : undefined
494
463
  }
@@ -504,7 +473,7 @@ function FeatureCard({
504
473
  <div className="relative flex flex-col gap-2 pt-1">
505
474
  <span
506
475
  className={`text-sm font-bold tracking-[0.15em] uppercase ${labelClass} transition-all duration-300`}
507
- style={{ letterSpacing: hovered ? "0.2em" : "0.15em" }}
476
+ style={{ letterSpacing: hovered ? '0.2em' : '0.15em' }}
508
477
  >
509
478
  {card.label}
510
479
  </span>
@@ -519,15 +488,15 @@ function FeatureCard({
519
488
  <ArrowRight
520
489
  size={14}
521
490
  style={{
522
- transform: hovered ? "translateX(4px)" : "translateX(0)",
523
- transition: "transform 0.3s ease",
491
+ transform: hovered ? 'translateX(4px)' : 'translateX(0)',
492
+ transition: 'transform 0.3s ease',
524
493
  }}
525
494
  />
526
495
  </span>
527
496
  )}
528
497
  </div>
529
498
  </>
530
- );
499
+ )
531
500
 
532
501
  if (card.href) {
533
502
  return (
@@ -540,7 +509,7 @@ function FeatureCard({
540
509
  >
541
510
  {innerContent}
542
511
  </Link>
543
- );
512
+ )
544
513
  }
545
514
 
546
515
  return (
@@ -552,12 +521,12 @@ function FeatureCard({
552
521
  >
553
522
  {innerContent}
554
523
  </div>
555
- );
524
+ )
556
525
  }
557
526
 
558
527
  /* ── Block ───────────────────────────────────────────────────── */
559
528
 
560
- export type DDFeatStripProps = Record<string, any>;
529
+ export type DDFeatStripProps = Record<string, any>
561
530
 
562
531
  export const DDFeatStrip: React.FC<DDFeatStripProps> = ({
563
532
  cards,
@@ -565,27 +534,28 @@ export const DDFeatStrip: React.FC<DDFeatStripProps> = ({
565
534
  sectionId,
566
535
  customCSS,
567
536
  }) => {
568
- if (!cards || cards.length === 0) return null;
537
+ if (!cards || cards.length === 0) return null
569
538
 
570
- const cardItems = cards as CardData[];
571
- const [isMobile, setIsMobile] = useState(false);
539
+ const cardItems = cards as CardData[]
540
+ const [isMobile, setIsMobile] = useState(false)
572
541
 
573
542
  useEffect(() => {
574
- const check = () => setIsMobile(window.innerWidth < 768);
575
- check();
576
- window.addEventListener("resize", check);
577
- return () => window.removeEventListener("resize", check);
578
- }, []);
543
+ const check = () => setIsMobile(window.innerWidth < 768)
544
+ check()
545
+ window.addEventListener('resize', check)
546
+ return () => window.removeEventListener('resize', check)
547
+ }, [])
579
548
 
580
- const totalCards = cardItems.length;
549
+ const totalCards = cardItems.length
581
550
 
582
551
  return (
583
552
  <>
584
553
  {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
585
554
 
586
555
  <div id={sectionId ?? undefined} className="py-24">
556
+ #BlockName-DDFeatStrip
587
557
  <div
588
- className={`w-full bg-stone-100 dark:bg-stone-950 border-t border-b border-white/6 ${containerClass || ""}`}
558
+ className={`w-full bg-stone-100 dark:bg-stone-950 border-t border-b border-white/6 ${containerClass || ''}`}
589
559
  >
590
560
  <div
591
561
  className="grid grid-cols-1"
@@ -611,5 +581,5 @@ export const DDFeatStrip: React.FC<DDFeatStripProps> = ({
611
581
  </div>
612
582
  </div>
613
583
  </>
614
- );
615
- };
584
+ )
585
+ }