blocks-dusted 0.1.0 → 0.1.2
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/README.md +967 -3
- package/package.json +40 -40
- package/src/commands/add.js +143 -45
- package/src/commands/list.js +2 -2
- package/src/installers/checkRequirements.js +1 -0
- package/src/installers/copyTemplateFiles.js +23 -2
- package/src/installers/patchPayloadConfigCollections.js +92 -0
- package/src/installers/writeInstalledBlockReadme.js +17 -4
- package/src/registry/listTemplates.js +16 -7
- package/src/registry/loadTemplateManifest.js +20 -11
- package/src/registry/validateTemplateManifest.js +6 -5
- package/src/utils/paths.js +7 -0
- package/templates/blocks/DD-BookCall/Component.tsx +535 -573
- package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
- package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
- package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
- package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
- package/templates/blocks/DD-Contact/Component.tsx +434 -439
- package/templates/blocks/DD-Contact/config.ts +8 -3
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
- package/templates/blocks/DD-FeatCat/config.ts +201 -204
- package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
- package/templates/blocks/DD-Hero/Component.tsx +297 -317
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
- package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
- package/templates/blocks/DD-Pricing/Component.tsx +372 -380
- package/templates/blocks/DD-Process/Component.tsx +149 -155
- package/templates/blocks/DD-Section/Component.tsx +266 -327
- package/templates/blocks/DD-Services/Component.tsx +176 -188
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
- package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
- package/templates/blocks/DD-StackCards/Component.tsx +137 -160
- package/templates/blocks/DD-Team/Component.tsx +247 -265
- package/templates/blocks/DD-TechStack/Component.tsx +57 -72
- package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
- package/templates/blocks/DD-Testimonials/config.ts +66 -66
- package/templates/blocks/DD-Work/Component.tsx +315 -328
- package/templates/blocks/DD-Work-B/Component.tsx +294 -320
- package/templates/collections/Testimonials/README.md +11 -0
- package/templates/collections/Testimonials/Testimonials.ts +161 -0
- package/templates/collections/Testimonials/manifest.json +55 -0
- package/templates/components/RichText/README.md +13 -0
- package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
- package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
- package/templates/components/RichText/converter/index.tsx +18 -0
- package/templates/components/RichText/converter/internalLinks.tsx +45 -0
- package/templates/components/RichText/converter/textConverter.tsx +27 -0
- package/templates/components/RichText/index.tsx +35 -0
- package/templates/components/RichText/manifest.json +80 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
2
|
|
|
3
|
-
import { useLayoutEffect, useRef, useMemo } from
|
|
4
|
-
import Image from
|
|
5
|
-
import { gsap } from
|
|
6
|
-
import { ScrollTrigger } from
|
|
7
|
-
import dynamic from
|
|
3
|
+
import { useLayoutEffect, useRef, useMemo } from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import { gsap } from 'gsap'
|
|
6
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
7
|
+
import dynamic from 'next/dynamic'
|
|
8
8
|
|
|
9
9
|
const DotLottieReact = dynamic(
|
|
10
|
-
() => import(
|
|
10
|
+
() => import('@lottiefiles/dotlottie-react').then((m) => m.DotLottieReact),
|
|
11
11
|
{ ssr: false, loading: () => null },
|
|
12
|
-
)
|
|
12
|
+
)
|
|
13
13
|
import {
|
|
14
14
|
PencilRuler,
|
|
15
15
|
Code2,
|
|
@@ -30,63 +30,57 @@ import {
|
|
|
30
30
|
Send,
|
|
31
31
|
ArrowUp,
|
|
32
32
|
Hammer,
|
|
33
|
-
} from
|
|
34
|
-
import type { LucideIcon } from
|
|
35
|
-
type Media = { url?: string | null; alt?: string | null; [key: string]: any }
|
|
33
|
+
} from 'lucide-react'
|
|
34
|
+
import type { LucideIcon } from 'lucide-react'
|
|
35
|
+
type Media = { url?: string | null; alt?: string | null; [key: string]: any }
|
|
36
36
|
|
|
37
|
-
if (typeof window !==
|
|
38
|
-
gsap.registerPlugin(ScrollTrigger)
|
|
37
|
+
if (typeof window !== 'undefined') {
|
|
38
|
+
gsap.registerPlugin(ScrollTrigger)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/* ── Types ─────────────────────────────────────────────────────── */
|
|
42
42
|
|
|
43
43
|
interface TagItem {
|
|
44
|
-
id?: string
|
|
45
|
-
tag: string
|
|
44
|
+
id?: string
|
|
45
|
+
tag: string
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
interface SlideItem {
|
|
49
|
-
id?: string
|
|
50
|
-
phase?: string | null
|
|
51
|
-
watermark?: string | null
|
|
52
|
-
icon?: string | null
|
|
53
|
-
accent?:
|
|
54
|
-
headline?: string | null
|
|
55
|
-
sub?: string | null
|
|
56
|
-
body?: string | null
|
|
57
|
-
tags?: TagItem[] | null
|
|
58
|
-
visualType?:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| "pipeline"
|
|
62
|
-
| "funnel"
|
|
63
|
-
| "automation"
|
|
64
|
-
| null;
|
|
65
|
-
customMedia?: Media | string | null;
|
|
66
|
-
customMediaType?: "image" | "video" | "lottie" | null;
|
|
49
|
+
id?: string
|
|
50
|
+
phase?: string | null
|
|
51
|
+
watermark?: string | null
|
|
52
|
+
icon?: string | null
|
|
53
|
+
accent?: 'lime' | 'purple' | null
|
|
54
|
+
headline?: string | null
|
|
55
|
+
sub?: string | null
|
|
56
|
+
body?: string | null
|
|
57
|
+
tags?: TagItem[] | null
|
|
58
|
+
visualType?: 'wireframe' | 'codeEditor' | 'pipeline' | 'funnel' | 'automation' | null
|
|
59
|
+
customMedia?: Media | string | null
|
|
60
|
+
customMediaType?: 'image' | 'video' | 'lottie' | null
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
interface DDHorizontalScrollProps {
|
|
70
|
-
id?: string | null
|
|
71
|
-
sectionLabel?: string | null
|
|
72
|
-
slides?: SlideItem[] | null
|
|
73
|
-
sectionId?: string | null
|
|
74
|
-
enableCustomCSS?: boolean | null
|
|
75
|
-
customCSS?: string | null
|
|
76
|
-
[key: string]: unknown
|
|
64
|
+
id?: string | null
|
|
65
|
+
sectionLabel?: string | null
|
|
66
|
+
slides?: SlideItem[] | null
|
|
67
|
+
sectionId?: string | null
|
|
68
|
+
enableCustomCSS?: boolean | null
|
|
69
|
+
customCSS?: string | null
|
|
70
|
+
[key: string]: unknown
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
/* ── Accent helpers ─────────────────────────────────────────────── */
|
|
80
74
|
|
|
81
75
|
const ACCENTS = {
|
|
82
|
-
lime: { hex:
|
|
83
|
-
purple: { hex:
|
|
84
|
-
} as const
|
|
76
|
+
lime: { hex: '#84cc16', rgb: '132,204,22' },
|
|
77
|
+
purple: { hex: '#9803e2', rgb: '152,3,226' },
|
|
78
|
+
} as const
|
|
85
79
|
|
|
86
|
-
type AccentKey = keyof typeof ACCENTS
|
|
80
|
+
type AccentKey = keyof typeof ACCENTS
|
|
87
81
|
|
|
88
82
|
function getAccent(key?: string | null) {
|
|
89
|
-
return ACCENTS[(key as AccentKey) ??
|
|
83
|
+
return ACCENTS[(key as AccentKey) ?? 'lime'] ?? ACCENTS.lime
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
/* ── Icon map ───────────────────────────────────────────────────── */
|
|
@@ -106,25 +100,25 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
|
|
106
100
|
lightbulb: Lightbulb,
|
|
107
101
|
puzzle: Puzzle,
|
|
108
102
|
star: Star,
|
|
109
|
-
}
|
|
103
|
+
}
|
|
110
104
|
|
|
111
105
|
function SlideIcon({
|
|
112
106
|
name,
|
|
113
107
|
size = 14,
|
|
114
108
|
style,
|
|
115
109
|
}: {
|
|
116
|
-
name?: string | null
|
|
117
|
-
size?: number
|
|
118
|
-
style?: React.CSSProperties
|
|
110
|
+
name?: string | null
|
|
111
|
+
size?: number
|
|
112
|
+
style?: React.CSSProperties
|
|
119
113
|
}) {
|
|
120
|
-
const Icon = ICON_MAP[name ??
|
|
121
|
-
return <Icon size={size} style={style}
|
|
114
|
+
const Icon = ICON_MAP[name ?? 'star'] ?? Star
|
|
115
|
+
return <Icon size={size} style={style} />
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
/* ── Visual panels ─────────────────────────────────────────────── */
|
|
125
119
|
|
|
126
120
|
function WireframeVisual({ accent }: { accent: string }) {
|
|
127
|
-
const { hex, rgb } = getAccent(accent)
|
|
121
|
+
const { hex, rgb } = getAccent(accent)
|
|
128
122
|
return (
|
|
129
123
|
<section className="relative w-full h-full flex items-center justify-center">
|
|
130
124
|
<div className="relative w-64 h-64 scale-80 md:scale-100">
|
|
@@ -136,17 +130,14 @@ function WireframeVisual({ accent }: { accent: string }) {
|
|
|
136
130
|
}}
|
|
137
131
|
>
|
|
138
132
|
<div className="p-4 space-y-3">
|
|
139
|
-
<div
|
|
140
|
-
className="h-3 rounded-full w-3/4"
|
|
141
|
-
style={{ background: `rgba(${rgb},0.3)` }}
|
|
142
|
-
/>
|
|
133
|
+
<div className="h-3 rounded-full w-3/4" style={{ background: `rgba(${rgb},0.3)` }} />
|
|
143
134
|
<div
|
|
144
135
|
className="h-2 rounded-full w-full"
|
|
145
|
-
style={{ background:
|
|
136
|
+
style={{ background: 'rgba(255,255,255,0.08)' }}
|
|
146
137
|
/>
|
|
147
138
|
<div
|
|
148
139
|
className="h-2 rounded-full w-5/6"
|
|
149
|
-
style={{ background:
|
|
140
|
+
style={{ background: 'rgba(255,255,255,0.08)' }}
|
|
150
141
|
/>
|
|
151
142
|
<div className="grid grid-cols-2 gap-2 mt-4">
|
|
152
143
|
<div
|
|
@@ -159,8 +150,8 @@ function WireframeVisual({ accent }: { accent: string }) {
|
|
|
159
150
|
<div
|
|
160
151
|
className="h-16 rounded-lg"
|
|
161
152
|
style={{
|
|
162
|
-
background:
|
|
163
|
-
border:
|
|
153
|
+
background: 'rgba(255,255,255,0.04)',
|
|
154
|
+
border: '1px solid rgba(255,255,255,0.08)',
|
|
164
155
|
}}
|
|
165
156
|
/>
|
|
166
157
|
</div>
|
|
@@ -178,52 +169,40 @@ function WireframeVisual({ accent }: { accent: string }) {
|
|
|
178
169
|
className="absolute -right-4 top-1/3 w-5 h-5 rounded-full border-2 flex items-center justify-center"
|
|
179
170
|
style={{ borderColor: hex, background: `rgba(${rgb},0.15)` }}
|
|
180
171
|
>
|
|
181
|
-
<div
|
|
182
|
-
className="w-1.5 h-1.5 rounded-full"
|
|
183
|
-
style={{ background: hex }}
|
|
184
|
-
/>
|
|
172
|
+
<div className="w-1.5 h-1.5 rounded-full" style={{ background: hex }} />
|
|
185
173
|
</div>
|
|
186
174
|
{/* Grid bg */}
|
|
187
175
|
<div
|
|
188
176
|
className="absolute -inset-6 pointer-events-none"
|
|
189
177
|
style={{
|
|
190
178
|
backgroundImage: `linear-gradient(rgba(${rgb},0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(${rgb},0.05) 1px, transparent 1px)`,
|
|
191
|
-
backgroundSize:
|
|
179
|
+
backgroundSize: '24px 24px',
|
|
192
180
|
}}
|
|
193
181
|
/>
|
|
194
182
|
</div>
|
|
195
183
|
</section>
|
|
196
|
-
)
|
|
184
|
+
)
|
|
197
185
|
}
|
|
198
186
|
|
|
199
187
|
function CodeEditorVisual({ accent }: { accent: string }) {
|
|
200
|
-
const { hex, rgb } = getAccent(accent)
|
|
188
|
+
const { hex, rgb } = getAccent(accent)
|
|
201
189
|
return (
|
|
202
190
|
<div className="relative w-full h-full flex items-center justify-center scale-80 md:scale-100">
|
|
203
191
|
<div
|
|
204
192
|
className="relative w-72 rounded-xl overflow-hidden"
|
|
205
|
-
style={{ background:
|
|
193
|
+
style={{ background: '#0d0d0d', border: `1px solid rgba(${rgb},0.15)` }}
|
|
206
194
|
>
|
|
207
195
|
{/* Title bar */}
|
|
208
196
|
<div
|
|
209
197
|
className="flex items-center gap-2 px-4 py-3 border-b"
|
|
210
|
-
style={{ borderColor:
|
|
198
|
+
style={{ borderColor: 'rgba(255,255,255,0.06)' }}
|
|
211
199
|
>
|
|
212
|
-
<div
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
/>
|
|
216
|
-
<div
|
|
217
|
-
className="w-3 h-3 rounded-full"
|
|
218
|
-
style={{ background: "#febc2e" }}
|
|
219
|
-
/>
|
|
220
|
-
<div
|
|
221
|
-
className="w-3 h-3 rounded-full"
|
|
222
|
-
style={{ background: "#28c840" }}
|
|
223
|
-
/>
|
|
200
|
+
<div className="w-3 h-3 rounded-full" style={{ background: '#ff5f57' }} />
|
|
201
|
+
<div className="w-3 h-3 rounded-full" style={{ background: '#febc2e' }} />
|
|
202
|
+
<div className="w-3 h-3 rounded-full" style={{ background: '#28c840' }} />
|
|
224
203
|
<span
|
|
225
204
|
className="ml-2 text-xs"
|
|
226
|
-
style={{ color:
|
|
205
|
+
style={{ color: 'rgba(255,255,255,0.3)', fontFamily: 'monospace' }}
|
|
227
206
|
>
|
|
228
207
|
index.tsx
|
|
229
208
|
</span>
|
|
@@ -231,65 +210,61 @@ function CodeEditorVisual({ accent }: { accent: string }) {
|
|
|
231
210
|
{/* Code lines */}
|
|
232
211
|
<div className="p-4 space-y-1.5 font-mono text-xs">
|
|
233
212
|
<div>
|
|
234
|
-
<span style={{ color:
|
|
235
|
-
<span style={{ color:
|
|
236
|
-
<span style={{ color:
|
|
237
|
-
<span style={{ color:
|
|
238
|
-
<span style={{ color:
|
|
239
|
-
<span style={{ color: "rgba(255,255,255,0.4)" }}>{"{"}</span>
|
|
213
|
+
<span style={{ color: '#9803e2' }}>const</span> <span style={{ color: hex }}>Hero</span>{' '}
|
|
214
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>=</span>{' '}
|
|
215
|
+
<span style={{ color: '#9803e2' }}>()</span>{' '}
|
|
216
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>=></span>{' '}
|
|
217
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>{'{'}</span>
|
|
240
218
|
</div>
|
|
241
219
|
<div className="pl-4">
|
|
242
|
-
<span style={{ color:
|
|
243
|
-
<span style={{ color:
|
|
220
|
+
<span style={{ color: '#9803e2' }}>return</span>{' '}
|
|
221
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>(</span>
|
|
244
222
|
</div>
|
|
245
223
|
<div className="pl-8">
|
|
246
|
-
<span style={{ color:
|
|
247
|
-
<span style={{ color: hex }}>section</span>{
|
|
248
|
-
<span style={{ color:
|
|
249
|
-
<span style={{ color:
|
|
250
|
-
<span style={{ color:
|
|
251
|
-
<span style={{ color:
|
|
224
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}><</span>
|
|
225
|
+
<span style={{ color: hex }}>section</span>{' '}
|
|
226
|
+
<span style={{ color: '#9803e2' }}>className</span>
|
|
227
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}>=</span>
|
|
228
|
+
<span style={{ color: '#febc2e' }}>"hero"</span>
|
|
229
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}>></span>
|
|
252
230
|
</div>
|
|
253
231
|
<div className="pl-12">
|
|
254
|
-
<span style={{ color:
|
|
232
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}><</span>
|
|
255
233
|
<span style={{ color: hex }}>h1</span>
|
|
256
|
-
<span style={{ color:
|
|
257
|
-
<span style={{ color:
|
|
258
|
-
<span style={{ color:
|
|
234
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}>></span>
|
|
235
|
+
<span style={{ color: 'rgba(255,255,255,0.6)' }}>Convert.</span>
|
|
236
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}></</span>
|
|
259
237
|
<span style={{ color: hex }}>h1</span>
|
|
260
|
-
<span style={{ color:
|
|
238
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}>></span>
|
|
261
239
|
</div>
|
|
262
240
|
<div className="pl-8">
|
|
263
|
-
<span style={{ color:
|
|
241
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}></</span>
|
|
264
242
|
<span style={{ color: hex }}>section</span>
|
|
265
|
-
<span style={{ color:
|
|
243
|
+
<span style={{ color: 'rgba(255,255,255,0.3)' }}>></span>
|
|
266
244
|
</div>
|
|
267
245
|
<div className="pl-4">
|
|
268
|
-
<span style={{ color:
|
|
246
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>)</span>
|
|
269
247
|
</div>
|
|
270
248
|
<div>
|
|
271
|
-
<span style={{ color:
|
|
249
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>{'}'}</span>
|
|
272
250
|
</div>
|
|
273
251
|
<div className="mt-2 flex items-center gap-2">
|
|
274
|
-
<span style={{ color:
|
|
275
|
-
<span
|
|
276
|
-
className="inline-block w-2 h-4 animate-pulse"
|
|
277
|
-
style={{ background: hex }}
|
|
278
|
-
/>
|
|
252
|
+
<span style={{ color: 'rgba(255,255,255,0.2)' }}>{'// '}</span>
|
|
253
|
+
<span className="inline-block w-2 h-4 animate-pulse" style={{ background: hex }} />
|
|
279
254
|
</div>
|
|
280
255
|
</div>
|
|
281
256
|
</div>
|
|
282
257
|
</div>
|
|
283
|
-
)
|
|
258
|
+
)
|
|
284
259
|
}
|
|
285
260
|
|
|
286
261
|
function PipelineVisual() {
|
|
287
262
|
const steps = [
|
|
288
|
-
{ label:
|
|
289
|
-
{ label:
|
|
290
|
-
{ label:
|
|
291
|
-
{ label:
|
|
292
|
-
]
|
|
263
|
+
{ label: 'Build', status: 'done', Icon: Hammer },
|
|
264
|
+
{ label: 'Test', status: 'done', Icon: ShieldCheck },
|
|
265
|
+
{ label: 'Deploy', status: 'active', Icon: Rocket },
|
|
266
|
+
{ label: 'Live', status: 'pending', Icon: Globe },
|
|
267
|
+
]
|
|
293
268
|
return (
|
|
294
269
|
<div className="relative w-full h-full flex items-center justify-center scale-80 md:scale-100">
|
|
295
270
|
<div className="relative w-64">
|
|
@@ -299,17 +274,17 @@ function PipelineVisual() {
|
|
|
299
274
|
className="w-9 h-9 rounded-full flex items-center justify-center shrink-0"
|
|
300
275
|
style={{
|
|
301
276
|
background:
|
|
302
|
-
step.status ===
|
|
303
|
-
?
|
|
304
|
-
: step.status ===
|
|
305
|
-
?
|
|
306
|
-
:
|
|
277
|
+
step.status === 'done'
|
|
278
|
+
? 'rgba(132,204,22,0.15)'
|
|
279
|
+
: step.status === 'active'
|
|
280
|
+
? 'rgba(152,3,226,0.2)'
|
|
281
|
+
: 'rgba(255,255,255,0.04)',
|
|
307
282
|
border: `1px solid ${
|
|
308
|
-
step.status ===
|
|
309
|
-
?
|
|
310
|
-
: step.status ===
|
|
311
|
-
?
|
|
312
|
-
:
|
|
283
|
+
step.status === 'done'
|
|
284
|
+
? 'rgba(132,204,22,0.4)'
|
|
285
|
+
: step.status === 'active'
|
|
286
|
+
? 'rgba(152,3,226,0.5)'
|
|
287
|
+
: 'rgba(255,255,255,0.1)'
|
|
313
288
|
}`,
|
|
314
289
|
}}
|
|
315
290
|
>
|
|
@@ -317,11 +292,11 @@ function PipelineVisual() {
|
|
|
317
292
|
size={14}
|
|
318
293
|
style={{
|
|
319
294
|
color:
|
|
320
|
-
step.status ===
|
|
321
|
-
?
|
|
322
|
-
: step.status ===
|
|
323
|
-
?
|
|
324
|
-
:
|
|
295
|
+
step.status === 'done'
|
|
296
|
+
? '#84cc16'
|
|
297
|
+
: step.status === 'active'
|
|
298
|
+
? '#9803e2'
|
|
299
|
+
: 'rgba(255,255,255,0.2)',
|
|
325
300
|
}}
|
|
326
301
|
/>
|
|
327
302
|
</div>
|
|
@@ -331,40 +306,29 @@ function PipelineVisual() {
|
|
|
331
306
|
className="text-sm font-semibold"
|
|
332
307
|
style={{
|
|
333
308
|
color:
|
|
334
|
-
step.status ===
|
|
335
|
-
? "rgba(255,255,255,0.2)"
|
|
336
|
-
: "rgba(255,255,255,0.8)",
|
|
309
|
+
step.status === 'pending' ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.8)',
|
|
337
310
|
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
338
311
|
}}
|
|
339
312
|
>
|
|
340
313
|
{step.label}
|
|
341
314
|
</span>
|
|
342
|
-
{step.status ===
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
{step.status === "active" && (
|
|
346
|
-
<span
|
|
347
|
-
className="text-xs animate-pulse"
|
|
348
|
-
style={{ color: "#9803e2" }}
|
|
349
|
-
>
|
|
315
|
+
{step.status === 'done' && <Check size={12} style={{ color: '#84cc16' }} />}
|
|
316
|
+
{step.status === 'active' && (
|
|
317
|
+
<span className="text-xs animate-pulse" style={{ color: '#9803e2' }}>
|
|
350
318
|
Running…
|
|
351
319
|
</span>
|
|
352
320
|
)}
|
|
353
321
|
</div>
|
|
354
322
|
<div
|
|
355
323
|
className="h-1 rounded-full mt-1.5"
|
|
356
|
-
style={{ background:
|
|
324
|
+
style={{ background: 'rgba(255,255,255,0.06)' }}
|
|
357
325
|
>
|
|
358
326
|
<div
|
|
359
327
|
className="h-full rounded-full"
|
|
360
328
|
style={{
|
|
361
329
|
width:
|
|
362
|
-
step.status ===
|
|
363
|
-
|
|
364
|
-
: step.status === "active"
|
|
365
|
-
? "60%"
|
|
366
|
-
: "0%",
|
|
367
|
-
background: step.status === "done" ? "#84cc16" : "#9803e2",
|
|
330
|
+
step.status === 'done' ? '100%' : step.status === 'active' ? '60%' : '0%',
|
|
331
|
+
background: step.status === 'done' ? '#84cc16' : '#9803e2',
|
|
368
332
|
}}
|
|
369
333
|
/>
|
|
370
334
|
</div>
|
|
@@ -373,17 +337,17 @@ function PipelineVisual() {
|
|
|
373
337
|
))}
|
|
374
338
|
</div>
|
|
375
339
|
</div>
|
|
376
|
-
)
|
|
340
|
+
)
|
|
377
341
|
}
|
|
378
342
|
|
|
379
343
|
function FunnelVisual({ accent }: { accent: string }) {
|
|
380
|
-
const { hex, rgb } = getAccent(accent)
|
|
344
|
+
const { hex, rgb } = getAccent(accent)
|
|
381
345
|
const rows = [
|
|
382
|
-
{ label:
|
|
383
|
-
{ label:
|
|
384
|
-
{ label:
|
|
385
|
-
{ label:
|
|
386
|
-
]
|
|
346
|
+
{ label: 'Visitors', value: '24,800', pct: 100 },
|
|
347
|
+
{ label: 'Engaged', value: '11,200', pct: 65 },
|
|
348
|
+
{ label: 'Leads', value: '3,840', pct: 40 },
|
|
349
|
+
{ label: 'Converted', value: '960', pct: 22 },
|
|
350
|
+
]
|
|
387
351
|
return (
|
|
388
352
|
<div className="relative w-full h-full flex items-center justify-center scale-80 md:scale-100">
|
|
389
353
|
<div className="relative w-64 space-y-3">
|
|
@@ -393,17 +357,10 @@ function FunnelVisual({ accent }: { accent: string }) {
|
|
|
393
357
|
className="flex justify-between text-xs mb-1"
|
|
394
358
|
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}
|
|
395
359
|
>
|
|
396
|
-
<span style={{ color:
|
|
397
|
-
|
|
398
|
-
</span>
|
|
399
|
-
<span style={{ color: "rgba(255,255,255,0.7)" }}>
|
|
400
|
-
{row.value}
|
|
401
|
-
</span>
|
|
360
|
+
<span style={{ color: 'rgba(255,255,255,0.4)' }}>{row.label}</span>
|
|
361
|
+
<span style={{ color: 'rgba(255,255,255,0.7)' }}>{row.value}</span>
|
|
402
362
|
</div>
|
|
403
|
-
<div
|
|
404
|
-
className="h-2 rounded-full"
|
|
405
|
-
style={{ background: "rgba(255,255,255,0.06)" }}
|
|
406
|
-
>
|
|
363
|
+
<div className="h-2 rounded-full" style={{ background: 'rgba(255,255,255,0.06)' }}>
|
|
407
364
|
<div
|
|
408
365
|
className="h-full rounded-full"
|
|
409
366
|
style={{ width: `${row.pct}%`, background: hex }}
|
|
@@ -432,7 +389,7 @@ function FunnelVisual({ accent }: { accent: string }) {
|
|
|
432
389
|
<div
|
|
433
390
|
className="text-xs"
|
|
434
391
|
style={{
|
|
435
|
-
color:
|
|
392
|
+
color: 'rgba(255,255,255,0.3)',
|
|
436
393
|
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
437
394
|
}}
|
|
438
395
|
>
|
|
@@ -442,33 +399,33 @@ function FunnelVisual({ accent }: { accent: string }) {
|
|
|
442
399
|
</div>
|
|
443
400
|
</div>
|
|
444
401
|
</div>
|
|
445
|
-
)
|
|
402
|
+
)
|
|
446
403
|
}
|
|
447
404
|
|
|
448
405
|
function AutomationVisual() {
|
|
449
406
|
const flows = [
|
|
450
407
|
{
|
|
451
|
-
from:
|
|
452
|
-
to:
|
|
408
|
+
from: 'New Lead',
|
|
409
|
+
to: 'CRM',
|
|
453
410
|
Icon: UserPlus,
|
|
454
|
-
color:
|
|
455
|
-
rgb:
|
|
411
|
+
color: '#9803e2',
|
|
412
|
+
rgb: '152,3,226',
|
|
456
413
|
},
|
|
457
414
|
{
|
|
458
|
-
from:
|
|
459
|
-
to:
|
|
415
|
+
from: 'CRM',
|
|
416
|
+
to: 'Email Seq.',
|
|
460
417
|
Icon: Send,
|
|
461
|
-
color:
|
|
462
|
-
rgb:
|
|
418
|
+
color: '#84cc16',
|
|
419
|
+
rgb: '132,204,22',
|
|
463
420
|
},
|
|
464
421
|
{
|
|
465
|
-
from:
|
|
466
|
-
to:
|
|
422
|
+
from: 'Email Seq.',
|
|
423
|
+
to: 'Sales Team',
|
|
467
424
|
Icon: Users,
|
|
468
|
-
color:
|
|
469
|
-
rgb:
|
|
425
|
+
color: '#9803e2',
|
|
426
|
+
rgb: '152,3,226',
|
|
470
427
|
},
|
|
471
|
-
]
|
|
428
|
+
]
|
|
472
429
|
return (
|
|
473
430
|
<div className="relative w-full h-full flex items-center justify-center scale-80 md:scale-100">
|
|
474
431
|
<div className="relative w-64">
|
|
@@ -477,19 +434,16 @@ function AutomationVisual() {
|
|
|
477
434
|
<div
|
|
478
435
|
className="px-3 py-2 rounded-lg text-xs font-semibold whitespace-nowrap"
|
|
479
436
|
style={{
|
|
480
|
-
background:
|
|
481
|
-
border:
|
|
482
|
-
color:
|
|
437
|
+
background: 'rgba(255,255,255,0.05)',
|
|
438
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
439
|
+
color: 'rgba(255,255,255,0.6)',
|
|
483
440
|
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
484
441
|
}}
|
|
485
442
|
>
|
|
486
443
|
{flow.from}
|
|
487
444
|
</div>
|
|
488
445
|
<div className="flex-1 flex items-center gap-1">
|
|
489
|
-
<div
|
|
490
|
-
className="flex-1 h-px"
|
|
491
|
-
style={{ background: `rgba(${flow.rgb},0.4)` }}
|
|
492
|
-
/>
|
|
446
|
+
<div className="flex-1 h-px" style={{ background: `rgba(${flow.rgb},0.4)` }} />
|
|
493
447
|
<div
|
|
494
448
|
className="w-6 h-6 rounded-full flex items-center justify-center"
|
|
495
449
|
style={{
|
|
@@ -499,10 +453,7 @@ function AutomationVisual() {
|
|
|
499
453
|
>
|
|
500
454
|
<flow.Icon size={10} style={{ color: flow.color }} />
|
|
501
455
|
</div>
|
|
502
|
-
<div
|
|
503
|
-
className="flex-1 h-px"
|
|
504
|
-
style={{ background: `rgba(${flow.rgb},0.4)` }}
|
|
505
|
-
/>
|
|
456
|
+
<div className="flex-1 h-px" style={{ background: `rgba(${flow.rgb},0.4)` }} />
|
|
506
457
|
</div>
|
|
507
458
|
<div
|
|
508
459
|
className="px-3 py-2 rounded-lg text-xs font-semibold whitespace-nowrap"
|
|
@@ -520,14 +471,14 @@ function AutomationVisual() {
|
|
|
520
471
|
<div
|
|
521
472
|
className="mt-2 p-3 rounded-xl text-center"
|
|
522
473
|
style={{
|
|
523
|
-
background:
|
|
524
|
-
border:
|
|
474
|
+
background: 'rgba(152,3,226,0.08)',
|
|
475
|
+
border: '1px solid rgba(152,3,226,0.2)',
|
|
525
476
|
}}
|
|
526
477
|
>
|
|
527
478
|
<div
|
|
528
479
|
className="text-xs font-bold flex items-center justify-center gap-1"
|
|
529
480
|
style={{
|
|
530
|
-
color:
|
|
481
|
+
color: '#9803e2',
|
|
531
482
|
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
532
483
|
}}
|
|
533
484
|
>
|
|
@@ -537,7 +488,7 @@ function AutomationVisual() {
|
|
|
537
488
|
</div>
|
|
538
489
|
</div>
|
|
539
490
|
</div>
|
|
540
|
-
)
|
|
491
|
+
)
|
|
541
492
|
}
|
|
542
493
|
|
|
543
494
|
const VISUAL_MAP: Record<string, React.ComponentType<{ accent: string }>> = {
|
|
@@ -546,47 +497,43 @@ const VISUAL_MAP: Record<string, React.ComponentType<{ accent: string }>> = {
|
|
|
546
497
|
pipeline: PipelineVisual as React.ComponentType<{ accent: string }>,
|
|
547
498
|
funnel: FunnelVisual,
|
|
548
499
|
automation: AutomationVisual as React.ComponentType<{ accent: string }>,
|
|
549
|
-
}
|
|
500
|
+
}
|
|
550
501
|
|
|
551
502
|
/* ── Slide panel ────────────────────────────────────────────────── */
|
|
552
503
|
|
|
553
504
|
interface SlidePanelProps {
|
|
554
|
-
slide: SlideItem
|
|
555
|
-
index: number
|
|
556
|
-
total: number
|
|
557
|
-
panelRef: (el: HTMLDivElement | null) => void
|
|
505
|
+
slide: SlideItem
|
|
506
|
+
index: number
|
|
507
|
+
total: number
|
|
508
|
+
panelRef: (el: HTMLDivElement | null) => void
|
|
558
509
|
}
|
|
559
510
|
|
|
560
511
|
function isLocalMediaSrc(src?: string | null): src is string {
|
|
561
|
-
return typeof src ===
|
|
512
|
+
return typeof src === 'string' && src.startsWith('/api/media/file/')
|
|
562
513
|
}
|
|
563
514
|
|
|
564
515
|
function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
565
|
-
const { hex, rgb } = getAccent(slide.accent)
|
|
566
|
-
const Visual = VISUAL_MAP[slide.visualType ??
|
|
516
|
+
const { hex, rgb } = getAccent(slide.accent)
|
|
517
|
+
const Visual = VISUAL_MAP[slide.visualType ?? 'wireframe'] ?? WireframeVisual
|
|
567
518
|
|
|
568
519
|
// Extract media URL safely
|
|
569
520
|
const mediaUrl: string =
|
|
570
|
-
typeof slide.customMedia ===
|
|
571
|
-
|
|
572
|
-
: (slide.customMedia?.url ?? "");
|
|
573
|
-
const safeMediaUrl = isLocalMediaSrc(mediaUrl) ? mediaUrl : "";
|
|
521
|
+
typeof slide.customMedia === 'string' ? slide.customMedia : (slide.customMedia?.url ?? '')
|
|
522
|
+
const safeMediaUrl = isLocalMediaSrc(mediaUrl) ? mediaUrl : ''
|
|
574
523
|
const mediaAlt =
|
|
575
|
-
slide.customMedia && typeof slide.customMedia !==
|
|
576
|
-
? (slide.customMedia.alt ?? "")
|
|
577
|
-
: "";
|
|
524
|
+
slide.customMedia && typeof slide.customMedia !== 'string' ? (slide.customMedia.alt ?? '') : ''
|
|
578
525
|
|
|
579
526
|
return (
|
|
580
527
|
<div
|
|
581
528
|
ref={panelRef}
|
|
582
529
|
className={`shs-slide shs-slide--${index} relative shrink-0 h-full flex items-center overflow-hidden`}
|
|
583
|
-
style={{ width:
|
|
530
|
+
style={{ width: '100vw' }}
|
|
584
531
|
>
|
|
585
532
|
{/* Watermark number */}
|
|
586
533
|
<div
|
|
587
534
|
className="absolute top-1/2 -translate-y-1/2 right-16 font-black select-none pointer-events-none text-stone-200/40 dark:text-stone-200/5"
|
|
588
535
|
style={{
|
|
589
|
-
fontSize:
|
|
536
|
+
fontSize: 'clamp(120px, 18vw, 220px)',
|
|
590
537
|
|
|
591
538
|
lineHeight: 1,
|
|
592
539
|
}}
|
|
@@ -601,7 +548,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
601
548
|
className="absolute right-0 top-1/4 bottom-1/4 w-px"
|
|
602
549
|
style={{
|
|
603
550
|
background:
|
|
604
|
-
|
|
551
|
+
'linear-gradient(to bottom, transparent, rgba(255,255,255,0.08), transparent)',
|
|
605
552
|
}}
|
|
606
553
|
/>
|
|
607
554
|
)}
|
|
@@ -610,7 +557,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
610
557
|
{/* ── Left: content ── */}
|
|
611
558
|
<div className="space-y-2 md:space-y-7">
|
|
612
559
|
{/* Phase badge */}
|
|
613
|
-
<div className="anim-el flex items-center gap-3">
|
|
560
|
+
<div className="anim-el flex items-center justify-end gap-3">
|
|
614
561
|
<div
|
|
615
562
|
className="w-7 h-7 md:w-8 md:h-8 rounded-full flex items-center justify-center"
|
|
616
563
|
style={{
|
|
@@ -634,15 +581,13 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
634
581
|
<h2
|
|
635
582
|
className="anim-el font-extrabold leading-none tracking-tight text-stone-900 dark:text-stone-50"
|
|
636
583
|
style={{
|
|
637
|
-
fontSize:
|
|
584
|
+
fontSize: 'clamp(42px, 5vw, 72px)',
|
|
638
585
|
|
|
639
|
-
whiteSpace:
|
|
586
|
+
whiteSpace: 'pre-line',
|
|
640
587
|
}}
|
|
641
588
|
>
|
|
642
|
-
{
|
|
643
|
-
<span className="relative">
|
|
644
|
-
{(slide.headline ?? "").replace(/\\n/g, "\n")}
|
|
645
|
-
</span>
|
|
589
|
+
{' '}
|
|
590
|
+
<span className="relative">{(slide.headline ?? '').replace(/\\n/g, '\n')}</span>
|
|
646
591
|
</h2>
|
|
647
592
|
|
|
648
593
|
{/* Sub */}
|
|
@@ -653,7 +598,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
653
598
|
color: hex,
|
|
654
599
|
}}
|
|
655
600
|
>
|
|
656
|
-
{
|
|
601
|
+
{' '}
|
|
657
602
|
<span className="relative">{slide.sub}</span>
|
|
658
603
|
</p>
|
|
659
604
|
)}
|
|
@@ -666,7 +611,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
666
611
|
// fontFamily: "'Plus Jakarta Sans', sans-serif",
|
|
667
612
|
// }}
|
|
668
613
|
>
|
|
669
|
-
{
|
|
614
|
+
{' '}
|
|
670
615
|
<span className="relative"> {slide.body}</span>
|
|
671
616
|
</p>
|
|
672
617
|
)}
|
|
@@ -710,7 +655,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
710
655
|
<div className="relative z-10 w-full h-full">
|
|
711
656
|
{slide.customMedia && safeMediaUrl ? (
|
|
712
657
|
<>
|
|
713
|
-
{slide.customMediaType ===
|
|
658
|
+
{slide.customMediaType === 'image' && (
|
|
714
659
|
<div className="relative w-full h-full bg-dots">
|
|
715
660
|
<Image
|
|
716
661
|
src={safeMediaUrl}
|
|
@@ -723,7 +668,7 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
723
668
|
/>
|
|
724
669
|
</div>
|
|
725
670
|
)}
|
|
726
|
-
{slide.customMediaType ===
|
|
671
|
+
{slide.customMediaType === 'video' && (
|
|
727
672
|
<video
|
|
728
673
|
src={safeMediaUrl}
|
|
729
674
|
autoPlay
|
|
@@ -732,10 +677,10 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
732
677
|
playsInline
|
|
733
678
|
preload="metadata"
|
|
734
679
|
className="w-full h-full object-cover bg-dots"
|
|
735
|
-
aria-label={mediaAlt ||
|
|
680
|
+
aria-label={mediaAlt || 'Video content'}
|
|
736
681
|
/>
|
|
737
682
|
)}
|
|
738
|
-
{slide.customMediaType ===
|
|
683
|
+
{slide.customMediaType === 'lottie' && (
|
|
739
684
|
<DotLottieReact
|
|
740
685
|
src={safeMediaUrl}
|
|
741
686
|
loop
|
|
@@ -745,123 +690,123 @@ function SlidePanel({ slide, index, total, panelRef }: SlidePanelProps) {
|
|
|
745
690
|
)}
|
|
746
691
|
</>
|
|
747
692
|
) : (
|
|
748
|
-
<Visual accent={slide.accent ??
|
|
693
|
+
<Visual accent={slide.accent ?? 'lime'} />
|
|
749
694
|
)}
|
|
750
695
|
</div>
|
|
751
696
|
</div>
|
|
752
697
|
</div>
|
|
753
698
|
</div>
|
|
754
|
-
)
|
|
699
|
+
)
|
|
755
700
|
}
|
|
756
701
|
|
|
757
702
|
/* ── Main component ─────────────────────────────────────────────── */
|
|
758
703
|
|
|
759
704
|
export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
760
|
-
const { sectionLabel, slides, sectionId, enableCustomCSS, customCSS } = props
|
|
705
|
+
const { sectionLabel, slides, sectionId, enableCustomCSS, customCSS } = props
|
|
761
706
|
|
|
762
|
-
const outerRef = useRef<HTMLElement>(null)
|
|
763
|
-
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
764
|
-
const trackRef = useRef<HTMLDivElement>(null)
|
|
765
|
-
const slideRefs = useRef<(HTMLDivElement | null)[]>([])
|
|
766
|
-
const activeSlideRef = useRef<number>(-1)
|
|
707
|
+
const outerRef = useRef<HTMLElement>(null)
|
|
708
|
+
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
709
|
+
const trackRef = useRef<HTMLDivElement>(null)
|
|
710
|
+
const slideRefs = useRef<(HTMLDivElement | null)[]>([])
|
|
711
|
+
const activeSlideRef = useRef<number>(-1)
|
|
767
712
|
|
|
768
713
|
// Memoize normalized slides to avoid unnecessary re-renders
|
|
769
|
-
const normalSlides: SlideItem[] = useMemo(() => slides ?? [], [slides])
|
|
770
|
-
const slideCount = normalSlides.length
|
|
714
|
+
const normalSlides: SlideItem[] = useMemo(() => slides ?? [], [slides])
|
|
715
|
+
const slideCount = normalSlides.length
|
|
771
716
|
|
|
772
717
|
/* ── GSAP horizontal scroll + pin + snap ── */
|
|
773
718
|
useLayoutEffect(() => {
|
|
774
|
-
if (typeof window ===
|
|
775
|
-
if (slideCount === 0) return
|
|
719
|
+
if (typeof window === 'undefined') return
|
|
720
|
+
if (slideCount === 0) return
|
|
776
721
|
|
|
777
722
|
// Store ctx outside the timer so cleanup always reaches it
|
|
778
|
-
let ctx: ReturnType<typeof gsap.context> | null = null
|
|
779
|
-
let initTimer: ReturnType<typeof setTimeout
|
|
780
|
-
let resizeTimer: ReturnType<typeof setTimeout
|
|
781
|
-
let rafId: number
|
|
723
|
+
let ctx: ReturnType<typeof gsap.context> | null = null
|
|
724
|
+
let initTimer: ReturnType<typeof setTimeout>
|
|
725
|
+
let resizeTimer: ReturnType<typeof setTimeout>
|
|
726
|
+
let rafId: number
|
|
782
727
|
|
|
783
728
|
const init = () => {
|
|
784
|
-
const outerEl = outerRef.current
|
|
785
|
-
const wrapper = wrapperRef.current
|
|
786
|
-
const track = trackRef.current
|
|
787
|
-
if (!outerEl || !wrapper || !track) return
|
|
729
|
+
const outerEl = outerRef.current
|
|
730
|
+
const wrapper = wrapperRef.current
|
|
731
|
+
const track = trackRef.current
|
|
732
|
+
if (!outerEl || !wrapper || !track) return
|
|
788
733
|
|
|
789
734
|
// Kill any stale ScrollTriggers on this element (e.g. from Strict-Mode double mount)
|
|
790
735
|
ScrollTrigger.getAll()
|
|
791
736
|
.filter((st) => st.vars.trigger === outerEl)
|
|
792
|
-
.forEach((st) => st.kill())
|
|
737
|
+
.forEach((st) => st.kill())
|
|
793
738
|
|
|
794
739
|
ctx = gsap.context(() => {
|
|
795
740
|
ScrollTrigger.create({
|
|
796
741
|
trigger: outerEl,
|
|
797
|
-
start:
|
|
742
|
+
start: 'top top',
|
|
798
743
|
end: () => `+=${(slideCount - 1) * window.innerWidth}`,
|
|
799
744
|
scrub: 0.5,
|
|
800
745
|
invalidateOnRefresh: true,
|
|
801
746
|
onUpdate: (self) => {
|
|
802
747
|
gsap.set(track, {
|
|
803
748
|
x: -(slideCount - 1) * window.innerWidth * self.progress,
|
|
804
|
-
})
|
|
749
|
+
})
|
|
805
750
|
|
|
806
|
-
const newActive = Math.round(self.progress * (slideCount - 1))
|
|
751
|
+
const newActive = Math.round(self.progress * (slideCount - 1))
|
|
807
752
|
if (newActive !== activeSlideRef.current) {
|
|
808
|
-
activeSlideRef.current = newActive
|
|
809
|
-
animateSlideIn(slideRefs.current[newActive])
|
|
753
|
+
activeSlideRef.current = newActive
|
|
754
|
+
animateSlideIn(slideRefs.current[newActive])
|
|
810
755
|
}
|
|
811
756
|
},
|
|
812
|
-
})
|
|
813
|
-
}, outerEl)
|
|
757
|
+
})
|
|
758
|
+
}, outerEl)
|
|
814
759
|
|
|
815
760
|
// Animate first slide in on mount
|
|
816
|
-
animateSlideIn(slideRefs.current[1])
|
|
817
|
-
activeSlideRef.current = 1
|
|
761
|
+
animateSlideIn(slideRefs.current[1])
|
|
762
|
+
activeSlideRef.current = 1
|
|
818
763
|
|
|
819
764
|
// Refresh so ScrollTrigger measures the fully-painted layout
|
|
820
|
-
ScrollTrigger.refresh()
|
|
821
|
-
}
|
|
765
|
+
ScrollTrigger.refresh()
|
|
766
|
+
}
|
|
822
767
|
|
|
823
768
|
// Double rAF inside a timeout: waits for SSR hydration, LocomotiveScroll init
|
|
824
769
|
// and web-font paint before measuring. 200 ms covers the loco 100 ms delay too.
|
|
825
770
|
initTimer = setTimeout(() => {
|
|
826
771
|
rafId = requestAnimationFrame(() => {
|
|
827
|
-
requestAnimationFrame(init)
|
|
828
|
-
})
|
|
829
|
-
}, 200)
|
|
772
|
+
requestAnimationFrame(init)
|
|
773
|
+
})
|
|
774
|
+
}, 200)
|
|
830
775
|
|
|
831
776
|
// Debounced resize — recalculates pin length and slide positions
|
|
832
777
|
const onResize = () => {
|
|
833
|
-
clearTimeout(resizeTimer)
|
|
778
|
+
clearTimeout(resizeTimer)
|
|
834
779
|
resizeTimer = setTimeout(() => {
|
|
835
|
-
ScrollTrigger.refresh(true)
|
|
836
|
-
}, 150)
|
|
837
|
-
}
|
|
780
|
+
ScrollTrigger.refresh(true)
|
|
781
|
+
}, 150)
|
|
782
|
+
}
|
|
838
783
|
|
|
839
|
-
window.addEventListener(
|
|
784
|
+
window.addEventListener('resize', onResize)
|
|
840
785
|
// Also respond to locomotive:resize dispatched by PageClient
|
|
841
|
-
window.addEventListener(
|
|
786
|
+
window.addEventListener('locomotive:resize', onResize)
|
|
842
787
|
|
|
843
788
|
return () => {
|
|
844
|
-
clearTimeout(initTimer)
|
|
845
|
-
clearTimeout(resizeTimer)
|
|
846
|
-
cancelAnimationFrame(rafId)
|
|
847
|
-
window.removeEventListener(
|
|
848
|
-
window.removeEventListener(
|
|
849
|
-
ctx?.revert()
|
|
850
|
-
ctx = null
|
|
851
|
-
}
|
|
852
|
-
}, [slideCount, sectionId])
|
|
789
|
+
clearTimeout(initTimer)
|
|
790
|
+
clearTimeout(resizeTimer)
|
|
791
|
+
cancelAnimationFrame(rafId)
|
|
792
|
+
window.removeEventListener('resize', onResize)
|
|
793
|
+
window.removeEventListener('locomotive:resize', onResize)
|
|
794
|
+
ctx?.revert()
|
|
795
|
+
ctx = null
|
|
796
|
+
}
|
|
797
|
+
}, [slideCount, sectionId])
|
|
853
798
|
|
|
854
799
|
// offset next block
|
|
855
800
|
const getOffset = () => {
|
|
856
|
-
if (typeof window ===
|
|
801
|
+
if (typeof window === 'undefined') return 25
|
|
857
802
|
|
|
858
|
-
if (window.matchMedia(
|
|
859
|
-
if (window.matchMedia(
|
|
803
|
+
if (window.matchMedia('(max-width: 420px)').matches) return 550
|
|
804
|
+
if (window.matchMedia('(max-width: 768px)').matches) return 300
|
|
860
805
|
|
|
861
|
-
return 25
|
|
862
|
-
}
|
|
806
|
+
return 25
|
|
807
|
+
}
|
|
863
808
|
|
|
864
|
-
const offset = getOffset()
|
|
809
|
+
const offset = getOffset()
|
|
865
810
|
return (
|
|
866
811
|
<section
|
|
867
812
|
ref={outerRef}
|
|
@@ -876,7 +821,7 @@ export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
|
876
821
|
ref={wrapperRef}
|
|
877
822
|
className="shs-wrapper sticky overflow-hidden top-0"
|
|
878
823
|
style={{
|
|
879
|
-
height:
|
|
824
|
+
height: '100svh',
|
|
880
825
|
}}
|
|
881
826
|
>
|
|
882
827
|
{/* Custom CSS */}
|
|
@@ -885,11 +830,10 @@ export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
|
885
830
|
dangerouslySetInnerHTML={{
|
|
886
831
|
__html: sectionId
|
|
887
832
|
? customCSS.replace(/:host/g, `#${sectionId}`)
|
|
888
|
-
: customCSS.replace(/:host/g,
|
|
833
|
+
: customCSS.replace(/:host/g, '.shs-wrapper'),
|
|
889
834
|
}}
|
|
890
835
|
/>
|
|
891
836
|
)}
|
|
892
|
-
|
|
893
837
|
{/* Subtle grid overlay */}
|
|
894
838
|
{/* <div
|
|
895
839
|
className="absolute inset-0 pointer-events-none z-0"
|
|
@@ -901,38 +845,36 @@ export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
|
901
845
|
backgroundSize: '60px 60px'
|
|
902
846
|
}}
|
|
903
847
|
/> */}
|
|
904
|
-
|
|
905
848
|
{/* Section eyebrow label */}
|
|
849
|
+
#BlockName-DDHorizontalScroll
|
|
906
850
|
{sectionLabel && (
|
|
907
|
-
<div className="section-label absolute top-
|
|
908
|
-
<div className="w-6 h-px" style={{ background:
|
|
851
|
+
<div className="section-label absolute top-8 left-4 z-20 flex items-center gap-3">
|
|
852
|
+
<div className="w-6 h-px" style={{ background: '#84cc16' }} />
|
|
909
853
|
<span
|
|
910
854
|
className="text-[11.5px] font-semibold tracking-wide uppercase"
|
|
911
855
|
style={{
|
|
912
|
-
color:
|
|
856
|
+
color: '#84cc16',
|
|
913
857
|
}}
|
|
914
858
|
>
|
|
915
859
|
{sectionLabel}
|
|
916
860
|
</span>
|
|
917
861
|
</div>
|
|
918
862
|
)}
|
|
919
|
-
|
|
920
863
|
{/* Slide dot counter */}
|
|
921
864
|
<div className="slide-counter absolute top-16 right-6 z-20 flex items-center gap-2">
|
|
922
865
|
{normalSlides.map((_, i) => (
|
|
923
866
|
<div
|
|
924
867
|
key={i}
|
|
925
868
|
className="w-1.5 h-1.5 rounded-full hidden"
|
|
926
|
-
style={{ background:
|
|
869
|
+
style={{ background: 'rgba(255,255,255,0.2)' }}
|
|
927
870
|
/>
|
|
928
871
|
))}
|
|
929
872
|
</div>
|
|
930
|
-
|
|
931
873
|
{/* Horizontal track */}
|
|
932
874
|
<div
|
|
933
875
|
ref={trackRef}
|
|
934
876
|
className="absolute top-0 left-0 flex h-full"
|
|
935
|
-
style={{ willChange:
|
|
877
|
+
style={{ willChange: 'transform' }}
|
|
936
878
|
>
|
|
937
879
|
{normalSlides.map((slide, i) => (
|
|
938
880
|
<SlidePanel
|
|
@@ -941,12 +883,11 @@ export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
|
941
883
|
index={i}
|
|
942
884
|
total={normalSlides.length - 1}
|
|
943
885
|
panelRef={(el) => {
|
|
944
|
-
slideRefs.current[i] = el
|
|
886
|
+
slideRefs.current[i] = el
|
|
945
887
|
}}
|
|
946
888
|
/>
|
|
947
889
|
))}
|
|
948
890
|
</div>
|
|
949
|
-
|
|
950
891
|
{/* Scroll hint */}
|
|
951
892
|
{/* <div
|
|
952
893
|
className="scroll-hint absolute bottom-16 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-2"
|
|
@@ -970,15 +911,15 @@ export function DDHorizontalScroll(props: DDHorizontalScrollProps) {
|
|
|
970
911
|
</div> */}
|
|
971
912
|
</div>
|
|
972
913
|
</section>
|
|
973
|
-
)
|
|
914
|
+
)
|
|
974
915
|
}
|
|
975
916
|
|
|
976
917
|
/* ── Entrance animation helper ──────────────────────────────────── */
|
|
977
918
|
|
|
978
919
|
function animateSlideIn(el: HTMLDivElement | null) {
|
|
979
|
-
if (!el) return
|
|
980
|
-
const animEls = el.querySelectorAll<HTMLElement>(
|
|
981
|
-
if (!animEls.length) return
|
|
920
|
+
if (!el) return
|
|
921
|
+
const animEls = el.querySelectorAll<HTMLElement>('.anim-el')
|
|
922
|
+
if (!animEls.length) return
|
|
982
923
|
gsap.fromTo(
|
|
983
924
|
animEls,
|
|
984
925
|
{ opacity: 1, y: 0 },
|
|
@@ -987,9 +928,9 @@ function animateSlideIn(el: HTMLDivElement | null) {
|
|
|
987
928
|
y: 0,
|
|
988
929
|
stagger: 0.07,
|
|
989
930
|
duration: 0.55,
|
|
990
|
-
ease:
|
|
931
|
+
ease: 'power4.out',
|
|
991
932
|
overwrite: true,
|
|
992
|
-
clearProps:
|
|
933
|
+
clearProps: 'transform',
|
|
993
934
|
},
|
|
994
|
-
)
|
|
935
|
+
)
|
|
995
936
|
}
|