blocks-dusted 0.1.12 → 0.1.13

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 (148) hide show
  1. package/package.json +1 -1
  2. package/src/installers/patchRenderBlocks.js +1 -1
  3. package/src/registry/validateTemplateManifest.js +2 -1
  4. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  5. package/templates/blocks/BannerDD790432/config.ts +54 -0
  6. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  7. package/templates/blocks/Button47/Component.tsx +124 -0
  8. package/templates/blocks/Button47/config.ts +75 -0
  9. package/templates/blocks/Button47/link.ts +22 -0
  10. package/templates/blocks/Button47/manifest.json +105 -0
  11. package/templates/blocks/Button47/styles.module.scss +75 -0
  12. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  13. package/templates/blocks/DD-Accordion/config.ts +71 -59
  14. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  15. package/templates/blocks/DD-AnimText/config.ts +296 -295
  16. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  17. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  18. package/templates/blocks/DD-BookCall/Component.tsx +535 -535
  19. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  20. package/templates/blocks/DD-Card/Component.tsx +4 -4
  21. package/templates/blocks/DD-CardTemplate01/Component.tsx +139 -140
  22. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  23. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  24. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  25. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  28. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  29. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  30. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  31. package/templates/blocks/DD-Contact/Component.tsx +433 -435
  32. package/templates/blocks/DD-Counter/Component.tsx +11 -6
  33. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  34. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  35. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  36. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  37. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  38. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  39. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  40. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  41. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  42. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  43. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  44. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  45. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  46. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  47. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  48. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  49. package/templates/blocks/DD-Hero/Component.tsx +296 -297
  50. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  51. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  52. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  53. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  54. package/templates/blocks/DD-HorizontalScroll/Component.tsx +937 -936
  55. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  56. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  57. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  58. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  59. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  60. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  61. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -545
  62. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  63. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  64. package/templates/blocks/DD-Masonry/config.ts +475 -475
  65. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  66. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  67. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  68. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  69. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  70. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  71. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  72. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  73. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  74. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  75. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  76. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  77. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  78. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  79. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  80. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  81. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  82. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  83. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  84. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  85. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  86. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  87. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  88. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  89. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  90. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  91. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  92. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  95. package/templates/blocks/DD-Pricing/Component.tsx +2 -3
  96. package/templates/blocks/DD-Pricing/config.ts +215 -215
  97. package/templates/blocks/DD-Process/Component.tsx +2 -2
  98. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  99. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  100. package/templates/blocks/DD-Section/Component.tsx +265 -266
  101. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  102. package/templates/blocks/DD-Services/Component.tsx +176 -176
  103. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  104. package/templates/blocks/DD-Slider-A/Component.tsx +2 -2
  105. package/templates/blocks/DD-StackCards/Component.tsx +172 -171
  106. package/templates/blocks/DD-Team/Component.tsx +247 -247
  107. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  108. package/templates/blocks/DD-TechStack/Component.tsx +173 -174
  109. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  110. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  111. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  112. package/templates/blocks/DD-Testimonials/Component.tsx +139 -139
  113. package/templates/blocks/DD-Thanks/Component.tsx +1 -1
  114. package/templates/blocks/DD-Work/Component.tsx +318 -318
  115. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  116. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  117. package/templates/blocks/DDHG-HighlightArc/Component.tsx +6 -3
  118. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +45 -8
  119. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +1 -3
  120. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +9 -22
  121. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  122. package/templates/blocks/DDHG-TeamCards/Component.tsx +6 -35
  123. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  124. package/templates/blocks/Form/Component.tsx +163 -0
  125. package/templates/blocks/Form/Country/index.tsx +65 -0
  126. package/templates/blocks/Form/Country/options.ts +982 -0
  127. package/templates/blocks/Form/Email/index.tsx +38 -0
  128. package/templates/blocks/Form/Error/index.tsx +15 -0
  129. package/templates/blocks/Form/Message/index.tsx +13 -0
  130. package/templates/blocks/Form/Number/index.tsx +36 -0
  131. package/templates/blocks/Form/Select/index.tsx +63 -0
  132. package/templates/blocks/Form/State/index.tsx +64 -0
  133. package/templates/blocks/Form/State/options.ts +52 -0
  134. package/templates/blocks/Form/Text/index.tsx +32 -0
  135. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  136. package/templates/blocks/Form/Width/index.tsx +13 -0
  137. package/templates/blocks/Form/config.ts +51 -0
  138. package/templates/blocks/Form/fields.tsx +21 -0
  139. package/templates/blocks/Form/manifest.json +168 -0
  140. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  141. package/templates/blocks/MediaBlock/config.ts +14 -0
  142. package/templates/blocks/MediaBlock/manifest.json +92 -0
  143. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  144. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  145. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  146. package/templates/blocks/rawHtml/Component.tsx +25 -0
  147. package/templates/blocks/rawHtml/config.ts +56 -0
  148. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -1,307 +1,306 @@
