blocks-dusted 0.1.10 → 0.1.12

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 (249) hide show
  1. package/README.md +35 -0
  2. package/package.json +5 -2
  3. package/src/cli.js +39 -3
  4. package/src/commands/shop.js +59 -0
  5. package/src/commands/warehouse.js +73 -0
  6. package/src/utils/paths.js +13 -13
  7. package/templates/blocks/DD-Accordion/Component.tsx +229 -0
  8. package/templates/blocks/DD-Accordion/README.md +3 -0
  9. package/templates/blocks/DD-Accordion/config.ts +73 -0
  10. package/templates/blocks/DD-Accordion/manifest.json +75 -0
  11. package/templates/blocks/DD-AnimText/Component.tsx +788 -0
  12. package/templates/blocks/DD-AnimText/README.md +3 -0
  13. package/templates/blocks/DD-AnimText/config.ts +295 -0
  14. package/templates/blocks/DD-AnimText/manifest.json +85 -0
  15. package/templates/blocks/DD-AsideNav/Component.tsx +321 -0
  16. package/templates/blocks/DD-AsideNav/config.ts +204 -0
  17. package/templates/blocks/DD-AsideNav/manifest.json +80 -0
  18. package/templates/blocks/DD-Banner/Component.tsx +51 -0
  19. package/templates/blocks/DD-Banner/config.ts +57 -0
  20. package/templates/blocks/DD-Banner/manifest.json +72 -0
  21. package/templates/blocks/DD-BgLightning/Component.tsx +44 -0
  22. package/templates/blocks/DD-BgLightning/README.md +3 -0
  23. package/templates/blocks/DD-BgLightning/config.ts +97 -0
  24. package/templates/blocks/DD-BgLightning/manifest.json +79 -0
  25. package/templates/blocks/DD-BookCall/Component.tsx +535 -535
  26. package/templates/blocks/DD-BookCall/config.ts +238 -238
  27. package/templates/blocks/DD-ButtonPop/Component.tsx +117 -0
  28. package/templates/blocks/DD-ButtonPop/config.ts +105 -0
  29. package/templates/blocks/DD-ButtonPop/manifest.json +88 -0
  30. package/templates/blocks/DD-ButtonPop/svg-icons/Dust2.tsx +167 -0
  31. package/templates/blocks/DD-Card/Component.tsx +251 -0
  32. package/templates/blocks/DD-Card/README.md +4 -0
  33. package/templates/blocks/DD-Card/config.ts +154 -0
  34. package/templates/blocks/DD-Card/manifest.json +82 -0
  35. package/templates/blocks/DD-CardTemplate01/Component.tsx +140 -140
  36. package/templates/blocks/DD-CardTemplate01/config.ts +106 -106
  37. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -249
  38. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -403
  39. package/templates/blocks/DD-Chip/Component.tsx +250 -0
  40. package/templates/blocks/DD-Chip/EXAMPLES.md +313 -0
  41. package/templates/blocks/DD-Chip/README.md +202 -0
  42. package/templates/blocks/DD-Chip/config.ts +369 -0
  43. package/templates/blocks/DD-Chip/manifest.json +74 -0
  44. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -256
  45. package/templates/blocks/DD-Contact/Component.tsx +435 -434
  46. package/templates/blocks/DD-Contact/config.ts +22 -22
  47. package/templates/blocks/DD-Counter/Component.tsx +190 -0
  48. package/templates/blocks/DD-Counter/README.md +4 -0
  49. package/templates/blocks/DD-Counter/config.ts +291 -0
  50. package/templates/blocks/DD-Counter/manifest.json +74 -0
  51. package/templates/blocks/DD-CustomCSS/Component.tsx +8 -0
  52. package/templates/blocks/DD-CustomCSS/README.md +4 -0
  53. package/templates/blocks/DD-CustomCSS/addToLayout.md +5 -0
  54. package/templates/blocks/DD-CustomCSS/config.ts +22 -0
  55. package/templates/blocks/DD-CustomCSS/manifest.json +72 -0
  56. package/templates/blocks/DD-CustomCSS/renderCustomCSS.md +7 -0
  57. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  58. package/templates/blocks/DD-FeatCat/config.ts +201 -201
  59. package/templates/blocks/DD-FeatStrip/Component.tsx +585 -585
  60. package/templates/blocks/DD-FeatStrip/config.ts +134 -134
  61. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +628 -0
  62. package/templates/blocks/DD-FluidBtn/Component.tsx +43 -0
  63. package/templates/blocks/DD-FluidBtn/README.md +6 -0
  64. package/templates/blocks/DD-FluidBtn/config.ts +102 -0
  65. package/templates/blocks/DD-FluidBtn/manifest.json +86 -0
  66. package/templates/blocks/DD-GooeyInfo/Component.tsx +149 -0
  67. package/templates/blocks/DD-GooeyInfo/config.ts +135 -0
  68. package/templates/blocks/DD-GooeyInfo/manifest.json +72 -0
  69. package/templates/blocks/DD-Hero/Component.tsx +297 -297
  70. package/templates/blocks/DD-Hero/config.ts +267 -267
  71. package/templates/blocks/DD-HorizontalScroll/Component.tsx +936 -936
  72. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -231
  73. package/templates/blocks/DD-Icon/Component.tsx +287 -0
  74. package/templates/blocks/DD-Icon/README.md +4 -0
  75. package/templates/blocks/DD-Icon/config.ts +258 -0
  76. package/templates/blocks/DD-Icon/hooks/revalidateBlockIds.ts +66 -0
  77. package/templates/blocks/DD-Icon/manifest.json +82 -0
  78. package/templates/blocks/DD-IconButton/Component.tsx +455 -0
  79. package/templates/blocks/DD-IconButton/README.md +326 -0
  80. package/templates/blocks/DD-IconButton/config.ts +239 -0
  81. package/templates/blocks/DD-IconButton/manifest.json +85 -0
  82. package/templates/blocks/DD-IdTag/Component.tsx +21 -0
  83. package/templates/blocks/DD-IdTag/README.md +4 -0
  84. package/templates/blocks/DD-IdTag/config.ts +41 -0
  85. package/templates/blocks/DD-IdTag/manifest.json +72 -0
  86. package/templates/blocks/DD-Iframe/Component.tsx +45 -0
  87. package/templates/blocks/DD-Iframe/README.md +4 -0
  88. package/templates/blocks/DD-Iframe/config.ts +47 -0
  89. package/templates/blocks/DD-Iframe/manifest.json +72 -0
  90. package/templates/blocks/DD-IframeMedia/Component.tsx +133 -0
  91. package/templates/blocks/DD-IframeMedia/README.md +4 -0
  92. package/templates/blocks/DD-IframeMedia/config.ts +79 -0
  93. package/templates/blocks/DD-IframeMedia/manifest.json +72 -0
  94. package/templates/blocks/DD-LineAnim/Component.tsx +36 -0
  95. package/templates/blocks/DD-LineAnim/README.md +4 -0
  96. package/templates/blocks/DD-LineAnim/config.ts +90 -0
  97. package/templates/blocks/DD-LineAnim/manifest.json +80 -0
  98. package/templates/blocks/DD-LinkGrid/Component.tsx +119 -0
  99. package/templates/blocks/DD-LinkGrid/README.md +4 -0
  100. package/templates/blocks/DD-LinkGrid/config.ts +51 -0
  101. package/templates/blocks/DD-LinkGrid/manifest.json +74 -0
  102. package/templates/blocks/DD-LogoMarq/Component.tsx +132 -0
  103. package/templates/blocks/DD-LogoMarq/README.md +4 -0
  104. package/templates/blocks/DD-LogoMarq/config.ts +264 -0
  105. package/templates/blocks/DD-LogoMarq/manifest.json +82 -0
  106. package/templates/blocks/DD-Lottie/Component.tsx +459 -0
  107. package/templates/blocks/DD-Lottie/README.md +4 -0
  108. package/templates/blocks/DD-Lottie/config.ts +379 -0
  109. package/templates/blocks/DD-Lottie/index.tsx +28 -0
  110. package/templates/blocks/DD-Lottie/manifest.json +83 -0
  111. package/templates/blocks/DD-MagicBento/Component.tsx +777 -0
  112. package/templates/blocks/DD-MagicBento/README.md +4 -0
  113. package/templates/blocks/DD-MagicBento/config.ts +264 -0
  114. package/templates/blocks/DD-MagicBento/manifest.json +74 -0
  115. package/templates/blocks/DD-MasonaryMedia/Component.tsx +545 -545
  116. package/templates/blocks/DD-MasonaryMedia/config.ts +2 -2
  117. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -2
  118. package/templates/blocks/DD-Masonry/Component.tsx +645 -0
  119. package/templates/blocks/DD-Masonry/README.md +4 -0
  120. package/templates/blocks/DD-Masonry/config.ts +475 -0
  121. package/templates/blocks/DD-Masonry/manifest.json +82 -0
  122. package/templates/blocks/DD-Pricing/Component.tsx +2 -5
  123. package/templates/blocks/DD-Process/Component.tsx +2 -4
  124. package/templates/blocks/DD-Process/config.ts +150 -150
  125. package/templates/blocks/DD-Progress/Component.tsx +156 -0
  126. package/templates/blocks/DD-Progress/README.md +4 -0
  127. package/templates/blocks/DD-Progress/config.ts +175 -0
  128. package/templates/blocks/DD-Progress/manifest.json +74 -0
  129. package/templates/blocks/DD-ProjHeader/Component.tsx +91 -0
  130. package/templates/blocks/DD-ProjHeader/README.md +4 -0
  131. package/templates/blocks/DD-ProjHeader/config.ts +96 -0
  132. package/templates/blocks/DD-ProjHeader/manifest.json +72 -0
  133. package/templates/blocks/DD-ProjSlider/Component.tsx +123 -0
  134. package/templates/blocks/DD-ProjSlider/README.md +4 -0
  135. package/templates/blocks/DD-ProjSlider/config.ts +64 -0
  136. package/templates/blocks/DD-ProjSlider/manifest.json +72 -0
  137. package/templates/blocks/DD-ScrollText/Component.tsx +114 -0
  138. package/templates/blocks/DD-ScrollText/README.md +4 -0
  139. package/templates/blocks/DD-ScrollText/config.ts +58 -0
  140. package/templates/blocks/DD-ScrollText/manifest.json +72 -0
  141. package/templates/blocks/DD-Section/Component.tsx +266 -266
  142. package/templates/blocks/DD-Separator/Component.tsx +66 -0
  143. package/templates/blocks/DD-Separator/README.md +4 -0
  144. package/templates/blocks/DD-Separator/config.ts +58 -0
  145. package/templates/blocks/DD-Separator/manifest.json +72 -0
  146. package/templates/blocks/DD-Services/Component.tsx +176 -176
  147. package/templates/blocks/DD-Services/config.ts +149 -149
  148. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -307
  149. package/templates/blocks/DD-Slider-A/Component.tsx +1 -1
  150. package/templates/blocks/DD-StackCards/Component.tsx +1 -1
  151. package/templates/blocks/DD-StackCards/config.ts +399 -399
  152. package/templates/blocks/DD-Team/Component.tsx +247 -247
  153. package/templates/blocks/DD-Team/config.ts +173 -173
  154. package/templates/blocks/DD-TeamCards/Component.tsx +251 -0
  155. package/templates/blocks/DD-TeamCards/README.md +4 -0
  156. package/templates/blocks/DD-TeamCards/config.ts +302 -0
  157. package/templates/blocks/DD-TeamCards/manifest.json +74 -0
  158. package/templates/blocks/DD-TechStack/Component.tsx +174 -174
  159. package/templates/blocks/DD-TechStack/config.ts +153 -153
  160. package/templates/blocks/DD-TestMarq/Component.tsx +132 -0
  161. package/templates/blocks/DD-TestMarq/README.md +4 -0
  162. package/templates/blocks/DD-TestMarq/RowLabel.tsx +9 -0
  163. package/templates/blocks/DD-TestMarq/config.ts +88 -0
  164. package/templates/blocks/DD-TestMarq/manifest.json +72 -0
  165. package/templates/blocks/DD-Testim/Component.tsx +247 -0
  166. package/templates/blocks/DD-Testim/README.md +4 -0
  167. package/templates/blocks/DD-Testim/config.ts +101 -0
  168. package/templates/blocks/DD-Testim/manifest.json +75 -0
  169. package/templates/blocks/DD-Testim2/Component.tsx +256 -0
  170. package/templates/blocks/DD-Testim2/README.md +4 -0
  171. package/templates/blocks/DD-Testim2/config.ts +100 -0
  172. package/templates/blocks/DD-Testim2/manifest.json +74 -0
  173. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  174. package/templates/blocks/DD-Testimonials/config.ts +120 -120
  175. package/templates/blocks/DD-TextMarq/Component.tsx +157 -0
  176. package/templates/blocks/DD-TextMarq/README.md +4 -0
  177. package/templates/blocks/DD-TextMarq/config.ts +237 -0
  178. package/templates/blocks/DD-TextMarq/manifest.json +82 -0
  179. package/templates/blocks/DD-Thanks/Component.tsx +501 -0
  180. package/templates/blocks/DD-Thanks/README.md +4 -0
  181. package/templates/blocks/DD-Thanks/config.ts +224 -0
  182. package/templates/blocks/DD-Thanks/manifest.json +72 -0
  183. package/templates/blocks/DD-VertMarq/Component.tsx +154 -0
  184. package/templates/blocks/DD-VertMarq/README.md +4 -0
  185. package/templates/blocks/DD-VertMarq/config.ts +236 -0
  186. package/templates/blocks/DD-VertMarq/dist/index.css +6 -0
  187. package/templates/blocks/DD-VertMarq/dist/index.min.css +1 -0
  188. package/templates/blocks/DD-VertMarq/manifest.json +74 -0
  189. package/templates/blocks/DD-Work/Component.tsx +318 -315
  190. package/templates/blocks/DD-Work/config.ts +165 -165
  191. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  192. package/templates/blocks/DDHG-CTASection2/Component.tsx +185 -0
  193. package/templates/blocks/DDHG-CTASection2/config.ts +140 -0
  194. package/templates/blocks/DDHG-CTASection2/manifest.json +45 -0
  195. package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -0
  196. package/templates/blocks/DDHG-HighlightArc/config.ts +85 -0
  197. package/templates/blocks/DDHG-HighlightArc/manifest.json +46 -0
  198. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +143 -0
  199. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -0
  200. package/templates/blocks/DDHG-LogoMarquee/manifest.json +39 -0
  201. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +227 -0
  202. package/templates/blocks/DDHG-NavCodeGrid/config.ts +64 -0
  203. package/templates/blocks/DDHG-NavCodeGrid/manifest.json +45 -0
  204. package/templates/blocks/DDHG-ProcessSec/Component.tsx +574 -0
  205. package/templates/blocks/DDHG-ProcessSec/config.ts +170 -0
  206. package/templates/blocks/DDHG-ProcessSec/manifest.json +56 -0
  207. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +164 -0
  208. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +390 -0
  209. package/templates/blocks/DDHG-ServiceDetails/config.ts +77 -0
  210. package/templates/blocks/DDHG-ServiceDetails/manifest.json +50 -0
  211. package/templates/blocks/DDHG-ServiceHero/Component.tsx +234 -0
  212. package/templates/blocks/DDHG-ServiceHero/config.ts +49 -0
  213. package/templates/blocks/DDHG-ServiceHero/manifest.json +26 -0
  214. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +277 -0
  215. package/templates/blocks/DDHG-ServiceOverview/config.ts +64 -0
  216. package/templates/blocks/DDHG-ServiceOverview/manifest.json +44 -0
  217. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +389 -0
  218. package/templates/blocks/DDHG-StatsFAQ/config.ts +204 -0
  219. package/templates/blocks/DDHG-StatsFAQ/manifest.json +45 -0
  220. package/templates/blocks/DDHG-TeamCards/Component.tsx +169 -0
  221. package/templates/blocks/DDHG-TeamCards/config.ts +48 -0
  222. package/templates/blocks/DDHG-TeamCards/manifest.json +29 -0
  223. package/templates/shared/src/components/BgLightning/index.tsx +242 -0
  224. package/templates/shared/src/components/ui/ShareButton/index.tsx +80 -0
  225. package/templates/shared/src/components/ui/anims/FadeOnScroll.tsx +48 -0
  226. package/templates/shared/src/components/ui/anims/FadeUpOnInView.tsx +63 -0
  227. package/templates/shared/src/components/ui/anims/GlareHover.tsx +111 -0
  228. package/templates/shared/src/components/ui/anims/LineAnims.tsx +81 -0
  229. package/templates/shared/src/components/ui/anims/TitleAnims01.tsx +54 -0
  230. package/templates/shared/src/components/ui/anims/animation-container.tsx +29 -0
  231. package/templates/shared/src/components/ui/anims/blur-in.tsx +26 -0
  232. package/templates/shared/src/components/ui/anims/counter.tsx +53 -0
  233. package/templates/shared/src/components/ui/anims/fade.tsx +83 -0
  234. package/templates/shared/src/components/ui/anims/gradual-spacing.tsx +39 -0
  235. package/templates/shared/src/components/ui/anims/pull-up.tsx +93 -0
  236. package/templates/shared/src/components/ui/anims/reveal-text.tsx +76 -0
  237. package/templates/shared/src/components/ui/anims/rotate-words.tsx +43 -0
  238. package/templates/shared/src/components/ui/anims/shiny-text.tsx +58 -0
  239. package/templates/shared/src/components/ui/anims/shuffle-text.tsx +85 -0
  240. package/templates/shared/src/components/ui/anims/split-text.tsx +211 -0
  241. package/templates/shared/src/components/ui/anims/staggered-fade.tsx +133 -0
  242. package/templates/shared/src/components/ui/anims/text-scroll.tsx +111 -0
  243. package/templates/shared/src/components/ui/anims/typing-effect.tsx +112 -0
  244. package/templates/shared/src/components/ui/button-dusted-fluid/index.tsx +1521 -0
  245. package/templates/shared/src/components/ui/separator.tsx +85 -0
  246. package/templates/shared/src/hooks/useBodyScrollLock.ts +97 -0
  247. package/templates/shared/src/hooks/useClickOutside.ts +28 -0
  248. package/templates/shared/src/utilities/renderCustomCSS.ts +5 -0
  249. package/templates/shared/src/utilities/smoothScroll.ts +65 -0
