This commit is contained in:
Iliyan Angelov
2025-10-10 02:01:46 +03:00
parent dd8eb1c7aa
commit f962401565
36 changed files with 2560 additions and 659 deletions

View File

@@ -1,16 +1,61 @@
"use client";
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
import Preloader from "./Preloader";
import ScrollToTop from "./ScrollToTop";
import { usePathname } from "next/navigation";
interface LayoutWrapperProps {
children: ReactNode;
}
const LayoutWrapper = ({ children }: LayoutWrapperProps) => {
const pathname = usePathname();
useEffect(() => {
// Force scroll to top on every pathname change - runs FIRST
window.history.scrollRestoration = 'manual';
// Immediate scroll
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
// Disable any smooth scroll temporarily
const html = document.documentElement;
const body = document.body;
const originalHtmlScroll = html.style.scrollBehavior;
const originalBodyScroll = body.style.scrollBehavior;
html.style.scrollBehavior = 'auto';
body.style.scrollBehavior = 'auto';
// Multiple forced scrolls
const scrollInterval = setInterval(() => {
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}, 10);
// Clean up after 300ms
const cleanup = setTimeout(() => {
clearInterval(scrollInterval);
html.style.scrollBehavior = originalHtmlScroll;
body.style.scrollBehavior = originalBodyScroll;
}, 300);
return () => {
clearInterval(scrollInterval);
clearTimeout(cleanup);
};
}, [pathname]);
return (
<Preloader>
{children}
</Preloader>
<>
<ScrollToTop />
<Preloader>
{children}
</Preloader>
</>
);
};

View File