1
- 'use client'
2
-
3
- import Image from 'next/image'
4
- import React, { useEffect, useMemo, useRef, useState } from 'react'
5
- import { cn } from '@/utilities/ui'
6
- type Media = { url?: string | null; alt?: string | null; [key: string]: any }
7
-
8
- export type DDShowcaseGridProps = Record<string, any> & {
9
- stickToTop?: boolean | null
10
- fadeOutOnScroll?: boolean | null
11
- }
12
-
13
- type ColumnImage = {
14
- image?: Media | string | null
15
- imageUrl?: string | null
16
- [key: string]: any
17
- }
18
-
19
- type ShowcaseColumn = {
20
- id?: string | null
21
- images?: ColumnImage[] | null
22
- speed?: number | null
23
- initialOffset?: number | null
24
- }
25
-
26
- interface ScrollColumnProps {
27
- images: ColumnImage[]
28
- speed: number
29
- initialOffset: number
30
- priority?: boolean
31
- }
32
-
33
- function isLocalImageSrc(src?: string | null): src is string {
34
- return typeof src === 'string' && src.startsWith('/api/media/file/')
35
- }
36
-
37
- function ScrollColumn({ images, speed, initialOffset, priority = false }: ScrollColumnProps) {
38
- const colRef = useRef<HTMLDivElement>(null)
39
- const posRef = useRef(initialOffset)
40
- const rafRef = useRef<number | null>(null)
41
- const [mounted, setMounted] = useState(false)
42
-
43
- const resolveSrc = (item: ColumnImage): string | null => {
44
- if (item.image && typeof item.image === 'object') {
45
- const media = item.image as Media
46
- const url = media.url
47
-
48
- if (!url) return isLocalImageSrc(item.imageUrl) ? item.imageUrl : null
49
-
50
- return isLocalImageSrc(url) ? url : null
51
- }
52
-
53
- return isLocalImageSrc(item.imageUrl) ? item.imageUrl : null
54
- }
55
-
56
- const validImages = useMemo(() => {
57
- return (images ?? []).filter((item) => resolveSrc(item) !== null)
58
- }, [images])
59
-
60
- useEffect(() => {
61
- setMounted(true)
62
- }, [])
63
-
64
- useEffect(() => {
65
- if (!mounted) return
66
-
67
- const el = colRef.current
68
- if (!el) return
69
-
70
- const animate = () => {
71
- posRef.current -= speed
72
-
73
- const totalHeight = el.scrollHeight / 2
74
-
75
- if (totalHeight > 0 && Math.abs(posRef.current) >= totalHeight) {
76
- posRef.current = 0
77
- }
78
-
79
- el.style.transform = `translateY(${posRef.current}px)`
80
- rafRef.current = requestAnimationFrame(animate)
81
- }
82
-
83
- const timer = window.setTimeout(() => {
84
- rafRef.current = requestAnimationFrame(animate)
85
- }, 1200)
86
-
87
- return () => {
88
- window.clearTimeout(timer)
89
-
90
- if (rafRef.current) {
91
- cancelAnimationFrame(rafRef.current)
92
- rafRef.current = null
93
- }
94
- }
95
- }, [mounted, speed])
96
-
97
- if (validImages.length === 0) return null
98
-
99
- const renderImages = mounted ? [...validImages, ...validImages] : validImages
100
-
101
- return (
102
- <div className="flex flex-col gap-4 will-change-transform" ref={colRef}>
103
- {renderImages.map((item, i) => {
104
- const src = resolveSrc(item)
105
- if (!src) return null
106
-
107
- const isClone = i >= validImages.length
108
- const shouldPrioritize = Boolean(priority) && !isClone
109
-
110
- return (
111
- <div
112
- key={`${item.id ?? 'image'}-${i}`}
113
- className="relative aspect-4/3 w-full shrink-0 overflow-hidden rounded-lg border border-white/6"
114
- >
115
- <Image
116
- src={src}
117
- alt={item.alt ?? ''}
118
- fill
119
- sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
120
- className="object-cover object-top"
121
- priority={shouldPrioritize}
122
- loading="eager"
123
- // loading={shouldPrioritize ? 'eager' : 'lazy'}
124
- fetchPriority={shouldPrioritize ? 'high' : 'auto'}
125
- />
126
- </div>
127
- )
128
- })}
129
- </div>
130
- )
131
- }
132
-
133
- export const DDShowcaseGrid: React.FC<DDShowcaseGridProps> = ({
134
- columns,
135
- height = 520,
136
- columnHeight = 3200,
137
- sectionId,
138
- customCSS,
139
- priority = false,
140
- stickToTop = false,
141
- fadeOutOnScroll = false,
142
- }) => {
143
- const sectionHeight = height ?? 520
144
- const colHeight = columnHeight ?? 3200
145
- const shouldStick = Boolean(stickToTop)
146
- const shouldFade = Boolean(fadeOutOnScroll)
147
- const columnItems = (columns ?? []) as ShowcaseColumn[]
148
- const wrapperRef = useRef<HTMLDivElement | null>(null)
149
- const stickyRef = useRef<HTMLDivElement | null>(null)
150
- const fadeDistance = Math.max(1, sectionHeight)
151
-
152
- useEffect(() => {
153
- if (!shouldFade) {
154
- if (stickyRef.current) stickyRef.current.style.opacity = '1'
155
- return
156
- }
157
-
158
- let rafId = 0
159
-
160
- const updateOpacity = () => {
161
- if (rafId) window.cancelAnimationFrame(rafId)
162
-
163
- rafId = window.requestAnimationFrame(() => {
164
- const wrapper = wrapperRef.current
165
- const sticky = stickyRef.current
166
-
167
- if (!wrapper || !sticky) return
168
-
169
- const rect = wrapper.getBoundingClientRect()
170
- const progress = Math.min(1, Math.max(0, -rect.top / fadeDistance))
171
-
172
- sticky.style.opacity = String(1 - progress)
173
- })
174
- }
175
-
176
- updateOpacity()
177
-
178
- window.addEventListener('scroll', updateOpacity, { passive: true })
179
- window.addEventListener('resize', updateOpacity)
180
- window.addEventListener('locomotive:scroll', updateOpacity)
181
- window.addEventListener('locomotive:resize', updateOpacity)
182
-
183
- return () => {
184
- window.removeEventListener('scroll', updateOpacity)
185
- window.removeEventListener('resize', updateOpacity)
186
- window.removeEventListener('locomotive:scroll', updateOpacity)
187
- window.removeEventListener('locomotive:resize', updateOpacity)
188
-
189
- if (rafId) {
190
- window.cancelAnimationFrame(rafId)
191
- }
192
- }
193
- }, [fadeDistance, shouldFade])
194
-
195
- return (
196
- <>
197
- <style>{`
198
- .studio-showcase-grid {
199
- --sg-perspective: 900px;
200
- --sg-perspective-origin: 50% 40%;
201
- --sg-transform: rotateX(52deg) rotateZ(-42deg);
202
- --sg-top: -160%;
203
- --sg-bottom: -90%;
204
- --sg-sides: -30%;
205
- }
206
-
207
- @media (max-width: 1023px) {
208
- .studio-showcase-grid {
209
- --sg-perspective-origin: 50% 40%;
210
- --sg-transform: rotateX(52deg) rotateZ(-42deg);
211
- --sg-top: -90%;
212
- --sg-bottom: -30%;
213
- --sg-sides: -50%;
214
- max-height: 420px;
215
- }
216
- }
217
-
218
- @media (max-width: 639px) {
219
- .studio-showcase-grid {
220
- --sg-perspective-origin: 50% 40%;
221
- --sg-transform: rotateX(52deg) rotateZ(-42deg);
222
- --sg-top: -60%;
223
- --sg-bottom: 0%;
224
- --sg-sides: -90%;
225
- max-height: 300px;
226
- }
227
- }
228
- `}</style>
229
-
230
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
231
-
232
- <div
233
- data-content-type="ddShowcaseGrid"
234
- ref={wrapperRef}
235
- className={cn('relative overflow-clip', shouldStick || shouldFade ? 'z-0' : '')}
236
- style={{
237
- height: shouldStick || shouldFade ? `${sectionHeight + fadeDistance}px` : undefined,
238
- }}
239
- >
240
- <div
241
- ref={stickyRef}
242
- className={cn(
243
- 'relative will-change-opacity',
244
- shouldStick || shouldFade ? 'sticky top-0' : '',
245
- )}
246
- >
247
- <div className="pointer-events-none absolute left-0 right-0 top-0 bottom-0 z-10 h-[150vh] bg-[linear-gradient(0deg,#ffffff,#ffffff,#ffffff,#ffffff,transparent)] dark:bg-[linear-gradient(0deg,#0a0a0a,#0a0a0a,#0a0a0a,#0a0a0a,transparent)] opacity-80 mix-blend-color-dodge" />
248
- <div
249
- id={sectionId ?? undefined}
250
- className="studio-showcase-grid relative z-0! overflow-hidden"
251
- style={{ height: `${sectionHeight}px` }}
252
- >
253
- #BlockName-DDShowcaseGrid
254
- <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-80 bg-linear-to-b from-white to-transparent dark:from-[#0a0a0a]" />
255
- <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-80 bg-linear-to-t from-white to-transparent dark:from-[#0a0a0a]" />
256
- <div className="pointer-events-none absolute inset-y-0 left-0 z-10 w-40 bg-linear-to-r from-white to-transparent dark:from-[#0a0a0a]" />
257
- <div className="pointer-events-none absolute inset-y-0 right-0 z-10 w-40 bg-linear-to-l from-white to-transparent dark:from-[#0a0a0a]" />
258
-
259
- <div
260
- className="absolute inset-0"
261
- style={{
262
- perspective: 'var(--sg-perspective)',
263
- perspectiveOrigin: 'var(--sg-perspective-origin)',
264
- }}
265
- >
266
- <div
267
- style={{
268
- transform: 'var(--sg-transform)',
269
- transformOrigin: 'center center',
270
- position: 'absolute',
271
- top: 'var(--sg-top)',
272
- left: 'var(--sg-sides)',
273
- right: 'var(--sg-sides)',
274
- bottom: 'var(--sg-bottom)',
275
- display: 'flex',
276
- gap: '16px',
277
- alignItems: 'flex-start',
278
- }}
279
- >
280
- {columnItems.map((col, colIndex) => (
281
- <div
282
- key={col.id ?? colIndex}
283
- className="studio-showcase-col"
284
- style={{
285
- flex: '1',
286
- overflow: 'hidden',
287
- height: `${colHeight}px`,
288
- }}
289
- >
290
- <ScrollColumn
291
- images={col.images ?? []}
292
- speed={col.speed ?? 0.5}
293
- initialOffset={col.initialOffset ?? 0}
294
- priority={Boolean(priority)}
295
- />
296
- </div>
297
- ))}
298
- </div>
299
- </div>
300
- </div>
301
- </div>
302
- </div>
303
- </>
304
- )
305
- }
306
-
307
- export default DDShowcaseGrid
1
+ 'use client'
2
+
3
+ import Image from 'next/image'
4
+ import React, { useEffect, useMemo, useRef, useState } from 'react'
5
+ import { cn } from '@/utilities/ui'
6
+ type Media = { url?: string | null; alt?: string | null; [key: string]: any }
7
+
8
+ export type DDShowcaseGridProps = Record<string, any> & {
9
+ stickToTop?: boolean | null
10
+ fadeOutOnScroll?: boolean | null
11
+ }
12
+
13
+ type ColumnImage = {
14
+ image?: Media | string | null
15
+ imageUrl?: string | null
16
+ [key: string]: any
17
+ }
18
+
19
+ type ShowcaseColumn = {
20
+ id?: string | null
21
+ images?: ColumnImage[] | null
22
+ speed?: number | null
23
+ initialOffset?: number | null
24
+ }
25
+
26
+ interface ScrollColumnProps {
27
+ images: ColumnImage[]
28
+ speed: number
29
+ initialOffset: number
30
+ priority?: boolean
31
+ }
32
+
33
+ function isLocalImageSrc(src?: string | null): src is string {
34
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
35
+ }
36
+
37
+ function ScrollColumn({ images, speed, initialOffset, priority = false }: ScrollColumnProps) {
38
+ const colRef = useRef<HTMLDivElement>(null)
39
+ const posRef = useRef(initialOffset)
40
+ const rafRef = useRef<number | null>(null)
41
+ const [mounted, setMounted] = useState(false)
42
+
43
+ const resolveSrc = (item: ColumnImage): string | null => {
44
+ if (item.image && typeof item.image === 'object') {
45
+ const media = item.image as Media
46
+ const url = media.url
47
+
48
+ if (!url) return isLocalImageSrc(item.imageUrl) ? item.imageUrl : null
49
+
50
+ return isLocalImageSrc(url) ? url : null
51
+ }
52
+
53
+ return isLocalImageSrc(item.imageUrl) ? item.imageUrl : null
54
+ }
55
+
56
+ const validImages = useMemo(() => {
57
+ return (images ?? []).filter((item) => resolveSrc(item) !== null)
58
+ }, [images])
59
+
60
+ useEffect(() => {
61
+ setMounted(true)
62
+ }, [])
63
+
64
+ useEffect(() => {
65
+ if (!mounted) return
66
+
67
+ const el = colRef.current
68
+ if (!el) return
69
+
70
+ const animate = () => {
71
+ posRef.current -= speed
72
+
73
+ const totalHeight = el.scrollHeight / 2
74
+
75
+ if (totalHeight > 0 && Math.abs(posRef.current) >= totalHeight) {
76
+ posRef.current = 0
77
+ }
78
+
79
+ el.style.transform = `translateY(${posRef.current}px)`
80
+ rafRef.current = requestAnimationFrame(animate)
81
+ }
82
+
83
+ const timer = window.setTimeout(() => {
84
+ rafRef.current = requestAnimationFrame(animate)
85
+ }, 1200)
86
+
87
+ return () => {
88
+ window.clearTimeout(timer)
89
+
90
+ if (rafRef.current) {
91
+ cancelAnimationFrame(rafRef.current)
92
+ rafRef.current = null
93
+ }
94
+ }
95
+ }, [mounted, speed])
96
+
97
+ if (validImages.length === 0) return null
98
+
99
+ const renderImages = mounted ? [...validImages, ...validImages] : validImages
100
+
101
+ return (
102
+ <div className="flex flex-col gap-4 will-change-transform" ref={colRef}>
103
+ {renderImages.map((item, i) => {
104
+ const src = resolveSrc(item)
105
+ if (!src) return null
106
+
107
+ const isClone = i >= validImages.length
108
+ const shouldPrioritize = Boolean(priority) && !isClone
109
+
110
+ return (
111
+ <div
112
+ key={`${item.id ?? 'image'}-${i}`}
113
+ className="relative aspect-4/3 w-full shrink-0 overflow-hidden rounded-lg border border-white/6"
114
+ >
115
+ <Image
116
+ src={src}
117
+ alt={item.alt ?? ''}
118
+ fill
119
+ sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
120
+ className="object-cover object-top"
121
+ priority={shouldPrioritize}
122
+ loading="eager"
123
+ // loading={shouldPrioritize ? 'eager' : 'lazy'}
124
+ fetchPriority={shouldPrioritize ? 'high' : 'auto'}
125
+ />
126
+ </div>
127
+ )
128
+ })}
129
+ </div>
130
+ )
131
+ }
132
+
133
+ export const DDShowcaseGrid: React.FC<DDShowcaseGridProps> = ({
134
+ columns,
135
+ height = 520,
136
+ columnHeight = 3200,
137
+ sectionId,
138
+ customCSS,
139
+ priority = false,
140
+ stickToTop = false,
141
+ fadeOutOnScroll = false,
142
+ }) => {
143
+ const sectionHeight = height ?? 520
144
+ const colHeight = columnHeight ?? 3200
145
+ const shouldStick = Boolean(stickToTop)
146
+ const shouldFade = Boolean(fadeOutOnScroll)
147
+ const columnItems = (columns ?? []) as ShowcaseColumn[]
148
+ const wrapperRef = useRef<HTMLDivElement | null>(null)
149
+ const stickyRef = useRef<HTMLDivElement | null>(null)
150
+ const fadeDistance = Math.max(1, sectionHeight)
151
+
152
+ useEffect(() => {
153
+ if (!shouldFade) {
154
+ if (stickyRef.current) stickyRef.current.style.opacity = '1'
155
+ return
156
+ }
157
+
158
+ let rafId = 0
159
+
160
+ const updateOpacity = () => {
161
+ if (rafId) window.cancelAnimationFrame(rafId)
162
+
163
+ rafId = window.requestAnimationFrame(() => {
164
+ const wrapper = wrapperRef.current
165
+ const sticky = stickyRef.current
166
+
167
+ if (!wrapper || !sticky) return
168
+
169
+ const rect = wrapper.getBoundingClientRect()
170
+ const progress = Math.min(1, Math.max(0, -rect.top / fadeDistance))
171
+
172
+ sticky.style.opacity = String(1 - progress)
173
+ })
174
+ }
175
+
176
+ updateOpacity()
177
+
178
+ window.addEventListener('scroll', updateOpacity, { passive: true })
179
+ window.addEventListener('resize', updateOpacity)
180
+ window.addEventListener('locomotive:scroll', updateOpacity)
181
+ window.addEventListener('locomotive:resize', updateOpacity)
182
+
183
+ return () => {
184
+ window.removeEventListener('scroll', updateOpacity)
185
+ window.removeEventListener('resize', updateOpacity)
186
+ window.removeEventListener('locomotive:scroll', updateOpacity)
187
+ window.removeEventListener('locomotive:resize', updateOpacity)
188
+
189
+ if (rafId) {
190
+ window.cancelAnimationFrame(rafId)
191
+ }
192
+ }
193
+ }, [fadeDistance, shouldFade])
194
+
195
+ return (
196
+ <>
197
+ <style>{`
198
+ .studio-showcase-grid {
199
+ --sg-perspective: 900px;
200
+ --sg-perspective-origin: 50% 40%;
201
+ --sg-transform: rotateX(52deg) rotateZ(-42deg);
202
+ --sg-top: -160%;
203
+ --sg-bottom: -90%;
204
+ --sg-sides: -30%;
205
+ }
206
+
207
+ @media (max-width: 1023px) {
208
+ .studio-showcase-grid {
209
+ --sg-perspective-origin: 50% 40%;
210
+ --sg-transform: rotateX(52deg) rotateZ(-42deg);
211
+ --sg-top: -90%;
212
+ --sg-bottom: -30%;
213
+ --sg-sides: -50%;
214
+ max-height: 420px;
215
+ }
216
+ }
217
+
218
+ @media (max-width: 639px) {
219
+ .studio-showcase-grid {
220
+ --sg-perspective-origin: 50% 40%;
221
+ --sg-transform: rotateX(52deg) rotateZ(-42deg);
222
+ --sg-top: -60%;
223
+ --sg-bottom: 0%;
224
+ --sg-sides: -90%;
225
+ max-height: 300px;
226
+ }
227
+ }
228
+ `}</style>
229
+
230
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
231
+
232
+ <div
233
+ data-content-type="ddShowcaseGrid"
234
+ ref={wrapperRef}
235
+ className={cn('relative w-full overflow-clip', shouldStick || shouldFade ? 'z-0' : '')}
236
+ style={{
237
+ height: shouldStick || shouldFade ? `${sectionHeight + fadeDistance}px` : undefined,
238
+ }}
239
+ >
240
+ <div
241
+ ref={stickyRef}
242
+ className={cn(
243
+ 'relative w-full will-change-opacity',
244
+ shouldStick || shouldFade ? 'sticky top-0' : '',
245
+ )}
246
+ >
247
+ <div className="pointer-events-none absolute left-0 right-0 top-0 bottom-0 z-10 h-[150vh] bg-[linear-gradient(0deg,#ffffff,#ffffff,#ffffff,#ffffff,transparent)] dark:bg-[linear-gradient(0deg,#0a0a0a,#0a0a0a,#0a0a0a,#0a0a0a,transparent)] opacity-80 mix-blend-color-dodge" />
248
+ <div
249
+ id={sectionId ?? undefined}
250
+ className="studio-showcase-grid relative z-0! w-full overflow-hidden"
251
+ style={{ height: `${sectionHeight}px` }}
252
+ >
253
+ <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-80 bg-linear-to-b from-white to-transparent dark:from-[#0a0a0a]" />
254
+ <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-80 bg-linear-to-t from-white to-transparent dark:from-[#0a0a0a]" />
255
+ <div className="pointer-events-none absolute inset-y-0 left-0 z-10 w-40 bg-linear-to-r from-white to-transparent dark:from-[#0a0a0a]" />
256
+ <div className="pointer-events-none absolute inset-y-0 right-0 z-10 w-40 bg-linear-to-l from-white to-transparent dark:from-[#0a0a0a]" />
257
+
258
+ <div
259
+ className="absolute inset-0"
260
+ style={{
261
+ perspective: 'var(--sg-perspective)',
262
+ perspectiveOrigin: 'var(--sg-perspective-origin)',
263
+ }}
264
+ >
265
+ <div
266
+ style={{
267
+ transform: 'var(--sg-transform)',
268
+ transformOrigin: 'center center',
269
+ position: 'absolute',
270
+ top: 'var(--sg-top)',
271
+ left: 'var(--sg-sides)',
272
+ right: 'var(--sg-sides)',
273
+ bottom: 'var(--sg-bottom)',
274
+ display: 'flex',
275
+ gap: '16px',
276
+ alignItems: 'flex-start',
277
+ }}
278
+ >
279
+ {columnItems.map((col, colIndex) => (
280
+ <div
281
+ key={col.id ?? colIndex}
282
+ className="studio-showcase-col"
283
+ style={{
284
+ flex: '1',
285
+ overflow: 'hidden',
286
+ height: `${colHeight}px`,
287
+ }}
288
+ >
289
+ <ScrollColumn
290
+ images={col.images ?? []}
291
+ speed={col.speed ?? 0.5}
292
+ initialOffset={col.initialOffset ?? 0}
293
+ priority={Boolean(priority)}
294
+ />
295
+ </div>
296
+ ))}
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </>
303
+ )
304
+ }
305
+
306
+ export default DDShowcaseGrid
@@ -78,11 +78,11 @@ export const DDSliderA: React.FC<DDSliderAProps> = ({
78
78
  if (!slides.length) return null
79
79
 
80
80
  return (
81
- <section id={sectionId ?? undefined} className="py-20 overflow-hidden">
81
+ <section id={sectionId ?? undefined} className="relative left-0 mx-auto w-screen max-w-none overflow-hidden py-20">
82
82
  {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
83
83
  <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
84
84
  {/* Optional section header */}
85
- #BlockName-DDSliderA
85
+
86
86
  {(eyebrow || heading || subtext) && (
87
87
  <div className="mb-10 text-center">
88
88
  {eyebrow && (