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,496 @@
1
+ "use client";
2
+
3
+ import React, { useLayoutEffect, useRef, useState } from "react";
4
+ import Image from "next/image";
5
+ import * as LucideIcons from "lucide-react";
6
+ import { ArrowRight } from "lucide-react";
7
+ import gsap from "gsap";
8
+ import { ScrollTrigger } from "gsap/ScrollTrigger";
9
+ type Media = { url?: string | null; alt?: string | null; [key: string]: any };
10
+
11
+ import RichText from "@/components/RichText";
12
+
13
+ if (typeof window !== "undefined") {
14
+ gsap.registerPlugin(ScrollTrigger);
15
+ }
16
+
17
+ const ACCENTS = {
18
+ lime: { hex: "#84cc16", rgb: "132,204,22" },
19
+ purple: { hex: "#9803e2", rgb: "152,3,226" },
20
+ } as const;
21
+
22
+ type AccentVariant = keyof typeof ACCENTS;
23
+
24
+ function isLocalImageSrc(src?: string | null): src is string {
25
+ return typeof src === "string" && src.startsWith("/api/media/file/");
26
+ }
27
+
28
+ function DynamicIcon({
29
+ name,
30
+ size = 22,
31
+ ...props
32
+ }: { name?: string | null } & Omit<LucideIcons.LucideProps, "name">) {
33
+ if (!name) return null;
34
+
35
+ const Icon = (
36
+ LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>
37
+ )[name];
38
+
39
+ if (!Icon) return null;
40
+
41
+ return <Icon size={size} {...props} />;
42
+ }
43
+
44
+ interface TagItem {
45
+ id?: string | null;
46
+ tag: string;
47
+ }
48
+
49
+ interface CardItem {
50
+ id?: string | null;
51
+ iconName?: string | null;
52
+ number?: string | null;
53
+ title?: string | null;
54
+ content?: unknown;
55
+ statValue?: string | null;
56
+ statLabel?: string | null;
57
+ tags?: TagItem[] | null;
58
+ ctaLabel?: string | null;
59
+ ctaHref?: string | null;
60
+ accentVariant?: AccentVariant | null;
61
+ image?: Media | string | null;
62
+ }
63
+
64
+ interface DDStackCardsProps {
65
+ cards?: CardItem[] | null;
66
+
67
+ cardOffset?: number | null;
68
+ initialStickyTop?: number | null;
69
+ depthZ?: number | null;
70
+ scaleStep?: number | null;
71
+
72
+ cardOffsetTablet?: number | null;
73
+ initialStickyTopTablet?: number | null;
74
+ depthZTablet?: number | null;
75
+ scaleStepTablet?: number | null;
76
+
77
+ cardOffsetMobile?: number | null;
78
+ initialStickyTopMobile?: number | null;
79
+ depthZMobile?: number | null;
80
+ scaleStepMobile?: number | null;
81
+
82
+ sectionId?: string | null;
83
+ [key: string]: unknown;
84
+ }
85
+
86
+ type ResponsiveStackValues = {
87
+ cardOffset: number;
88
+ initialStickyTop: number;
89
+ depthZ: number;
90
+ scaleStep: number;
91
+ };
92
+
93
+ function getResponsiveStackValues({
94
+ cardOffset,
95
+ initialStickyTop,
96
+ depthZ,
97
+ scaleStep,
98
+ cardOffsetTablet,
99
+ initialStickyTopTablet,
100
+ depthZTablet,
101
+ scaleStepTablet,
102
+ cardOffsetMobile,
103
+ initialStickyTopMobile,
104
+ depthZMobile,
105
+ scaleStepMobile,
106
+ }: DDStackCardsProps): ResponsiveStackValues {
107
+ const desktopValues = {
108
+ cardOffset: typeof cardOffset === "number" ? cardOffset : 14,
109
+ initialStickyTop:
110
+ typeof initialStickyTop === "number" ? initialStickyTop : 0,
111
+ depthZ: typeof depthZ === "number" ? depthZ : 40,
112
+ scaleStep: typeof scaleStep === "number" ? scaleStep : 0.18,
113
+ };
114
+
115
+ if (typeof window === "undefined") return desktopValues;
116
+
117
+ const tabletValues = {
118
+ cardOffset:
119
+ typeof cardOffsetTablet === "number"
120
+ ? cardOffsetTablet
121
+ : Math.max(8, desktopValues.cardOffset * 0.75),
122
+ initialStickyTop:
123
+ typeof initialStickyTopTablet === "number"
124
+ ? initialStickyTopTablet
125
+ : desktopValues.initialStickyTop,
126
+ depthZ:
127
+ typeof depthZTablet === "number"
128
+ ? depthZTablet
129
+ : Math.max(16, desktopValues.depthZ * 0.65),
130
+ scaleStep:
131
+ typeof scaleStepTablet === "number"
132
+ ? scaleStepTablet
133
+ : Math.min(desktopValues.scaleStep, 0.08),
134
+ };
135
+
136
+ const mobileValues = {
137
+ cardOffset:
138
+ typeof cardOffsetMobile === "number"
139
+ ? cardOffsetMobile
140
+ : Math.max(6, desktopValues.cardOffset * 0.5),
141
+ initialStickyTop:
142
+ typeof initialStickyTopMobile === "number"
143
+ ? initialStickyTopMobile
144
+ : desktopValues.initialStickyTop,
145
+ depthZ:
146
+ typeof depthZMobile === "number"
147
+ ? depthZMobile
148
+ : Math.max(8, desktopValues.depthZ * 0.35),
149
+ scaleStep:
150
+ typeof scaleStepMobile === "number"
151
+ ? scaleStepMobile
152
+ : Math.min(desktopValues.scaleStep, 0.045),
153
+ };
154
+
155
+ if (window.matchMedia("(max-width: 767px)").matches) return mobileValues;
156
+ if (window.matchMedia("(max-width: 1023px)").matches) return tabletValues;
157
+
158
+ return desktopValues;
159
+ }
160
+
161
+ export function DDStackCards(props: DDStackCardsProps) {
162
+ const { cards, sectionId } = props;
163
+
164
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
165
+ const cardRefs = useRef<(HTMLElement | null)[]>([]);
166
+ const [layoutValues, setLayoutValues] = useState<ResponsiveStackValues>(
167
+ () => ({
168
+ cardOffset: typeof props.cardOffset === "number" ? props.cardOffset : 14,
169
+ initialStickyTop:
170
+ typeof props.initialStickyTop === "number" ? props.initialStickyTop : 0,
171
+ depthZ: typeof props.depthZ === "number" ? props.depthZ : 40,
172
+ scaleStep: typeof props.scaleStep === "number" ? props.scaleStep : 0.18,
173
+ }),
174
+ );
175
+
176
+ useLayoutEffect(() => {
177
+ const updateValues = () => {
178
+ setLayoutValues(getResponsiveStackValues(props));
179
+ };
180
+
181
+ updateValues();
182
+
183
+ window.addEventListener("resize", updateValues);
184
+
185
+ return () => {
186
+ window.removeEventListener("resize", updateValues);
187
+ };
188
+ }, [
189
+ props.cardOffset,
190
+ props.initialStickyTop,
191
+ props.depthZ,
192
+ props.scaleStep,
193
+ props.cardOffsetTablet,
194
+ props.initialStickyTopTablet,
195
+ props.depthZTablet,
196
+ props.scaleStepTablet,
197
+ props.cardOffsetMobile,
198
+ props.initialStickyTopMobile,
199
+ props.depthZMobile,
200
+ props.scaleStepMobile,
201
+ ]);
202
+
203
+ useLayoutEffect(() => {
204
+ const timer = window.setTimeout(() => {
205
+ const wrapper = wrapperRef.current;
206
+ const cardEls = cardRefs.current.filter(Boolean) as HTMLDivElement[];
207
+
208
+ if (!wrapper || !cardEls.length) return;
209
+
210
+ const ctx = gsap.context(() => {
211
+ gsap.set(wrapper, {
212
+ autoAlpha: 1,
213
+ willChange: "opacity",
214
+ });
215
+
216
+ gsap.set(cardEls, {
217
+ transformPerspective: 1200,
218
+ transformOrigin: "top center",
219
+ });
220
+
221
+ cardEls.forEach((card, i) => {
222
+ for (let j = i + 1; j < cardEls.length; j++) {
223
+ const step = j - i;
224
+ const prevStep = step - 1;
225
+ const triggerStickyTop =
226
+ layoutValues.initialStickyTop + (j - 1) * layoutValues.cardOffset;
227
+
228
+ gsap.fromTo(
229
+ card,
230
+ {
231
+ scale: 1 - prevStep * layoutValues.scaleStep,
232
+ z: -prevStep * layoutValues.depthZ,
233
+ },
234
+ {
235
+ scale: 1 - step * layoutValues.scaleStep,
236
+ z: -step * layoutValues.depthZ,
237
+ force3D: true,
238
+ filter: `brightness(${Math.max(0.65, 1 - step * 0.06)})`,
239
+ borderRadius: "20px",
240
+ immediateRender: false,
241
+ scrollTrigger: {
242
+ trigger: cardEls[j],
243
+ start: `top ${triggerStickyTop + layoutValues.cardOffset}px`,
244
+ end: `top ${triggerStickyTop}px`,
245
+ scrub: true,
246
+ invalidateOnRefresh: true,
247
+ },
248
+ },
249
+ );
250
+ }
251
+ });
252
+
253
+ const lastCard = cardEls[cardEls.length - 1];
254
+
255
+ gsap.to(wrapper, {
256
+ autoAlpha: 0,
257
+ ease: "none",
258
+ scrollTrigger: {
259
+ trigger: lastCard,
260
+ start: "top top",
261
+ end: () => `+=${window.innerHeight * 0.3}`,
262
+ scrub: true,
263
+ invalidateOnRefresh: true,
264
+ },
265
+ });
266
+
267
+ requestAnimationFrame(() => {
268
+ ScrollTrigger.refresh();
269
+ });
270
+ }, wrapper);
271
+
272
+ return () => ctx.revert();
273
+ }, 120);
274
+
275
+ return () => {
276
+ window.clearTimeout(timer);
277
+ };
278
+ }, [
279
+ layoutValues.cardOffset,
280
+ layoutValues.initialStickyTop,
281
+ layoutValues.depthZ,
282
+ layoutValues.scaleStep,
283
+ ]);
284
+
285
+ if (!cards?.length) return null;
286
+
287
+ const totalCards = cards.length;
288
+
289
+ return (
290
+ <section
291
+ ref={wrapperRef}
292
+ className="stack-cards-block relative w-full invert-[0.95] brightness-110 contrast-125 dark:brightness-100 dark:contrast-100 dark:invert-[0]"
293
+ >
294
+ {cards.map((card, i) => {
295
+ const accent = ACCENTS[card.accentVariant ?? "lime"];
296
+ const cardMedia =
297
+ card.image && typeof card.image === "object"
298
+ ? (card.image as Media)
299
+ : null;
300
+
301
+ return (
302
+ <section
303
+ key={card.id ?? i}
304
+ id={i === 0 ? (sectionId ?? undefined) : undefined}
305
+ ref={(el) => {
306
+ cardRefs.current[i] = el;
307
+ }}
308
+ className="stack-card renderblock-container sticky! w-full overflow-hidden px-4 py-16 sm:px-6 lg:py-32"
309
+ style={{
310
+ top: `${layoutValues.initialStickyTop + i * layoutValues.cardOffset}px`,
311
+ zIndex: i + 1,
312
+ willChange: "transform",
313
+ }}
314
+ >
315
+ <div
316
+ className="relative mx-auto w-full max-w-5xl overflow-hidden rounded-2xl"
317
+ style={{
318
+ background: "rgb(8 8 10 / 90%)",
319
+ border: "1px solid rgba(255,255,255,0.07)",
320
+ backdropFilter: "blur(8px)",
321
+ }}
322
+ >
323
+ <div
324
+ className="absolute left-0 right-0 top-0 h-px"
325
+ style={{
326
+ background: `linear-gradient(90deg, transparent, rgba(${accent.rgb},0.5), transparent)`,
327
+ }}
328
+ />
329
+
330
+ <div className="grid min-h-70 grid-cols-[1fr_5.5rem] sm:grid-cols-[1fr_8rem] md:min-h-85 md:grid-cols-[1fr_auto]">
331
+ <div className="stackcard-bg flex flex-col justify-between p-6 md:p-10 lg:p-14">
332
+ <div>
333
+ <div className="mb-6 flex items-start justify-between md:mb-8">
334
+ <div className="flex items-center gap-3 md:gap-4">
335
+ <div
336
+ className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl md:h-12 md:w-12"
337
+ style={{
338
+ background: `rgba(${accent.rgb},0.1)`,
339
+ border: `1px solid rgba(${accent.rgb},0.25)`,
340
+ }}
341
+ >
342
+ <span style={{ color: accent.hex }}>
343
+ <DynamicIcon
344
+ className="shrink-0"
345
+ name={card.iconName}
346
+ size={20}
347
+ />
348
+ </span>
349
+ </div>
350
+
351
+ <div>
352
+ {card.number && (
353
+ <div
354
+ className="mb-0.5 text-[10px] font-black uppercase tracking-widest md:text-xs"
355
+ style={{ color: `rgba(${accent.rgb},0.6)` }}
356
+ >
357
+ {card.number} /{" "}
358
+ {String(totalCards).padStart(2, "0")}
359
+ </div>
360
+ )}
361
+
362
+ {card.title && (
363
+ <h3 className="text-xl font-extrabold tracking-tight text-stone-50/80 md:text-2xl lg:text-3xl">
364
+ {card.title}
365
+ </h3>
366
+ )}
367
+ </div>
368
+ </div>
369
+
370
+ {(card.statValue || card.statLabel) && (
371
+ <div className="hidden flex-col items-end md:flex">
372
+ {card.statValue && (
373
+ <span
374
+ className="text-4xl font-black leading-none"
375
+ style={{ color: accent.hex }}
376
+ >
377
+ {card.statValue}
378
+ </span>
379
+ )}
380
+
381
+ {card.statLabel && (
382
+ <span
383
+ className="mt-1 text-xs"
384
+ style={{ color: "rgba(255,255,255,0.3)" }}
385
+ >
386
+ {card.statLabel}
387
+ </span>
388
+ )}
389
+ </div>
390
+ )}
391
+ </div>
392
+
393
+ {card.content ? (
394
+ <RichText
395
+ data={card.content as any}
396
+ enableProse={false}
397
+ enableGutter={false}
398
+ className="max-w-xl text-sm leading-relaxed text-stone-200 dark:text-stone-200 md:text-base"
399
+ style={{ color: "rgba(255,255,255,0.45)" }}
400
+ />
401
+ ) : null}
402
+ </div>
403
+
404
+ <div className="mt-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center md:mt-8">
405
+ {card.tags && card.tags.length > 0 && (
406
+ <div className="flex flex-wrap gap-2">
407
+ {card.tags.map((tagItem, ti) => (
408
+ <span
409
+ key={tagItem.id ?? ti}
410
+ className="rounded-full px-3 py-1 text-xs font-semibold"
411
+ style={{
412
+ background: `rgba(${accent.rgb},0.07)`,
413
+ border: `1px solid rgba(${accent.rgb},0.18)`,
414
+ color: accent.hex,
415
+ }}
416
+ >
417
+ {tagItem.tag}
418
+ </span>
419
+ ))}
420
+ </div>
421
+ )}
422
+
423
+ {card.ctaLabel && (
424
+ <a
425
+ href={card.ctaHref ?? "#"}
426
+ className="group flex cursor-pointer items-center gap-2 whitespace-nowrap text-xs font-black uppercase tracking-widest"
427
+ style={{ color: accent.hex }}
428
+ >
429
+ {card.ctaLabel}
430
+ <ArrowRight
431
+ size={14}
432
+ className="transition-transform group-hover:translate-x-1"
433
+ style={{ color: accent.hex }}
434
+ />
435
+ </a>
436
+ )}
437
+ </div>
438
+ </div>
439
+
440
+ <div
441
+ className="relative flex w-22 items-center justify-center overflow-hidden sm:w-32 md:w-48 lg:w-64 xl:w-80"
442
+ style={{
443
+ borderLeft: `1px solid rgba(${accent.rgb},0.08)`,
444
+ background: `linear-gradient(135deg, rgba(${accent.rgb},0.04) 0%, transparent 100%)`,
445
+ }}
446
+ >
447
+ <div
448
+ className="absolute inset-0"
449
+ style={{
450
+ background: `radial-gradient(ellipse at 50% 50%, rgba(${accent.rgb},0.25) 0%, transparent 70%)`,
451
+ }}
452
+ />
453
+
454
+ {isLocalImageSrc(cardMedia?.url) ? (
455
+ <Image
456
+ src={cardMedia.url}
457
+ alt={cardMedia.alt ?? card.title ?? ""}
458
+ fill
459
+ className="relative z-10 object-cover opacity-80"
460
+ sizes="(max-width: 640px) 88px, (max-width: 768px) 128px, (max-width: 1280px) 192px, 320px"
461
+ />
462
+ ) : (
463
+ <>
464
+ <span
465
+ className="relative z-10"
466
+ style={{ color: `rgba(${accent.rgb},1)` }}
467
+ >
468
+ <DynamicIcon
469
+ className="shrink-0"
470
+ name={card.iconName}
471
+ size={96}
472
+ />
473
+ </span>
474
+
475
+ {card.number && (
476
+ <div
477
+ className="absolute bottom-4 right-6 select-none font-black leading-none"
478
+ style={{
479
+ fontSize: "80px",
480
+ color: `rgba(${accent.rgb},0.2)`,
481
+ }}
482
+ >
483
+ {card.number}
484
+ </div>
485
+ )}
486
+ </>
487
+ )}
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </section>
492
+ );
493
+ })}
494
+ </section>
495
+ );
496
+ }