531 lines
21 KiB
TypeScript
531 lines
21 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
import { gsap } from 'gsap';
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
import Lenis from 'lenis';
|
|
import { DEFAULT_EASE, DEFAULT_DURATION } from '@/utils/animation';
|
|
import Link from 'next/link';
|
|
import ProblemsShift from '@/components/home/ProblemsShift';
|
|
import WhatWeDo from '@/components/home/WhatWeDo';
|
|
import ProductsSection from '@/components/home/ProductsSection';
|
|
import TheLoop from '@/components/home/TheLoop';
|
|
import initGlobalReveal from '@/utils/scrollReveal';
|
|
|
|
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
|
|
if (typeof window !== 'undefined') {
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
}
|
|
|
|
export default function IndexPage() {
|
|
const [imagesLoaded, setImagesLoaded] = useState(false);
|
|
const [loadProgress, setLoadProgress] = useState(0);
|
|
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
const heroRef = useRef<HTMLDivElement | null>(null);
|
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
const overlayRef = useRef<HTMLDivElement | null>(null);
|
|
const preloaderRef = useRef<HTMLDivElement | null>(null);
|
|
const welcomeRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
const imagesRef = useRef<HTMLImageElement[]>([]);
|
|
const currentFrameIndexRef = useRef<number>(0);
|
|
const canvasWidthRef = useRef<number>(0);
|
|
const canvasHeightRef = useRef<number>(0);
|
|
// tracks loaded count without re-rendering on every frame
|
|
const loadedCountRef = useRef<number>(0);
|
|
|
|
const frameCount = 192;
|
|
|
|
/* ── draw ─────────────────────────────────────────────────────── */
|
|
const drawFrame = useCallback((frameIndex: number) => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
const img = imagesRef.current[frameIndex];
|
|
if (!img) return;
|
|
|
|
let canvasWidth = canvasWidthRef.current;
|
|
let canvasHeight = canvasHeightRef.current;
|
|
if (canvasWidth === 0 || canvasHeight === 0) {
|
|
canvasWidth = canvas.offsetWidth;
|
|
canvasHeight = canvas.offsetHeight;
|
|
canvasWidthRef.current = canvasWidth;
|
|
canvasHeightRef.current = canvasHeight;
|
|
}
|
|
|
|
const imgWidth = img.naturalWidth || img.width;
|
|
const imgHeight = img.naturalHeight || img.height;
|
|
if (imgWidth && imgHeight) {
|
|
const imgRatio = imgWidth / imgHeight;
|
|
const canvasRatio = canvasWidth / canvasHeight;
|
|
let drawWidth = canvasWidth;
|
|
let drawHeight = canvasHeight;
|
|
let offsetX = 0;
|
|
let offsetY = 0;
|
|
if (canvasRatio > imgRatio) {
|
|
drawHeight = canvasWidth / imgRatio;
|
|
offsetY = (canvasHeight - drawHeight) / 2;
|
|
} else {
|
|
drawWidth = canvasHeight * imgRatio;
|
|
offsetX = (canvasWidth - drawWidth) / 2;
|
|
}
|
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
|
|
}
|
|
}, []);
|
|
|
|
/* ── resize ───────────────────────────────────────────────────── */
|
|
const handleResize = useCallback(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const dpr = window.devicePixelRatio || 1;
|
|
const logicalWidth = canvas.offsetWidth;
|
|
const logicalHeight = canvas.offsetHeight;
|
|
canvasWidthRef.current = logicalWidth;
|
|
canvasHeightRef.current = logicalHeight;
|
|
canvas.width = logicalWidth * dpr;
|
|
canvas.height = logicalHeight * dpr;
|
|
const ctx = canvas.getContext('2d');
|
|
if (ctx) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.scale(dpr, dpr);
|
|
}
|
|
drawFrame(currentFrameIndexRef.current);
|
|
}, [drawFrame]);
|
|
|
|
/* ── lock scroll until preloader exits ───────────────────────── */
|
|
useEffect(() => {
|
|
document.body.style.overflow = 'hidden';
|
|
return () => { document.body.style.overflow = ''; };
|
|
}, []);
|
|
|
|
/* ── load frame 0 immediately (canvas visible behind preloader) ─ */
|
|
useEffect(() => {
|
|
const firstImg = new Image();
|
|
firstImg.onload = () => {
|
|
if (imagesRef.current.length === 0) {
|
|
imagesRef.current = [firstImg];
|
|
const canvas = canvasRef.current;
|
|
if (canvas) {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
canvas.width = canvas.offsetWidth * dpr;
|
|
canvas.height = canvas.offsetHeight * dpr;
|
|
const ctx = canvas.getContext('2d');
|
|
if (ctx) { ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.scale(dpr, dpr); }
|
|
drawFrame(0);
|
|
}
|
|
}
|
|
};
|
|
firstImg.src = '/img_frames/frame_000_delay-0.041s.jpg';
|
|
}, [drawFrame]);
|
|
|
|
/* ── preload all frames, update progress counter ─────────────── */
|
|
useEffect(() => {
|
|
// Reset counter every time this effect runs (guards against Strict Mode
|
|
// double-invocation and any navigation-back scenario that re-mounts the component)
|
|
loadedCountRef.current = 0;
|
|
|
|
const images: HTMLImageElement[] = [];
|
|
|
|
// Use RAF-throttled progress updates to avoid 192 rapid re-renders
|
|
let rafId = 0;
|
|
const scheduleUpdate = (pct: number, done: boolean) => {
|
|
cancelAnimationFrame(rafId);
|
|
rafId = requestAnimationFrame(() => {
|
|
setLoadProgress(pct);
|
|
if (done) {
|
|
imagesRef.current = images;
|
|
setImagesLoaded(true);
|
|
}
|
|
});
|
|
};
|
|
|
|
for (let i = 0; i < frameCount; i++) {
|
|
const img = new Image();
|
|
const paddedIndex = String(i).padStart(3, '0');
|
|
const onLoad = () => {
|
|
loadedCountRef.current++;
|
|
const pct = Math.min(100, Math.round((loadedCountRef.current / frameCount) * 100));
|
|
const done = loadedCountRef.current >= frameCount;
|
|
scheduleUpdate(pct, done);
|
|
};
|
|
img.onload = onLoad;
|
|
img.onerror = onLoad;
|
|
img.src = `/img_frames/frame_${paddedIndex}_delay-0.041s.jpg`;
|
|
images.push(img);
|
|
}
|
|
|
|
return () => {
|
|
cancelAnimationFrame(rafId);
|
|
// Reset so a re-run (Strict Mode or re-mount) always starts clean
|
|
loadedCountRef.current = 0;
|
|
};
|
|
}, []);
|
|
|
|
/* ── resize listener ──────────────────────────────────────────── */
|
|
useEffect(() => {
|
|
window.addEventListener('resize', handleResize);
|
|
handleResize();
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, [handleResize, imagesLoaded]);
|
|
|
|
/* ── ScrollTrigger refresh after all frames load ──────────────── */
|
|
useEffect(() => {
|
|
if (!imagesLoaded) return;
|
|
ScrollTrigger.sort(); ScrollTrigger.refresh(); handleResize();
|
|
const t1 = setTimeout(() => { ScrollTrigger.sort(); ScrollTrigger.refresh(); handleResize(); }, 200);
|
|
const t2 = setTimeout(() => { ScrollTrigger.sort(); ScrollTrigger.refresh(); }, 600);
|
|
return () => { clearTimeout(t1); clearTimeout(t2); };
|
|
}, [imagesLoaded, handleResize]);
|
|
|
|
/* ── animate preloader out when all frames are ready ─────────── */
|
|
useEffect(() => {
|
|
if (!imagesLoaded) return;
|
|
const preloader = preloaderRef.current;
|
|
if (!preloader) return;
|
|
|
|
const tl = gsap.timeline({
|
|
delay: 0.2,
|
|
onComplete: () => {
|
|
preloader.style.display = 'none';
|
|
document.body.style.overflow = '';
|
|
},
|
|
});
|
|
|
|
tl.to('.pl-number', { opacity: 0, y: -50, duration: 0.55, ease: 'power3.in' }, 0)
|
|
.to('.pl-pct', { opacity: 0, duration: 0.4, ease: 'power2.in' }, 0.05)
|
|
.to('.pl-label', { opacity: 0, duration: 0.4, ease: 'power2.in' }, 0.1)
|
|
.to('.pl-bar', { scaleX: 1, duration: 0.25, ease: 'power3.out' }, 0)
|
|
.to(preloader, { yPercent: -100, duration: 1.05, ease: 'expo.inOut' }, 0.4);
|
|
|
|
// Kill the timeline on unmount so stale onComplete never runs after navigation
|
|
return () => { tl.kill(); };
|
|
}, [imagesLoaded]);
|
|
|
|
/* ── homepage header show/hide on scroll direction ───────────── */
|
|
useEffect(() => {
|
|
document.body.classList.add('is-home-page', 'scroll-down');
|
|
const DELTA = 6;
|
|
let lastY = window.scrollY;
|
|
let ticking = false;
|
|
const onScroll = () => {
|
|
if (ticking) return;
|
|
window.requestAnimationFrame(() => {
|
|
const y = window.scrollY;
|
|
const delta = y - lastY;
|
|
if (y <= 100) {
|
|
document.body.classList.add('scroll-down');
|
|
document.body.classList.remove('scroll-up');
|
|
} else if (Math.abs(delta) >= DELTA) {
|
|
if (delta > 0) {
|
|
document.body.classList.add('scroll-down');
|
|
document.body.classList.remove('scroll-up');
|
|
} else {
|
|
document.body.classList.add('scroll-up');
|
|
document.body.classList.remove('scroll-down');
|
|
}
|
|
}
|
|
lastY = y;
|
|
ticking = false;
|
|
});
|
|
ticking = true;
|
|
};
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
return () => {
|
|
document.body.classList.remove('is-home-page', 'scroll-down', 'scroll-up');
|
|
window.removeEventListener('scroll', onScroll);
|
|
};
|
|
}, []);
|
|
|
|
/* ── Lenis + GSAP scrub (frame seq + welcome text + hero text) ── */
|
|
useIsomorphicLayoutEffect(() => {
|
|
if (!imagesLoaded) return;
|
|
|
|
const lenis = new Lenis({
|
|
duration: 1.2,
|
|
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
|
smoothWheel: true,
|
|
});
|
|
lenis.on('scroll', ScrollTrigger.update);
|
|
const updateTicker = (time: number) => lenis.raf(time * 1000);
|
|
gsap.ticker.add(updateTicker);
|
|
gsap.ticker.lagSmoothing(0);
|
|
|
|
const cleanupReveal = initGlobalReveal();
|
|
const air = { frame: 0 };
|
|
|
|
const ctx = gsap.context(() => {
|
|
const tl = gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: containerRef.current,
|
|
start: 'top top',
|
|
end: '+=300%',
|
|
pin: true,
|
|
pinType: 'transform',
|
|
scrub: true,
|
|
anticipatePin: 1,
|
|
invalidateOnRefresh: true,
|
|
refreshPriority: 10,
|
|
},
|
|
});
|
|
|
|
// Frame scrub (full duration = 1.0)
|
|
tl.to(air, {
|
|
frame: frameCount - 1,
|
|
ease: 'none',
|
|
duration: 1.0,
|
|
onUpdate: () => {
|
|
const f = Math.floor(air.frame);
|
|
currentFrameIndexRef.current = f;
|
|
drawFrame(f);
|
|
},
|
|
}, 0);
|
|
|
|
// "Welcome Buddy" text: visible at frame 0, fades OUT by frame 25
|
|
const welcomeEnd = 25 / (frameCount - 1); // ≈ 0.131
|
|
if (welcomeRef.current) {
|
|
tl.fromTo(
|
|
welcomeRef.current,
|
|
{ opacity: 1, y: 0, scale: 1 },
|
|
{ opacity: 0, y: -28, scale: 0.93, ease: 'power2.in', duration: welcomeEnd },
|
|
0
|
|
);
|
|
}
|
|
|
|
// Hero text + white overlay: fade IN starting at frame 120
|
|
const startProgress = 120 / (frameCount - 1);
|
|
const fadeDuration = 30 / (frameCount - 1);
|
|
|
|
if (heroRef.current) {
|
|
tl.to(heroRef.current, { opacity: 1, y: 0, ease: DEFAULT_EASE, duration: fadeDuration || DEFAULT_DURATION }, startProgress);
|
|
}
|
|
if (overlayRef.current) {
|
|
tl.to(overlayRef.current, { opacity: 1, ease: DEFAULT_EASE, duration: fadeDuration || DEFAULT_DURATION }, startProgress);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
ctx.revert();
|
|
lenis.destroy();
|
|
gsap.ticker.remove(updateTicker);
|
|
if (typeof cleanupReveal === 'function') cleanupReveal();
|
|
};
|
|
}, [imagesLoaded, drawFrame]);
|
|
|
|
/* ── render ───────────────────────────────────────────────────── */
|
|
return (
|
|
<>
|
|
{/* ══════════════════════════════════════════
|
|
PRELOADER
|
|
Fixed overlay — slides up when images done
|
|
══════════════════════════════════════════ */}
|
|
<div
|
|
ref={preloaderRef}
|
|
style={{
|
|
position: 'fixed', inset: 0, zIndex: 9999,
|
|
display: 'flex', flexDirection: 'column',
|
|
alignItems: 'center', justifyContent: 'center',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
{/* Background: frame_000 blurred + dark overlay */}
|
|
<div style={{
|
|
position: 'absolute', inset: 0,
|
|
backgroundImage: `url('/img_frames/frame_000_delay-0.041s.jpg')`,
|
|
backgroundSize: 'cover', backgroundPosition: 'center',
|
|
filter: 'blur(3px)', transform: 'scale(1.06)',
|
|
}} />
|
|
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(160deg,rgba(7,7,10,0.88) 0%,rgba(10,8,5,0.92) 100%)' }} />
|
|
|
|
{/* Noise grain */}
|
|
<div style={{
|
|
position: 'absolute', inset: 0, opacity: 0.035, mixBlendMode: 'overlay',
|
|
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
|
|
}} />
|
|
|
|
{/* Brand wordmark */}
|
|
<div className="pl-label" style={{
|
|
position: 'relative', zIndex: 2,
|
|
fontSize: 11, fontWeight: 800, letterSpacing: '0.3em',
|
|
textTransform: 'uppercase', color: 'rgba(244,190,40,0.75)',
|
|
fontFamily: 'Sora, sans-serif', marginBottom: 36,
|
|
userSelect: 'none',
|
|
}}>
|
|
Loyaly
|
|
</div>
|
|
|
|
{/* Big percentage counter */}
|
|
<div style={{ position: 'relative', zIndex: 2, display: 'flex', alignItems: 'flex-start', gap: 6 }}>
|
|
<span className="pl-number" style={{
|
|
fontSize: 'clamp(88px, 14vw, 156px)',
|
|
fontWeight: 900, lineHeight: 0.86,
|
|
letterSpacing: '-0.05em', color: '#ffffff',
|
|
fontFamily: 'Sora, sans-serif',
|
|
fontVariantNumeric: 'tabular-nums',
|
|
userSelect: 'none',
|
|
willChange: 'transform, opacity',
|
|
}}>
|
|
{loadProgress}
|
|
</span>
|
|
<span className="pl-pct" style={{
|
|
fontSize: 'clamp(22px, 3.5vw, 38px)',
|
|
fontWeight: 800, color: 'rgba(255,255,255,0.35)',
|
|
fontFamily: 'Sora, sans-serif', marginTop: 10,
|
|
userSelect: 'none',
|
|
}}>%</span>
|
|
</div>
|
|
|
|
{/* Sub-label */}
|
|
<div className="pl-label" style={{
|
|
position: 'relative', zIndex: 2,
|
|
fontSize: 11, fontWeight: 600, letterSpacing: '0.2em',
|
|
textTransform: 'uppercase', color: 'rgba(255,255,255,0.28)',
|
|
fontFamily: 'Sora, sans-serif', marginTop: 28,
|
|
userSelect: 'none',
|
|
}}>
|
|
Loading experience
|
|
</div>
|
|
|
|
{/* Progress bar (full width, bottom) */}
|
|
<div className="pl-bar-track" style={{
|
|
position: 'absolute', bottom: 0, left: 0, right: 0, height: 3,
|
|
background: 'rgba(255,255,255,0.06)',
|
|
}}>
|
|
<div className="pl-bar" style={{
|
|
height: '100%',
|
|
width: `${loadProgress}%`,
|
|
background: 'linear-gradient(90deg, #f4be28, #FF9800)',
|
|
transition: 'width 0.12s linear',
|
|
borderRadius: '0 2px 2px 0',
|
|
}} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* ══════════════════════════════════════════
|
|
HERO — pinned, drives canvas frame scrub
|
|
══════════════════════════════════════════ */}
|
|
<main
|
|
ref={containerRef}
|
|
className="loyaly-home relative bg-white overflow-hidden"
|
|
style={{
|
|
width: 'calc(100% - 40px)',
|
|
height: 'calc(100vh - 20px)',
|
|
margin: '0 20px 20px',
|
|
borderRadius: '25px',
|
|
}}
|
|
>
|
|
{/* Canvas */}
|
|
<div className="absolute inset-0 z-0 pointer-events-none" style={{ width: '100%', height: '100%' }}>
|
|
<canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />
|
|
</div>
|
|
|
|
{/* ── "Welcome Buddy" — centered, visible at frame 0, fades by frame 25 ── */}
|
|
<div
|
|
ref={welcomeRef}
|
|
style={{
|
|
position: 'absolute', top: '50%', left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
zIndex: 12, textAlign: 'center',
|
|
pointerEvents: 'none', userSelect: 'none',
|
|
willChange: 'transform, opacity',
|
|
}}
|
|
>
|
|
{/* Glow ring behind text */}
|
|
<div style={{
|
|
position: 'absolute', top: '50%', left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
width: 320, height: 120,
|
|
background: 'radial-gradient(ellipse 60% 60% at 50% 50%, rgba(244,190,40,0.18) 0%, transparent 70%)',
|
|
filter: 'blur(20px)',
|
|
pointerEvents: 'none',
|
|
}} />
|
|
|
|
<p style={{
|
|
margin: 0,
|
|
fontSize: 'clamp(2rem, 4.5vw, 4.4rem)',
|
|
fontWeight: 900,
|
|
letterSpacing: '-0.04em',
|
|
lineHeight: 1,
|
|
color: '#ffffff',
|
|
fontFamily: 'Sora, sans-serif',
|
|
textShadow: '0 2px 32px rgba(0,0,0,0.5)',
|
|
position: 'relative',
|
|
}}>
|
|
Welcome{' '}
|
|
<span style={{
|
|
background: 'linear-gradient(105deg,#f4be28 0%,#ffd95a 50%,#FF9800 100%)',
|
|
WebkitBackgroundClip: 'text',
|
|
WebkitTextFillColor: 'transparent',
|
|
}}>Buddy</span>
|
|
</p>
|
|
|
|
<p style={{
|
|
margin: '14px 0 0',
|
|
fontSize: 'clamp(0.78rem, 1.1vw, 1rem)',
|
|
fontWeight: 500,
|
|
letterSpacing: '0.12em',
|
|
textTransform: 'uppercase',
|
|
color: 'rgba(255,255,255,0.42)',
|
|
fontFamily: 'Sora, sans-serif',
|
|
position: 'relative',
|
|
}}>
|
|
Scroll to explore
|
|
</p>
|
|
|
|
{/* Animated scroll chevron */}
|
|
<div style={{ marginTop: 18, display: 'flex', justifyContent: 'center', position: 'relative' }}>
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="rgba(244,190,40,0.7)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="animate-scroll-arrow">
|
|
<path d="M19 13l-7 7-7-7m7-7v14" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
{/* White shade behind the text content on the left only — right side stays fully clear (fades in with hero text at frame 120) */}
|
|
<div
|
|
ref={overlayRef}
|
|
className="absolute inset-0 z-[5] pointer-events-none bg-gradient-to-r from-white/90 via-white/40 via-35% to-transparent to-55%"
|
|
style={{ opacity: 0 }}
|
|
/>
|
|
|
|
{/* Hero content (fades in at frame 120) */}
|
|
<div
|
|
ref={heroRef}
|
|
className="absolute inset-y-0 left-0 z-10 flex items-center justify-start pointer-events-none h-full"
|
|
style={{ opacity: 0, transform: 'translateY(30px)' }}
|
|
>
|
|
<div className="w-full lg:w-[50vw] px-8 sm:px-12 md:px-16 lg:pl-20 lg:pr-8 pointer-events-auto flex flex-col justify-center py-12 md:py-16">
|
|
<h1 className="text-[#171512] hero-heading text-3xl sm:text-4xl md:text-5xl lg:text-5xl xl:text-6xl font-extrabold tracking-tight leading-[1.12] mb-6 select-none">
|
|
Turn Every <br />
|
|
Walk In Into a <br />
|
|
<span className="hero-gradient-text">Loyal Customer</span>
|
|
</h1>
|
|
<p className="text-[#4f463a] hero-description text-sm sm:text-base md:text-lg xl:text-xl mb-8 leading-relaxed max-w-2xl select-none">
|
|
Loyaly.ai turns everyday digital engagement into real world shopping rewards — customers earn coins through games and daily activities, then redeem them at offline stores near them.
|
|
</p>
|
|
<div className="flex flex-wrap gap-5">
|
|
<Link href="/contacts" className="flex items-center gap-3 bg-[#f4be28] hover:bg-[#ffd95a] hero-btn-primary text-[#171512] px-8 py-4 rounded-xl font-bold text-base md:text-lg transition-all duration-300 shadow-lg shadow-yellow-500/30 hover:shadow-yellow-500/50 hover:scale-[1.02] cursor-pointer no-underline">
|
|
Book a Demo
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.6} d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
|
</svg>
|
|
</Link>
|
|
<Link href="/contacts" className="flex items-center border-2 border-[#f4be28] hover:bg-[#f4be28]/15 hero-btn-secondary text-[#f4be28] px-8 py-4 rounded-xl font-bold text-base md:text-lg transition-all duration-300 hover:scale-[1.02] cursor-pointer no-underline">
|
|
Get Started
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
{/* Other sections */}
|
|
<ProblemsShift isLoaded={imagesLoaded} />
|
|
<WhatWeDo isLoaded={imagesLoaded} />
|
|
<ProductsSection isLoaded={imagesLoaded} />
|
|
<TheLoop />
|
|
</>
|
|
);
|
|
}
|