blocks-dusted 0.1.10 → 0.1.11

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 (246) hide show
  1. package/README.md +35 -0
  2. package/package.json +1 -1
  3. package/src/utils/paths.js +13 -13
  4. package/templates/blocks/DD-Accordion/Component.tsx +229 -0
  5. package/templates/blocks/DD-Accordion/README.md +3 -0
  6. package/templates/blocks/DD-Accordion/config.ts +73 -0
  7. package/templates/blocks/DD-Accordion/manifest.json +75 -0
  8. package/templates/blocks/DD-AnimText/Component.tsx +788 -0
  9. package/templates/blocks/DD-AnimText/README.md +3 -0
  10. package/templates/blocks/DD-AnimText/config.ts +295 -0
  11. package/templates/blocks/DD-AnimText/manifest.json +85 -0
  12. package/templates/blocks/DD-AsideNav/Component.tsx +321 -0
  13. package/templates/blocks/DD-AsideNav/config.ts +204 -0
  14. package/templates/blocks/DD-AsideNav/manifest.json +80 -0
  15. package/templates/blocks/DD-Banner/Component.tsx +51 -0
  16. package/templates/blocks/DD-Banner/config.ts +57 -0
  17. package/templates/blocks/DD-Banner/manifest.json +72 -0
  18. package/templates/blocks/DD-BgLightning/Component.tsx +44 -0
  19. package/templates/blocks/DD-BgLightning/README.md +3 -0
  20. package/templates/blocks/DD-BgLightning/config.ts +97 -0
  21. package/templates/blocks/DD-BgLightning/manifest.json +79 -0
  22. package/templates/blocks/DD-BookCall/Component.tsx +10 -11
  23. package/templates/blocks/DD-BookCall/config.ts +238 -238
  24. package/templates/blocks/DD-ButtonPop/Component.tsx +117 -0
  25. package/templates/blocks/DD-ButtonPop/config.ts +105 -0
  26. package/templates/blocks/DD-ButtonPop/manifest.json +88 -0
  27. package/templates/blocks/DD-ButtonPop/svg-icons/Dust2.tsx +167 -0
  28. package/templates/blocks/DD-Card/Component.tsx +251 -0
  29. package/templates/blocks/DD-Card/README.md +4 -0
  30. package/templates/blocks/DD-Card/config.ts +154 -0
  31. package/templates/blocks/DD-Card/manifest.json +82 -0
  32. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -6
  33. package/templates/blocks/DD-CardTemplate01/config.ts +106 -106
  34. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -249
  35. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -403
  36. package/templates/blocks/DD-Chip/Component.tsx +250 -0
  37. package/templates/blocks/DD-Chip/EXAMPLES.md +313 -0
  38. package/templates/blocks/DD-Chip/README.md +202 -0
  39. package/templates/blocks/DD-Chip/config.ts +369 -0
  40. package/templates/blocks/DD-Chip/manifest.json +74 -0
  41. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -256
  42. package/templates/blocks/DD-Contact/Component.tsx +434 -434
  43. package/templates/blocks/DD-Contact/config.ts +22 -22
  44. package/templates/blocks/DD-Counter/Component.tsx +190 -0
  45. package/templates/blocks/DD-Counter/README.md +4 -0
  46. package/templates/blocks/DD-Counter/config.ts +291 -0
  47. package/templates/blocks/DD-Counter/manifest.json +74 -0
  48. package/templates/blocks/DD-CustomCSS/Component.tsx +8 -0
  49. package/templates/blocks/DD-CustomCSS/README.md +4 -0
  50. package/templates/blocks/DD-CustomCSS/addToLayout.md +5 -0
  51. package/templates/blocks/DD-CustomCSS/config.ts +22 -0
  52. package/templates/blocks/DD-CustomCSS/manifest.json +72 -0
  53. package/templates/blocks/DD-CustomCSS/renderCustomCSS.md +7 -0
  54. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  55. package/templates/blocks/DD-FeatCat/config.ts +201 -201
  56. package/templates/blocks/DD-FeatStrip/Component.tsx +585 -585
  57. package/templates/blocks/DD-FeatStrip/config.ts +134 -134
  58. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +628 -0
  59. package/templates/blocks/DD-FluidBtn/Component.tsx +43 -0
  60. package/templates/blocks/DD-FluidBtn/README.md +6 -0
  61. package/templates/blocks/DD-FluidBtn/config.ts +102 -0
  62. package/templates/blocks/DD-FluidBtn/manifest.json +86 -0
  63. package/templates/blocks/DD-GooeyInfo/Component.tsx +149 -0
  64. package/templates/blocks/DD-GooeyInfo/config.ts +135 -0
  65. package/templates/blocks/DD-GooeyInfo/manifest.json +72 -0
  66. package/templates/blocks/DD-Hero/Component.tsx +40 -17
  67. package/templates/blocks/DD-Hero/config.ts +267 -267
  68. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -2
  69. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -231
  70. package/templates/blocks/DD-Icon/Component.tsx +287 -0
  71. package/templates/blocks/DD-Icon/README.md +4 -0
  72. package/templates/blocks/DD-Icon/config.ts +258 -0
  73. package/templates/blocks/DD-Icon/hooks/revalidateBlockIds.ts +66 -0
  74. package/templates/blocks/DD-Icon/manifest.json +82 -0
  75. package/templates/blocks/DD-IconButton/Component.tsx +455 -0
  76. package/templates/blocks/DD-IconButton/README.md +326 -0
  77. package/templates/blocks/DD-IconButton/config.ts +239 -0
  78. package/templates/blocks/DD-IconButton/manifest.json +85 -0
  79. package/templates/blocks/DD-IdTag/Component.tsx +21 -0
  80. package/templates/blocks/DD-IdTag/README.md +4 -0
  81. package/templates/blocks/DD-IdTag/config.ts +41 -0
  82. package/templates/blocks/DD-IdTag/manifest.json +72 -0
  83. package/templates/blocks/DD-Iframe/Component.tsx +45 -0
  84. package/templates/blocks/DD-Iframe/README.md +4 -0
  85. package/templates/blocks/DD-Iframe/config.ts +47 -0
  86. package/templates/blocks/DD-Iframe/manifest.json +72 -0
  87. package/templates/blocks/DD-IframeMedia/Component.tsx +129 -0
  88. package/templates/blocks/DD-IframeMedia/README.md +4 -0
  89. package/templates/blocks/DD-IframeMedia/config.ts +79 -0
  90. package/templates/blocks/DD-IframeMedia/manifest.json +72 -0
  91. package/templates/blocks/DD-LineAnim/Component.tsx +36 -0
  92. package/templates/blocks/DD-LineAnim/README.md +4 -0
  93. package/templates/blocks/DD-LineAnim/config.ts +90 -0
  94. package/templates/blocks/DD-LineAnim/manifest.json +80 -0
  95. package/templates/blocks/DD-LinkGrid/Component.tsx +119 -0
  96. package/templates/blocks/DD-LinkGrid/README.md +4 -0
  97. package/templates/blocks/DD-LinkGrid/config.ts +51 -0
  98. package/templates/blocks/DD-LinkGrid/manifest.json +74 -0
  99. package/templates/blocks/DD-LogoMarq/Component.tsx +132 -0
  100. package/templates/blocks/DD-LogoMarq/README.md +4 -0
  101. package/templates/blocks/DD-LogoMarq/config.ts +264 -0
  102. package/templates/blocks/DD-LogoMarq/manifest.json +82 -0
  103. package/templates/blocks/DD-Lottie/Component.tsx +459 -0
  104. package/templates/blocks/DD-Lottie/README.md +4 -0
  105. package/templates/blocks/DD-Lottie/config.ts +379 -0
  106. package/templates/blocks/DD-Lottie/index.tsx +28 -0
  107. package/templates/blocks/DD-Lottie/manifest.json +83 -0
  108. package/templates/blocks/DD-MagicBento/Component.tsx +777 -0
  109. package/templates/blocks/DD-MagicBento/README.md +4 -0
  110. package/templates/blocks/DD-MagicBento/config.ts +264 -0
  111. package/templates/blocks/DD-MagicBento/manifest.json +74 -0
  112. package/templates/blocks/DD-MasonaryMedia/Component.tsx +547 -545
  113. package/templates/blocks/DD-MasonaryMedia/config.ts +2 -2
  114. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -2
  115. package/templates/blocks/DD-Masonry/Component.tsx +645 -0
  116. package/templates/blocks/DD-Masonry/README.md +4 -0
  117. package/templates/blocks/DD-Masonry/config.ts +475 -0
  118. package/templates/blocks/DD-Masonry/manifest.json +82 -0
  119. package/templates/blocks/DD-Pricing/Component.tsx +372 -372
  120. package/templates/blocks/DD-Process/Component.tsx +149 -149
  121. package/templates/blocks/DD-Process/config.ts +150 -150
  122. package/templates/blocks/DD-Progress/Component.tsx +141 -0
  123. package/templates/blocks/DD-Progress/README.md +4 -0
  124. package/templates/blocks/DD-Progress/config.ts +175 -0
  125. package/templates/blocks/DD-Progress/manifest.json +74 -0
  126. package/templates/blocks/DD-ProjHeader/Component.tsx +91 -0
  127. package/templates/blocks/DD-ProjHeader/README.md +4 -0
  128. package/templates/blocks/DD-ProjHeader/config.ts +96 -0
  129. package/templates/blocks/DD-ProjHeader/manifest.json +72 -0
  130. package/templates/blocks/DD-ProjSlider/Component.tsx +123 -0
  131. package/templates/blocks/DD-ProjSlider/README.md +4 -0
  132. package/templates/blocks/DD-ProjSlider/config.ts +64 -0
  133. package/templates/blocks/DD-ProjSlider/manifest.json +72 -0
  134. package/templates/blocks/DD-ScrollText/Component.tsx +114 -0
  135. package/templates/blocks/DD-ScrollText/README.md +4 -0
  136. package/templates/blocks/DD-ScrollText/config.ts +58 -0
  137. package/templates/blocks/DD-ScrollText/manifest.json +72 -0
  138. package/templates/blocks/DD-Section/Component.tsx +266 -266
  139. package/templates/blocks/DD-Separator/Component.tsx +66 -0
  140. package/templates/blocks/DD-Separator/README.md +4 -0
  141. package/templates/blocks/DD-Separator/config.ts +58 -0
  142. package/templates/blocks/DD-Separator/manifest.json +72 -0
  143. package/templates/blocks/DD-Services/Component.tsx +176 -176
  144. package/templates/blocks/DD-Services/config.ts +149 -149
  145. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -307
  146. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  147. package/templates/blocks/DD-StackCards/Component.tsx +473 -473
  148. package/templates/blocks/DD-StackCards/config.ts +399 -399
  149. package/templates/blocks/DD-Team/Component.tsx +247 -247
  150. package/templates/blocks/DD-Team/config.ts +173 -173
  151. package/templates/blocks/DD-TeamCards/Component.tsx +251 -0
  152. package/templates/blocks/DD-TeamCards/README.md +4 -0
  153. package/templates/blocks/DD-TeamCards/config.ts +302 -0
  154. package/templates/blocks/DD-TeamCards/manifest.json +74 -0
  155. package/templates/blocks/DD-TechStack/Component.tsx +168 -162
  156. package/templates/blocks/DD-TechStack/config.ts +153 -153
  157. package/templates/blocks/DD-TestMarq/Component.tsx +132 -0
  158. package/templates/blocks/DD-TestMarq/README.md +4 -0
  159. package/templates/blocks/DD-TestMarq/RowLabel.tsx +9 -0
  160. package/templates/blocks/DD-TestMarq/config.ts +88 -0
  161. package/templates/blocks/DD-TestMarq/manifest.json +72 -0
  162. package/templates/blocks/DD-Testim/Component.tsx +247 -0
  163. package/templates/blocks/DD-Testim/README.md +4 -0
  164. package/templates/blocks/DD-Testim/config.ts +101 -0
  165. package/templates/blocks/DD-Testim/manifest.json +75 -0
  166. package/templates/blocks/DD-Testim2/Component.tsx +256 -0
  167. package/templates/blocks/DD-Testim2/README.md +4 -0
  168. package/templates/blocks/DD-Testim2/config.ts +100 -0
  169. package/templates/blocks/DD-Testim2/manifest.json +74 -0
  170. package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
  171. package/templates/blocks/DD-Testimonials/config.ts +120 -120
  172. package/templates/blocks/DD-TextMarq/Component.tsx +157 -0
  173. package/templates/blocks/DD-TextMarq/README.md +4 -0
  174. package/templates/blocks/DD-TextMarq/config.ts +237 -0
  175. package/templates/blocks/DD-TextMarq/manifest.json +82 -0
  176. package/templates/blocks/DD-Thanks/Component.tsx +500 -0
  177. package/templates/blocks/DD-Thanks/README.md +4 -0
  178. package/templates/blocks/DD-Thanks/config.ts +224 -0
  179. package/templates/blocks/DD-Thanks/manifest.json +72 -0
  180. package/templates/blocks/DD-VertMarq/Component.tsx +154 -0
  181. package/templates/blocks/DD-VertMarq/README.md +4 -0
  182. package/templates/blocks/DD-VertMarq/config.ts +236 -0
  183. package/templates/blocks/DD-VertMarq/dist/index.css +6 -0
  184. package/templates/blocks/DD-VertMarq/dist/index.min.css +1 -0
  185. package/templates/blocks/DD-VertMarq/manifest.json +74 -0
  186. package/templates/blocks/DD-Work/Component.tsx +318 -315
  187. package/templates/blocks/DD-Work/config.ts +165 -165
  188. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  189. package/templates/blocks/DDHG-CTASection2/Component.tsx +185 -0
  190. package/templates/blocks/DDHG-CTASection2/config.ts +140 -0
  191. package/templates/blocks/DDHG-CTASection2/manifest.json +45 -0
  192. package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -0
  193. package/templates/blocks/DDHG-HighlightArc/config.ts +85 -0
  194. package/templates/blocks/DDHG-HighlightArc/manifest.json +46 -0
  195. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +93 -0
  196. package/templates/blocks/DDHG-LogoMarquee/config.ts +66 -0
  197. package/templates/blocks/DDHG-LogoMarquee/manifest.json +39 -0
  198. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +213 -0
  199. package/templates/blocks/DDHG-NavCodeGrid/config.ts +64 -0
  200. package/templates/blocks/DDHG-NavCodeGrid/manifest.json +45 -0
  201. package/templates/blocks/DDHG-ProcessSec/Component.tsx +575 -0
  202. package/templates/blocks/DDHG-ProcessSec/config.ts +170 -0
  203. package/templates/blocks/DDHG-ProcessSec/manifest.json +56 -0
  204. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +164 -0
  205. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +394 -0
  206. package/templates/blocks/DDHG-ServiceDetails/config.ts +77 -0
  207. package/templates/blocks/DDHG-ServiceDetails/manifest.json +50 -0
  208. package/templates/blocks/DDHG-ServiceHero/Component.tsx +234 -0
  209. package/templates/blocks/DDHG-ServiceHero/config.ts +49 -0
  210. package/templates/blocks/DDHG-ServiceHero/manifest.json +26 -0
  211. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +277 -0
  212. package/templates/blocks/DDHG-ServiceOverview/config.ts +64 -0
  213. package/templates/blocks/DDHG-ServiceOverview/manifest.json +44 -0
  214. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +389 -0
  215. package/templates/blocks/DDHG-StatsFAQ/config.ts +204 -0
  216. package/templates/blocks/DDHG-StatsFAQ/manifest.json +45 -0
  217. package/templates/blocks/DDHG-TeamCards/Component.tsx +131 -0
  218. package/templates/blocks/DDHG-TeamCards/config.ts +48 -0
  219. package/templates/blocks/DDHG-TeamCards/manifest.json +29 -0
  220. package/templates/shared/src/components/BgLightning/index.tsx +242 -0
  221. package/templates/shared/src/components/ui/ShareButton/index.tsx +80 -0
  222. package/templates/shared/src/components/ui/anims/FadeOnScroll.tsx +48 -0
  223. package/templates/shared/src/components/ui/anims/FadeUpOnInView.tsx +63 -0
  224. package/templates/shared/src/components/ui/anims/GlareHover.tsx +111 -0
  225. package/templates/shared/src/components/ui/anims/LineAnims.tsx +81 -0
  226. package/templates/shared/src/components/ui/anims/TitleAnims01.tsx +54 -0
  227. package/templates/shared/src/components/ui/anims/animation-container.tsx +29 -0
  228. package/templates/shared/src/components/ui/anims/blur-in.tsx +26 -0
  229. package/templates/shared/src/components/ui/anims/counter.tsx +53 -0
  230. package/templates/shared/src/components/ui/anims/fade.tsx +83 -0
  231. package/templates/shared/src/components/ui/anims/gradual-spacing.tsx +39 -0
  232. package/templates/shared/src/components/ui/anims/pull-up.tsx +93 -0
  233. package/templates/shared/src/components/ui/anims/reveal-text.tsx +76 -0
  234. package/templates/shared/src/components/ui/anims/rotate-words.tsx +43 -0
  235. package/templates/shared/src/components/ui/anims/shiny-text.tsx +58 -0
  236. package/templates/shared/src/components/ui/anims/shuffle-text.tsx +85 -0
  237. package/templates/shared/src/components/ui/anims/split-text.tsx +211 -0
  238. package/templates/shared/src/components/ui/anims/staggered-fade.tsx +133 -0
  239. package/templates/shared/src/components/ui/anims/text-scroll.tsx +111 -0
  240. package/templates/shared/src/components/ui/anims/typing-effect.tsx +112 -0
  241. package/templates/shared/src/components/ui/button-dusted-fluid/index.tsx +1521 -0
  242. package/templates/shared/src/components/ui/separator.tsx +85 -0
  243. package/templates/shared/src/hooks/useBodyScrollLock.ts +97 -0
  244. package/templates/shared/src/hooks/useClickOutside.ts +28 -0
  245. package/templates/shared/src/utilities/renderCustomCSS.ts +5 -0
  246. package/templates/shared/src/utilities/smoothScroll.ts +65 -0