@@ -0,0 +1,234 @@
1
+ 'use client'
2
+
3
+ import { motion } from 'framer-motion'
4
+ import { ArrowDown } from 'lucide-react'
5
+ import React, { useEffect, useId, useRef, useState } from 'react'
6
+ import { cn } from '@/utilities/ui'
7
+
8
+ export type DDHGServiceHeroProps = {
9
+ sectionId?: string | null
10
+ customClassName?: string | null
11
+ customCSS?: string | null
12
+ blockId?: string | null
13
+ scrollSpeed?: string | number | null
14
+ eyebrowPrefix?: string | null
15
+ eyebrowEmphasis?: string | null
16
+ heading?: string | null
17
+ description?: string | null
18
+ minHeight?: 'screen' | 'large' | 'medium' | 'compact' | null
19
+ showDivider?: boolean | null
20
+ showScrollIndicator?: boolean | null
21
+ }
22
+
23
+ const minHeightClassMap: Record<NonNullable<DDHGServiceHeroProps['minHeight']>, string> = {
24
+ screen: 'min-h-screen',
25
+ large: 'min-h-[70vh]',
26
+ medium: 'min-h-[56vh]',
27
+ compact: 'min-h-[42vh]'
28
+ }
29
+
30
+ let globalInstanceCounter = 0
31
+
32
+ function FadeUpOnInView({
33
+ children,
34
+ className = '',
35
+ delay = 0
36
+ }: {
37
+ children: React.ReactNode
38
+ className?: string
39
+ delay?: number
40
+ }) {
41
+ const ref = useRef<HTMLSpanElement | null>(null)
42
+ useEffect(() => {
43
+ const node = ref.current
44
+ if (!node) return
45
+ const observer = new IntersectionObserver(
46
+ ([entry]) => {
47
+ if (entry?.isIntersecting) {
48
+ node.style.opacity = '1'
49
+ node.style.transform = 'translateY(0)'
50
+ node.style.transitionDelay = `${delay}s`
51
+ } else {
52
+ node.style.opacity = '0'
53
+ node.style.transform = 'translateY(24px)'
54
+ node.style.transitionDelay = '0s'
55
+ }
56
+ },
57
+ { threshold: 0.25 }
58
+ )
59
+ observer.observe(node)
60
+ return () => observer.disconnect()
61
+ }, [delay])
62
+ return (
63
+ <span
64
+ ref={ref}
65
+ className={className}
66
+ style={{
67
+ display: 'inline-block',
68
+ opacity: 0,
69
+ transform: 'translateY(24px)',
70
+ transition: 'opacity 700ms ease, transform 700ms ease'
71
+ }}
72
+ >
73
+ {children}
74
+ </span>
75
+ )
76
+ }
77
+
78
+ function RevealText({
79
+ text,
80
+ className = '',
81
+ instanceIndex
82
+ }: {
83
+ text: string[]
84
+ className?: string
85
+ instanceIndex?: number
86
+ }) {
87
+ const uniqueId = useId()
88
+ const uniqueInstanceId = useRef<number | undefined>(undefined)
89
+ const textRef = useRef<HTMLSpanElement | null>(null)
90
+ const [isVisible, setIsVisible] = useState(false)
91
+ const [animationKey, setAnimationKey] = useState(0)
92
+ if (uniqueInstanceId.current === undefined)
93
+ uniqueInstanceId.current = instanceIndex ?? globalInstanceCounter++
94
+ useEffect(() => {
95
+ const node = textRef.current
96
+ if (!node) return
97
+ const observer = new IntersectionObserver(
98
+ ([entry]) => {
99
+ if (entry?.isIntersecting) {
100
+ setIsVisible(true)
101
+ setAnimationKey((key) => key + 1)
102
+ } else setIsVisible(false)
103
+ },
104
+ { threshold: 0.3 }
105
+ )
106
+ observer.observe(node)
107
+ return () => observer.disconnect()
108
+ }, [uniqueId])
109
+ const value = text?.[0] || ''
110
+ const words = value.split(' ')
111
+ const baseDelay = (uniqueInstanceId.current ?? 0) * 0.05
112
+ return (
113
+ <>
114
+ <style>{`@keyframes ddhgServiceHeroReveal { from { opacity: 0; transform: translateX(200px); } to { opacity: 1; transform: translateX(0%); } }`}</style>
115
+ <FadeUpOnInView delay={(uniqueInstanceId.current ?? 0) * 0.1}>
116
+ <span
117
+ ref={textRef}
118
+ className={cn(
119
+ 'inline-block h-auto bg-transparent text-left font-bold uppercase leading-none tracking-[1px]',
120
+ className
121
+ )}
122
+ >
123
+ {words.map((word, wordIndex) => {
124
+ const previousLength = words.slice(0, wordIndex).join(' ').length
125
+ const offset = wordIndex === 0 ? 0 : previousLength + 1
126
+ return (
127
+ <span
128
+ key={`${animationKey}-word-${wordIndex}`}
129
+ className="inline-block whitespace-nowrap"
130
+ >
131
+ {word.split('').map((char, charIndex) => {
132
+ const index = offset + charIndex
133
+ return (
134
+ <span
135
+ key={`${animationKey}-${index}-${char}`}
136
+ style={{
137
+ display: 'inline-block',
138
+ opacity: 0,
139
+ transform: isVisible ? undefined : 'translateX(200px)',
140
+ animationName: isVisible ? 'ddhgServiceHeroReveal' : 'none',
141
+ animationDuration: '1s',
142
+ animationFillMode: 'forwards',
143
+ animationDelay: `${baseDelay + index * 0.05}s`
144
+ }}
145
+ >
146
+ {char}
147
+ </span>
148
+ )
149
+ })}
150
+ {wordIndex < words.length - 1 ? <span>&nbsp;</span> : null}
151
+ </span>
152
+ )
153
+ })}
154
+ </span>
155
+ </FadeUpOnInView>
156
+ </>
157
+ )
158
+ }
159
+
160
+ export const DDHGServiceHero: React.FC<DDHGServiceHeroProps> = ({
161
+ sectionId,
162
+ customClassName,
163
+ customCSS,
164
+ blockId,
165
+ scrollSpeed,
166
+ eyebrowPrefix = 'what we',
167
+ eyebrowEmphasis = 'do',
168
+ heading = 'SERVICES',
169
+ description = 'From initial vision to final handover, our services span the full spectrum of interior architecture. Each engagement is shaped by the same principle: spaces should serve the people inside them.',
170
+ minHeight = 'large',
171
+ showDivider = true,
172
+ showScrollIndicator = true
173
+ }) => {
174
+ const minHeightClass = minHeightClassMap[minHeight || 'large']
175
+ return (
176
+ <div
177
+ id={sectionId ?? undefined}
178
+ data-block-id={blockId ?? undefined}
179
+ data-scroll={scrollSpeed ? true : undefined}
180
+ data-scroll-speed={scrollSpeed ? String(scrollSpeed) : undefined}
181
+ className={cn('relative flex w-full overflow-hidden', minHeightClass, customClassName)}
182
+ >
183
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
184
+ <motion.div
185
+ className="ServiceHeroCard relative z-10 mx-auto max-w-4xl overflow-clip rounded-md squircle bg-stone-100/50 p-6 dark:bg-stone-900/50 md:p-8"
186
+ initial={{ opacity: 0, y: 20 }}
187
+ whileInView={{ opacity: 1, y: 0 }}
188
+ viewport={{ once: true, amount: 0.35 }}
189
+ transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
190
+ >
191
+ {(eyebrowPrefix || eyebrowEmphasis) && (
192
+ <p className="mb-2 origin-left !scale-80 text-xs uppercase tracking-[0.2em] text-stone-400 dark:text-stone-400">
193
+ {eyebrowPrefix && <span className="font-light">{eyebrowPrefix} </span>}
194
+ {eyebrowEmphasis && <span className="font-medium">{eyebrowEmphasis}</span>}
195
+ </p>
196
+ )}
197
+ {heading && (
198
+ <span className="mt-2 block leading-[0.82] tracking-[0.08em]">
199
+ <RevealText
200
+ text={[heading]}
201
+ instanceIndex={1}
202
+ className="text-4xl font-extrabold md:text-6xl lg:text-8xl"
203
+ />
204
+ </span>
205
+ )}
206
+ {showDivider && <div className="mx-auto mb-6 h-px w-full bg-stone-400" />}
207
+ {description && (
208
+ <p className="mx-auto max-w-xl text-sm font-light leading-relaxed text-stone-700 dark:text-stone-200">
209
+ {description}
210
+ </p>
211
+ )}
212
+ </motion.div>
213
+ {showScrollIndicator && (
214
+ <motion.div
215
+ className="absolute bottom-8 left-1/2 flex -translate-x-1/2 flex-col items-center gap-2"
216
+ initial={{ opacity: 0, y: -8 }}
217
+ whileInView={{ opacity: 1, y: 0 }}
218
+ viewport={{ once: true, amount: 0.35 }}
219
+ transition={{ delay: 0.4, duration: 0.7 }}
220
+ aria-hidden="true"
221
+ >
222
+ <motion.div
223
+ className="h-10 w-px bg-stone-500"
224
+ animate={{ scaleY: [0.65, 1, 0.65], opacity: [0.45, 1, 0.45] }}
225
+ transition={{ duration: 1.8, repeat: Infinity, ease: 'easeInOut' }}
226
+ />
227
+ <ArrowDown className="h-3.5 w-3.5 text-stone-600 dark:text-stone-300" strokeWidth={1.5} />
228
+ </motion.div>
229
+ )}
230
+ </div>
231
+ )
232
+ }
233
+
234
+ export default DDHGServiceHero
@@ -0,0 +1,49 @@
1
+ import type { Block } from 'payload'
2
+
3
+ export const DDHGServiceHero: Block = {
4
+ slug: 'DDHG-ServiceHero',
5
+ interfaceName: 'DDHGServiceHero',
6
+ dbName: 'ddhg_svc_hero',
7
+ labels: { singular: 'Service Hero', plural: 'Service Heroes' },
8
+ fields: [
9
+ {
10
+ type: 'tabs',
11
+ tabs: [
12
+ {
13
+ label: 'Content',
14
+ fields: [
15
+ { type: 'row', fields: [
16
+ { name: 'eyebrowPrefix', type: 'text', label: 'Eyebrow Prefix', defaultValue: 'what we', admin: { width: '50%' } },
17
+ { name: 'eyebrowEmphasis', type: 'text', label: 'Eyebrow Emphasis', defaultValue: 'do', admin: { width: '50%' } },
18
+ ] },
19
+ { name: 'heading', type: 'text', label: 'Heading', defaultValue: 'SERVICES', required: true },
20
+ { name: 'description', type: 'textarea', label: 'Description', defaultValue: 'From initial vision to final handover, our services span the full spectrum of interior architecture. Each engagement is shaped by the same principle: spaces should serve the people inside them.' },
21
+ ],
22
+ },
23
+ {
24
+ label: 'Layout',
25
+ fields: [
26
+ { name: 'minHeight', type: 'select', label: 'Minimum Height', defaultValue: 'large', options: [
27
+ { label: 'Screen', value: 'screen' }, { label: 'Large', value: 'large' }, { label: 'Medium', value: 'medium' }, { label: 'Compact', value: 'compact' },
28
+ ] },
29
+ { type: 'row', fields: [
30
+ { name: 'showDivider', type: 'checkbox', label: 'Show Divider', defaultValue: true, admin: { width: '50%' } },
31
+ { name: 'showScrollIndicator', type: 'checkbox', label: 'Show Scroll Indicator', defaultValue: true, admin: { width: '50%' } },
32
+ ] },
33
+ ],
34
+ },
35
+ {
36
+ label: 'Advanced',
37
+ fields: [
38
+ { type: 'row', fields: [
39
+ { name: 'sectionId', type: 'text', label: 'Section ID', admin: { width: '50%' } },
40
+ { name: 'customClassName', type: 'text', defaultValue: 'items-center justify-center', label: 'Custom CSS Classes', admin: { width: '50%' } },
41
+ ] },
42
+ { name: 'scrollSpeed', type: 'text', label: 'Scroll Speed', admin: { description: 'Optional data-scroll-speed value.' } },
43
+ { name: 'customCSS', type: 'code', label: 'Custom CSS', admin: { language: 'css', description: 'Injected as a style tag. Use with caution.' } },
44
+ ],
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "type": "block",
3
+ "name": "DDHG-ServiceHero",
4
+ "label": "Service Hero",
5
+ "sourceName": "ServiceHero",
6
+ "portableName": "DDHG-ServiceHero",
7
+ "slug": "DDHG-ServiceHero",
8
+ "version": "0.0.1",
9
+ "source": { "repo": "Hadizainal/HeatherGrey2025", "originalPath": "src/blocks/ServiceHero", "originalSlug": "serviceHero", "originalConfigExport": "ServiceHero", "originalComponentExport": "ServiceHeroBlock", "originalInterfaceName": "ServiceHeroBlockProps" },
10
+ "files": [
11
+ { "from": "Component.tsx", "to": "src/blocks/DDHG-ServiceHero/Component.tsx" },
12
+ { "from": "config.ts", "to": "src/blocks/DDHG-ServiceHero/config.ts" }
13
+ ],
14
+ "dependencies": [{ "name": "framer-motion" }, { "name": "lucide-react" }],
15
+ "sharedFiles": [],
16
+ "requiredProjectFiles": [{ "path": "src/utilities/ui.ts", "reason": "Component uses the cn utility." }],
17
+ "optionalProjectFiles": [],
18
+ "collectionRegistration": { "enabled": true, "collections": ["Pages", "Posts"] },
19
+ "renderBlocksRegistration": { "enabled": true },
20
+ "renderBlocksBehavior": { "wrapperless": false },
21
+ "pageRouteIntegration": { "required": false, "manualSteps": [] },
22
+ "manualSteps": ["Run payload generate:types after schema changes."],
23
+ "notes": ["Converted from HeatherGrey2025 ServiceHero.", "dbName namespaced to ddhg_svc_hero.", "Installer must not overwrite existing target files."],
24
+ "exportName": "DDHGServiceHero",
25
+ "interfaceName": "DDHGServiceHero"
26
+ }
@@ -0,0 +1,277 @@
1
+ 'use client'
2
+
3
+ import { motion } from 'framer-motion'
4
+ import { CircleDot } from 'lucide-react'
5
+ import React, { useMemo, useState } from 'react'
6
+ import { cn } from '@/utilities/ui'
7
+
8
+ type ServiceOverviewItem = {
9
+ code?: string | null
10
+ title?: string | null
11
+ description?: string | null
12
+ details?: { label?: string | null; id?: string | null }[] | null
13
+ timeline?: string | null
14
+ engagement?: string | null
15
+ id?: string | null
16
+ }
17
+
18
+ export type DDHGServiceOverviewProps = {
19
+ sectionId?: string | null
20
+ customClassName?: string | null
21
+ customCSS?: string | null
22
+ blockId?: string | null
23
+ scrollSpeed?: string | number | null
24
+ eyebrowPrefix?: string | null
25
+ eyebrowEmphasis?: string | null
26
+ services?: ServiceOverviewItem[] | null
27
+ }
28
+
29
+ const defaultServices: ServiceOverviewItem[] = [
30
+ {
31
+ code: 'SVC-01',
32
+ title: 'Discovery',
33
+ description:
34
+ 'We begin by understanding the brief, the existing space, operational needs, and the people who will use it every day.',
35
+ timeline: '8-12 weeks',
36
+ engagement: 'Fixed Fee',
37
+ },
38
+ {
39
+ code: 'SVC-02',
40
+ title: 'Concept',
41
+ description:
42
+ 'We translate business goals into a clear spatial direction, balancing brand, movement, function, and atmosphere.',
43
+ timeline: '12-16 weeks',
44
+ engagement: 'Project-based',
45
+ },
46
+ {
47
+ code: 'SVC-03',
48
+ title: 'Design',
49
+ description:
50
+ 'We develop the interior architecture, material language, lighting, furniture strategy, and practical user experience.',
51
+ timeline: '16-24 weeks',
52
+ engagement: 'Fixed Fee',
53
+ },
54
+ {
55
+ code: 'SVC-04',
56
+ title: 'Documentation',
57
+ description:
58
+ 'We prepare the technical drawings, schedules, and coordination notes required for approval, pricing, and buildability.',
59
+ timeline: '8-12 weeks',
60
+ engagement: 'Project-based',
61
+ },
62
+ {
63
+ code: 'SVC-05',
64
+ title: 'Procurement',
65
+ description:
66
+ 'We support selection, specification, and sourcing so the design intent remains consistent through delivery.',
67
+ timeline: '12-16 weeks',
68
+ engagement: 'Fixed Fee',
69
+ },
70
+ {
71
+ code: 'SVC-06',
72
+ title: 'Handover',
73
+ description:
74
+ 'We help close the project with practical review, refinements, and alignment between the completed space and the original intent.',
75
+ timeline: '16-24 weeks',
76
+ engagement: 'Project-based',
77
+ },
78
+ ]
79
+
80
+ function getServices(services?: ServiceOverviewItem[] | null) {
81
+ return Array.isArray(services) && services.length > 0 ? services : defaultServices
82
+ }
83
+
84
+ function getDeliverableCount(service: ServiceOverviewItem) {
85
+ return Array.isArray(service.details) && service.details.length > 0 ? service.details.length : 4
86
+ }
87
+
88
+ function slugifyServiceTitle(value?: string | null) {
89
+ if (!value) return ''
90
+ return value
91
+ .trim()
92
+ .toLowerCase()
93
+ .replace(/&/g, 'and')
94
+ .replace(/[^a-z0-9]+/g, '-')
95
+ .replace(/^-+|-+$/g, '')
96
+ }
97
+
98
+ function scrollToServiceDetail(title?: string | null) {
99
+ const targetId = slugifyServiceTitle(title)
100
+ if (!targetId) return
101
+ const targetEl = document.getElementById(targetId)
102
+ if (targetEl) {
103
+ const targetTop = targetEl.getBoundingClientRect().top + window.scrollY - 120
104
+ window.history.pushState(null, '', `#${targetId}`)
105
+ window.scrollTo({ top: targetTop, behavior: 'smooth' })
106
+ return
107
+ }
108
+ window.location.hash = targetId
109
+ }
110
+
111
+ export const DDHGServiceOverview: React.FC<DDHGServiceOverviewProps> = ({
112
+ sectionId,
113
+ customClassName,
114
+ customCSS,
115
+ blockId,
116
+ scrollSpeed,
117
+ eyebrowPrefix = 'service',
118
+ eyebrowEmphasis = 'catalog',
119
+ services,
120
+ }) => {
121
+ const serviceItems = useMemo(() => getServices(services), [services])
122
+ const [activeIndex, setActiveIndex] = useState(0)
123
+ const activeService = serviceItems[activeIndex] || serviceItems[0]
124
+ if (!activeService) return null
125
+
126
+ return (
127
+ <div
128
+ id={sectionId ?? undefined}
129
+ data-block-id={blockId ?? undefined}
130
+ data-scroll={scrollSpeed ? true : undefined}
131
+ data-scroll-speed={scrollSpeed ? String(scrollSpeed) : undefined}
132
+ className={cn('px-5 py-16 md:px-8', customClassName)}
133
+ >
134
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
135
+ <div className="ServiceOverview01 mx-auto max-w-7xl">
136
+ <motion.div
137
+ className="ServiceOverview02 mb-10"
138
+ initial={{ opacity: 0, y: 20 }}
139
+ whileInView={{ opacity: 1, y: 0 }}
140
+ viewport={{ once: true, amount: 0.25 }}
141
+ transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
142
+ >
143
+ {(eyebrowPrefix || eyebrowEmphasis) && (
144
+ <p className="ServiceOverview03 mb-2 text-xs uppercase tracking-[0.2em] text-stone-600 dark:text-stone-300">
145
+ {eyebrowPrefix && <span className="font-light">{eyebrowPrefix} </span>}
146
+ {eyebrowEmphasis && <span className="font-medium">{eyebrowEmphasis}</span>}
147
+ </p>
148
+ )}
149
+ </motion.div>
150
+ <div className="ServiceOverview04 relative">
151
+ <div className="ServiceOverview05 absolute left-0 right-0 top-6 hidden h-px bg-[#444444] md:block" />
152
+ <div className="ServiceOverview06 grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">
153
+ {serviceItems.map((service, i) => {
154
+ const isActive = activeIndex === i
155
+ return (
156
+ <motion.button
157
+ key={service.id || service.code || `service-${i}`}
158
+ type="button"
159
+ className="ServiceOverview07 group relative cursor-pointer text-left"
160
+ initial={{ opacity: 0, y: 20 }}
161
+ whileInView={{ opacity: 1, y: 0 }}
162
+ viewport={{ once: true, amount: 0.2 }}
163
+ transition={{ duration: 0.9, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] }}
164
+ onMouseEnter={() => setActiveIndex(i)}
165
+ onFocus={() => setActiveIndex(i)}
166
+ onClick={() => scrollToServiceDetail(service.title)}
167
+ >
168
+ <div className="ServiceOverview08 flex flex-col items-center text-center">
169
+ <div
170
+ className={cn(
171
+ 'ServiceOverview09 mb-3 flex h-12 w-12 items-center justify-center rounded-md squircle border transition-all duration-500',
172
+ isActive
173
+ ? 'border-[#444444] bg-[#444444]'
174
+ : 'border-[#444444] bg-[#e5e7eb] group-hover:border-[#A8A29E]',
175
+ )}
176
+ style={{ borderRadius: '6px' }}
177
+ >
178
+ <span
179
+ className={cn(
180
+ 'ServiceOverview10 text-xs font-semibold transition-colors duration-500',
181
+ isActive ? 'text-[#F5F3EF]' : 'text-stone-600 dark:text-stone-300',
182
+ )}
183
+ >
184
+ {String(i + 1).padStart(2, '0')}
185
+ </span>
186
+ </div>
187
+ <span
188
+ className={cn(
189
+ 'ServiceOverview11 text-base font-medium uppercase tracking-widest transition-colors duration-500',
190
+ isActive
191
+ ? 'text-stone-700 dark:text-stone-200'
192
+ : 'text-stone-600 dark:text-stone-300 group-hover:text-stone-700',
193
+ )}
194
+ >
195
+ {service.title}
196
+ </span>
197
+ {service.code && (
198
+ <span className="ServiceOverview12 mt-1 font-mono text-xs text-stone-600 dark:text-stone-300">
199
+ {service.code}
200
+ </span>
201
+ )}
202
+ </div>
203
+ </motion.button>
204
+ )
205
+ })}
206
+ </div>
207
+ </div>
208
+ <motion.div
209
+ key={`service-overview-card-${activeIndex}`}
210
+ className="ServiceOverview13 mt-10"
211
+ initial={{ opacity: 0, x: 20 }}
212
+ animate={{ opacity: 1, x: 0 }}
213
+ transition={{ duration: 0.2, delay: 0.05, ease: [0.22, 1, 0.36, 1] }}
214
+ >
215
+ <motion.div
216
+ className="ServiceOverview14 z-plane-1 rounded-md squircle border border-stone-300 bg-stone-100/50 p-6 transition-all duration-300 dark:border-stone-700/10 dark:bg-stone-900/50 md:p-8"
217
+ initial={{ x: -40, filter: 'blur(20px)', opacity: 0 }}
218
+ animate={{ x: 0, filter: 'blur(0px)', opacity: 1 }}
219
+ transition={{ duration: 0.2, delay: 0.08, ease: [0.22, 1, 0.36, 1] }}
220
+ >
221
+ <div className="flex flex-col gap-6 md:flex-row md:gap-10">
222
+ <div className="shrink-0">
223
+ {activeService.code && (
224
+ <span className="mb-2 block font-mono text-xs uppercase tracking-[0.15em] text-stone-600 dark:text-stone-300">
225
+ {activeService.code}
226
+ </span>
227
+ )}
228
+ {activeService.title && (
229
+ <h3 className="mb-2 text-xl font-semibold text-purple-700 dark:text-purple-500">
230
+ {activeService.title}
231
+ </h3>
232
+ )}
233
+ <div className="mb-3 h-px w-8 bg-stone-500" />
234
+ {activeService.description && (
235
+ <p className="max-w-md text-[14px] font-light leading-relaxed text-stone-700 dark:text-stone-200">
236
+ {activeService.description}
237
+ </p>
238
+ )}
239
+ </div>
240
+ <div className="flex grow flex-col gap-5 md:flex-row md:items-center md:gap-8">
241
+ <div className="text-center">
242
+ <p className="mb-1 text-xs uppercase tracking-[0.15em] text-stone-600 dark:text-stone-300">
243
+ Typical Timeline
244
+ </p>
245
+ <p className="text-md font-semibold text-stone-700 dark:text-stone-200">
246
+ {activeService.timeline || '8-12 weeks'}
247
+ </p>
248
+ </div>
249
+ <div className="hidden h-10 w-px bg-stone-500 md:block" />
250
+ <div className="text-center">
251
+ <p className="mb-1 text-xs uppercase tracking-[0.15em] text-stone-600 dark:text-stone-300">
252
+ Deliverables
253
+ </p>
254
+ <p className="inline-flex items-center gap-2 text-md font-semibold text-stone-700 dark:text-stone-200">
255
+ <CircleDot className="h-3.5 w-3.5" strokeWidth={1.7} />
256
+ {getDeliverableCount(activeService)}
257
+ </p>
258
+ </div>
259
+ <div className="hidden h-10 w-px bg-stone-500 md:block" />
260
+ <div className="text-center">
261
+ <p className="mb-1 text-xs uppercase tracking-[0.15em] text-stone-600 dark:text-stone-300">
262
+ Engagement
263
+ </p>
264
+ <p className="text-md font-semibold text-stone-700 dark:text-stone-200">
265
+ {activeService.engagement || 'Fixed Fee'}
266
+ </p>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </motion.div>
271
+ </motion.div>
272
+ </div>
273
+ </div>
274
+ )
275
+ }
276
+
277
+ export default DDHGServiceOverview
@@ -0,0 +1,64 @@
1
+ import type { Block } from 'payload'
2
+
3
+ export const DDHGServiceOverview: Block = {
4
+ slug: 'DDHG-ServiceOverview',
5
+ interfaceName: 'DDHGServiceOverview',
6
+ dbName: 'ddhg_svc_overview',
7
+ labels: {
8
+ singular: 'Service Overview',
9
+ plural: 'Service Overviews',
10
+ },
11
+ fields: [
12
+ {
13
+ type: 'tabs',
14
+ tabs: [
15
+ {
16
+ label: 'Content',
17
+ fields: [
18
+ {
19
+ type: 'row',
20
+ fields: [
21
+ { name: 'eyebrowPrefix', type: 'text', label: 'Eyebrow Prefix', defaultValue: 'service', admin: { width: '50%' } },
22
+ { name: 'eyebrowEmphasis', type: 'text', label: 'Eyebrow Emphasis', defaultValue: 'catalog', admin: { width: '50%' } },
23
+ ],
24
+ },
25
+ {
26
+ name: 'services', type: 'array', label: 'Services', minRows: 1, maxRows: 8,
27
+ labels: { singular: 'Service', plural: 'Services' },
28
+ fields: [
29
+ {
30
+ type: 'row', fields: [
31
+ { name: 'code', type: 'text', label: 'Code', defaultValue: 'SVC-01', admin: { width: '33.33%' } },
32
+ { name: 'title', type: 'text', label: 'Title', required: true, admin: { width: '33.33%' } },
33
+ { name: 'timeline', type: 'text', label: 'Typical Timeline', defaultValue: '8-12 weeks', admin: { width: '33.33%' } },
34
+ ],
35
+ },
36
+ { name: 'description', type: 'textarea', label: 'Description' },
37
+ {
38
+ type: 'row', fields: [
39
+ { name: 'engagement', type: 'text', label: 'Engagement', defaultValue: 'Fixed Fee', admin: { width: '50%' } },
40
+ {
41
+ name: 'details', type: 'array', label: 'Deliverables', admin: { width: '50%' },
42
+ fields: [{ name: 'label', type: 'text', label: 'Deliverable', required: true }],
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ label: 'Advanced',
52
+ fields: [
53
+ { type: 'row', fields: [
54
+ { name: 'sectionId', type: 'text', label: 'Section ID', admin: { width: '50%' } },
55
+ { name: 'customClassName', type: 'text', label: 'Custom CSS Classes', admin: { width: '50%' } },
56
+ ] },
57
+ { name: 'scrollSpeed', type: 'text', label: 'Scroll Speed', admin: { description: 'Optional data-scroll-speed value.' } },
58
+ { name: 'customCSS', type: 'code', label: 'Custom CSS', admin: { language: 'css', description: 'Injected as a style tag. Use with caution.' } },
59
+ ],
60
+ },
61
+ ],
62
+ },
63
+ ],
64
+ }