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
@@ -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"
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-[10px] 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 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: '2px' }}
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-[13px] font-medium uppercase tracking-[0.1em] 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-[10px] 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 p-6 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="flex-shrink-0">
223
+ {activeService.code && (
224
+ <span className="mb-2 block font-mono text-[10px] 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 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-[10px] 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-[10px] 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-[10px] 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
+ }