@@ -0,0 +1,405 @@
/* Enterprise Preloader Overlay */
.gnx-preloader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999 !important;
animation: fadeIn 0.4s ease-in;
overflow: hidden;
}
/* Geometric Background Pattern */
.gnx-preloader-bg-pattern {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(148, 163, 184, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(148, 163, 184, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
animation: patternMove 20s linear infinite;
opacity: 0.5;
}
.gnx-preloader-bg-pattern::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(59, 130, 246, 0.1) 0%, transparent 70%);
animation: pulse 4s ease-in-out infinite;
}
.gnx-preloader-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
text-align: center;
padding: 3rem;
z-index: 10;
}
/* Professional Logo Styling */
.gnx-preloader-logo {
position: relative;
margin-bottom: 1rem;
}
.gnx-logo-wrapper {
position: relative;
display: inline-block;
padding: 2rem;
background: linear-gradient(135deg, rgba(30, 41, 59, 0.8) 0%, rgba(15, 23, 42, 0.8) 100%);
border-radius: 20px;
overflow: visible;
border: 1px solid rgba(148, 163, 184, 0.2);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.gnx-logo-border {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(135deg, #3b82f6, #8b5cf6, #ec4899);
border-radius: 22px;
opacity: 0.5;
z-index: -1;
animation: borderGlow 3s ease-in-out infinite;
}
.gnx-logo-image {
position: relative;
z-index: 2;
filter: brightness(1.2) contrast(1.1);
transition: all 0.3s ease;
display: block !important;
}
/* Enterprise Branding */
.gnx-enterprise-brand {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.gnx-brand-title {
font-size: 1.75rem;
font-weight: 700;
color: #f1f5f9;
letter-spacing: -0.02em;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
text-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
}
.gnx-brand-subtitle {
font-size: 0.875rem;
font-weight: 400;
color: #94a3b8;
letter-spacing: 0.1em;
text-transform: uppercase;
margin: 0;
}
/* Professional Progress Container */
.gnx-progress-container {
width: 320px;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.gnx-progress-bar {
width: 100%;
height: 4px;
background: rgba(148, 163, 184, 0.1);
border-radius: 4px;
overflow: hidden;
position: relative;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}
.gnx-progress-fill {
height: 100%;
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
border-radius: 4px;
transition: width 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.5);
}
.gnx-progress-shine {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent
);
animation: progressShine 2s infinite;
}
.gnx-progress-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.gnx-progress-text {
font-size: 0.875rem;
color: #94a3b8;
font-weight: 500;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.gnx-progress-percentage {
font-size: 1rem;
color: #f1f5f9;
font-weight: 600;
font-family: 'SF Mono', 'Courier New', monospace;
min-width: 45px;
text-align: right;
}
/* Professional Loading Indicator */
.gnx-loading-indicator {
margin-top: 1rem;
}
.gnx-spinner {
position: relative;
width: 50px;
height: 50px;
}
.gnx-spinner-ring {
position: absolute;
width: 100%;
height: 100%;
border: 2px solid transparent;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spinRing 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}
.gnx-spinner-ring:nth-child(1) {
border-top-color: #3b82f6;
animation-delay: 0s;
}
.gnx-spinner-ring:nth-child(2) {
border-top-color: #8b5cf6;
animation-delay: 0.2s;
width: 75%;
height: 75%;
top: 12.5%;
left: 12.5%;
}
.gnx-spinner-ring:nth-child(3) {
border-top-color: #ec4899;
animation-delay: 0.4s;
width: 50%;
height: 50%;
top: 25%;
left: 25%;
}
/* Corporate Footer */
.gnx-preloader-footer {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid rgba(148, 163, 184, 0.1);
}
.gnx-footer-text {
font-size: 0.75rem;
color: #64748b;
font-weight: 400;
letter-spacing: 0.05em;
margin: 0;
text-transform: uppercase;
}
/* Content visibility */
.gnx-content-hidden {
opacity: 0;
pointer-events: none;
position: absolute;
left: -9999px;
}
.gnx-content-visible {
opacity: 1;
pointer-events: auto;
animation: contentFadeIn 0.5s ease-in;
}
/* Enterprise Animations */
@keyframes fadeIn {
from {
opacity: 0;
backdrop-filter: blur(0px);
}
to {
opacity: 1;
backdrop-filter: blur(10px);
}
}
@keyframes contentFadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes patternMove {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
@keyframes pulse {
0%, 100% {
opacity: 0.3;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 0.5;
transform: translate(-50%, -50%) scale(1.1);
}
}
@keyframes borderGlow {
0%, 100% {
opacity: 0.3;
filter: blur(10px);
}
50% {
opacity: 0.6;
filter: blur(15px);
}
}
@keyframes progressShine {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
@keyframes spinRing {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.gnx-preloader-container {
gap: 1.5rem;
padding: 2rem;
}
.gnx-logo-wrapper {
padding: 1.5rem;
}
.gnx-logo-image {
width: 75px !important;
height: 56px !important;
}
.gnx-brand-title {
font-size: 1.5rem;
}
.gnx-brand-subtitle {
font-size: 0.75rem;
}
.gnx-progress-container {
width: 260px;
}
.gnx-spinner {
width: 40px;
height: 40px;
}
}
@media (max-width: 480px) {
.gnx-preloader-container {
gap: 1rem;
padding: 1.5rem;
}
.gnx-logo-wrapper {
padding: 1rem;
}
.gnx-logo-image {
width: 60px !important;
height: 45px !important;
}
.gnx-brand-title {
font-size: 1.25rem;
}
.gnx-brand-subtitle {
font-size: 0.625rem;
}
.gnx-progress-container {
width: 200px;
}
.gnx-spinner {
width: 35px;
height: 35px;
}
.gnx-footer-text {
font-size: 0.625rem;
}
}

View File

@@ -2,6 +2,7 @@
import { useState, useEffect } from "react";
import { usePathname } from "next/navigation";
import Image from "next/image";
import "./Preloader.css";
interface PreloaderProps {
children: React.ReactNode;
@@ -9,377 +10,140 @@ interface PreloaderProps {
const Preloader = ({ children }: PreloaderProps) => {
const [isLoading, setIsLoading] = useState(true);
const [isMounted, setIsMounted] = useState(false);
const [progress, setProgress] = useState(0);
const [currentPath, setCurrentPath] = useState("");
const pathname = usePathname();
// Debug mode - set to true to see console logs
const DEBUG = false;
// Skip preloader for faster development/testing (set to true to disable preloader)
const SKIP_PRELOADER = false;
// Fast mode - set to true for even faster loading (200ms total)
const FAST_MODE = true;
// Initial mount - show preloader on first load
useEffect(() => {
// Only show preloader if path has changed or it's initial load
if (currentPath !== pathname) {
if (DEBUG) console.log('Preloader: Starting transition from', currentPath, 'to', pathname);
setIsLoading(true);
setProgress(0);
// Simulate loading progress - faster and more responsive
const progressInterval = setInterval(() => {
setProgress((prev) => {
if (prev >= 85) {
clearInterval(progressInterval);
return 85;
}
return prev + Math.random() * 25 + 10; // Faster progress increments
});
}, 50); // Reduced interval from 100ms to 50ms for smoother animation
setIsMounted(true);
setIsLoading(true);
setProgress(0);
// Simulate loading progress
const progressInterval = setInterval(() => {
setProgress((prev) => {
if (prev >= 90) {
clearInterval(progressInterval);
return 90;
}
return prev + Math.random() * 15 + 5;
});
}, 50);
// Complete loading much faster - adjust timing based on FAST_MODE
const loadingDuration = FAST_MODE ? 200 : 400;
const fadeOutDuration = FAST_MODE ? 50 : 100;
const completeTimer = setTimeout(() => {
setProgress(100);
setTimeout(() => {
if (DEBUG) console.log('Preloader: Transition complete');
setIsLoading(false);
setCurrentPath(pathname);
}, fadeOutDuration);
}, loadingDuration);
// Fallback: Force complete after reasonable time
const fallbackDuration = FAST_MODE ? 800 : 1500;
const fallbackTimer = setTimeout(() => {
if (DEBUG) console.log('Preloader: Fallback triggered - force completing');
// Complete loading after minimum duration
const completeTimer = setTimeout(() => {
setProgress(100);
setTimeout(() => {
setIsLoading(false);
setCurrentPath(pathname);
setProgress(100);
}, fallbackDuration);
}, 200);
}, 600);
return () => {
clearInterval(progressInterval);
clearTimeout(completeTimer);
clearTimeout(fallbackTimer);
};
}
}, [pathname, currentPath, DEBUG, FAST_MODE]);
return () => {
clearInterval(progressInterval);
clearTimeout(completeTimer);
};
}, []);
// Skip preloader entirely if SKIP_PRELOADER is true
if (SKIP_PRELOADER) {
return <>{children}</>;
}
// Handle route changes
useEffect(() => {
if (!isMounted) return;
// Don't show preloader if not loading
if (!isLoading) {
return <>{children}</>;
}
// Show preloader on route change
setIsLoading(true);
setProgress(0);
// Simulate loading progress
const progressInterval = setInterval(() => {
setProgress((prev) => {
if (prev >= 90) {
clearInterval(progressInterval);
return 90;
}
return prev + Math.random() * 20 + 10;
});
}, 40);
// Complete loading
const completeTimer = setTimeout(() => {
setProgress(100);
setTimeout(() => {
setIsLoading(false);
}, 150);
}, 400);
return () => {
clearInterval(progressInterval);
clearTimeout(completeTimer);
};
}, [pathname, isMounted]);
return (
<>
<div className="preloader-overlay">
<div className="preloader-container">
{/* Logo with shine effect */}
<div className="preloader-logo">
<div className="logo-wrapper">
<Image
src="/images/logo.png"
alt="GNX Logo"
width={80}
height={60}
className="logo-image"
priority
/>
<div className="shine-effect"></div>
{isLoading && (
<div className="gnx-preloader-overlay">
{/* Geometric background pattern */}
<div className="gnx-preloader-bg-pattern"></div>
<div className="gnx-preloader-container">
{/* Logo with professional wrapper */}
<div className="gnx-preloader-logo">
<div className="gnx-logo-wrapper">
<div className="gnx-logo-border"></div>
<Image
src="/images/logo.png"
alt="GNX Logo"
width={100}
height={75}
className="gnx-logo-image"
priority
unoptimized
/>
</div>
</div>
{/* Enterprise branding */}
<div className="gnx-enterprise-brand">
<h1 className="gnx-brand-title">GNX Enterprise</h1>
<p className="gnx-brand-subtitle">Digital Transformation Solutions</p>
</div>
{/* Professional progress indicator */}
<div className="gnx-progress-container">
<div className="gnx-progress-bar">
<div
className="gnx-progress-fill"
style={{ width: `${progress}%` }}
>
<div className="gnx-progress-shine"></div>
</div>
</div>
<div className="gnx-progress-info">
<span className="gnx-progress-text">Loading</span>
<span className="gnx-progress-percentage">{Math.round(progress)}%</span>
</div>
</div>
{/* Professional loading indicator */}
<div className="gnx-loading-indicator">
<div className="gnx-spinner">
<div className="gnx-spinner-ring"></div>
<div className="gnx-spinner-ring"></div>
<div className="gnx-spinner-ring"></div>
</div>
</div>
{/* Corporate footer */}
<div className="gnx-preloader-footer">
<p className="gnx-footer-text">Powered by Advanced Technology</p>
</div>
</div>
{/* Progress bar */}
<div className="progress-container">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
></div>
</div>
<div className="progress-text">{Math.round(progress)}%</div>
</div>
{/* Loading text */}
<div className="loading-text">
<span className="loading-dots">
<span>.</span>
<span>.</span>
<span>.</span>
</span>
</div>
{/* Enterprise tagline */}
<div className="enterprise-tagline">
<span>Enterprise Solutions</span>
<span>Excellence in Every Project</span>
</div>
</div>
</div>
)}
<style jsx>{`
.preloader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0f0f0f 100%);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
backdrop-filter: blur(10px);
}
.preloader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
text-align: center;
}
.preloader-logo {
position: relative;
animation: logoFloat 2s ease-in-out infinite;
}
.logo-wrapper {
position: relative;
display: inline-block;
padding: 1.5rem;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
transition: all 0.2s ease; /* Faster transitions */
}
.logo-image {
position: relative;
z-index: 2;
filter: brightness(1.1);
transition: all 0.2s ease; /* Faster transitions */
}
.shine-effect {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent
);
animation: shine 2s infinite;
z-index: 3;
}
.progress-container {
width: 200px;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.progress-bar {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4f46e5, #7c3aed, #ec4899);
border-radius: 2px;
transition: width 0.2s ease; /* Faster progress bar animation */
position: relative;
overflow: hidden;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.6),
transparent
);
animation: progressShine 1.5s infinite;
}
.progress-text {
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
letter-spacing: 0.05em;
}
.loading-text {
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.1em;
}
.loading-dots {
display: inline-flex;
gap: 0.25rem;
}
.loading-dots span {
animation: dotPulse 1.4s infinite ease-in-out;
animation-fill-mode: both;
}
.loading-dots span:nth-child(1) {
animation-delay: -0.32s;
}
.loading-dots span:nth-child(2) {
animation-delay: -0.16s;
}
.loading-dots span:nth-child(3) {
animation-delay: 0s;
}
.enterprise-tagline {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 1rem;
text-align: center;
}
.enterprise-tagline span {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
font-weight: 300;
letter-spacing: 0.15em;
text-transform: uppercase;
animation: fadeInUp 0.8s ease-out 0.5s both;
}
.enterprise-tagline span:first-child {
animation-delay: 0.7s;
}
.enterprise-tagline span:last-child {
animation-delay: 0.9s;
}
@keyframes logoFloat {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
@keyframes shine {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
@keyframes progressShine {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
@keyframes dotPulse {
0%, 80%, 100% {
opacity: 0.3;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.preloader-container {
gap: 1.5rem;
}
.logo-wrapper {
padding: 1rem;
}
.logo-image {
width: 60px;
height: 45px;
}
.progress-container {
width: 150px;
}
}
@media (max-width: 480px) {
.preloader-container {
gap: 1rem;
}
.logo-wrapper {
padding: 0.75rem;
}
.logo-image {
width: 50px;
height: 37px;
}
.progress-container {
width: 120px;
}
}
`}</style>
<div className={isLoading ? "gnx-content-hidden" : "gnx-content-visible"}>
{children}
</div>
</>
);
};

View File

@@ -1,106 +0,0 @@
# Preloader Component Documentation
## Overview
The Preloader component provides a sophisticated, enterprise-grade loading experience for the GNX React application. It features a compact design with the company logo, shine effects, and smooth animations that activate during page transitions.
## Features
### 🎨 Enterprise Design
- **Compact Layout**: Minimal, professional design that doesn't overwhelm users
- **Company Logo**: Features the GNX logo with floating animation
- **Shine Effect**: Animated shine effect that sweeps across the logo periodically
- **Dark Theme**: Professional dark gradient background with subtle transparency
### ⚡ Smart Loading Logic
- **Initial Load**: Shows for 1.5 seconds on first page load
- **Page Transitions**: Activates automatically when navigating between pages
- **Progress Bar**: Realistic loading progress with gradient colors
- **Smooth Transitions**: Fade-in/out animations for seamless user experience
### 📱 Responsive Design
- **Mobile Optimized**: Scales appropriately on all device sizes
- **Touch Friendly**: Optimized for mobile interactions
- **Performance**: Lightweight animations that don't impact performance
## Components
### 1. Preloader.tsx
Main preloader component that handles the loading UI and animations.
**Key Features:**
- Logo with shine effect animation
- Progress bar with gradient fill
- Loading dots animation
- Enterprise tagline
- Responsive design
### 2. LayoutWrapper.tsx
Wrapper component that integrates the preloader with the main layout.
### 3. usePageTransition.ts
Custom hook that manages page transition states and progress.
**Hook Methods:**
- `isTransitioning`: Boolean indicating if a transition is active
- `progress`: Current loading progress (0-100)
- `startTransition()`: Initiates a new page transition
- `updateProgress()`: Updates the loading progress
- `completeTransition()`: Marks transition as complete
## Usage
The preloader is automatically integrated into the main layout and requires no additional setup. It will:
1. **Show on initial page load** for 1.5 seconds
2. **Activate on page navigation** with realistic loading simulation
3. **Display progress** with animated progress bar
4. **Hide smoothly** when loading is complete
## Customization
### Logo
To change the logo, update the image path in the Preloader component:
```tsx
<Image
src="/images/logo.png" // Update this path
alt="GNX Logo"
width={80}
height={60}
className="logo-image"
priority
/>
```
### Colors
The preloader uses CSS custom properties that can be customized:
- Background gradient colors
- Progress bar gradient
- Text colors and opacity
### Timing
Adjust timing in the usePageTransition hook:
- Initial load duration: 1500ms
- Transition duration: 600-1000ms (randomized)
- Progress update interval: 100ms
## Performance Considerations
- **Optimized Animations**: Uses CSS transforms and opacity for smooth performance
- **Minimal DOM**: Lightweight component structure
- **Smart Loading**: Only shows when necessary (transitions)
- **Memory Efficient**: Proper cleanup of timers and intervals
## Browser Support
- **Modern Browsers**: Full support for Chrome, Firefox, Safari, Edge
- **Mobile Browsers**: Optimized for iOS Safari and Chrome Mobile
- **Fallbacks**: Graceful degradation for older browsers
## Future Enhancements
Potential improvements for future versions:
- Custom loading messages per page
- Brand-specific color themes
- Loading state indicators for API calls
- Accessibility improvements (reduced motion support)
- Analytics integration for loading performance

View File

@@ -0,0 +1,40 @@
"use client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
const ScrollToTop = () => {
const pathname = usePathname();
useEffect(() => {
// Aggressive scroll to top - run immediately and synchronously
const scrollToTop = () => {
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
};
// 1. Immediate execution
scrollToTop();
// 2. After microtask
Promise.resolve().then(scrollToTop);
// 3. After next frame
requestAnimationFrame(scrollToTop);
// 4. Multiple delayed attempts to override any smooth scroll libraries
const timeouts = [0, 10, 50, 100, 150, 200].map(delay =>
setTimeout(scrollToTop, delay)
);
return () => {
timeouts.forEach(clearTimeout);
};
}, [pathname]);
return null;
};
export default ScrollToTop;

View File

@@ -1,19 +1,106 @@
"use client";
import { useEffect } from "react";
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
import Lenis from "lenis";
import { usePathname } from "next/navigation";
const SmoothScroll = () => {
const lenisRef = useRef<Lenis | null>(null);
const pathname = usePathname();
const [isNavigating, setIsNavigating] = useState(false);
// Handle pathname changes - PRIORITY 1
useEffect(() => {
setIsNavigating(true);
// Stop Lenis completely
if (lenisRef.current) {
lenisRef.current.stop();
lenisRef.current.scrollTo(0, { immediate: true, force: true, lock: true });
}
// Force scroll to top with all methods
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
// Keep forcing scroll for a brief period
const forceScroll = () => {
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
};
// Force scroll every 16ms (one frame) for 200ms
const intervalId = setInterval(forceScroll, 16);
// After navigation is settled, restart Lenis
const restartTimeout = setTimeout(() => {
clearInterval(intervalId);
if (lenisRef.current) {
lenisRef.current.scrollTo(0, { immediate: true, force: true });
lenisRef.current.start();
}
setIsNavigating(false);
// Final scroll enforcement
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}, 200);
return () => {
clearInterval(intervalId);
clearTimeout(restartTimeout);
};
}, [pathname]);
// Initialize Lenis - PRIORITY 2
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis();
gsap.ticker.add((time) => {
lenis.raf(time * 350);
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
orientation: 'vertical',
gestureOrientation: 'vertical',
smoothWheel: true,
wheelMultiplier: 1,
smoothTouch: false,
touchMultiplier: 2,
infinite: false,
});
lenisRef.current = lenis;
// Force initial scroll to top
lenis.scrollTo(0, { immediate: true, force: true });
window.scrollTo(0, 0);
// Connect to GSAP ticker
const tickerCallback = (time: number) => {
if (!isNavigating) {
lenis.raf(time * 350);
}
};
gsap.ticker.add(tickerCallback);
gsap.ticker.lagSmoothing(0);
ScrollTrigger.update();
// Sync with ScrollTrigger
lenis.on('scroll', ScrollTrigger.update);
return () => {
lenis.destroy();
gsap.ticker.remove(tickerCallback);
lenisRef.current = null;
};
}, []);
return null;
};

View File

@@ -4,9 +4,13 @@ import Image from "next/legacy/image";
import location from "@/public/images/footer/location.png";
import phone from "@/public/images/footer/phone.png";
import gmail from "@/public/images/footer/gmail.png";
import { useNavigationServices } from "@/lib/hooks/useServices";
import { useJobs } from "@/lib/hooks/useCareer";
const Footer = () => {
const currentYear = new Date().getFullYear();
const { services: dynamicServices, loading: servicesLoading } = useNavigationServices();
const { jobs, loading: jobsLoading } = useJobs();
// Static header data
const headerData = {
@@ -96,36 +100,54 @@ const Footer = () => {
</ul>
</div>
</div>
<div className="col-12 col-lg-2 col-md-6">
<div className="footer-section">
<h6 className="text-white fm fw-6 mb-24">Solutions</h6>
<ul className="footer-links">
<li><Link href="services">Incident Management Software</Link></li>
<li><Link href="services">Custom Software Development</Link></li>
<li><Link href="services">System Integrations & APIs</Link></li>
<li><Link href="services">Data Replication</Link></li>
</ul>
</div>
</div>
<div className="col-12 col-lg-2 col-md-6">
<div className="footer-section">
<h6 className="text-white fm fw-6 mb-24">Resources</h6>
<ul className="footer-links">
<li><Link href="/insights">Software Insights</Link></li>
<li><Link href="case-study">Development Resources</Link></li>
<li><Link href="services">API Documentation</Link></li>
<li><Link href="contact-us">Technical Support</Link></li>
</ul>
</div>
</div>
<div className="col-12 col-lg-2 col-md-6">
<div className="footer-section">
<h6 className="text-white fm fw-6 mb-24">Services</h6>
<ul className="footer-links">
<li><Link href="services">Software Consulting</Link></li>
<li><Link href="contact-us">Custom Development</Link></li>
<li><Link href="case-study">API & Integration Services</Link></li>
<li><Link href="contact-us">Data Solutions</Link></li>
{servicesLoading ? (
<>
<li><Link href="/services">Our Services</Link></li>
</>
) : (
dynamicServices.slice(0, 6).map((service) => (
<li key={service.slug}>
<Link href={`/services/${service.slug}`}>
{service.title}
</Link>
</li>
))
)}
</ul>
</div>
</div>
<div className="col-12 col-lg-2 col-md-6">
<div className="footer-section">
<h6 className="text-white fm fw-6 mb-24">Latest Jobs</h6>
<ul className="footer-links">
{jobsLoading ? (
<>
<li><Link href="/career">View All Jobs</Link></li>
</>
) : (
jobs.slice(0, 4).map((job) => (
<li key={job.slug}>
<Link href={`/career/${job.slug}`}>
{job.title}
</Link>
</li>
))
)}
</ul>
</div>
</div>
<div className="col-12 col-lg-2 col-md-6">
<div className="footer-section">
<h6 className="text-white fm fw-6 mb-24">Support</h6>
<ul className="footer-links">
<li><Link href="/support-center">Support Center</Link></li>
<li><Link href="/policy?type=privacy">Privacy Policy</Link></li>
<li><Link href="/policy?type=terms">Terms of Use</Link></li>
<li><Link href="/policy?type=support">Support Policy</Link></li>
</ul>
</div>
</div>