snow-flow 3.4.36 → 3.4.39
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 +412 -287
- package/dist/cli/deploy-artifact.js +2 -2
- package/dist/mcp/servicenow-deployment-mcp.js +1 -1
- package/dist/mcp/servicenow-mcp-server.js +2 -2
- package/dist/queen/agent-factory.js +134 -52
- package/dist/queen/servicenow-queen.d.ts +127 -2
- package/dist/queen/servicenow-queen.js +978 -618
- package/dist/services/widget-deployment-service.d.ts +1 -1
- package/dist/services/widget-deployment-service.js +1 -1
- package/dist/templates/claude-md-template.d.ts +1 -1
- package/dist/templates/claude-md-template.js +2 -2
- package/dist/types/servicenow.types.d.ts +1 -1
- package/dist/utils/dependency-detector.d.ts +1 -1
- package/dist/utils/dependency-detector.js +1 -1
- package/dist/utils/servicenow-client.d.ts +1 -1
- package/dist/utils/servicenow-client.js +4 -8
- package/package.json +1 -1
- package/website/components/README.md +419 -0
- package/website/components/code-display/CodeDisplay.css +583 -0
- package/website/components/code-display/CodeDisplay.html +200 -0
- package/website/components/code-display/CodeDisplay.js +375 -0
- package/website/components/code-display/CodeDisplay.jsx +268 -0
- package/website/components/demo/ComponentLibraryDemo.html +845 -0
- package/website/components/feature-cards/FeatureCards.css +573 -0
- package/website/components/feature-cards/FeatureCards.html +247 -0
- package/website/components/feature-cards/FeatureCards.js +382 -0
- package/website/components/feature-cards/FeatureCards.jsx +235 -0
- package/website/components/hero/Hero.css +558 -0
- package/website/components/hero/Hero.html +98 -0
- package/website/components/hero/Hero.js +415 -0
- package/website/components/hero/Hero.jsx +214 -0
- package/website/components/interactive/InteractiveElements.css +776 -0
- package/website/components/interactive/InteractiveElements.html +283 -0
- package/website/components/interactive/InteractiveElements.js +489 -0
- package/website/components/interactive/InteractiveElements.jsx +444 -0
- package/website/components/layout/LayoutComponents.css +697 -0
- package/website/components/layout/LayoutComponents.html +374 -0
- package/website/components/layout/LayoutComponents.js +447 -0
- package/website/components/layout/LayoutComponents.jsx +379 -0
- package/website/components/navigation/Navigation.css +383 -0
- package/website/components/navigation/Navigation.html +89 -0
- package/website/components/navigation/Navigation.js +248 -0
- package/website/components/navigation/Navigation.jsx +124 -0
- package/website/css/animations.css +854 -0
- package/website/css/style.css +916 -948
- package/website/index.html +394 -424
- package/website/js/animations.js +707 -0
- package/website/js/main.js +310 -383
- package/website/mcp-servers.html +310 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// Snow-Flow Hero Component JavaScript
|
|
2
|
+
|
|
3
|
+
class SnowFlowHero {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
// Configuration
|
|
6
|
+
this.config = {
|
|
7
|
+
heroId: options.heroId || 'sf-hero',
|
|
8
|
+
titleId: options.titleId || 'sf-hero-title',
|
|
9
|
+
subtitleId: options.subtitleId || 'sf-hero-subtitle',
|
|
10
|
+
descriptionId: options.descriptionId || 'sf-hero-description',
|
|
11
|
+
actionsId: options.actionsId || 'sf-hero-actions',
|
|
12
|
+
statsId: options.statsId || 'sf-hero-stats',
|
|
13
|
+
scrollId: options.scrollId || 'sf-hero-scroll',
|
|
14
|
+
meshId: options.meshId || 'sf-hero-mesh',
|
|
15
|
+
|
|
16
|
+
// Content
|
|
17
|
+
title: options.title || 'Snow-Flow',
|
|
18
|
+
subtitle: options.subtitle || 'Advanced ServiceNow Development Framework',
|
|
19
|
+
|
|
20
|
+
// Effects
|
|
21
|
+
typewriterEffect: options.typewriterEffect !== false,
|
|
22
|
+
backgroundAnimation: options.backgroundAnimation !== false,
|
|
23
|
+
typewriterSpeed: {
|
|
24
|
+
title: options.titleSpeed || 100,
|
|
25
|
+
subtitle: options.subtitleSpeed || 50
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Animation states
|
|
29
|
+
isVisible: false,
|
|
30
|
+
titleComplete: false,
|
|
31
|
+
subtitleComplete: false,
|
|
32
|
+
descriptionVisible: false,
|
|
33
|
+
actionsVisible: false,
|
|
34
|
+
statsVisible: false,
|
|
35
|
+
scrollVisible: false
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// DOM elements
|
|
39
|
+
this.elements = {
|
|
40
|
+
hero: document.getElementById(this.config.heroId),
|
|
41
|
+
title: document.getElementById(this.config.titleId),
|
|
42
|
+
subtitle: document.getElementById(this.config.subtitleId),
|
|
43
|
+
description: document.getElementById(this.config.descriptionId),
|
|
44
|
+
actions: document.getElementById(this.config.actionsId),
|
|
45
|
+
stats: document.getElementById(this.config.statsId),
|
|
46
|
+
scroll: document.getElementById(this.config.scrollId),
|
|
47
|
+
mesh: document.getElementById(this.config.meshId)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.init();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
init() {
|
|
54
|
+
if (!this.elements.hero) {
|
|
55
|
+
console.warn('SnowFlowHero: Hero element not found');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.setupIntersectionObserver();
|
|
60
|
+
this.setupEventListeners();
|
|
61
|
+
this.generateMeshDots();
|
|
62
|
+
this.setupProgressIndicator();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setupIntersectionObserver() {
|
|
66
|
+
const observer = new IntersectionObserver(
|
|
67
|
+
(entries) => {
|
|
68
|
+
entries.forEach(entry => {
|
|
69
|
+
if (entry.isIntersecting && !this.config.isVisible) {
|
|
70
|
+
this.config.isVisible = true;
|
|
71
|
+
this.startAnimations();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
{ threshold: 0.1 }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
observer.observe(this.elements.hero);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setupEventListeners() {
|
|
82
|
+
// CTA Button clicks
|
|
83
|
+
const primaryCTA = this.elements.hero.querySelector('#sf-hero-primary-cta');
|
|
84
|
+
const secondaryCTA = this.elements.hero.querySelector('#sf-hero-secondary-cta');
|
|
85
|
+
|
|
86
|
+
if (primaryCTA) {
|
|
87
|
+
primaryCTA.addEventListener('click', (e) => {
|
|
88
|
+
this.handleCTAClick(e, primaryCTA.getAttribute('data-href'));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (secondaryCTA) {
|
|
93
|
+
secondaryCTA.addEventListener('click', (e) => {
|
|
94
|
+
this.handleCTAClick(e, secondaryCTA.getAttribute('data-href'));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Scroll indicator click
|
|
99
|
+
if (this.elements.scroll) {
|
|
100
|
+
this.elements.scroll.addEventListener('click', () => {
|
|
101
|
+
this.scrollToNextSection();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Keyboard accessibility
|
|
106
|
+
document.addEventListener('keydown', (e) => {
|
|
107
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
108
|
+
const focusedElement = document.activeElement;
|
|
109
|
+
if (focusedElement && focusedElement.classList.contains('sf-hero__cta')) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
focusedElement.click();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
generateMeshDots() {
|
|
118
|
+
if (!this.config.backgroundAnimation || !this.elements.mesh) return;
|
|
119
|
+
|
|
120
|
+
// Clear existing dots
|
|
121
|
+
this.elements.mesh.innerHTML = '';
|
|
122
|
+
|
|
123
|
+
// Generate animated dots
|
|
124
|
+
for (let i = 0; i < 50; i++) {
|
|
125
|
+
const dot = document.createElement('div');
|
|
126
|
+
dot.className = 'sf-hero__mesh-dot';
|
|
127
|
+
dot.style.cssText = `
|
|
128
|
+
animation-delay: ${Math.random() * 5}s;
|
|
129
|
+
left: ${Math.random() * 100}%;
|
|
130
|
+
top: ${Math.random() * 100}%;
|
|
131
|
+
`;
|
|
132
|
+
this.elements.mesh.appendChild(dot);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setupProgressIndicator() {
|
|
137
|
+
// Update scroll progress for the scroll indicator
|
|
138
|
+
window.addEventListener('scroll', () => {
|
|
139
|
+
const scrolled = window.pageYOffset;
|
|
140
|
+
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
|
|
141
|
+
const progress = Math.min(scrolled / maxScroll, 1);
|
|
142
|
+
|
|
143
|
+
// You can use this progress value for additional effects if needed
|
|
144
|
+
this.updateScrollProgress(progress);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
updateScrollProgress(progress) {
|
|
149
|
+
// Optional: Add scroll-based effects here
|
|
150
|
+
// For example, changing the hero opacity as user scrolls
|
|
151
|
+
if (this.elements.hero) {
|
|
152
|
+
const opacity = Math.max(0.3, 1 - progress * 0.7);
|
|
153
|
+
this.elements.hero.style.opacity = opacity;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
startAnimations() {
|
|
158
|
+
// Add visible class to hero
|
|
159
|
+
this.elements.hero.classList.add('sf-hero--visible');
|
|
160
|
+
|
|
161
|
+
// Start typewriter effects
|
|
162
|
+
if (this.config.typewriterEffect) {
|
|
163
|
+
setTimeout(() => this.typewriteTitle(), 500);
|
|
164
|
+
} else {
|
|
165
|
+
// Set text immediately if typewriter is disabled
|
|
166
|
+
this.elements.title.textContent = this.config.title;
|
|
167
|
+
this.elements.subtitle.textContent = this.config.subtitle;
|
|
168
|
+
this.config.titleComplete = true;
|
|
169
|
+
this.config.subtitleComplete = true;
|
|
170
|
+
this.showRemainingElements();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
typewriteTitle() {
|
|
175
|
+
if (!this.elements.title) return;
|
|
176
|
+
|
|
177
|
+
const title = this.config.title;
|
|
178
|
+
let index = 0;
|
|
179
|
+
|
|
180
|
+
const typeChar = () => {
|
|
181
|
+
if (index < title.length) {
|
|
182
|
+
this.elements.title.textContent = title.slice(0, index + 1);
|
|
183
|
+
|
|
184
|
+
// Add cursor
|
|
185
|
+
const cursor = document.createElement('span');
|
|
186
|
+
cursor.className = 'sf-hero__cursor';
|
|
187
|
+
cursor.textContent = '|';
|
|
188
|
+
this.elements.title.appendChild(cursor);
|
|
189
|
+
|
|
190
|
+
index++;
|
|
191
|
+
setTimeout(typeChar, this.config.typewriterSpeed.title);
|
|
192
|
+
} else {
|
|
193
|
+
// Remove cursor and mark title complete
|
|
194
|
+
const cursor = this.elements.title.querySelector('.sf-hero__cursor');
|
|
195
|
+
if (cursor) cursor.remove();
|
|
196
|
+
|
|
197
|
+
this.config.titleComplete = true;
|
|
198
|
+
setTimeout(() => this.typewriteSubtitle(), 300);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
typeChar();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
typewriteSubtitle() {
|
|
206
|
+
if (!this.elements.subtitle) return;
|
|
207
|
+
|
|
208
|
+
const subtitle = this.config.subtitle;
|
|
209
|
+
let index = 0;
|
|
210
|
+
|
|
211
|
+
const typeChar = () => {
|
|
212
|
+
if (index < subtitle.length) {
|
|
213
|
+
this.elements.subtitle.textContent = subtitle.slice(0, index + 1);
|
|
214
|
+
|
|
215
|
+
// Add cursor
|
|
216
|
+
const cursor = document.createElement('span');
|
|
217
|
+
cursor.className = 'sf-hero__cursor';
|
|
218
|
+
cursor.textContent = '|';
|
|
219
|
+
this.elements.subtitle.appendChild(cursor);
|
|
220
|
+
|
|
221
|
+
index++;
|
|
222
|
+
setTimeout(typeChar, this.config.typewriterSpeed.subtitle);
|
|
223
|
+
} else {
|
|
224
|
+
// Remove cursor and mark subtitle complete
|
|
225
|
+
const cursor = this.elements.subtitle.querySelector('.sf-hero__cursor');
|
|
226
|
+
if (cursor) cursor.remove();
|
|
227
|
+
|
|
228
|
+
this.config.subtitleComplete = true;
|
|
229
|
+
this.showRemainingElements();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
typeChar();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
showRemainingElements() {
|
|
237
|
+
// Show description
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
if (this.elements.description) {
|
|
240
|
+
this.elements.description.classList.add('sf-hero__description--visible');
|
|
241
|
+
this.config.descriptionVisible = true;
|
|
242
|
+
}
|
|
243
|
+
}, 200);
|
|
244
|
+
|
|
245
|
+
// Show actions
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
if (this.elements.actions) {
|
|
248
|
+
this.elements.actions.classList.add('sf-hero__actions--visible');
|
|
249
|
+
this.config.actionsVisible = true;
|
|
250
|
+
}
|
|
251
|
+
}, 400);
|
|
252
|
+
|
|
253
|
+
// Show stats
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
if (this.elements.stats) {
|
|
256
|
+
this.elements.stats.classList.add('sf-hero__stats--visible');
|
|
257
|
+
this.config.statsVisible = true;
|
|
258
|
+
this.animateStats();
|
|
259
|
+
}
|
|
260
|
+
}, 600);
|
|
261
|
+
|
|
262
|
+
// Show scroll indicator
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
if (this.elements.scroll) {
|
|
265
|
+
this.elements.scroll.classList.add('sf-hero__scroll-indicator--visible');
|
|
266
|
+
this.config.scrollVisible = true;
|
|
267
|
+
}
|
|
268
|
+
}, 800);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
animateStats() {
|
|
272
|
+
const statElements = this.elements.stats.querySelectorAll('.sf-hero__stat');
|
|
273
|
+
statElements.forEach((stat, index) => {
|
|
274
|
+
setTimeout(() => {
|
|
275
|
+
stat.style.animationDelay = `${0.5 + index * 0.1}s`;
|
|
276
|
+
stat.style.opacity = '1';
|
|
277
|
+
stat.style.transform = 'translateY(0)';
|
|
278
|
+
}, index * 100);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
handleCTAClick(e, href) {
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
|
|
285
|
+
if (!href) return;
|
|
286
|
+
|
|
287
|
+
// Add ripple effect for primary button
|
|
288
|
+
if (e.target.classList.contains('sf-hero__cta--primary')) {
|
|
289
|
+
this.createRippleEffect(e.target, e);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Navigate to target
|
|
293
|
+
if (href.startsWith('#')) {
|
|
294
|
+
this.scrollToSection(href);
|
|
295
|
+
} else {
|
|
296
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
createRippleEffect(button, event) {
|
|
301
|
+
const ripple = button.querySelector('.sf-hero__cta-ripple');
|
|
302
|
+
if (!ripple) return;
|
|
303
|
+
|
|
304
|
+
// Reset ripple
|
|
305
|
+
ripple.style.width = '0';
|
|
306
|
+
ripple.style.height = '0';
|
|
307
|
+
|
|
308
|
+
// Trigger ripple animation
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
ripple.style.width = '300px';
|
|
311
|
+
ripple.style.height = '300px';
|
|
312
|
+
}, 10);
|
|
313
|
+
|
|
314
|
+
// Reset after animation
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
ripple.style.width = '0';
|
|
317
|
+
ripple.style.height = '0';
|
|
318
|
+
}, 500);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
scrollToSection(target) {
|
|
322
|
+
const element = document.querySelector(target);
|
|
323
|
+
if (element) {
|
|
324
|
+
const offset = 80; // Account for fixed navbar
|
|
325
|
+
const targetPosition = element.offsetTop - offset;
|
|
326
|
+
|
|
327
|
+
window.scrollTo({
|
|
328
|
+
top: targetPosition,
|
|
329
|
+
behavior: 'smooth'
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
scrollToNextSection() {
|
|
335
|
+
// Find the next section after hero
|
|
336
|
+
const nextSection = this.elements.hero.nextElementSibling;
|
|
337
|
+
if (nextSection) {
|
|
338
|
+
nextSection.scrollIntoView({ behavior: 'smooth' });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Public API methods
|
|
343
|
+
updateContent(newContent) {
|
|
344
|
+
if (newContent.title) {
|
|
345
|
+
this.config.title = newContent.title;
|
|
346
|
+
if (!this.config.typewriterEffect) {
|
|
347
|
+
this.elements.title.textContent = newContent.title;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (newContent.subtitle) {
|
|
352
|
+
this.config.subtitle = newContent.subtitle;
|
|
353
|
+
if (!this.config.typewriterEffect) {
|
|
354
|
+
this.elements.subtitle.textContent = newContent.subtitle;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (newContent.description && this.elements.description) {
|
|
359
|
+
this.elements.description.textContent = newContent.description;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
resetAnimations() {
|
|
364
|
+
// Reset all animation states
|
|
365
|
+
this.config.isVisible = false;
|
|
366
|
+
this.config.titleComplete = false;
|
|
367
|
+
this.config.subtitleComplete = false;
|
|
368
|
+
this.config.descriptionVisible = false;
|
|
369
|
+
this.config.actionsVisible = false;
|
|
370
|
+
this.config.statsVisible = false;
|
|
371
|
+
this.config.scrollVisible = false;
|
|
372
|
+
|
|
373
|
+
// Remove classes
|
|
374
|
+
this.elements.hero.classList.remove('sf-hero--visible');
|
|
375
|
+
if (this.elements.description) {
|
|
376
|
+
this.elements.description.classList.remove('sf-hero__description--visible');
|
|
377
|
+
}
|
|
378
|
+
if (this.elements.actions) {
|
|
379
|
+
this.elements.actions.classList.remove('sf-hero__actions--visible');
|
|
380
|
+
}
|
|
381
|
+
if (this.elements.stats) {
|
|
382
|
+
this.elements.stats.classList.remove('sf-hero__stats--visible');
|
|
383
|
+
}
|
|
384
|
+
if (this.elements.scroll) {
|
|
385
|
+
this.elements.scroll.classList.remove('sf-hero__scroll-indicator--visible');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Clear content
|
|
389
|
+
this.elements.title.textContent = '';
|
|
390
|
+
this.elements.subtitle.textContent = '';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
destroy() {
|
|
394
|
+
// Cleanup method
|
|
395
|
+
this.resetAnimations();
|
|
396
|
+
// Remove event listeners if needed
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Auto-initialize on DOM content loaded
|
|
401
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
402
|
+
// Check if hero exists before initializing
|
|
403
|
+
const heroElement = document.getElementById('sf-hero');
|
|
404
|
+
if (heroElement) {
|
|
405
|
+
window.snowFlowHero = new SnowFlowHero();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Export for module usage
|
|
410
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
411
|
+
module.exports = SnowFlowHero;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Global access
|
|
415
|
+
window.SnowFlowHero = SnowFlowHero;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import './Hero.css';
|
|
3
|
+
|
|
4
|
+
const Hero = ({
|
|
5
|
+
title = "Snow-Flow",
|
|
6
|
+
subtitle = "Advanced ServiceNow Development Framework",
|
|
7
|
+
description = "Revolutionize your ServiceNow development with AI-powered automation, intelligent orchestration, and cutting-edge tools designed for the modern enterprise.",
|
|
8
|
+
primaryCTA = { text: "Get Started", href: "#get-started" },
|
|
9
|
+
secondaryCTA = { text: "Learn More", href: "#features" },
|
|
10
|
+
stats = [
|
|
11
|
+
{ number: "17", label: "MCP Servers" },
|
|
12
|
+
{ number: "200+", label: "Tools" },
|
|
13
|
+
{ number: "80%", label: "Time Saved" },
|
|
14
|
+
{ number: "100%", label: "Real Data" }
|
|
15
|
+
],
|
|
16
|
+
backgroundAnimation = true,
|
|
17
|
+
typewriterEffect = true,
|
|
18
|
+
className = ""
|
|
19
|
+
}) => {
|
|
20
|
+
const [displayedTitle, setDisplayedTitle] = useState('');
|
|
21
|
+
const [titleComplete, setTitleComplete] = useState(false);
|
|
22
|
+
const [displayedSubtitle, setDisplayedSubtitle] = useState('');
|
|
23
|
+
const [subtitleComplete, setSubtitleComplete] = useState(false);
|
|
24
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
25
|
+
const heroRef = useRef(null);
|
|
26
|
+
|
|
27
|
+
// Intersection Observer for entrance animation
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const observer = new IntersectionObserver(
|
|
30
|
+
([entry]) => {
|
|
31
|
+
if (entry.isIntersecting) {
|
|
32
|
+
setIsVisible(true);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{ threshold: 0.1 }
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (heroRef.current) {
|
|
39
|
+
observer.observe(heroRef.current);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
// Typewriter effect for title
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!typewriterEffect || !isVisible) return;
|
|
48
|
+
|
|
49
|
+
let index = 0;
|
|
50
|
+
const typeTitle = () => {
|
|
51
|
+
if (index < title.length) {
|
|
52
|
+
setDisplayedTitle(title.slice(0, index + 1));
|
|
53
|
+
index++;
|
|
54
|
+
setTimeout(typeTitle, 100);
|
|
55
|
+
} else {
|
|
56
|
+
setTitleComplete(true);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const timer = setTimeout(typeTitle, 500);
|
|
61
|
+
return () => clearTimeout(timer);
|
|
62
|
+
}, [title, typewriterEffect, isVisible]);
|
|
63
|
+
|
|
64
|
+
// Typewriter effect for subtitle
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!typewriterEffect || !titleComplete) return;
|
|
67
|
+
|
|
68
|
+
let index = 0;
|
|
69
|
+
const typeSubtitle = () => {
|
|
70
|
+
if (index < subtitle.length) {
|
|
71
|
+
setDisplayedSubtitle(subtitle.slice(0, index + 1));
|
|
72
|
+
index++;
|
|
73
|
+
setTimeout(typeSubtitle, 50);
|
|
74
|
+
} else {
|
|
75
|
+
setSubtitleComplete(true);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const timer = setTimeout(typeSubtitle, 300);
|
|
80
|
+
return () => clearTimeout(timer);
|
|
81
|
+
}, [subtitle, typewriterEffect, titleComplete]);
|
|
82
|
+
|
|
83
|
+
// Set initial text if typewriter is disabled
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!typewriterEffect) {
|
|
86
|
+
setDisplayedTitle(title);
|
|
87
|
+
setDisplayedSubtitle(subtitle);
|
|
88
|
+
setTitleComplete(true);
|
|
89
|
+
setSubtitleComplete(true);
|
|
90
|
+
}
|
|
91
|
+
}, [title, subtitle, typewriterEffect]);
|
|
92
|
+
|
|
93
|
+
const handleCTAClick = (e, href) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
if (href.startsWith('#')) {
|
|
96
|
+
const target = document.querySelector(href);
|
|
97
|
+
if (target) {
|
|
98
|
+
const offset = 80;
|
|
99
|
+
const targetPosition = target.offsetTop - offset;
|
|
100
|
+
window.scrollTo({
|
|
101
|
+
top: targetPosition,
|
|
102
|
+
behavior: 'smooth'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<section
|
|
112
|
+
ref={heroRef}
|
|
113
|
+
className={`sf-hero ${isVisible ? 'sf-hero--visible' : ''} ${className}`}
|
|
114
|
+
>
|
|
115
|
+
{backgroundAnimation && (
|
|
116
|
+
<>
|
|
117
|
+
{/* Animated Background Mesh */}
|
|
118
|
+
<div className="sf-hero__background">
|
|
119
|
+
<div className="sf-hero__mesh">
|
|
120
|
+
{Array.from({ length: 50 }, (_, i) => (
|
|
121
|
+
<div
|
|
122
|
+
key={i}
|
|
123
|
+
className="sf-hero__mesh-dot"
|
|
124
|
+
style={{
|
|
125
|
+
animationDelay: `${Math.random() * 5}s`,
|
|
126
|
+
left: `${Math.random() * 100}%`,
|
|
127
|
+
top: `${Math.random() * 100}%`
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Gradient Orbs */}
|
|
134
|
+
<div className="sf-hero__orb sf-hero__orb--1"></div>
|
|
135
|
+
<div className="sf-hero__orb sf-hero__orb--2"></div>
|
|
136
|
+
<div className="sf-hero__orb sf-hero__orb--3"></div>
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
<div className="sf-hero__container">
|
|
142
|
+
<div className="sf-hero__content">
|
|
143
|
+
{/* Main Title */}
|
|
144
|
+
<h1 className="sf-hero__title">
|
|
145
|
+
{displayedTitle}
|
|
146
|
+
{typewriterEffect && !titleComplete && (
|
|
147
|
+
<span className="sf-hero__cursor">|</span>
|
|
148
|
+
)}
|
|
149
|
+
</h1>
|
|
150
|
+
|
|
151
|
+
{/* Subtitle */}
|
|
152
|
+
<h2 className="sf-hero__subtitle">
|
|
153
|
+
{displayedSubtitle}
|
|
154
|
+
{typewriterEffect && titleComplete && !subtitleComplete && (
|
|
155
|
+
<span className="sf-hero__cursor">|</span>
|
|
156
|
+
)}
|
|
157
|
+
</h2>
|
|
158
|
+
|
|
159
|
+
{/* Description */}
|
|
160
|
+
<p className={`sf-hero__description ${subtitleComplete ? 'sf-hero__description--visible' : ''}`}>
|
|
161
|
+
{description}
|
|
162
|
+
</p>
|
|
163
|
+
|
|
164
|
+
{/* CTA Buttons */}
|
|
165
|
+
<div className={`sf-hero__actions ${subtitleComplete ? 'sf-hero__actions--visible' : ''}`}>
|
|
166
|
+
<button
|
|
167
|
+
className="sf-hero__cta sf-hero__cta--primary"
|
|
168
|
+
onClick={(e) => handleCTAClick(e, primaryCTA.href)}
|
|
169
|
+
>
|
|
170
|
+
{primaryCTA.text}
|
|
171
|
+
<span className="sf-hero__cta-ripple"></span>
|
|
172
|
+
</button>
|
|
173
|
+
|
|
174
|
+
<button
|
|
175
|
+
className="sf-hero__cta sf-hero__cta--secondary"
|
|
176
|
+
onClick={(e) => handleCTAClick(e, secondaryCTA.href)}
|
|
177
|
+
>
|
|
178
|
+
{secondaryCTA.text}
|
|
179
|
+
<span className="sf-hero__cta-arrow">→</span>
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Stats */}
|
|
184
|
+
{stats && stats.length > 0 && (
|
|
185
|
+
<div className={`sf-hero__stats ${subtitleComplete ? 'sf-hero__stats--visible' : ''}`}>
|
|
186
|
+
{stats.map((stat, index) => (
|
|
187
|
+
<div
|
|
188
|
+
key={index}
|
|
189
|
+
className="sf-hero__stat"
|
|
190
|
+
style={{ animationDelay: `${0.5 + index * 0.1}s` }}
|
|
191
|
+
>
|
|
192
|
+
<div className="sf-hero__stat-number">{stat.number}</div>
|
|
193
|
+
<div className="sf-hero__stat-label">{stat.label}</div>
|
|
194
|
+
</div>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Scroll Indicator */}
|
|
201
|
+
<div className={`sf-hero__scroll-indicator ${subtitleComplete ? 'sf-hero__scroll-indicator--visible' : ''}`}>
|
|
202
|
+
<div className="sf-hero__scroll-arrow">
|
|
203
|
+
<span></span>
|
|
204
|
+
<span></span>
|
|
205
|
+
<span></span>
|
|
206
|
+
</div>
|
|
207
|
+
<span className="sf-hero__scroll-text">Scroll to explore</span>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</section>
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export default Hero;
|