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,320 +1,294 @@
1
- "use client";
2
- import React, { useState } from "react";
3
- // import Link from 'next/link'
4
- import Image from "next/image";
5
- type Media = { url?: string | null; alt?: string | null; [key: string]: any };
6
-
7
- // ─── Placeholder SVG (same as Card2Item) ──────────────────────────────────────
8
- const PlaceholderSVG = () => (
9
- <svg
10
- width="57"
11
- height="63"
12
- viewBox="0 0 57 63"
13
- fill="none"
14
- xmlns="http://www.w3.org/2000/svg"
15
- style={{ opacity: 0.75 }}
16
- aria-hidden="true"
17
- >
18
- <svg
19
- width="50"
20
- height="50"
21
- viewBox="0 0 50 50"
22
- fill="none"
23
- xmlns="http://www.w3.org/2000/svg"
24
- >
25
- <g clipPath="url(#clip0_swb_placeholder)">
26
- <g filter="url(#filter0_swb)">
27
- <path
28
- fillRule="evenodd"
29
- clipRule="evenodd"
30
- d="M13.3637 45.1903C14.5322 45.1736 15.6117 44.9574 16.4837 44.2476C16.4695 44.2362 16.4579 44.2256 16.4484 44.2169C16.4324 44.2022 16.4219 44.1927 16.4155 44.1943C15.3937 44.3995 14.3289 44.2994 13.2637 44.1993L13.2244 44.1956C12.7931 44.155 12.3636 44.1337 11.9391 44.1604C11.2208 44.2048 10.6076 44.4381 10.1212 44.901C9.35358 45.6313 8.85477 46.5077 8.82298 47.6979C8.37839 46.9398 8.10265 46.2052 7.95857 45.4475C7.71394 44.1613 7.70605 42.9033 8.0205 41.6964C8.07955 41.4684 8.07162 41.2377 8.00801 40.9983C7.69705 39.8256 7.74132 38.7159 8.17589 37.6744C8.20586 37.4626C8.1563 37.4133 8.08878 37.4264 8.02462 37.4387C7.96983 37.4478C6.7989 37.5726 5.88891 38.4878 5.79809 39.6298C5.7969 39.644 5.79566 39.6582 5.79344 39.6724C5.78678 39.7153 5.77121 39.759 5.72194 39.8052C5.62218 39.3614C5.36356 37.3127 5.95812 35.6806 7.67 34.6321C8.23622 34.285 8.8845 34.0598 9.57736 33.9423C9.78912 33.931 9.78241 33.836C9.77959 33.7972 9.75968 33.7734 9.732 33.7572C9.58299 33.7082C8.15118 33.175 6.79313 33.1071 5.55339 33.7785C5.43374 33.7787C5.52845 33.6446 5.57262 33.5804C5.62594 33.5028 5.67631 33.4296 5.72838 33.3599C5.76893 33.3057 5.81053 33.2535 5.85499 33.2039C6.84014 32.1032 8.02016 31.2683 9.60154 30.9985C10.1231 30.9096 10.6637 30.9136 11.209 30.9538C11.3941 30.9703C11.4499 30.8448 11.3704 30.8016 11.2989 30.7627C10.254 30.1131 9.17843 29.7292 8.07517 29.4331C7.79202 29.2986C7.89144 29.2948 7.99076 29.2901 8.09012 29.2854C8.81056 29.2629C9.85296 29.3009C10.4616 29.3587C10.9307 29.4191C11.6709 29.5461C12.0657 29.6184C11.5415 29.089 11.1555 28.5815 10.8177 28.0437C10.4265 27.3547H8.1736V10.4696H15.065C16.8543 10.4696 18.4341 10.8314 19.8043 11.5551C21.1745 12.2627 22.2385 13.2597 22.9961 14.5462C23.7538 15.8166 24.1326 17.2719 24.1326 18.9122C24.1326 20.5525 23.7538 22.0158 22.9961 23.3023C22.2385 24.5727 21.1745 25.5697 19.8043 26.2934C18.4341 27.001 16.8543 27.3547 15.065 27.3547H11.1365C12.3905 28.3263 13.8202 28.8632 15.373 29.0576C16.8259 29.2389 18.226 29.1241 19.5697 28.7671C19.9394 28.2888C19.8863 27.6372 20.466 27.1227 21.1627 27.1708C21.4095 27.1877 21.5266 27.0999 21.6496 26.9498C22.457 25.9638 22.9261 25.3919 23.3775 24.8418C23.8791 24.2305 24.3585 23.6461 25.2554 22.5501C25.6084 22.1507 25.6501 22.0102 25.5938 21.7874C25.3988 21.0133 25.8471 20.384 26.6335 20.3236C26.8273 20.3086 26.832 20.2019 26.8371 20.0866C26.8902 19.2076 26.7543 18.3595 26.475 17.5191C25.6439 15.0202 24.0979 12.9489 21.8591 11.2366C22.5596 11.2381 23.2207 11.3835 23.8778 11.5788C24.5425 11.7759 25.1871 12.0454 25.8956 12.4235C25.1475 11.1113 24.6409 9.83396 24.4762 8.39412C24.5858 8.54905 24.6921 8.70246 24.7974 8.85436C25.2719 9.53906 25.7248 10.1926 26.3586 10.8132C25.9123 7.80637 27.1174 5.82188 29.6223 4.596C28.8856 5.53527 28.695 6.64612 28.9036 7.90196C30.1753 5.1413 32.5663 4.65657 35.6205 5.50789C34.4507 5.52434 33.3712 5.74046 32.4992 6.45034C32.5134 6.46172 32.525 6.47238 32.5346 6.48112C32.5619 6.4985 32.5674 6.50362C33.5891 6.29836 34.6539 6.39843 35.7189 6.49854C36.1897 6.54291 36.6192 6.56418 37.0438 6.53758C37.7621 6.49305 38.3752 6.25979 38.8617 5.79694C39.6293 5.06658 40.1281 4.19024 40.1599 3C40.6045 3.75805 40.8802 4.4927 41.0243 5.25039C41.2689 6.53659 41.2769 7.7946 40.9624 9.00153C40.9033 9.22947 40.9113 9.46019 40.9749 9.69956C41.2858 10.8723 41.2415 11.982 40.807 13.0235C40.777 13.2353 40.8127 13.2708 40.9041 13.2683C41.0479 13.265 41.013 13.2501C42.184 13.1253 43.094 12.2101 43.1848 11.0681C43.2609 10.8927C43.3607 11.3365C43.6193 13.3852 43.0247 15.0173 41.3129 16.0658C40.7466 16.4129 40.0984 16.6381 39.4055 16.7555C39.2005 16.7602 39.2744 16.7634 39.1937 16.7669C39.2005 16.8619C39.3433 16.9732C39.3999 16.9897C40.8317 17.5229 42.1897 17.5908 43.4295 16.9194C43.5491 16.9192C43.4705 17.0301C43.4101 17.1176C43.2961 17.2812C43.2368 17.3614C43.2143 17.3907C43.1863 17.4261 43.1577 17.4606 43.1279 17.494C42.1427 18.5947 40.9627 19.4296 39.3813 19.6994C38.8598 19.7883 38.3192 19.7843 37.7739 19.7441C37.5943 19.7253C37.5562 19.7693C37.5456 19.8073 37.5562 19.837 37.5777 19.8617C37.6526 19.9178C37.698 19.9429C37.7302 19.9614C38.7289 20.5848 39.8044 20.9687 40.9077 21.2648C40.9526 21.277C41.0517 21.3112C41.1586 21.3734C41.1908 21.3993C40.8927 21.4125C40.1723 21.435C39.1299 21.397C38.6784 21.3564C38.0521 21.2788C37.8735 21.2516C37.7718 21.2348C37.316 21.1525C36.9171 21.0794C37.4414 21.6089 37.8274 22.1164 38.1651 22.6542C38.3801 22.9966 38.5738 23.3457 38.725 23.7124H40.9042V40.8532H34.0128C32.2235 40.8532 30.6437 40.4859 29.2735 39.7513C27.9033 39.033 26.8394 38.0209 26.0817 36.715C25.3241 35.4253 24.9452 33.9479 24.9452 32.2828C24.9452 30.6177 25.3241 29.1322 26.0817 27.8262C26.8394 26.5366 27.9033 25.5245 29.2735 24.7899C30.6437 24.0716 32.2235 23.7124 34.0128 23.7124H38.2908C36.9342 22.5055 35.3493 21.8581 33.6098 21.6403C32.1569 21.459 30.7569 21.5738 29.4131 21.9308C29.0435 22.4091C29.0966 23.0607 28.5169 23.5752 27.8202 23.5271C27.5734 23.5102 27.4562 23.598 27.3333 23.748C26.894 24.2846 26.5549 24.6984 26.2641 25.0529C25.6054 25.8561C25.1038 26.4674 24.6244 27.0518 23.7275 28.1477C23.6115 28.2799 23.5472 28.3368C23.3891 28.9105C23.584 29.6846 23.1357 30.3139 22.3494 30.3743C22.1509 30.4961 22.1442 30.646C22.0927 31.4903 22.2285 32.3384 22.5079 33.1787C23.339 35.6777 24.885 37.749 27.1238 39.4613C26.4233 39.4598 25.7621 39.3144 25.1051 39.1191C24.4404 38.922 23.7957 38.6525 23.0873 38.2744C23.8353 39.5866 24.342 40.8639 24.5066 42.3038C24.3971 42.1488 24.2907 41.9954 24.1855 41.8435C23.711 41.1588 23.258 40.5053 22.6242 39.8847C23.0706 42.8915 21.8655 44.876 19.3606 46.1019C20.0972 45.1626 20.2878 44.0518 20.0793 42.7959C18.8076 45.5566 16.4166 46.0413 13.3624 45.19L13.3637 45.1903ZM14.9683 25.811C16.4513 25.811 17.749 25.5215 18.8613 24.9426C19.9736 24.3476 20.836 23.5355 21.4486 22.5063C22.0612 21.461 22.3674 20.263 22.3674 18.9122C22.3674 17.5614 22.0612 16.3714 21.4486 15.3422C20.836 14.2969 19.9736 13.4848 18.8613 12.9059C17.749 12.3109 16.4513 12.0134 14.9683 12.0134H9.96294V25.811H14.9683ZM30.2165 26.1611C31.3288 25.5734 32.6265 25.2796 34.1096 25.2796H39.1149V39.2861H34.1096C32.6265 39.2861 31.3288 38.9841 30.2165 38.3801C29.1042 37.7924 28.2418 36.968 27.6292 35.9069C27.0167 34.8621 26.7104 33.6541 26.7104 32.2828C26.7104 30.9116 27.0167 29.6954 27.6292 28.6343C28.2418 27.5895 29.1042 26.7651 30.2165 26.1611Z"
31
- fill="#F0F8FF"
32
- />
33
- </g>
34
- </g>
35
- <defs>
36
- <filter
37
- id="filter0_swb"
38
- x="1"
39
- y="-1"
40
- width="48"
41
- height="53"
42
- filterUnits="userSpaceOnUse"
43
- colorInterpolationFilters="sRGB"
44
- >
45
- <feFlood floodOpacity="0" result="BackgroundImageFix" />
46
- <feColorMatrix
47
- in="SourceAlpha"
48
- type="matrix"
49
- values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
50
- result="hardAlpha"
51
- />
52
- <feOffset />
53
- <feGaussianBlur stdDeviation="2" />
54
- <feComposite in2="hardAlpha" operator="out" />
55
- <feColorMatrix
56
- type="matrix"
57
- values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
58
- />
59
- <feBlend
60
- mode="normal"
61
- in2="BackgroundImageFix"
62
- result="effect1_dropShadow"
63
- />
64
- <feBlend
65
- mode="normal"
66
- in="SourceGraphic"
67
- in2="effect1_dropShadow"
68
- result="shape"
69
- />
70
- </filter>
71
- <clipPath id="clip0_swb_placeholder">
72
- <rect width="50" height="50" fill="white" />
73
- </clipPath>
74
- </defs>
75
- </svg>
76
- </svg>
77
- );
78
-
79
- // ─── Project type (mirrors StudioWorkBlock project row) ───────────────────────
80
- export type DDWorkBProps = Record<string, any>;
81
-
82
- type Category = { id?: string | null; label?: string | null };
83
- type ProjectTag = { id?: string | null; tag?: string | null };
84
- type Project = {
85
- id?: string | null;
86
- title?: string | null;
87
- category?: string | null;
88
- description?: string | null;
89
- highlight?: string | null;
90
- link?: string | null;
91
- image?: Media | string | null;
92
- imageFallbackUrl?: string | null;
93
- href?: string | null;
94
- tags?: ProjectTag[] | null;
95
- [key: string]: any;
96
- };
97
-
98
- // ─── Single project card in Card2Item style ───────────────────────────────────
99
- function isLocalImageSrc(src?: string | null): src is string {
100
- return typeof src === "string" && src.startsWith("/api/media/file/");
101
- }
102
-
103
- function ProjectCard({ project }: { project: Project }) {
104
- const title = project.title ?? "";
105
- const category = project.category ?? "";
106
- const description = project.description ?? "";
107
- const highlight = project.highlight ?? "";
108
- const link = project.link ?? "";
109
- const imgSrc =
110
- project.image && typeof project.image === "object"
111
- ? ((project.image as Media).url ?? project.imageFallbackUrl ?? "")
112
- : (project.imageFallbackUrl ?? "");
113
- const safeImgSrc = isLocalImageSrc(imgSrc) ? imgSrc : null;
114
-
115
- const card = (
116
- <article
117
- // data-theme="dark"
118
- className="article-card max-w-full! rounded-[2.5rem] w-full border border-border overflow-clip bgTransBlur hover:cursor-pointer h-full relative shadow-md group hover:shadow-2xl hover:-translate-y-1 transition-all duration-300"
119
- style={{ cornerShape: "squircle" } as React.CSSProperties}
120
- >
121
- {/* Media area */}
122
- <div
123
- className="card-media card-bottom-lit relative w-full overflow-hidden border border-border rounded-[2.5rem]"
124
- style={{ cornerShape: "squircle" } as React.CSSProperties}
125
- >
126
- <div
127
- className="w-full bg-linear-to-br from-violet-950 to-indigo-950 flex items-center justify-center relative"
128
- style={{ aspectRatio: "16/9", minHeight: 209 }}
129
- >
130
- {safeImgSrc ? (
131
- <Image
132
- src={safeImgSrc}
133
- alt={title}
134
- fill
135
- sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
136
- className="w-full h-full object-cover object-top absolute inset-0 rounded-3xl!"
137
- style={{ cornerShape: "squircle" } as React.CSSProperties}
138
- />
139
- ) : (
140
- <PlaceholderSVG />
141
- )}
142
-
143
- {/* Year badge */}
144
- {project.year && (
145
- <div className="absolute top-2 right-2 z-20 border bg-stone-900/70 border-stone-700 text-stone-300 text-xs font-mono px-2.5 py-1 rounded-full">
146
- {project.year}
147
- </div>
148
- )}
149
-
150
- {/* Category badge */}
151
- {category && (
152
- <div className="absolute top-2 left-2 z-20 border bg-lime-500/50 border-lime-500 text-stone-50 text-xs font-semibold px-2.5 py-1 rounded-full">
153
- {category}
154
- </div>
155
- )}
156
-
157
- {/* Title overlay */}
158
- <div className="card-title-overlay absolute bottom-4 left-4 right-4 z-20">
159
- <h3 className="text-sm font-semibold text-stone-50 dark:text-stone-50 drop-shadow-lg">
160
- {title}
161
- </h3>
162
- </div>
163
-
164
- {/* Bottom gradient */}
165
- <div
166
- aria-hidden="true"
167
- className="absolute pointer-events-none left-0 bottom-0 w-full h-1/2 bg-linear-to-t from-black to-transparent"
168
- />
169
- </div>
170
- </div>
171
-
172
- {/* Card body */}
173
- <div className="card-content-wrapper relative -z-1 mx-3! -mt-1 bg-blend-darken card-bottom-lit bg-stone-100 dark:bg-stone-900/50 backdrop-blur-lg px-4 pt-4 pb-8">
174
- <div className="card-content">
175
- <div className="flex items-center gap-2 mb-2 flex-wrap scale-90 origin-top-left">
176
- {category && (
177
- <span className="text-[11.5px] bg-stone-300/50 dark:bg-stone-800 text-stone-500 dark:text-stone-400 border border-stone-300 dark:border-stone-700 px-2.5 py-1 rounded-full">
178
- {category}
179
- </span>
180
- )}
181
- </div>
182
-
183
- <h4 className="text-md font-semibold text-stone-900 dark:text-stone-100 mb-1">
184
- {title}
185
- </h4>
186
-
187
- {description && (
188
- <p className="text-sm text-stone-400 leading-relaxed line-clamp-2 mb-4">
189
- {description}
190
- </p>
191
- )}
192
-
193
- {/* Tags */}
194
- {project.tags && project.tags.length > 0 && (
195
- <div className="flex flex-wrap gap-1.5 mb-4">
196
- {project.tags.slice(0, 3).map((t, ti) => (
197
- <span
198
- key={t.id ?? ti}
199
- className="text-[11.5px] bg-stone-300/50 dark:bg-stone-800 text-stone-500 dark:text-stone-400 border border-stone-300 dark:border-stone-700 px-2.5 py-1 rounded-full"
200
- >
201
- {t.tag}
202
- </span>
203
- ))}
204
- </div>
205
- )}
206
-
207
- <div className="flex items-center justify-between">
208
- {highlight ? (
209
- <span className="text-[11.5px] text-indigo-600 darK:text-lime-400 font-medium">
210
- {highlight}
211
- </span>
212
- ) : (
213
- <span />
214
- )}
215
- <span
216
- className="inline-flex items-center gap-1.5 bg-indigo-700 text-stone-50 text-xs font-semibold px-3 py-1.5 rounded-lg group-hover:bg-indigo-600 group-hover:scale-[1.05] group-hover:shadow-xl transition-all duration-150 whitespace-nowrap"
217
- style={{ cornerShape: "squircle" } as React.CSSProperties}
218
- >
219
- {link ? "View Project" : "View Details"}
220
- </span>
221
- </div>
222
- </div>
223
- </div>
224
- </article>
225
- );
226
-
227
- if (link) {
228
- return (
229
- <article className="card-item h-full">
230
- <a
231
- href={link}
232
- target="_blank"
233
- rel="noopener noreferrer"
234
- className="block h-full"
235
- >
236
- {card}
237
- </a>
238
- </article>
239
- );
240
- }
241
-
242
- return <article className="card-item h-full">{card}</article>;
243
- }
244
-
245
- // ─── Main Block ───────────────────────────────────────────────────────────────
246
- export const DDWorkB: React.FC<DDWorkBProps> = ({
247
- eyebrow,
248
- headlineMain,
249
- headlineAccent,
250
- categories,
251
- projects,
252
- sectionId,
253
- customCSS,
254
- }) => {
255
- const categoryItems = (categories ?? []) as Category[];
256
- const projectItems = (projects ?? []) as Project[];
257
- const allLabel = categoryItems[0]?.label ?? "All";
258
- const [activeCategory, setActiveCategory] = useState(allLabel);
259
-
260
- const filtered =
261
- activeCategory === allLabel
262
- ? projectItems
263
- : projectItems.filter((p) => p.category === activeCategory);
264
-
265
- return (
266
- <>
267
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
268
-
269
- <div id={sectionId ?? "work"} className="py-24">
270
- <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
271
- {/* Header */}
272
- <div className="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-12">
273
- <div>
274
- {eyebrow && (
275
- <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
276
- {eyebrow}
277
- </p>
278
- )}
279
- <h2 className="text-3d text-4xl md:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
280
- {headlineMain}
281
- <br />
282
- <span className="text-stone-400">{headlineAccent}</span>
283
- </h2>
284
- </div>
285
-
286
- {/* Filter tabs */}
287
- {categoryItems.length > 1 && (
288
- <div className="flex flex-wrap gap-2">
289
- {categoryItems.map((cat, i) => {
290
- const categoryLabel = cat.label ?? "";
291
-
292
- return (
293
- <button
294
- key={cat.id ?? i}
295
- onClick={() => setActiveCategory(categoryLabel)}
296
- className={`px-4 py-1.5 rounded-full text-xs font-medium transition-colors cursor-pointer whitespace-nowrap ${
297
- activeCategory === categoryLabel
298
- ? "border-indigo-500 bg-indigo-800 text-stone-200 shadow-lg shadow-blue-500/20"
299
- : "border-stone-300 dark:border-stone-700 bg-stone-50/80 dark:bg-stone-900/80 text-stone-600 dark:text-stone-400 hover:border-stone-400 dark:hover:border-stone-500 hover:bg-stone-100 dark:hover:bg-stone-800 hover:text-stone-900 dark:hover:text-stone-50"
300
- }`}
301
- >
302
- {categoryLabel}
303
- </button>
304
- );
305
- })}
306
- </div>
307
- )}
308
- </div>
309
-
310
- {/* Projects Grid */}
311
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 py-4">
312
- {filtered.map((project, i) => (
313
- <ProjectCard key={project.id ?? i} project={project} />
314
- ))}
315
- </div>
316
- </div>
317
- </div>
318
- </>
319
- );
320
- };
1
+ 'use client'
2
+ import React, { useState } from 'react'
3
+ // import Link from 'next/link'
4
+ import Image from 'next/image'
5
+ type Media = { url?: string | null; alt?: string | null; [key: string]: any }
6
+
7
+ // ─── Placeholder SVG (same as Card2Item) ──────────────────────────────────────
8
+ const PlaceholderSVG = () => (
9
+ <svg
10
+ width="57"
11
+ height="63"
12
+ viewBox="0 0 57 63"
13
+ fill="none"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ style={{ opacity: 0.75 }}
16
+ aria-hidden="true"
17
+ >
18
+ <svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
19
+ <g clipPath="url(#clip0_swb_placeholder)">
20
+ <g filter="url(#filter0_swb)">
21
+ <path
22
+ fillRule="evenodd"
23
+ clipRule="evenodd"
24
+ d="M13.3637 45.1903C14.5322 45.1736 15.6117 44.9574 16.4837 44.2476C16.4695 44.2362 16.4579 44.2256 16.4484 44.2169C16.4324 44.2022 16.4219 44.1927 16.4155 44.1943C15.3937 44.3995 14.3289 44.2994 13.2637 44.1993L13.2244 44.1956C12.7931 44.155 12.3636 44.1337 11.9391 44.1604C11.2208 44.2048 10.6076 44.4381 10.1212 44.901C9.35358 45.6313 8.85477 46.5077 8.82298 47.6979C8.37839 46.9398 8.10265 46.2052 7.95857 45.4475C7.71394 44.1613 7.70605 42.9033 8.0205 41.6964C8.07955 41.4684 8.07162 41.2377 8.00801 40.9983C7.69705 39.8256 7.74132 38.7159 8.17589 37.6744C8.20586 37.4626C8.1563 37.4133 8.08878 37.4264 8.02462 37.4387C7.96983 37.4478C6.7989 37.5726 5.88891 38.4878 5.79809 39.6298C5.7969 39.644 5.79566 39.6582 5.79344 39.6724C5.78678 39.7153 5.77121 39.759 5.72194 39.8052C5.62218 39.3614C5.36356 37.3127 5.95812 35.6806 7.67 34.6321C8.23622 34.285 8.8845 34.0598 9.57736 33.9423C9.78912 33.931 9.78241 33.836C9.77959 33.7972 9.75968 33.7734 9.732 33.7572C9.58299 33.7082C8.15118 33.175 6.79313 33.1071 5.55339 33.7785C5.43374 33.7787C5.52845 33.6446 5.57262 33.5804C5.62594 33.5028 5.67631 33.4296 5.72838 33.3599C5.76893 33.3057 5.81053 33.2535 5.85499 33.2039C6.84014 32.1032 8.02016 31.2683 9.60154 30.9985C10.1231 30.9096 10.6637 30.9136 11.209 30.9538C11.3941 30.9703C11.4499 30.8448 11.3704 30.8016 11.2989 30.7627C10.254 30.1131 9.17843 29.7292 8.07517 29.4331C7.79202 29.2986C7.89144 29.2948 7.99076 29.2901 8.09012 29.2854C8.81056 29.2629C9.85296 29.3009C10.4616 29.3587C10.9307 29.4191C11.6709 29.5461C12.0657 29.6184C11.5415 29.089 11.1555 28.5815 10.8177 28.0437C10.4265 27.3547H8.1736V10.4696H15.065C16.8543 10.4696 18.4341 10.8314 19.8043 11.5551C21.1745 12.2627 22.2385 13.2597 22.9961 14.5462C23.7538 15.8166 24.1326 17.2719 24.1326 18.9122C24.1326 20.5525 23.7538 22.0158 22.9961 23.3023C22.2385 24.5727 21.1745 25.5697 19.8043 26.2934C18.4341 27.001 16.8543 27.3547 15.065 27.3547H11.1365C12.3905 28.3263 13.8202 28.8632 15.373 29.0576C16.8259 29.2389 18.226 29.1241 19.5697 28.7671C19.9394 28.2888C19.8863 27.6372 20.466 27.1227 21.1627 27.1708C21.4095 27.1877 21.5266 27.0999 21.6496 26.9498C22.457 25.9638 22.9261 25.3919 23.3775 24.8418C23.8791 24.2305 24.3585 23.6461 25.2554 22.5501C25.6084 22.1507 25.6501 22.0102 25.5938 21.7874C25.3988 21.0133 25.8471 20.384 26.6335 20.3236C26.8273 20.3086 26.832 20.2019 26.8371 20.0866C26.8902 19.2076 26.7543 18.3595 26.475 17.5191C25.6439 15.0202 24.0979 12.9489 21.8591 11.2366C22.5596 11.2381 23.2207 11.3835 23.8778 11.5788C24.5425 11.7759 25.1871 12.0454 25.8956 12.4235C25.1475 11.1113 24.6409 9.83396 24.4762 8.39412C24.5858 8.54905 24.6921 8.70246 24.7974 8.85436C25.2719 9.53906 25.7248 10.1926 26.3586 10.8132C25.9123 7.80637 27.1174 5.82188 29.6223 4.596C28.8856 5.53527 28.695 6.64612 28.9036 7.90196C30.1753 5.1413 32.5663 4.65657 35.6205 5.50789C34.4507 5.52434 33.3712 5.74046 32.4992 6.45034C32.5134 6.46172 32.525 6.47238 32.5346 6.48112C32.5619 6.4985 32.5674 6.50362C33.5891 6.29836 34.6539 6.39843 35.7189 6.49854C36.1897 6.54291 36.6192 6.56418 37.0438 6.53758C37.7621 6.49305 38.3752 6.25979 38.8617 5.79694C39.6293 5.06658 40.1281 4.19024 40.1599 3C40.6045 3.75805 40.8802 4.4927 41.0243 5.25039C41.2689 6.53659 41.2769 7.7946 40.9624 9.00153C40.9033 9.22947 40.9113 9.46019 40.9749 9.69956C41.2858 10.8723 41.2415 11.982 40.807 13.0235C40.777 13.2353 40.8127 13.2708 40.9041 13.2683C41.0479 13.265 41.013 13.2501C42.184 13.1253 43.094 12.2101 43.1848 11.0681C43.2609 10.8927C43.3607 11.3365C43.6193 13.3852 43.0247 15.0173 41.3129 16.0658C40.7466 16.4129 40.0984 16.6381 39.4055 16.7555C39.2005 16.7602 39.2744 16.7634 39.1937 16.7669C39.2005 16.8619C39.3433 16.9732C39.3999 16.9897C40.8317 17.5229 42.1897 17.5908 43.4295 16.9194C43.5491 16.9192C43.4705 17.0301C43.4101 17.1176C43.2961 17.2812C43.2368 17.3614C43.2143 17.3907C43.1863 17.4261 43.1577 17.4606 43.1279 17.494C42.1427 18.5947 40.9627 19.4296 39.3813 19.6994C38.8598 19.7883 38.3192 19.7843 37.7739 19.7441C37.5943 19.7253C37.5562 19.7693C37.5456 19.8073 37.5562 19.837 37.5777 19.8617C37.6526 19.9178C37.698 19.9429C37.7302 19.9614C38.7289 20.5848 39.8044 20.9687 40.9077 21.2648C40.9526 21.277C41.0517 21.3112C41.1586 21.3734C41.1908 21.3993C40.8927 21.4125C40.1723 21.435C39.1299 21.397C38.6784 21.3564C38.0521 21.2788C37.8735 21.2516C37.7718 21.2348C37.316 21.1525C36.9171 21.0794C37.4414 21.6089 37.8274 22.1164 38.1651 22.6542C38.3801 22.9966 38.5738 23.3457 38.725 23.7124H40.9042V40.8532H34.0128C32.2235 40.8532 30.6437 40.4859 29.2735 39.7513C27.9033 39.033 26.8394 38.0209 26.0817 36.715C25.3241 35.4253 24.9452 33.9479 24.9452 32.2828C24.9452 30.6177 25.3241 29.1322 26.0817 27.8262C26.8394 26.5366 27.9033 25.5245 29.2735 24.7899C30.6437 24.0716 32.2235 23.7124 34.0128 23.7124H38.2908C36.9342 22.5055 35.3493 21.8581 33.6098 21.6403C32.1569 21.459 30.7569 21.5738 29.4131 21.9308C29.0435 22.4091C29.0966 23.0607 28.5169 23.5752 27.8202 23.5271C27.5734 23.5102 27.4562 23.598 27.3333 23.748C26.894 24.2846 26.5549 24.6984 26.2641 25.0529C25.6054 25.8561C25.1038 26.4674 24.6244 27.0518 23.7275 28.1477C23.6115 28.2799 23.5472 28.3368C23.3891 28.9105C23.584 29.6846 23.1357 30.3139 22.3494 30.3743C22.1509 30.4961 22.1442 30.646C22.0927 31.4903 22.2285 32.3384 22.5079 33.1787C23.339 35.6777 24.885 37.749 27.1238 39.4613C26.4233 39.4598 25.7621 39.3144 25.1051 39.1191C24.4404 38.922 23.7957 38.6525 23.0873 38.2744C23.8353 39.5866 24.342 40.8639 24.5066 42.3038C24.3971 42.1488 24.2907 41.9954 24.1855 41.8435C23.711 41.1588 23.258 40.5053 22.6242 39.8847C23.0706 42.8915 21.8655 44.876 19.3606 46.1019C20.0972 45.1626 20.2878 44.0518 20.0793 42.7959C18.8076 45.5566 16.4166 46.0413 13.3624 45.19L13.3637 45.1903ZM14.9683 25.811C16.4513 25.811 17.749 25.5215 18.8613 24.9426C19.9736 24.3476 20.836 23.5355 21.4486 22.5063C22.0612 21.461 22.3674 20.263 22.3674 18.9122C22.3674 17.5614 22.0612 16.3714 21.4486 15.3422C20.836 14.2969 19.9736 13.4848 18.8613 12.9059C17.749 12.3109 16.4513 12.0134 14.9683 12.0134H9.96294V25.811H14.9683ZM30.2165 26.1611C31.3288 25.5734 32.6265 25.2796 34.1096 25.2796H39.1149V39.2861H34.1096C32.6265 39.2861 31.3288 38.9841 30.2165 38.3801C29.1042 37.7924 28.2418 36.968 27.6292 35.9069C27.0167 34.8621 26.7104 33.6541 26.7104 32.2828C26.7104 30.9116 27.0167 29.6954 27.6292 28.6343C28.2418 27.5895 29.1042 26.7651 30.2165 26.1611Z"
25
+ fill="#F0F8FF"
26
+ />
27
+ </g>
28
+ </g>
29
+ <defs>
30
+ <filter
31
+ id="filter0_swb"
32
+ x="1"
33
+ y="-1"
34
+ width="48"
35
+ height="53"
36
+ filterUnits="userSpaceOnUse"
37
+ colorInterpolationFilters="sRGB"
38
+ >
39
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
40
+ <feColorMatrix
41
+ in="SourceAlpha"
42
+ type="matrix"
43
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
44
+ result="hardAlpha"
45
+ />
46
+ <feOffset />
47
+ <feGaussianBlur stdDeviation="2" />
48
+ <feComposite in2="hardAlpha" operator="out" />
49
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
50
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow" />
51
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
52
+ </filter>
53
+ <clipPath id="clip0_swb_placeholder">
54
+ <rect width="50" height="50" fill="white" />
55
+ </clipPath>
56
+ </defs>
57
+ </svg>
58
+ </svg>
59
+ )
60
+
61
+ // ─── Project type (mirrors StudioWorkBlock project row) ───────────────────────
62
+ export type DDWorkBProps = Record<string, any>
63
+
64
+ type Category = { id?: string | null; label?: string | null }
65
+ type ProjectTag = { id?: string | null; tag?: string | null }
66
+ type Project = {
67
+ id?: string | null
68
+ title?: string | null
69
+ category?: string | null
70
+ description?: string | null
71
+ highlight?: string | null
72
+ link?: string | null
73
+ image?: Media | string | null
74
+ imageFallbackUrl?: string | null
75
+ href?: string | null
76
+ tags?: ProjectTag[] | null
77
+ [key: string]: any
78
+ }
79
+
80
+ // ─── Single project card in Card2Item style ───────────────────────────────────
81
+ function isLocalImageSrc(src?: string | null): src is string {
82
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
83
+ }
84
+
85
+ function ProjectCard({ project }: { project: Project }) {
86
+ const title = project.title ?? ''
87
+ const category = project.category ?? ''
88
+ const description = project.description ?? ''
89
+ const highlight = project.highlight ?? ''
90
+ const link = project.link ?? ''
91
+ const imgSrc =
92
+ project.image && typeof project.image === 'object'
93
+ ? ((project.image as Media).url ?? project.imageFallbackUrl ?? '')
94
+ : (project.imageFallbackUrl ?? '')
95
+ const safeImgSrc = isLocalImageSrc(imgSrc) ? imgSrc : null
96
+
97
+ const card = (
98
+ <article
99
+ // data-theme="dark"
100
+ className="article-card max-w-full! rounded-[2.5rem] w-full border border-border overflow-clip bgTransBlur hover:cursor-pointer h-full relative shadow-md group hover:shadow-2xl hover:-translate-y-1 transition-all duration-300"
101
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
102
+ >
103
+ {/* Media area */}
104
+ <div
105
+ className="card-media card-bottom-lit relative w-full overflow-hidden border border-border rounded-[2.5rem]"
106
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
107
+ >
108
+ <div
109
+ className="w-full bg-linear-to-br from-violet-950 to-indigo-950 flex items-center justify-center relative"
110
+ style={{ aspectRatio: '16/9', minHeight: 209 }}
111
+ >
112
+ {safeImgSrc ? (
113
+ <Image
114
+ src={safeImgSrc}
115
+ alt={title}
116
+ fill
117
+ sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
118
+ className="w-full h-full object-cover object-top absolute inset-0 rounded-3xl!"
119
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
120
+ />
121
+ ) : (
122
+ <PlaceholderSVG />
123
+ )}
124
+
125
+ {/* Year badge */}
126
+ {project.year && (
127
+ <div className="absolute top-2 right-2 z-20 border bg-stone-900/70 border-stone-700 text-stone-300 text-xs font-mono px-2.5 py-1 rounded-full">
128
+ {project.year}
129
+ </div>
130
+ )}
131
+
132
+ {/* Category badge */}
133
+ {category && (
134
+ <div className="absolute top-2 left-2 z-20 border bg-lime-500/50 border-lime-500 text-stone-50 text-xs font-semibold px-2.5 py-1 rounded-full">
135
+ {category}
136
+ </div>
137
+ )}
138
+
139
+ {/* Title overlay */}
140
+ <div className="card-title-overlay absolute bottom-4 left-4 right-4 z-20">
141
+ <h3 className="text-sm font-semibold text-stone-50 dark:text-stone-50 drop-shadow-lg">
142
+ {title}
143
+ </h3>
144
+ </div>
145
+
146
+ {/* Bottom gradient */}
147
+ <div
148
+ aria-hidden="true"
149
+ className="absolute pointer-events-none left-0 bottom-0 w-full h-1/2 bg-linear-to-t from-black to-transparent"
150
+ />
151
+ </div>
152
+ </div>
153
+
154
+ {/* Card body */}
155
+ <div className="card-content-wrapper relative -z-1 mx-3! -mt-1 bg-blend-darken card-bottom-lit bg-stone-100 dark:bg-stone-900/50 backdrop-blur-lg px-4 pt-4 pb-8">
156
+ <div className="card-content">
157
+ <div className="flex items-center gap-2 mb-2 flex-wrap scale-90 origin-top-left">
158
+ {category && (
159
+ <span className="text-[11.5px] bg-stone-300/50 dark:bg-stone-800 text-stone-500 dark:text-stone-400 border border-stone-300 dark:border-stone-700 px-2.5 py-1 rounded-full">
160
+ {category}
161
+ </span>
162
+ )}
163
+ </div>
164
+
165
+ <h4 className="text-md font-semibold text-stone-900 dark:text-stone-100 mb-1">{title}</h4>
166
+
167
+ {description && (
168
+ <p className="text-sm text-stone-400 leading-relaxed line-clamp-2 mb-4">
169
+ {description}
170
+ </p>
171
+ )}
172
+
173
+ {/* Tags */}
174
+ {project.tags && project.tags.length > 0 && (
175
+ <div className="flex flex-wrap gap-1.5 mb-4">
176
+ {project.tags.slice(0, 3).map((t, ti) => (
177
+ <span
178
+ key={t.id ?? ti}
179
+ className="text-[11.5px] bg-stone-300/50 dark:bg-stone-800 text-stone-500 dark:text-stone-400 border border-stone-300 dark:border-stone-700 px-2.5 py-1 rounded-full"
180
+ >
181
+ {t.tag}
182
+ </span>
183
+ ))}
184
+ </div>
185
+ )}
186
+
187
+ <div className="flex items-center justify-between">
188
+ {highlight ? (
189
+ <span className="text-[11.5px] text-indigo-600 darK:text-lime-400 font-medium">
190
+ {highlight}
191
+ </span>
192
+ ) : (
193
+ <span />
194
+ )}
195
+ <span
196
+ className="inline-flex items-center gap-1.5 bg-indigo-700 text-stone-50 text-xs font-semibold px-3 py-1.5 rounded-lg group-hover:bg-indigo-600 group-hover:scale-[1.05] group-hover:shadow-xl transition-all duration-150 whitespace-nowrap"
197
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
198
+ >
199
+ {link ? 'View Project' : 'View Details'}
200
+ </span>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ </article>
205
+ )
206
+
207
+ if (link) {
208
+ return (
209
+ <article className="card-item h-full">
210
+ <a href={link} target="_blank" rel="noopener noreferrer" className="block h-full">
211
+ {card}
212
+ </a>
213
+ </article>
214
+ )
215
+ }
216
+
217
+ return <article className="card-item h-full">{card}</article>
218
+ }
219
+
220
+ // ─── Main Block ───────────────────────────────────────────────────────────────
221
+ export const DDWorkB: React.FC<DDWorkBProps> = ({
222
+ eyebrow,
223
+ headlineMain,
224
+ headlineAccent,
225
+ categories,
226
+ projects,
227
+ sectionId,
228
+ customCSS,
229
+ }) => {
230
+ const categoryItems = (categories ?? []) as Category[]
231
+ const projectItems = (projects ?? []) as Project[]
232
+ const allLabel = categoryItems[0]?.label ?? 'All'
233
+ const [activeCategory, setActiveCategory] = useState(allLabel)
234
+
235
+ const filtered =
236
+ activeCategory === allLabel
237
+ ? projectItems
238
+ : projectItems.filter((p) => p.category === activeCategory)
239
+
240
+ return (
241
+ <>
242
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
243
+
244
+ <div id={sectionId ?? 'work'} className="py-24">
245
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
246
+ {/* Header */}#BlockName-DDWorkB
247
+ <div className="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-12">
248
+ <div>
249
+ {eyebrow && (
250
+ <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
251
+ {eyebrow}
252
+ </p>
253
+ )}
254
+ <h2 className="text-3d text-4xl md:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
255
+ {headlineMain}
256
+ <br />
257
+ <span className="text-stone-400">{headlineAccent}</span>
258
+ </h2>
259
+ </div>
260
+
261
+ {/* Filter tabs */}
262
+ {categoryItems.length > 1 && (
263
+ <div className="flex flex-wrap gap-2">
264
+ {categoryItems.map((cat, i) => {
265
+ const categoryLabel = cat.label ?? ''
266
+
267
+ return (
268
+ <button
269
+ key={cat.id ?? i}
270
+ onClick={() => setActiveCategory(categoryLabel)}
271
+ className={`px-4 py-1.5 rounded-full text-xs font-medium transition-colors cursor-pointer whitespace-nowrap ${
272
+ activeCategory === categoryLabel
273
+ ? 'border-indigo-500 bg-indigo-800 text-stone-200 shadow-lg shadow-blue-500/20'
274
+ : 'border-stone-300 dark:border-stone-700 bg-stone-50/80 dark:bg-stone-900/80 text-stone-600 dark:text-stone-400 hover:border-stone-400 dark:hover:border-stone-500 hover:bg-stone-100 dark:hover:bg-stone-800 hover:text-stone-900 dark:hover:text-stone-50'
275
+ }`}
276
+ >
277
+ {categoryLabel}
278
+ </button>
279
+ )
280
+ })}
281
+ </div>
282
+ )}
283
+ </div>
284
+ {/* Projects Grid */}
285
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 py-4">
286
+ {filtered.map((project, i) => (
287
+ <ProjectCard key={project.id ?? i} project={project} />
288
+ ))}
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </>
293
+ )
294
+ }