@@ -1,585 +1,585 @@
1
- /* eslint-disable react-hooks/rules-of-hooks */
2
- 'use client'
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'
9
-
10
- const DotLottieReact = dynamic(
11
- () => import('@lottiefiles/dotlottie-react').then((m) => m.DotLottieReact),
12
- { ssr: false },
13
- )
14
-
15
- /* ── Label color maps ─────────────────────────────────────────── */
16
-
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<string, { base: string; hover: string; glow: string }> = {
27
- indigo: {
28
- base: 'text-indigo-300',
29
- hover: 'group-hover:text-indigo-400',
30
- glow: 'rgba(99,102,241,0.08)',
31
- },
32
- emerald: {
33
- base: 'text-emerald-300',
34
- hover: 'group-hover:text-emerald-400',
35
- glow: 'rgba(16,185,129,0.08)',
36
- },
37
- amber: {
38
- base: 'text-amber-300',
39
- hover: 'group-hover:text-amber-400',
40
- glow: 'rgba(245,158,11,0.08)',
41
- },
42
- rose: {
43
- base: 'text-rose-300',
44
- hover: 'group-hover:text-rose-400',
45
- glow: 'rgba(244,63,94,0.08)',
46
- },
47
- violet: {
48
- base: 'text-violet-300',
49
- hover: 'group-hover:text-violet-400',
50
- glow: 'rgba(167,139,250,0.08)',
51
- },
52
- sky: {
53
- base: 'text-sky-300',
54
- hover: 'group-hover:text-sky-400',
55
- glow: 'rgba(56,189,248,0.08)',
56
- },
57
- }
58
-
59
- /* ── Isometric SVG Icons ─────────────────────────────────────── */
60
-
61
- function UIBlocksIcon({ hovered }: { hovered: boolean }) {
62
- return (
63
- <svg
64
- viewBox="0 0 120 90"
65
- fill="none"
66
- xmlns="http://www.w3.org/2000/svg"
67
- className="w-full h-full"
68
- style={{
69
- transition: 'filter 0.4s ease',
70
- filter: hovered ? 'drop-shadow(0 0 12px rgba(99,102,241,0.7))' : 'none',
71
- }}
72
- >
73
- <g
74
- style={{
75
- transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
76
- transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
77
- }}
78
- >
79
- <polygon
80
- points="60,4 108,28 60,52 12,28"
81
- fill={hovered ? '#312e81' : '#1e1b4b'}
82
- stroke={hovered ? '#818cf8' : '#4f46e5'}
83
- strokeWidth="1.2"
84
- />
85
- <polygon
86
- points="12,28 12,58 60,82 60,52"
87
- fill={hovered ? '#1e1b4b' : '#161430'}
88
- stroke={hovered ? '#6366f1' : '#3730a3'}
89
- strokeWidth="1.2"
90
- />
91
- <polygon
92
- points="108,28 108,58 60,82 60,52"
93
- fill={hovered ? '#2e2a6e' : '#1a1740'}
94
- stroke={hovered ? '#818cf8' : '#4338ca'}
95
- strokeWidth="1.2"
96
- />
97
- <line
98
- x1="60"
99
- y1="4"
100
- x2="60"
101
- y2="52"
102
- stroke={hovered ? '#818cf8' : '#4f46e5'}
103
- strokeWidth="0.6"
104
- strokeDasharray="3 3"
105
- />
106
- <line
107
- x1="36"
108
- y1="16"
109
- x2="84"
110
- y2="40"
111
- stroke={hovered ? '#818cf8' : '#4f46e5'}
112
- strokeWidth="0.6"
113
- strokeDasharray="3 3"
114
- />
115
- <line
116
- x1="84"
117
- y1="16"
118
- x2="36"
119
- y2="40"
120
- stroke={hovered ? '#818cf8' : '#4f46e5'}
121
- strokeWidth="0.6"
122
- strokeDasharray="3 3"
123
- />
124
- <polygon
125
- points="60,14 78,23 60,32 42,23"
126
- fill={hovered ? '#4f46e5' : '#312e81'}
127
- stroke={hovered ? '#a5b4fc' : '#6366f1'}
128
- strokeWidth="1"
129
- />
130
- <polygon
131
- points="42,23 42,33 60,42 60,32"
132
- fill={hovered ? '#3730a3' : '#1e1b4b'}
133
- stroke={hovered ? '#818cf8' : '#4338ca'}
134
- strokeWidth="1"
135
- />
136
- <polygon
137
- points="78,23 78,33 60,42 60,32"
138
- fill={hovered ? '#4338ca' : '#272466'}
139
- stroke={hovered ? '#a5b4fc' : '#6366f1'}
140
- strokeWidth="1"
141
- />
142
- </g>
143
- </svg>
144
- )
145
- }
146
-
147
- function TemplatesIcon({ hovered }: { hovered: boolean }) {
148
- return (
149
- <svg
150
- viewBox="0 0 120 90"
151
- fill="none"
152
- xmlns="http://www.w3.org/2000/svg"
153
- className="w-full h-full"
154
- style={{
155
- transition: 'filter 0.4s ease',
156
- filter: hovered ? 'drop-shadow(0 0 12px rgba(16,185,129,0.6))' : 'none',
157
- }}
158
- >
159
- <g
160
- style={{
161
- transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
162
- transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
163
- }}
164
- >
165
- <polygon
166
- points="60,6 104,30 60,54 16,30"
167
- fill={hovered ? '#064e3b' : '#022c22'}
168
- stroke={hovered ? '#34d399' : '#059669'}
169
- strokeWidth="1.2"
170
- />
171
- <polygon
172
- points="16,30 16,60 60,84 60,54"
173
- fill={hovered ? '#022c22' : '#011a15'}
174
- stroke={hovered ? '#10b981' : '#047857'}
175
- strokeWidth="1.2"
176
- />
177
- <polygon
178
- points="104,30 104,60 60,84 60,54"
179
- fill={hovered ? '#043927' : '#011a15'}
180
- stroke={hovered ? '#34d399' : '#059669'}
181
- strokeWidth="1.2"
182
- />
183
- <line
184
- x1="30"
185
- y1="30"
186
- x2="60"
187
- y2="16"
188
- stroke={hovered ? '#6ee7b7' : '#34d399'}
189
- strokeWidth="1"
190
- opacity="0.7"
191
- />
192
- <line
193
- x1="30"
194
- y1="36"
195
- x2="60"
196
- y2="22"
197
- stroke={hovered ? '#6ee7b7' : '#34d399'}
198
- strokeWidth="0.8"
199
- opacity="0.5"
200
- />
201
- <line
202
- x1="30"
203
- y1="42"
204
- x2="52"
205
- y2="30"
206
- stroke={hovered ? '#6ee7b7' : '#34d399'}
207
- strokeWidth="0.8"
208
- opacity="0.4"
209
- />
210
- <line
211
- x1="72"
212
- y1="22"
213
- x2="96"
214
- y2="34"
215
- stroke={hovered ? '#6ee7b7' : '#34d399'}
216
- strokeWidth="1"
217
- opacity="0.7"
218
- />
219
- <line
220
- x1="72"
221
- y1="28"
222
- x2="90"
223
- y2="37"
224
- stroke={hovered ? '#6ee7b7' : '#34d399'}
225
- strokeWidth="0.8"
226
- opacity="0.5"
227
- />
228
- <line
229
- x1="72"
230
- y1="34"
231
- x2="84"
232
- y2="40"
233
- stroke={hovered ? '#6ee7b7' : '#34d399'}
234
- strokeWidth="0.8"
235
- opacity="0.4"
236
- />
237
- <line
238
- x1="66"
239
- y1="12"
240
- x2="66"
241
- y2="50"
242
- stroke={hovered ? '#34d399' : '#059669'}
243
- strokeWidth="0.8"
244
- strokeDasharray="2 2"
245
- />
246
- </g>
247
- </svg>
248
- )
249
- }
250
-
251
- function UIKitIcon({ hovered }: { hovered: boolean }) {
252
- return (
253
- <svg
254
- viewBox="0 0 120 90"
255
- fill="none"
256
- xmlns="http://www.w3.org/2000/svg"
257
- className="w-full h-full"
258
- style={{
259
- transition: 'filter 0.4s ease',
260
- filter: hovered ? 'drop-shadow(0 0 12px rgba(245,158,11,0.6))' : 'none',
261
- }}
262
- >
263
- <g
264
- style={{
265
- transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
266
- transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
267
- }}
268
- >
269
- <polygon
270
- points="60,42 100,62 60,82 20,62"
271
- fill={hovered ? '#451a03' : '#27100a'}
272
- stroke={hovered ? '#f59e0b' : '#b45309'}
273
- strokeWidth="1.2"
274
- />
275
- <polygon
276
- points="20,52 20,62 60,82 60,72"
277
- fill={hovered ? '#27100a' : '#1a0a05'}
278
- stroke={hovered ? '#d97706' : '#92400e'}
279
- strokeWidth="1"
280
- />
281
- <polygon
282
- points="100,52 100,62 60,82 60,72"
283
- fill={hovered ? '#3b1a06' : '#1a0a05'}
284
- stroke={hovered ? '#f59e0b' : '#b45309'}
285
- strokeWidth="1"
286
- />
287
- <polygon
288
- points="60,26 100,46 60,66 20,46"
289
- fill={hovered ? '#78350f' : '#451a03'}
290
- stroke={hovered ? '#fbbf24' : '#d97706'}
291
- strokeWidth="1.2"
292
- />
293
- <polygon
294
- points="20,36 20,46 60,66 60,56"
295
- fill={hovered ? '#451a03' : '#27100a'}
296
- stroke={hovered ? '#f59e0b' : '#b45309'}
297
- strokeWidth="1"
298
- />
299
- <polygon
300
- points="100,36 100,46 60,66 60,56"
301
- fill={hovered ? '#5c2a07' : '#2d1108'}
302
- stroke={hovered ? '#fbbf24' : '#d97706'}
303
- strokeWidth="1"
304
- />
305
- <polygon
306
- points="60,10 100,30 60,50 20,30"
307
- fill={hovered ? '#92400e' : '#78350f'}
308
- stroke={hovered ? '#fcd34d' : '#fbbf24'}
309
- strokeWidth="1.2"
310
- />
311
- <polygon
312
- points="20,20 20,30 60,50 60,40"
313
- fill={hovered ? '#78350f' : '#451a03'}
314
- stroke={hovered ? '#fbbf24' : '#d97706'}
315
- strokeWidth="1"
316
- />
317
- <polygon
318
- points="100,20 100,30 60,50 60,40"
319
- fill={hovered ? '#7c3a0e' : '#4a2008'}
320
- stroke={hovered ? '#fcd34d' : '#fbbf24'}
321
- strokeWidth="1"
322
- />
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" />
326
- </g>
327
- </svg>
328
- )
329
- }
330
-
331
- const iconMap: Record<string, React.FC<{ hovered: boolean }>> = {
332
- uiBlocks: UIBlocksIcon,
333
- templates: TemplatesIcon,
334
- uiKit: UIKitIcon,
335
- }
336
-
337
- /* ── Icon renderer ───────────────────────────────────────────── */
338
-
339
- type MediaLike = {
340
- url?: string | null
341
- mimeType?: string | null
342
- filename?: string | null
343
- }
344
-
345
- function isLocalMediaSrc(src?: string | null): src is string {
346
- return typeof src === 'string' && src.startsWith('/api/media/file/')
347
- }
348
-
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 ?? ''
354
- const isLottie =
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')
360
-
361
- if (!safeUrl) return null
362
-
363
- if (isDotLottie || isJson) {
364
- return (
365
- <DotLottieReact
366
- src={safeUrl}
367
- loop
368
- autoplay
369
- style={{
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)',
374
- }}
375
- />
376
- )
377
- }
378
-
379
- // SVG or raster image
380
- return (
381
- <Image
382
- src={safeUrl}
383
- alt=""
384
- fill
385
- className="object-contain"
386
- style={{
387
- transform: hovered ? 'scale(1.08)' : 'scale(1)',
388
- transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
389
- }}
390
- />
391
- )
392
- }
393
-
394
- /* ── Card ────────────────────────────────────────────────────── */
395
-
396
- type CardData = { [key: string]: any }
397
-
398
- function FeatureCard({
399
- card,
400
- index,
401
- isLast,
402
- totalCards,
403
- isMobile,
404
- }: {
405
- card: CardData
406
- index: number
407
- isLast: boolean
408
- totalCards: number
409
- isMobile: boolean
410
- }) {
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
416
- const iconMedia =
417
- card.iconMedia && typeof card.iconMedia === 'object' && (card.iconMedia as MediaLike).url
418
- ? (card.iconMedia as MediaLike)
419
- : null
420
-
421
- // On mobile, last card is full-width if total is odd
422
- const isFullWidthOnMobile = isMobile && totalCards % 2 !== 0 && isLast
423
-
424
- let borderStyle: React.CSSProperties = {}
425
- if (isMobile) {
426
- const isEvenPosition = index % 2 === 0
427
- const isSecondRowOrLater = index >= 2
428
- borderStyle = {
429
- borderRight:
430
- isEvenPosition && index + 1 < totalCards && !isFullWidthOnMobile
431
- ? '1px solid rgba(255,255,255,0.07)'
432
- : 'none',
433
- borderTop: isSecondRowOrLater ? '1px solid rgba(255,255,255,0.07)' : 'none',
434
- }
435
- } else {
436
- borderStyle = {
437
- borderRight: !isLast ? '1px solid rgba(255,255,255,0.07)' : 'none',
438
- }
439
- }
440
-
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' : ''}`
442
-
443
- const innerContent = (
444
- <>
445
- {/* Hover bg glow */}
446
- <div
447
- className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500"
448
- style={{
449
- background: `radial-gradient(ellipse at 30% 50%, ${colors.glow} 0%, transparent 70%)`,
450
- }}
451
- />
452
-
453
- {/* Icon */}
454
- <div
455
- className="relative shrink-0 w-14 h-11 sm:w-20 sm:h-16"
456
- style={
457
- !iconMedia
458
- ? {
459
- transform: hovered ? 'scale(1.08)' : 'scale(1)',
460
- transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
461
- }
462
- : undefined
463
- }
464
- >
465
- {iconMedia ? (
466
- <CardIconMedia media={iconMedia} hovered={hovered} />
467
- ) : (
468
- <FallbackIcon hovered={hovered} />
469
- )}
470
- </div>
471
-
472
- {/* Text */}
473
- <div className="relative flex flex-col gap-2 pt-1">
474
- <span
475
- className={`text-sm font-bold tracking-[0.15em] uppercase ${labelClass} transition-all duration-300`}
476
- style={{ letterSpacing: hovered ? '0.2em' : '0.15em' }}
477
- >
478
- {card.label}
479
- </span>
480
- <p className="text-sm md:text-md text-stone-700 dark:text-stone-400 leading-relaxed transition-colors duration-300">
481
- {card.description}
482
- </p>
483
- {card.href && (
484
- <span
485
- className={`inline-flex items-center gap-1 text-sm font-medium mt-4 transition-all duration-300 ${colors.base} ${colors.hover}`}
486
- >
487
- Explore
488
- <ArrowRight
489
- size={14}
490
- style={{
491
- transform: hovered ? 'translateX(4px)' : 'translateX(0)',
492
- transition: 'transform 0.3s ease',
493
- }}
494
- />
495
- </span>
496
- )}
497
- </div>
498
- </>
499
- )
500
-
501
- if (card.href) {
502
- return (
503
- <Link
504
- href={card.href}
505
- className={cardClass}
506
- style={borderStyle}
507
- onMouseEnter={() => setHovered(true)}
508
- onMouseLeave={() => setHovered(false)}
509
- >
510
- {innerContent}
511
- </Link>
512
- )
513
- }
514
-
515
- return (
516
- <div
517
- className={cardClass}
518
- style={borderStyle}
519
- onMouseEnter={() => setHovered(true)}
520
- onMouseLeave={() => setHovered(false)}
521
- >
522
- {innerContent}
523
- </div>
524
- )
525
- }
526
-
527
- /* ── Block ───────────────────────────────────────────────────── */
528
-
529
- export type DDFeatStripProps = Record<string, any>
530
-
531
- export const DDFeatStrip: React.FC<DDFeatStripProps> = ({
532
- cards,
533
- containerClass,
534
- sectionId,
535
- customCSS,
536
- }) => {
537
- if (!cards || cards.length === 0) return null
538
-
539
- const cardItems = cards as CardData[]
540
- const [isMobile, setIsMobile] = useState(false)
541
-
542
- useEffect(() => {
543
- const check = () => setIsMobile(window.innerWidth < 768)
544
- check()
545
- window.addEventListener('resize', check)
546
- return () => window.removeEventListener('resize', check)
547
- }, [])
548
-
549
- const totalCards = cardItems.length
550
-
551
- return (
552
- <>
553
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
554
-
555
- <div id={sectionId ?? undefined} className="py-24">
556
- #BlockName-DDFeatStrip
557
- <div
558
- className={`w-full bg-stone-100 dark:bg-stone-950 border-t border-b border-white/6 ${containerClass || ''}`}
559
- >
560
- <div
561
- className="grid grid-cols-1"
562
- style={
563
- !isMobile
564
- ? {
565
- gridTemplateColumns: `repeat(${totalCards}, minmax(0, 1fr))`,
566
- }
567
- : undefined
568
- }
569
- >
570
- {cardItems.map((card, i) => (
571
- <FeatureCard
572
- key={card.id ?? i}
573
- card={card}
574
- index={i}
575
- isLast={i === totalCards - 1}
576
- totalCards={totalCards}
577
- isMobile={isMobile}
578
- />
579
- ))}
580
- </div>
581
- </div>
582
- </div>
583
- </>
584
- )
585
- }
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ 'use client'
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'
9
+
10
+ const DotLottieReact = dynamic(
11
+ () => import('@lottiefiles/dotlottie-react').then((m) => m.DotLottieReact),
12
+ { ssr: false },
13
+ )
14
+
15
+ /* ── Label color maps ─────────────────────────────────────────── */
16
+
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<string, { base: string; hover: string; glow: string }> = {
27
+ indigo: {
28
+ base: 'text-indigo-300',
29
+ hover: 'group-hover:text-indigo-400',
30
+ glow: 'rgba(99,102,241,0.08)',
31
+ },
32
+ emerald: {
33
+ base: 'text-emerald-300',
34
+ hover: 'group-hover:text-emerald-400',
35
+ glow: 'rgba(16,185,129,0.08)',
36
+ },
37
+ amber: {
38
+ base: 'text-amber-300',
39
+ hover: 'group-hover:text-amber-400',
40
+ glow: 'rgba(245,158,11,0.08)',
41
+ },
42
+ rose: {
43
+ base: 'text-rose-300',
44
+ hover: 'group-hover:text-rose-400',
45
+ glow: 'rgba(244,63,94,0.08)',
46
+ },
47
+ violet: {
48
+ base: 'text-violet-300',
49
+ hover: 'group-hover:text-violet-400',
50
+ glow: 'rgba(167,139,250,0.08)',
51
+ },
52
+ sky: {
53
+ base: 'text-sky-300',
54
+ hover: 'group-hover:text-sky-400',
55
+ glow: 'rgba(56,189,248,0.08)',
56
+ },
57
+ }
58
+
59
+ /* ── Isometric SVG Icons ─────────────────────────────────────── */
60
+
61
+ function UIBlocksIcon({ hovered }: { hovered: boolean }) {
62
+ return (
63
+ <svg
64
+ viewBox="0 0 120 90"
65
+ fill="none"
66
+ xmlns="http://www.w3.org/2000/svg"
67
+ className="w-full h-full"
68
+ style={{
69
+ transition: 'filter 0.4s ease',
70
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(99,102,241,0.7))' : 'none',
71
+ }}
72
+ >
73
+ <g
74
+ style={{
75
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
76
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
77
+ }}
78
+ >
79
+ <polygon
80
+ points="60,4 108,28 60,52 12,28"
81
+ fill={hovered ? '#312e81' : '#1e1b4b'}
82
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
83
+ strokeWidth="1.2"
84
+ />
85
+ <polygon
86
+ points="12,28 12,58 60,82 60,52"
87
+ fill={hovered ? '#1e1b4b' : '#161430'}
88
+ stroke={hovered ? '#6366f1' : '#3730a3'}
89
+ strokeWidth="1.2"
90
+ />
91
+ <polygon
92
+ points="108,28 108,58 60,82 60,52"
93
+ fill={hovered ? '#2e2a6e' : '#1a1740'}
94
+ stroke={hovered ? '#818cf8' : '#4338ca'}
95
+ strokeWidth="1.2"
96
+ />
97
+ <line
98
+ x1="60"
99
+ y1="4"
100
+ x2="60"
101
+ y2="52"
102
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
103
+ strokeWidth="0.6"
104
+ strokeDasharray="3 3"
105
+ />
106
+ <line
107
+ x1="36"
108
+ y1="16"
109
+ x2="84"
110
+ y2="40"
111
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
112
+ strokeWidth="0.6"
113
+ strokeDasharray="3 3"
114
+ />
115
+ <line
116
+ x1="84"
117
+ y1="16"
118
+ x2="36"
119
+ y2="40"
120
+ stroke={hovered ? '#818cf8' : '#4f46e5'}
121
+ strokeWidth="0.6"
122
+ strokeDasharray="3 3"
123
+ />
124
+ <polygon
125
+ points="60,14 78,23 60,32 42,23"
126
+ fill={hovered ? '#4f46e5' : '#312e81'}
127
+ stroke={hovered ? '#a5b4fc' : '#6366f1'}
128
+ strokeWidth="1"
129
+ />
130
+ <polygon
131
+ points="42,23 42,33 60,42 60,32"
132
+ fill={hovered ? '#3730a3' : '#1e1b4b'}
133
+ stroke={hovered ? '#818cf8' : '#4338ca'}
134
+ strokeWidth="1"
135
+ />
136
+ <polygon
137
+ points="78,23 78,33 60,42 60,32"
138
+ fill={hovered ? '#4338ca' : '#272466'}
139
+ stroke={hovered ? '#a5b4fc' : '#6366f1'}
140
+ strokeWidth="1"
141
+ />
142
+ </g>
143
+ </svg>
144
+ )
145
+ }
146
+
147
+ function TemplatesIcon({ hovered }: { hovered: boolean }) {
148
+ return (
149
+ <svg
150
+ viewBox="0 0 120 90"
151
+ fill="none"
152
+ xmlns="http://www.w3.org/2000/svg"
153
+ className="w-full h-full"
154
+ style={{
155
+ transition: 'filter 0.4s ease',
156
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(16,185,129,0.6))' : 'none',
157
+ }}
158
+ >
159
+ <g
160
+ style={{
161
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
162
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
163
+ }}
164
+ >
165
+ <polygon
166
+ points="60,6 104,30 60,54 16,30"
167
+ fill={hovered ? '#064e3b' : '#022c22'}
168
+ stroke={hovered ? '#34d399' : '#059669'}
169
+ strokeWidth="1.2"
170
+ />
171
+ <polygon
172
+ points="16,30 16,60 60,84 60,54"
173
+ fill={hovered ? '#022c22' : '#011a15'}
174
+ stroke={hovered ? '#10b981' : '#047857'}
175
+ strokeWidth="1.2"
176
+ />
177
+ <polygon
178
+ points="104,30 104,60 60,84 60,54"
179
+ fill={hovered ? '#043927' : '#011a15'}
180
+ stroke={hovered ? '#34d399' : '#059669'}
181
+ strokeWidth="1.2"
182
+ />
183
+ <line
184
+ x1="30"
185
+ y1="30"
186
+ x2="60"
187
+ y2="16"
188
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
189
+ strokeWidth="1"
190
+ opacity="0.7"
191
+ />
192
+ <line
193
+ x1="30"
194
+ y1="36"
195
+ x2="60"
196
+ y2="22"
197
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
198
+ strokeWidth="0.8"
199
+ opacity="0.5"
200
+ />
201
+ <line
202
+ x1="30"
203
+ y1="42"
204
+ x2="52"
205
+ y2="30"
206
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
207
+ strokeWidth="0.8"
208
+ opacity="0.4"
209
+ />
210
+ <line
211
+ x1="72"
212
+ y1="22"
213
+ x2="96"
214
+ y2="34"
215
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
216
+ strokeWidth="1"
217
+ opacity="0.7"
218
+ />
219
+ <line
220
+ x1="72"
221
+ y1="28"
222
+ x2="90"
223
+ y2="37"
224
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
225
+ strokeWidth="0.8"
226
+ opacity="0.5"
227
+ />
228
+ <line
229
+ x1="72"
230
+ y1="34"
231
+ x2="84"
232
+ y2="40"
233
+ stroke={hovered ? '#6ee7b7' : '#34d399'}
234
+ strokeWidth="0.8"
235
+ opacity="0.4"
236
+ />
237
+ <line
238
+ x1="66"
239
+ y1="12"
240
+ x2="66"
241
+ y2="50"
242
+ stroke={hovered ? '#34d399' : '#059669'}
243
+ strokeWidth="0.8"
244
+ strokeDasharray="2 2"
245
+ />
246
+ </g>
247
+ </svg>
248
+ )
249
+ }
250
+
251
+ function UIKitIcon({ hovered }: { hovered: boolean }) {
252
+ return (
253
+ <svg
254
+ viewBox="0 0 120 90"
255
+ fill="none"
256
+ xmlns="http://www.w3.org/2000/svg"
257
+ className="w-full h-full"
258
+ style={{
259
+ transition: 'filter 0.4s ease',
260
+ filter: hovered ? 'drop-shadow(0 0 12px rgba(245,158,11,0.6))' : 'none',
261
+ }}
262
+ >
263
+ <g
264
+ style={{
265
+ transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
266
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
267
+ }}
268
+ >
269
+ <polygon
270
+ points="60,42 100,62 60,82 20,62"
271
+ fill={hovered ? '#451a03' : '#27100a'}
272
+ stroke={hovered ? '#f59e0b' : '#b45309'}
273
+ strokeWidth="1.2"
274
+ />
275
+ <polygon
276
+ points="20,52 20,62 60,82 60,72"
277
+ fill={hovered ? '#27100a' : '#1a0a05'}
278
+ stroke={hovered ? '#d97706' : '#92400e'}
279
+ strokeWidth="1"
280
+ />
281
+ <polygon
282
+ points="100,52 100,62 60,82 60,72"
283
+ fill={hovered ? '#3b1a06' : '#1a0a05'}
284
+ stroke={hovered ? '#f59e0b' : '#b45309'}
285
+ strokeWidth="1"
286
+ />
287
+ <polygon
288
+ points="60,26 100,46 60,66 20,46"
289
+ fill={hovered ? '#78350f' : '#451a03'}
290
+ stroke={hovered ? '#fbbf24' : '#d97706'}
291
+ strokeWidth="1.2"
292
+ />
293
+ <polygon
294
+ points="20,36 20,46 60,66 60,56"
295
+ fill={hovered ? '#451a03' : '#27100a'}
296
+ stroke={hovered ? '#f59e0b' : '#b45309'}
297
+ strokeWidth="1"
298
+ />
299
+ <polygon
300
+ points="100,36 100,46 60,66 60,56"
301
+ fill={hovered ? '#5c2a07' : '#2d1108'}
302
+ stroke={hovered ? '#fbbf24' : '#d97706'}
303
+ strokeWidth="1"
304
+ />
305
+ <polygon
306
+ points="60,10 100,30 60,50 20,30"
307
+ fill={hovered ? '#92400e' : '#78350f'}
308
+ stroke={hovered ? '#fcd34d' : '#fbbf24'}
309
+ strokeWidth="1.2"
310
+ />
311
+ <polygon
312
+ points="20,20 20,30 60,50 60,40"
313
+ fill={hovered ? '#78350f' : '#451a03'}
314
+ stroke={hovered ? '#fbbf24' : '#d97706'}
315
+ strokeWidth="1"
316
+ />
317
+ <polygon
318
+ points="100,20 100,30 60,50 60,40"
319
+ fill={hovered ? '#7c3a0e' : '#4a2008'}
320
+ stroke={hovered ? '#fcd34d' : '#fbbf24'}
321
+ strokeWidth="1"
322
+ />
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" />
326
+ </g>
327
+ </svg>
328
+ )
329
+ }
330
+
331
+ const iconMap: Record<string, React.FC<{ hovered: boolean }>> = {
332
+ uiBlocks: UIBlocksIcon,
333
+ templates: TemplatesIcon,
334
+ uiKit: UIKitIcon,
335
+ }
336
+
337
+ /* ── Icon renderer ───────────────────────────────────────────── */
338
+
339
+ type MediaLike = {
340
+ url?: string | null
341
+ mimeType?: string | null
342
+ filename?: string | null
343
+ }
344
+
345
+ function isLocalMediaSrc(src?: string | null): src is string {
346
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
347
+ }
348
+
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 ?? ''
354
+ const isLottie =
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')
360
+
361
+ if (!safeUrl) return null
362
+
363
+ if (isDotLottie || isJson) {
364
+ return (
365
+ <DotLottieReact
366
+ src={safeUrl}
367
+ loop
368
+ autoplay
369
+ style={{
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)',
374
+ }}
375
+ />
376
+ )
377
+ }
378
+
379
+ // SVG or raster image
380
+ return (
381
+ <Image
382
+ src={safeUrl}
383
+ alt=""
384
+ fill
385
+ className="object-contain"
386
+ style={{
387
+ transform: hovered ? 'scale(1.08)' : 'scale(1)',
388
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
389
+ }}
390
+ />
391
+ )
392
+ }
393
+
394
+ /* ── Card ────────────────────────────────────────────────────── */
395
+
396
+ type CardData = { [key: string]: any }
397
+
398
+ function FeatureCard({
399
+ card,
400
+ index,
401
+ isLast,
402
+ totalCards,
403
+ isMobile,
404
+ }: {
405
+ card: CardData
406
+ index: number
407
+ isLast: boolean
408
+ totalCards: number
409
+ isMobile: boolean
410
+ }) {
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
416
+ const iconMedia =
417
+ card.iconMedia && typeof card.iconMedia === 'object' && (card.iconMedia as MediaLike).url
418
+ ? (card.iconMedia as MediaLike)
419
+ : null
420
+
421
+ // On mobile, last card is full-width if total is odd
422
+ const isFullWidthOnMobile = isMobile && totalCards % 2 !== 0 && isLast
423
+
424
+ let borderStyle: React.CSSProperties = {}
425
+ if (isMobile) {
426
+ const isEvenPosition = index % 2 === 0
427
+ const isSecondRowOrLater = index >= 2
428
+ borderStyle = {
429
+ borderRight:
430
+ isEvenPosition && index + 1 < totalCards && !isFullWidthOnMobile
431
+ ? '1px solid rgba(255,255,255,0.07)'
432
+ : 'none',
433
+ borderTop: isSecondRowOrLater ? '1px solid rgba(255,255,255,0.07)' : 'none',
434
+ }
435
+ } else {
436
+ borderStyle = {
437
+ borderRight: !isLast ? '1px solid rgba(255,255,255,0.07)' : 'none',
438
+ }
439
+ }
440
+
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' : ''}`
442
+
443
+ const innerContent = (
444
+ <>
445
+ {/* Hover bg glow */}
446
+ <div
447
+ className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500"
448
+ style={{
449
+ background: `radial-gradient(ellipse at 30% 50%, ${colors.glow} 0%, transparent 70%)`,
450
+ }}
451
+ />
452
+
453
+ {/* Icon */}
454
+ <div
455
+ className="relative shrink-0 w-14 h-11 sm:w-20 sm:h-16"
456
+ style={
457
+ !iconMedia
458
+ ? {
459
+ transform: hovered ? 'scale(1.08)' : 'scale(1)',
460
+ transition: 'transform 0.4s cubic-bezier(0.34,1.56,0.64,1)',
461
+ }
462
+ : undefined
463
+ }
464
+ >
465
+ {iconMedia ? (
466
+ <CardIconMedia media={iconMedia} hovered={hovered} />
467
+ ) : (
468
+ <FallbackIcon hovered={hovered} />
469
+ )}
470
+ </div>
471
+
472
+ {/* Text */}
473
+ <div className="relative flex flex-col gap-2 pt-1">
474
+ <span
475
+ className={`text-sm font-bold tracking-[0.15em] uppercase ${labelClass} transition-all duration-300`}
476
+ style={{ letterSpacing: hovered ? '0.2em' : '0.15em' }}
477
+ >
478
+ {card.label}
479
+ </span>
480
+ <p className="text-sm md:text-md text-stone-700 dark:text-stone-400 leading-relaxed transition-colors duration-300">
481
+ {card.description}
482
+ </p>
483
+ {card.href && (
484
+ <span
485
+ className={`inline-flex items-center gap-1 text-sm font-medium mt-4 transition-all duration-300 ${colors.base} ${colors.hover}`}
486
+ >
487
+ Explore
488
+ <ArrowRight
489
+ size={14}
490
+ style={{
491
+ transform: hovered ? 'translateX(4px)' : 'translateX(0)',
492
+ transition: 'transform 0.3s ease',
493
+ }}
494
+ />
495
+ </span>
496
+ )}
497
+ </div>
498
+ </>
499
+ )
500
+
501
+ if (card.href) {
502
+ return (
503
+ <Link
504
+ href={card.href}
505
+ className={cardClass}
506
+ style={borderStyle}
507
+ onMouseEnter={() => setHovered(true)}
508
+ onMouseLeave={() => setHovered(false)}
509
+ >
510
+ {innerContent}
511
+ </Link>
512
+ )
513
+ }
514
+
515
+ return (
516
+ <div
517
+ className={cardClass}
518
+ style={borderStyle}
519
+ onMouseEnter={() => setHovered(true)}
520
+ onMouseLeave={() => setHovered(false)}
521
+ >
522
+ {innerContent}
523
+ </div>
524
+ )
525
+ }
526
+
527
+ /* ── Block ───────────────────────────────────────────────────── */
528
+
529
+ export type DDFeatStripProps = Record<string, any>
530
+
531
+ export const DDFeatStrip: React.FC<DDFeatStripProps> = ({
532
+ cards,
533
+ containerClass,
534
+ sectionId,
535
+ customCSS,
536
+ }) => {
537
+ if (!cards || cards.length === 0) return null
538
+
539
+ const cardItems = cards as CardData[]
540
+ const [isMobile, setIsMobile] = useState(false)
541
+
542
+ useEffect(() => {
543
+ const check = () => setIsMobile(window.innerWidth < 768)
544
+ check()
545
+ window.addEventListener('resize', check)
546
+ return () => window.removeEventListener('resize', check)
547
+ }, [])
548
+
549
+ const totalCards = cardItems.length
550
+
551
+ return (
552
+ <>
553
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
554
+
555
+ <div id={sectionId ?? undefined} className="py-24">
556
+
557
+ <div
558
+ className={`w-full bg-stone-100 dark:bg-stone-950 border-t border-b border-white/6 ${containerClass || ''}`}
559
+ >
560
+ <div
561
+ className="grid grid-cols-1"
562
+ style={
563
+ !isMobile
564
+ ? {
565
+ gridTemplateColumns: `repeat(${totalCards}, minmax(0, 1fr))`,
566
+ }
567
+ : undefined
568
+ }
569
+ >
570
+ {cardItems.map((card, i) => (
571
+ <FeatureCard
572
+ key={card.id ?? i}
573
+ card={card}
574
+ index={i}
575
+ isLast={i === totalCards - 1}
576
+ totalCards={totalCards}
577
+ isMobile={isMobile}
578
+ />
579
+ ))}
580
+ </div>
581
+ </div>
582
+ </div>
583
+ </>
584
+ )
585
+ }