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.
- package/package.json +1 -1
- package/src/installers/patchRenderBlocks.js +1 -1
- package/src/registry/validateTemplateManifest.js +2 -1
- package/templates/blocks/BannerDD790432/Component.tsx +63 -0
- package/templates/blocks/BannerDD790432/config.ts +54 -0
- package/templates/blocks/BannerDD790432/manifest.json +80 -0
- package/templates/blocks/Button47/Component.tsx +124 -0
- package/templates/blocks/Button47/config.ts +75 -0
- package/templates/blocks/Button47/link.ts +22 -0
- package/templates/blocks/Button47/manifest.json +105 -0
- package/templates/blocks/Button47/styles.module.scss +75 -0
- package/templates/blocks/DD-Accordion/Component.tsx +55 -36
- package/templates/blocks/DD-Accordion/config.ts +71 -59
- package/templates/blocks/DD-AnimText/Component.tsx +50 -32
- package/templates/blocks/DD-AnimText/config.ts +296 -295
- package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
- package/templates/blocks/DD-Banner/Component.tsx +54 -51
- package/templates/blocks/DD-BookCall/Component.tsx +535 -535
- package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
- package/templates/blocks/DD-Card/Component.tsx +4 -4
- package/templates/blocks/DD-CardTemplate01/Component.tsx +139 -140
- package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
- package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
- package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
- package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
- package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
- package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
- package/templates/blocks/DD-Contact/Component.tsx +433 -435
- package/templates/blocks/DD-Counter/Component.tsx +11 -6
- package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
- package/templates/blocks/DD-DeliCard/config.ts +186 -0
- package/templates/blocks/DD-DeliCard/manifest.json +94 -0
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
- package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
- package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
- package/templates/blocks/DD-FeatProd/config.ts +173 -0
- package/templates/blocks/DD-FeatProd/manifest.json +93 -0
- package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
- package/templates/blocks/DD-FeatServ/config.ts +213 -0
- package/templates/blocks/DD-FeatServ/manifest.json +113 -0
- package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
- package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
- package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
- package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
- package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
- package/templates/blocks/DD-Hero/Component.tsx +296 -297
- package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
- package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
- package/templates/blocks/DD-HeroPlas/config.ts +33 -0
- package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +937 -936
- package/templates/blocks/DD-Icon/Component.tsx +46 -81
- package/templates/blocks/DD-IconButton/Component.tsx +13 -20
- package/templates/blocks/DD-IdTag/Component.tsx +22 -21
- package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
- package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
- package/templates/blocks/DD-Lottie/Component.tsx +11 -3
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -545
- package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
- package/templates/blocks/DD-Masonry/Component.tsx +654 -644
- package/templates/blocks/DD-Masonry/config.ts +475 -475
- package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
- package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
- package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
- package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
- package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
- package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
- package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
- package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
- package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
- package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
- package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
- package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
- package/templates/blocks/DD-NineCanvas/config.ts +61 -0
- package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
- package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
- package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
- package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
- package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
- package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
- package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
- package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
- package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
- package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
- package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
- package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
- package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
- package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
- package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
- package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
- package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
- package/templates/blocks/DD-Pricing/Component.tsx +2 -3
- package/templates/blocks/DD-Pricing/config.ts +215 -215
- package/templates/blocks/DD-Process/Component.tsx +2 -2
- package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
- package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
- package/templates/blocks/DD-Section/Component.tsx +265 -266
- package/templates/blocks/DD-Separator/Component.tsx +66 -66
- package/templates/blocks/DD-Services/Component.tsx +176 -176
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
- package/templates/blocks/DD-Slider-A/Component.tsx +2 -2
- package/templates/blocks/DD-StackCards/Component.tsx +172 -171
- package/templates/blocks/DD-Team/Component.tsx +247 -247
- package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
- package/templates/blocks/DD-TechStack/Component.tsx +173 -174
- package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
- package/templates/blocks/DD-Testim/Component.tsx +247 -247
- package/templates/blocks/DD-Testim2/Component.tsx +265 -256
- package/templates/blocks/DD-Testimonials/Component.tsx +139 -139
- package/templates/blocks/DD-Thanks/Component.tsx +1 -1
- package/templates/blocks/DD-Work/Component.tsx +318 -318
- package/templates/blocks/DD-Work-B/Component.tsx +294 -294
- package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
- package/templates/blocks/DDHG-HighlightArc/Component.tsx +6 -3
- package/templates/blocks/DDHG-LogoMarquee/Component.tsx +45 -8
- package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +1 -3
- package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +9 -22
- package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
- package/templates/blocks/DDHG-TeamCards/Component.tsx +6 -35
- package/templates/blocks/Form/Checkbox/index.tsx +45 -0
- package/templates/blocks/Form/Component.tsx +163 -0
- package/templates/blocks/Form/Country/index.tsx +65 -0
- package/templates/blocks/Form/Country/options.ts +982 -0
- package/templates/blocks/Form/Email/index.tsx +38 -0
- package/templates/blocks/Form/Error/index.tsx +15 -0
- package/templates/blocks/Form/Message/index.tsx +13 -0
- package/templates/blocks/Form/Number/index.tsx +36 -0
- package/templates/blocks/Form/Select/index.tsx +63 -0
- package/templates/blocks/Form/State/index.tsx +64 -0
- package/templates/blocks/Form/State/options.ts +52 -0
- package/templates/blocks/Form/Text/index.tsx +32 -0
- package/templates/blocks/Form/Textarea/index.tsx +40 -0
- package/templates/blocks/Form/Width/index.tsx +13 -0
- package/templates/blocks/Form/config.ts +51 -0
- package/templates/blocks/Form/fields.tsx +21 -0
- package/templates/blocks/Form/manifest.json +168 -0
- package/templates/blocks/MediaBlock/Component.tsx +67 -0
- package/templates/blocks/MediaBlock/config.ts +14 -0
- package/templates/blocks/MediaBlock/manifest.json +92 -0
- package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
- package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
- package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
- package/templates/blocks/rawHtml/Component.tsx +25 -0
- package/templates/blocks/rawHtml/config.ts +56 -0
- package/templates/blocks/rawHtml/manifest.json +80 -0
|
@@ -1,249 +1,251 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React, { useState, useCallback, useEffect } from 'react'
|
|
4
|
-
import Link from 'next/link'
|
|
5
|
-
import Image from 'next/image'
|
|
6
|
-
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
7
|
-
import { cn } from '@/utilities/ui'
|
|
8
|
-
import useEmblaCarousel from 'embla-carousel-react'
|
|
9
|
-
|
|
10
|
-
type MediaObj = { url?: string | null; alt?: string | null }
|
|
11
|
-
|
|
12
|
-
function isValidImageSrc(src?: string | null): src is string {
|
|
13
|
-
return Boolean(src && src.startsWith('/api/media/file/'))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type CarouselItemA = {
|
|
17
|
-
id?: string | null
|
|
18
|
-
name: string
|
|
19
|
-
category?: string | null
|
|
20
|
-
badge?: string | null
|
|
21
|
-
image?: MediaObj | string | null
|
|
22
|
-
imageFallbackUrl?: string | null
|
|
23
|
-
description?: string | null
|
|
24
|
-
href?: string | null
|
|
25
|
-
features?: Array<{ id?: string | null; label?: string | null }> | null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type DDCarouselAProps = {
|
|
29
|
-
blockType: 'DD-Carousel-A'
|
|
30
|
-
blockName?: string | null
|
|
31
|
-
eyebrow?: string | null
|
|
32
|
-
heading?: string | null
|
|
33
|
-
subtext?: string | null
|
|
34
|
-
viewAllLabel?: string | null
|
|
35
|
-
viewAllHref?: string | null
|
|
36
|
-
items?: CarouselItemA[] | null
|
|
37
|
-
sectionId?: string | null
|
|
38
|
-
customCSS?: string | null
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const DDCarouselA: React.FC<DDCarouselAProps> = ({
|
|
42
|
-
eyebrow,
|
|
43
|
-
heading,
|
|
44
|
-
subtext,
|
|
45
|
-
viewAllLabel = 'View All',
|
|
46
|
-
viewAllHref,
|
|
47
|
-
items: itemsProp,
|
|
48
|
-
sectionId,
|
|
49
|
-
customCSS,
|
|
50
|
-
}) => {
|
|
51
|
-
const items = itemsProp ?? []
|
|
52
|
-
const [emblaRef, emblaApi] = useEmblaCarousel({
|
|
53
|
-
align: 'start',
|
|
54
|
-
slidesToScroll: 1,
|
|
55
|
-
containScroll: 'trimSnaps',
|
|
56
|
-
dragFree: false,
|
|
57
|
-
})
|
|
58
|
-
const [activeIdx, setActiveIdx] = useState(0)
|
|
59
|
-
const [canPrev, setCanPrev] = useState(false)
|
|
60
|
-
const [canNext, setCanNext] = useState(true)
|
|
61
|
-
|
|
62
|
-
const onSelect = useCallback(() => {
|
|
63
|
-
if (!emblaApi) return
|
|
64
|
-
setActiveIdx(emblaApi.selectedScrollSnap())
|
|
65
|
-
setCanPrev(emblaApi.canScrollPrev())
|
|
66
|
-
setCanNext(emblaApi.canScrollNext())
|
|
67
|
-
}, [emblaApi])
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (!emblaApi) return
|
|
71
|
-
onSelect()
|
|
72
|
-
emblaApi.on('select', onSelect)
|
|
73
|
-
emblaApi.on('reInit', onSelect)
|
|
74
|
-
return () => {
|
|
75
|
-
emblaApi.off('select', onSelect)
|
|
76
|
-
emblaApi.off('reInit', onSelect)
|
|
77
|
-
}
|
|
78
|
-
}, [emblaApi, onSelect])
|
|
79
|
-
|
|
80
|
-
const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi])
|
|
81
|
-
const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi])
|
|
82
|
-
const scrollTo = useCallback((i: number) => emblaApi?.scrollTo(i), [emblaApi])
|
|
83
|
-
|
|
84
|
-
const snapCount = emblaApi?.scrollSnapList().length ?? 0
|
|
85
|
-
|
|
86
|
-
if (!items.length) return null
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<section
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<div className="absolute
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback, useEffect } from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import Image from 'next/image'
|
|
6
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
7
|
+
import { cn } from '@/utilities/ui'
|
|
8
|
+
import useEmblaCarousel from 'embla-carousel-react'
|
|
9
|
+
|
|
10
|
+
type MediaObj = { url?: string | null; alt?: string | null }
|
|
11
|
+
|
|
12
|
+
function isValidImageSrc(src?: string | null): src is string {
|
|
13
|
+
return Boolean(src && src.startsWith('/api/media/file/'))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type CarouselItemA = {
|
|
17
|
+
id?: string | null
|
|
18
|
+
name: string
|
|
19
|
+
category?: string | null
|
|
20
|
+
badge?: string | null
|
|
21
|
+
image?: MediaObj | string | null
|
|
22
|
+
imageFallbackUrl?: string | null
|
|
23
|
+
description?: string | null
|
|
24
|
+
href?: string | null
|
|
25
|
+
features?: Array<{ id?: string | null; label?: string | null }> | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type DDCarouselAProps = {
|
|
29
|
+
blockType: 'DD-Carousel-A'
|
|
30
|
+
blockName?: string | null
|
|
31
|
+
eyebrow?: string | null
|
|
32
|
+
heading?: string | null
|
|
33
|
+
subtext?: string | null
|
|
34
|
+
viewAllLabel?: string | null
|
|
35
|
+
viewAllHref?: string | null
|
|
36
|
+
items?: CarouselItemA[] | null
|
|
37
|
+
sectionId?: string | null
|
|
38
|
+
customCSS?: string | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const DDCarouselA: React.FC<DDCarouselAProps> = ({
|
|
42
|
+
eyebrow,
|
|
43
|
+
heading,
|
|
44
|
+
subtext,
|
|
45
|
+
viewAllLabel = 'View All',
|
|
46
|
+
viewAllHref,
|
|
47
|
+
items: itemsProp,
|
|
48
|
+
sectionId,
|
|
49
|
+
customCSS,
|
|
50
|
+
}) => {
|
|
51
|
+
const items = itemsProp ?? []
|
|
52
|
+
const [emblaRef, emblaApi] = useEmblaCarousel({
|
|
53
|
+
align: 'start',
|
|
54
|
+
slidesToScroll: 1,
|
|
55
|
+
containScroll: 'trimSnaps',
|
|
56
|
+
dragFree: false,
|
|
57
|
+
})
|
|
58
|
+
const [activeIdx, setActiveIdx] = useState(0)
|
|
59
|
+
const [canPrev, setCanPrev] = useState(false)
|
|
60
|
+
const [canNext, setCanNext] = useState(true)
|
|
61
|
+
|
|
62
|
+
const onSelect = useCallback(() => {
|
|
63
|
+
if (!emblaApi) return
|
|
64
|
+
setActiveIdx(emblaApi.selectedScrollSnap())
|
|
65
|
+
setCanPrev(emblaApi.canScrollPrev())
|
|
66
|
+
setCanNext(emblaApi.canScrollNext())
|
|
67
|
+
}, [emblaApi])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!emblaApi) return
|
|
71
|
+
onSelect()
|
|
72
|
+
emblaApi.on('select', onSelect)
|
|
73
|
+
emblaApi.on('reInit', onSelect)
|
|
74
|
+
return () => {
|
|
75
|
+
emblaApi.off('select', onSelect)
|
|
76
|
+
emblaApi.off('reInit', onSelect)
|
|
77
|
+
}
|
|
78
|
+
}, [emblaApi, onSelect])
|
|
79
|
+
|
|
80
|
+
const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi])
|
|
81
|
+
const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi])
|
|
82
|
+
const scrollTo = useCallback((i: number) => emblaApi?.scrollTo(i), [emblaApi])
|
|
83
|
+
|
|
84
|
+
const snapCount = emblaApi?.scrollSnapList().length ?? 0
|
|
85
|
+
|
|
86
|
+
if (!items.length) return null
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<section
|
|
90
|
+
id={sectionId ?? undefined}
|
|
91
|
+
className="relative left-0 mx-auto w-screen max-w-none overflow-hidden py-20"
|
|
92
|
+
>
|
|
93
|
+
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
94
|
+
<div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
|
|
95
|
+
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 gap-4 px-0 md:px-4">
|
|
96
|
+
<div>
|
|
97
|
+
{eyebrow && (
|
|
98
|
+
<div className="text-purple-600 dark:text-lime-500 text-xs font-bold uppercase tracking-widest mb-2">
|
|
99
|
+
{eyebrow}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
{heading && (
|
|
103
|
+
<h2 className="text-4xl lg:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
|
|
104
|
+
{heading}
|
|
105
|
+
</h2>
|
|
106
|
+
)}
|
|
107
|
+
{subtext && <p className="text-stone-400 mt-3 text-base max-w-xl">{subtext}</p>}
|
|
108
|
+
</div>
|
|
109
|
+
<div className="flex items-center gap-3 shrink-0">
|
|
110
|
+
<button
|
|
111
|
+
onClick={scrollPrev}
|
|
112
|
+
disabled={!canPrev}
|
|
113
|
+
aria-label="Previous"
|
|
114
|
+
className="w-11 h-11 flex items-center justify-center rounded-full border-2 border-white/30 text-stone-900 dark:text-stone-50 hover:border-white hover:bg-stone-50 hover:text-stone-900 transition-all disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
|
115
|
+
>
|
|
116
|
+
<ChevronLeft className="w-5 h-5" />
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
onClick={scrollNext}
|
|
120
|
+
disabled={!canNext}
|
|
121
|
+
aria-label="Next"
|
|
122
|
+
className="w-11 h-11 flex items-center justify-center rounded-full bg-purple-600 text-stone-50 hover:bg-purple-500 dark:bg-lime-500 dark:text-stone-900 dark:hover:bg-lime-400 transition-all disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
|
123
|
+
>
|
|
124
|
+
<ChevronRight className="w-5 h-5" />
|
|
125
|
+
</button>
|
|
126
|
+
{viewAllHref && (
|
|
127
|
+
<Link
|
|
128
|
+
href={viewAllHref}
|
|
129
|
+
className="ml-2 text-sm font-semibold text-stone-600 dark:text-stone-300 hover:text-purple-600 dark:hover:text-lime-500 transition-colors whitespace-nowrap"
|
|
130
|
+
>
|
|
131
|
+
{viewAllLabel} →
|
|
132
|
+
</Link>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
{/* Embla viewport */}
|
|
137
|
+
<div
|
|
138
|
+
ref={emblaRef}
|
|
139
|
+
className="overflow-visible select-none cursor-grab active:cursor-grabbing"
|
|
140
|
+
>
|
|
141
|
+
<div className="flex gap-6 py-4">
|
|
142
|
+
{items.map((item, i) => {
|
|
143
|
+
const candidateImgSrc =
|
|
144
|
+
item.image && typeof item.image === 'object' && (item.image as MediaObj).url
|
|
145
|
+
? (item.image as MediaObj).url!
|
|
146
|
+
: (item.imageFallbackUrl ?? '')
|
|
147
|
+
const imgSrc = isValidImageSrc(candidateImgSrc) ? candidateImgSrc : ''
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
key={item.id ?? i}
|
|
152
|
+
className="flex-none w-[calc(100%-0px)] sm:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] min-w-70"
|
|
153
|
+
>
|
|
154
|
+
<Link href={item.href ?? '#'} className="block group h-full" draggable={false}>
|
|
155
|
+
<div className="border border-border rounded-2xl overflow-hidden hover:-translate-y-2 hover:outline-3 hover:outline-purple-500/30 dark:hover:outline-lime-500/30 hover:outline-solid hover:outline-offset-2 transition-all duration-300 hover:shadow-xl hover:border-purple-500/30 dark:hover:border-lime-500/30 h-full flex flex-col">
|
|
156
|
+
{/* Image */}
|
|
157
|
+
<div className="relative h-52 overflow-hidden bg-stone-900 shrink-0 ">
|
|
158
|
+
{imgSrc ? (
|
|
159
|
+
<Image
|
|
160
|
+
src={imgSrc}
|
|
161
|
+
alt={item.name}
|
|
162
|
+
fill
|
|
163
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
164
|
+
className="w-full h-full object-cover object-top group-hover:scale-105 transition-transform duration-500"
|
|
165
|
+
draggable={false}
|
|
166
|
+
/>
|
|
167
|
+
) : (
|
|
168
|
+
<div className="w-full h-full bg-linear-to-br from-violet-950 to-indigo-950" />
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{item.badge && (
|
|
172
|
+
<div className="absolute top-3 left-3 bg-purple-600 text-stone-50 dark:bg-lime-500 dark:text-stone-900 text-xs font-bold px-3 py-1 rounded-full z-10">
|
|
173
|
+
{item.badge}
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
<div className="absolute inset-0 bg-linear-to-t from-stone-900/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
|
178
|
+
|
|
179
|
+
<div className="absolute bottom-3 left-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
180
|
+
<span className="text-stone-900 dark:text-stone-50 text-sm font-semibold">
|
|
181
|
+
View Details →
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{/* Content */}
|
|
187
|
+
<div className="p-5 flex flex-col flex-1">
|
|
188
|
+
{item.category && (
|
|
189
|
+
<div className="text-purple-600 dark:text-lime-500 text-xs font-semibold uppercase tracking-widest mb-1">
|
|
190
|
+
{item.category}
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
<h3 className="text-stone-900 dark:text-stone-50 font-bold text-base leading-snug mb-2 line-clamp-2">
|
|
195
|
+
{item.name}
|
|
196
|
+
</h3>
|
|
197
|
+
|
|
198
|
+
{item.description && (
|
|
199
|
+
<div className="grid grid-rows-[0fr] group-hover:grid-rows-[1fr] transition-[grid-template-rows,margin] duration-300 ease-out mb-0 group-hover:mb-4">
|
|
200
|
+
<p className="min-h-0 overflow-hidden text-stone-400 text-sm leading-relaxed line-clamp-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
201
|
+
{item.description}
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{item.features && item.features.length > 0 && (
|
|
207
|
+
<div className="flex flex-wrap gap-1.5 mt-auto">
|
|
208
|
+
{item.features.slice(0, 3).map((f, fi) => (
|
|
209
|
+
<span
|
|
210
|
+
key={f.id ?? fi}
|
|
211
|
+
className="text-xs bg-purple-500/10 text-purple-600 dark:bg-lime-500/10 dark:text-lime-400 px-2.5 py-1 rounded-full"
|
|
212
|
+
>
|
|
213
|
+
{f.label}
|
|
214
|
+
</span>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</Link>
|
|
221
|
+
</div>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
{/* Dot indicators */}
|
|
227
|
+
{snapCount > 1 && (
|
|
228
|
+
<div className="flex justify-center gap-2 mt-0">
|
|
229
|
+
{Array.from({ length: snapCount }).map((_, i) => (
|
|
230
|
+
<button
|
|
231
|
+
key={i}
|
|
232
|
+
onClick={() => scrollTo(i)}
|
|
233
|
+
aria-label={`Go to slide ${i + 1}`}
|
|
234
|
+
className="flex h-11 w-11 items-center justify-center rounded-full cursor-pointer"
|
|
235
|
+
>
|
|
236
|
+
<span
|
|
237
|
+
className={cn(
|
|
238
|
+
'block rounded-full transition-all duration-300',
|
|
239
|
+
i === activeIdx
|
|
240
|
+
? 'h-2.5 w-8 bg-purple-600 dark:bg-lime-500'
|
|
241
|
+
: 'h-2.5 w-2.5 bg-stone-600 hover:bg-stone-400',
|
|
242
|
+
)}
|
|
243
|
+
/>
|
|
244
|
+
</button>
|
|
245
|
+
))}
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
</section>
|
|
250
|
+
)
|
|
251
|
+
}
|