Cleanup old PHP source files and update Next.js pages and components

This commit is contained in:
R-Bharathraj
2026-06-30 17:02:35 +05:30
parent 7175adb0db
commit 7cf5035c89
1223 changed files with 438 additions and 168007 deletions

View File

@@ -60,17 +60,8 @@ export default function AboutUsPage() {
<div style={{ position: 'relative', zIndex: 2, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 760 }}>
<div data-gsap="badge" style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase',
color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 32,
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '8px 18px', borderRadius: 100,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />Meet the Crew
</div>
<h1 data-gsap="heading" style={{
fontSize: 'clamp(52px, 9vw, 140px)', fontWeight: 900,
fontSize: 'clamp(52px, 9vw, 140px)', fontWeight: 800,
letterSpacing: '-0.06em', lineHeight: 0.88,
color: '#ffffff', margin: '0 0 36px', fontFamily: 'Sora, sans-serif',
}}>
@@ -86,10 +77,10 @@ export default function AboutUsPage() {
{/* ── OUR STORY ── */}
<section style={{ padding: '140px 8%', background: '#fff', margin: '0 20px 20px', borderRadius: 24 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8vw', alignItems: 'start' }}>
<div className="au-story-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8vw', alignItems: 'start' }}>
<Reveal>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 24 }}>Our Story</div>
<h2 style={{ fontSize: 'clamp(36px, 4.5vw, 68px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 32px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 4.5vw, 68px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 32px', fontFamily: 'Sora, sans-serif' }}>
We started with<br />a simple idea.
</h2>
</Reveal>
@@ -106,7 +97,7 @@ export default function AboutUsPage() {
{/* ── MISSION & VISION ── */}
<section style={{ padding: '0 8% 20px', background: '#ffffff' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20, alignItems: 'stretch' }}>
<div className="au-mv-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20, alignItems: 'stretch' }}>
{[
{
label: 'Mission', bg: '#07070d', textColor: '#fff', accent: '#f4be28',
@@ -138,14 +129,14 @@ export default function AboutUsPage() {
<section style={{ padding: '140px 8%', background: '#fff', margin: '0 20px 20px', borderRadius: 24 }}>
<Reveal>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 20 }}>How We Work</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 80px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 80px', fontFamily: 'Sora, sans-serif' }}>
What we believe.
</h2>
</Reveal>
<div style={{ display: 'flex', flexDirection: 'column' }}>
{VALUES.map((v, i) => (
<Reveal key={i} delay={i * 0.08}>
<div style={{
<div className="au-value-row" style={{
display: 'grid', gridTemplateColumns: '80px 1fr 1fr',
gap: '4vw', alignItems: 'center',
padding: '40px 0', borderBottom: '1px solid rgba(0,0,0,0.07)',
@@ -182,6 +173,15 @@ export default function AboutUsPage() {
}}>Join Us </Link>
</Reveal>
</section>
<style>{`
@media (max-width: 860px) {
.au-story-grid { grid-template-columns: 1fr !important; gap: 36px !important; }
.au-mv-grid { grid-template-columns: 1fr !important; }
.au-mv-grid > div > div { padding: 48px 32px !important; min-height: 280px !important; }
.au-value-row { grid-template-columns: 1fr !important; gap: 10px !important; padding: 28px 0 !important; }
}
`}</style>
</main>
);
}
@@ -211,7 +211,7 @@ function TeamCarousel() {
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: 72, position: 'relative', zIndex: 2, padding: '0 8%' }}>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 16 }}>The Team</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 16px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 16px', fontFamily: 'Sora, sans-serif' }}>
Builders. Thinkers.<br />Retail obsessives.
</h2>
<p style={{ fontSize: 15, color: 'rgba(255,255,255,0.35)', fontFamily: 'Sora, sans-serif', margin: 0 }}>

View File

@@ -82,15 +82,7 @@ export default function BlogMainPage() {
<div style={{ position: 'relative', zIndex: 2, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 720 }}>
<div style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 28,
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '8px 18px', borderRadius: 100,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />Market Insights
</div>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(48px, 8vw, 120px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.9, color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(48px, 8vw, 120px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.9, color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
Insights for the<br />
<span style={{ color: '#f4be28' }}>Future of Retail.</span>
</h1>
@@ -121,7 +113,7 @@ export default function BlogMainPage() {
{featured && (
<section style={{ padding: '64px 8%', background: '#fff' }}>
<Reveal>
<div style={{
<div className="bm-featured-grid" style={{
display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6vw',
padding: '64px', background: '#07070d', borderRadius: 24,
alignItems: 'center', cursor: 'pointer',
@@ -134,7 +126,7 @@ export default function BlogMainPage() {
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.3)', fontFamily: 'Sora, sans-serif' }}>{featured.readTime} read</span>
<span style={{ fontSize: 11, fontWeight: 700, color: '#f4be28', fontFamily: 'Sora, sans-serif', background: 'rgba(244,190,40,0.08)', padding: '4px 10px', borderRadius: 100 }}>Featured</span>
</div>
<h2 style={{ fontSize: 'clamp(28px, 3.5vw, 52px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 1.05, color: '#fff', margin: '0 0 20px', fontFamily: 'Sora, sans-serif' }}>{featured.title}</h2>
<h2 style={{ fontSize: 'clamp(28px, 3.5vw, 52px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 1.05, color: '#fff', margin: '0 0 20px', fontFamily: 'Sora, sans-serif' }}>{featured.title}</h2>
<p style={{ fontSize: 16, lineHeight: 1.75, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif', margin: '0 0 36px' }}>{featured.excerpt}</p>
<Link href="/blog_single_page" style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 15, fontWeight: 700, color: '#f4be28', fontFamily: 'Sora, sans-serif', textDecoration: 'none', borderBottom: '1px solid rgba(244,190,40,0.3)', paddingBottom: 4 }}>
Read Article
@@ -201,7 +193,7 @@ export default function BlogMainPage() {
<section style={{ padding: '120px 8%', background: '#07070d', margin: '0 20px 20px', borderRadius: 24, textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', width: 600, height: 600, borderRadius: '50%', background: 'radial-gradient(circle,rgba(244,190,40,0.06) 0%,transparent 70%)', pointerEvents: 'none' }} />
<Reveal>
<h2 style={{ fontSize: 'clamp(32px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 20px', fontFamily: 'Sora, sans-serif', position: 'relative', zIndex: 2 }}>
<h2 style={{ fontSize: 'clamp(32px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 20px', fontFamily: 'Sora, sans-serif', position: 'relative', zIndex: 2 }}>
Stay ahead of retail.
</h2>
<p style={{ fontSize: 16, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif', margin: '0 auto 44px', maxWidth: 400, position: 'relative', zIndex: 2 }}>
@@ -222,6 +214,12 @@ export default function BlogMainPage() {
</div>
</Reveal>
</section>
<style>{`
@media (max-width: 860px) {
.bm-featured-grid { grid-template-columns: 1fr !important; padding: 36px 28px !important; gap: 36px !important; }
}
`}</style>
</main>
);
}

View File

@@ -61,7 +61,7 @@ export default function BlogSinglePage() {
<span style={{ color: 'rgba(255,255,255,0.2)' }}>/</span>
<span style={{ fontSize: 11, fontWeight: 700, color: '#f4be28', background: 'rgba(244,190,40,0.1)', border: '1px solid rgba(244,190,40,0.2)', padding: '3px 10px', borderRadius: 100, fontFamily: 'Sora, sans-serif', textTransform: 'uppercase', letterSpacing: '0.08em' }}>{ARTICLE.tag}</span>
</div>
<h1 style={{ fontSize: 'clamp(36px, 6vw, 88px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
<h1 style={{ fontSize: 'clamp(36px, 6vw, 88px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
{ARTICLE.title}
</h1>
<p style={{ fontSize: 'clamp(16px, 1.5vw, 20px)', lineHeight: 1.75, color: 'rgba(255,255,255,0.55)', fontFamily: 'Sora, sans-serif', maxWidth: 600, margin: '0 0 40px' }}>
@@ -111,7 +111,7 @@ export default function BlogSinglePage() {
{/* CTA */}
<section style={{ padding: '80px 8%', background: '#07070d', margin: '0 20px 20px', borderRadius: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 32 }}>
<div>
<h2 style={{ fontSize: 'clamp(28px, 3.5vw, 52px)', fontWeight: 900, letterSpacing: '-0.04em', color: '#fff', margin: '0 0 12px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(28px, 3.5vw, 52px)', fontWeight: 800, letterSpacing: '-0.04em', color: '#fff', margin: '0 0 12px', fontFamily: 'Sora, sans-serif' }}>
Ready to see your store&apos;s data?
</h2>
<p style={{ fontSize: 16, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif', margin: 0 }}>Book a 30-minute demo and see Behavision AI in action.</p>

View File

@@ -35,17 +35,8 @@ export default function ContactsPage() {
<div style={{ position: 'relative', zIndex: 2, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 760 }}>
<div style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase',
color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 32,
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '8px 18px', borderRadius: 100,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />Contact
</div>
<h1 data-gsap="heading" style={{
fontSize: 'clamp(48px, 8vw, 120px)', fontWeight: 900,
fontSize: 'clamp(48px, 8vw, 120px)', fontWeight: 800,
letterSpacing: '-0.05em', lineHeight: 0.9,
color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif',
}}>

View File

@@ -65,15 +65,7 @@ export default function FAQPage() {
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(244,190,40,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(244,190,40,0.025) 1px,transparent 1px)', backgroundSize: '60px 60px', pointerEvents: 'none' }} />
<div style={{ position: 'relative', zIndex: 2, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 720 }}>
<div style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 28,
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '8px 18px', borderRadius: 100,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />FAQ
</div>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(48px, 8vw, 110px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.9, color: '#fff', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(48px, 8vw, 110px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.9, color: '#fff', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>
Got<br />
<span style={{ color: '#f4be28' }}>Questions?</span>
</h1>
@@ -93,7 +85,7 @@ export default function FAQPage() {
{/* Category header */}
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 40 }}>
<div style={{ width: 10, height: 10, borderRadius: '50%', background: section.color, boxShadow: `0 0 14px ${section.color}` }} />
<h2 style={{ fontSize: 'clamp(24px, 3vw, 40px)', fontWeight: 900, letterSpacing: '-0.03em', color: '#0a0a0f', margin: 0, fontFamily: 'Sora, sans-serif' }}>{section.category}</h2>
<h2 style={{ fontSize: 'clamp(24px, 3vw, 40px)', fontWeight: 800, letterSpacing: '-0.03em', color: '#0a0a0f', margin: 0, fontFamily: 'Sora, sans-serif' }}>{section.category}</h2>
</div>
{/* Accordion */}
@@ -142,7 +134,7 @@ export default function FAQPage() {
{/* CTA */}
<section style={{ padding: '80px 8%', background: '#f4be28', margin: '0 20px 20px', borderRadius: 24, textAlign: 'center' }}>
<h2 style={{ fontSize: 'clamp(28px, 4vw, 56px)', fontWeight: 900, letterSpacing: '-0.04em', color: '#111', margin: '0 0 16px', fontFamily: 'Sora, sans-serif' }}>Still have questions?</h2>
<h2 style={{ fontSize: 'clamp(28px, 4vw, 56px)', fontWeight: 800, letterSpacing: '-0.04em', color: '#111', margin: '0 0 16px', fontFamily: 'Sora, sans-serif' }}>Still have questions?</h2>
<p style={{ fontSize: 16, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif', margin: '0 0 36px' }}>Our team is happy to help get in touch and we&apos;ll respond within one business day.</p>
<Link href="/contacts" style={{ display: 'inline-flex', alignItems: 'center', gap: 10, padding: '16px 40px', background: '#111', color: '#f4be28', fontSize: 15, fontWeight: 800, borderRadius: 100, textDecoration: 'none', fontFamily: 'Sora, sans-serif' }}>
Contact Us

View File

@@ -54,19 +54,32 @@ html.lenis, html.lenis body {
.loyaly-home .hero-heading {
color: var(--loyaly-carbon) !important; /* Premium brand carbon (#171512) for high readability */
font-weight: 900 !important; /* Ultra-bold / Black font weight override */
text-shadow: 0 2px 24px rgba(255, 255, 255, 0.7);
}
.loyaly-home .hero-gradient-text {
background-image: linear-gradient(102deg, #f4be28 0%, #ff9500 100%) !important;
background-image: linear-gradient(105deg, #bc7122 0%, #f4be28 45%, #c9960f 60%, #8a4f0f 100%) !important;
background-size: 200% auto !important;
-webkit-background-clip: text !important;
background-clip: text !important;
-webkit-text-fill-color: transparent !important;
color: transparent !important;
display: inline-block;
animation: heroGradientLoop 7s linear infinite;
}
@keyframes heroGradientLoop {
0% { background-position: 200% center; }
100% { background-position: -200% center; }
}
@media (prefers-reduced-motion: reduce) {
.loyaly-home .hero-gradient-text { animation: none; }
}
.loyaly-home .hero-description {
color: var(--loyaly-warm-text) !important; /* Premium brand warm charcoal (#4f463a) */
color: var(--loyaly-carbon) !important; /* darker, higher-contrast text now that the hero image shows through clearly */
text-shadow: 0 2px 16px rgba(255, 255, 255, 0.6);
}
.loyaly-home .hero-btn-primary {

View File

@@ -483,10 +483,10 @@ export default function IndexPage() {
</div>
</div>
{/* White gradient overlay (fades in with hero text at frame 120) */}
{/* 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/95 via-white/70 to-transparent"
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 }}
/>
@@ -497,12 +497,6 @@ export default function IndexPage() {
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">
<div className="flex items-center gap-3 mb-6">
<span className="w-12 h-[3px] bg-[#f4be28] rounded-full" />
<span className="text-[#f4be28] hero-subheading text-sm sm:text-base md:text-lg font-bold tracking-widest uppercase">
AI-Powered Retail Intelligence
</span>
</div>
<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 />

View File

@@ -75,15 +75,7 @@ export default function SolutionsPage() {
<div style={{ width: '100%', position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 760 }}>
<div data-gsap="badge" style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 28,
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '8px 18px', borderRadius: 100,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />Solutions
</div>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(52px, 8.5vw, 130px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.88, color: '#fff', margin: '0 0 36px', fontFamily: 'Sora, sans-serif' }}>
<h1 data-gsap="heading" style={{ fontSize: 'clamp(52px, 8.5vw, 130px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.88, color: '#fff', margin: '0 0 36px', fontFamily: 'Sora, sans-serif' }}>
Built for<br />Modern<br />
<span style={{ color: '#f4be28' }}>Retail Growth.</span>
</h1>
@@ -104,7 +96,7 @@ export default function SolutionsPage() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 80, flexWrap: 'wrap', gap: 24 }}>
<div>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 16 }}>Capabilities</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: 0, fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: 0, fontFamily: 'Sora, sans-serif' }}>
Four tools.<br />One merchant platform.
</h2>
</div>
@@ -120,6 +112,7 @@ export default function SolutionsPage() {
<div
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
className="sp-cap-row"
style={{
display: 'grid', gridTemplateColumns: '80px 1fr 1fr auto',
alignItems: 'center', gap: '4vw',
@@ -166,9 +159,9 @@ export default function SolutionsPage() {
</section>
{/* ── FINAL CTA ── */}
<section style={{ padding: '140px 8%', background: '#fff8e8', margin: '0 20px 20px', borderRadius: 24, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6vw', alignItems: 'center' }}>
<section className="sp-cta-grid" style={{ padding: '140px 8%', background: '#fff8e8', margin: '0 20px 20px', borderRadius: 24, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6vw', alignItems: 'center' }}>
<RevealBlock>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>
See every solution working together.
</h2>
<p style={{ fontSize: 16, color: 'rgba(0,0,0,0.5)', fontFamily: 'Sora, sans-serif', margin: '0 0 40px', lineHeight: 1.7 }}>
@@ -193,6 +186,17 @@ export default function SolutionsPage() {
</div>
</RevealBlock>
</section>
<style>{`
@media (max-width: 860px) {
.sp-cap-row { grid-template-columns: 1fr !important; gap: 14px !important; padding: 28px 0 !important; }
.sp-cap-row > div:last-child { justify-content: flex-start !important; }
.sp-cta-grid { grid-template-columns: 1fr !important; gap: 40px !important; }
}
@media (max-width: 480px) {
.sp-cta-grid > div > div { grid-template-columns: 1fr !important; }
}
`}</style>
</main>
);
}

View File

@@ -109,17 +109,8 @@ export default function ForBusinessPage() {
<div style={{ width: '100%', position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<div style={{ maxWidth: 760 }}>
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 8, marginBottom: 32,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.2)',
borderRadius: 100, padding: '8px 20px',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />
<span style={{ fontSize: 12, fontWeight: 700, color: 'rgba(255,255,255,0.55)', fontFamily: 'Sora, sans-serif', letterSpacing: '0.12em', textTransform: 'uppercase' }}>For Business</span>
</div>
<h1 data-gsap="heading" style={{
fontSize: 'clamp(52px, 8vw, 120px)', fontWeight: 900,
fontSize: 'clamp(52px, 8vw, 120px)', fontWeight: 800,
letterSpacing: '-0.05em', lineHeight: 0.9,
color: '#ffffff', margin: '0 0 32px', fontFamily: 'Sora, sans-serif',
}}>
@@ -162,10 +153,10 @@ export default function ForBusinessPage() {
<RevealBlock>
<div style={{ maxWidth: 200, height: 2, background: '#f4be28', marginBottom: 48 }} />
</RevealBlock>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8vw', alignItems: 'center' }}>
<div className="uc-challenge-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8vw', alignItems: 'center' }}>
<RevealBlock>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 24 }}>The Challenge</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 28px', fontFamily: 'Sora, sans-serif' }}>
You're Getting<br />Visitors. But<br />
<em style={{ fontStyle: 'normal', color: '#c9960f' }}>Missing Revenue.</em>
</h2>
@@ -196,7 +187,7 @@ export default function ForBusinessPage() {
<section style={{ padding: '140px 8%', background: '#07070d', margin: '0 20px 20px', borderRadius: 24 }}>
<RevealBlock>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 20 }}>Our Products</div>
<h2 style={{ fontSize: 'clamp(40px, 5.5vw, 80px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#fff', margin: '0 0 80px', fontFamily: 'Sora, sans-serif', maxWidth: 700 }}>
<h2 style={{ fontSize: 'clamp(40px, 5.5vw, 80px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#fff', margin: '0 0 80px', fontFamily: 'Sora, sans-serif', maxWidth: 700 }}>
AI-Powered Retail Intelligence.
</h2>
</RevealBlock>
@@ -216,7 +207,7 @@ export default function ForBusinessPage() {
{/* Product detail */}
{PRODUCTS.map((p, i) => (
<div key={i} style={{
<div key={i} className="uc-product-grid" style={{
display: activeProduct === i ? 'grid' : 'none',
gridTemplateColumns: '1fr 1fr', gap: '6vw', alignItems: 'center',
}}>
@@ -227,7 +218,7 @@ export default function ForBusinessPage() {
marginBottom: 20, background: `${p.color}15`, padding: '6px 14px', borderRadius: 100,
border: `1px solid ${p.color}30`,
}}>{p.tag}</div>
<h3 style={{ fontSize: 'clamp(32px, 4vw, 60px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>{p.name}</h3>
<h3 style={{ fontSize: 'clamp(32px, 4vw, 60px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 24px', fontFamily: 'Sora, sans-serif' }}>{p.name}</h3>
<p style={{ fontSize: 18, lineHeight: 1.75, color: 'rgba(255,255,255,0.45)', fontFamily: 'Sora, sans-serif', margin: '0 0 40px' }}>{p.desc}</p>
<Link href="/contacts" style={{
display: 'inline-flex', alignItems: 'center', gap: 8,
@@ -252,11 +243,11 @@ export default function ForBusinessPage() {
<section ref={statsRef} style={{ padding: '140px 8%', background: '#fff', margin: '0 20px 20px', borderRadius: 24 }}>
<RevealBlock>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 20 }}>Outcomes</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 80px', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#0a0a0f', margin: '0 0 80px', fontFamily: 'Sora, sans-serif' }}>
Real Results.<br />Not Assumptions.
</h2>
</RevealBlock>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 2 }}>
<div className="uc-outcomes-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 2 }}>
{OUTCOMES.map((o, i) => (
<RevealBlock key={i} delay={i * 0.1}>
<div style={{
@@ -276,11 +267,11 @@ export default function ForBusinessPage() {
<section style={{ padding: '140px 8%', background: '#07070d', margin: '0 20px 20px', borderRadius: 24 }}>
<RevealBlock>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 20 }}>Who It's For</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 80px', fontFamily: 'Sora, sans-serif', maxWidth: 600 }}>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 68px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: '0 0 80px', fontFamily: 'Sora, sans-serif', maxWidth: 600 }}>
Built for every format of physical retail.
</h2>
</RevealBlock>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 16 }}>
<div className="uc-who-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 16 }}>
{WHO.map((w, i) => (
<RevealBlock key={i} delay={i * 0.1}>
<div style={{
@@ -301,7 +292,7 @@ export default function ForBusinessPage() {
<section style={{ padding: '160px 8%', background: '#f4be28', margin: '0 20px 20px', borderRadius: 24, textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: '-20%', left: '50%', transform: 'translateX(-50%)', width: '120%', height: '140%', background: 'radial-gradient(ellipse,rgba(255,255,255,0.15) 0%,transparent 60%)', pointerEvents: 'none' }} />
<RevealBlock>
<h2 style={{ fontSize: 'clamp(44px, 7vw, 100px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#111', margin: '0 0 24px', fontFamily: 'Sora, sans-serif', position: 'relative', zIndex: 2 }}>
<h2 style={{ fontSize: 'clamp(44px, 7vw, 100px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.92, color: '#111', margin: '0 0 24px', fontFamily: 'Sora, sans-serif', position: 'relative', zIndex: 2 }}>
Ready to Transform<br />Your Retail Business?
</h2>
<p style={{ fontSize: 18, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif', margin: '0 auto 52px', maxWidth: 480, position: 'relative', zIndex: 2 }}>Schedule a demo and see your store's potential in 30 minutes.</p>
@@ -315,6 +306,17 @@ export default function ForBusinessPage() {
<style>{`
@keyframes shimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
@keyframes floatY { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-12px)} }
@media (max-width: 860px) {
.uc-challenge-grid { grid-template-columns: 1fr !important; gap: 40px !important; }
.uc-product-grid { grid-template-columns: 1fr !important; gap: 40px !important; }
.uc-outcomes-grid { grid-template-columns: repeat(2,1fr) !important; }
.uc-outcomes-grid > div > div { border-radius: 16px !important; }
.uc-who-grid { grid-template-columns: repeat(2,1fr) !important; }
}
@media (max-width: 520px) {
.uc-outcomes-grid { grid-template-columns: 1fr !important; }
.uc-who-grid { grid-template-columns: 1fr !important; }
}
`}</style>
</main>
);

View File

@@ -61,7 +61,7 @@ export default function Footer() {
return (
<>
<footer style={{
<footer className="ly-footer" style={{
background: 'linear-gradient(to top, #3a2800 0%, #1e1600 18%, #0e0b04 36%, #07070d 58%)',
color: '#ffffff',
position: 'relative',
@@ -71,7 +71,7 @@ export default function Footer() {
}}>
{/* ── TOP CTA STRIP ── */}
<div style={{
<div className="ly-footer-cta" style={{
padding: '72px 64px 64px',
borderBottom: '1px solid rgba(255,255,255,0.06)',
display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between',
@@ -83,15 +83,15 @@ export default function Footer() {
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 20, display: 'flex', alignItems: 'center', gap: 10 }}>
<span style={{ width: 24, height: 1, background: '#f4be28', display: 'block' }} />Let's build together
</div>
<h2 style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: 0, fontFamily: 'Sora, sans-serif' }}>
<h2 className="ly-footer-heading" style={{ fontSize: 'clamp(36px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.04em', lineHeight: 0.95, color: '#fff', margin: 0, fontFamily: 'Sora, sans-serif' }}>
Turn every visit<br />
<span style={{ color: '#f4be28' }}>into value.</span>
</h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-end' }}>
<Link href="/contacts" style={{
display: 'inline-flex', alignItems: 'center', gap: 10,
<div className="ly-footer-cta-actions" style={{ display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-end' }}>
<Link href="/contacts" className="ly-footer-cta-btn" style={{
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10,
padding: '18px 40px', background: '#f4be28', color: '#111',
fontSize: 16, fontWeight: 800, borderRadius: 100, textDecoration: 'none',
fontFamily: 'Sora, sans-serif', boxShadow: '0 16px 48px rgba(244,190,40,0.25)',
@@ -112,14 +112,14 @@ export default function Footer() {
</div>
{/* ── MAIN COLUMNS ── */}
<div style={{
<div className="ly-footer-columns" style={{
display: 'grid',
gridTemplateColumns: '1.6fr 1fr 1fr 1fr',
gap: '48px', padding: '64px 64px 48px',
alignItems: 'start',
}}>
{/* Brand */}
<div>
<div className="ly-footer-brand">
<Link href="/" aria-label="Loyaly.AI home">
<Image src="/images/logo-dark.png" alt="Loyaly.AI" width={560} height={160} style={{ height: 140, width: 'auto', marginBottom: 24, display: 'block' }} />
</Link>
@@ -145,7 +145,7 @@ export default function Footer() {
{/* Nav columns */}
{NAV.map(col => (
<nav key={col.title} aria-label={col.title}>
<nav key={col.title} className="ly-footer-nav" aria-label={col.title}>
<h5 style={{ margin: '0 0 20px', fontSize: 12, fontWeight: 700, color: 'rgba(255,255,255,0.3)', letterSpacing: '0.16em', textTransform: 'uppercase', fontFamily: 'Sora, sans-serif' }}>
{col.title}
</h5>
@@ -164,7 +164,7 @@ export default function Footer() {
</div>
{/* ── BOTTOM BAR ── */}
<div style={{
<div className="ly-footer-bottom" style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
flexWrap: 'wrap', gap: 12,
padding: '20px 64px 36px',
@@ -181,6 +181,25 @@ export default function Footer() {
))}
</div>
</div>
<style>{`
@media (max-width: 900px) {
.ly-footer { margin: 0 12px 12px; border-radius: 0 0 20px 20px; }
.ly-footer-cta { padding: 48px 28px 40px !important; align-items: flex-start !important; }
.ly-footer-cta-actions { align-items: flex-start !important; width: 100%; }
.ly-footer-cta-btn { width: 100%; padding: 16px 28px !important; }
.ly-footer-columns { grid-template-columns: 1fr 1fr !important; gap: 36px 24px !important; padding: 48px 28px 36px !important; }
.ly-footer-brand { grid-column: 1 / -1; }
.ly-footer-bottom { padding: 20px 28px 28px !important; flex-direction: column !important; align-items: flex-start !important; }
}
@media (max-width: 560px) {
.ly-footer-heading { font-size: clamp(30px, 9vw, 44px) !important; }
.ly-footer-columns { grid-template-columns: 1fr !important; gap: 32px !important; }
.ly-footer-brand img { height: 110px !important; }
.ly-footer-bottom { gap: 16px !important; }
.ly-footer-bottom > div { gap: 18px !important; }
}
`}</style>
</footer>
{/* Scroll-to-top */}

View File

@@ -79,7 +79,7 @@ export default function GetInTouch() {
}}
>
<div style={{
<div className="git-grid" style={{
position: 'relative', zIndex: 2,
maxWidth: 1200, margin: '0 auto',
padding: 'clamp(56px, 8vh, 100px) clamp(28px, 5vw, 72px)',
@@ -100,7 +100,7 @@ export default function GetInTouch() {
<h2 style={{
margin: '0 0 24px',
fontSize: 'clamp(2rem, 4vw, 3.4rem)', fontWeight: 900,
fontSize: 'clamp(2rem, 4vw, 3.4rem)', fontWeight: 800,
lineHeight: 0.98, letterSpacing: '-0.04em',
color: '#fff', fontFamily: 'Sora, sans-serif',
}}>
@@ -242,6 +242,9 @@ export default function GetInTouch() {
color: rgba(255,255,255,0.5); transition: all 0.2s ease;
}
.git-social:hover { background: rgba(244,190,40,0.12); border-color: rgba(244,190,40,0.3); color: #f4be28; }
@media (max-width: 420px) {
.git-grid { grid-template-columns: 1fr !important; }
}
`}</style>
</section>
);

View File

@@ -14,7 +14,7 @@ export default function MapSection() {
overflow: 'hidden',
background: '#fff',
}}>
<div style={{
<div className="map-section-grid" style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
alignItems: 'stretch',
@@ -37,7 +37,7 @@ export default function MapSection() {
</div>
<h2 style={{
fontSize: 'clamp(32px, 4vw, 56px)', fontWeight: 900,
fontSize: 'clamp(32px, 4vw, 56px)', fontWeight: 800,
letterSpacing: '-0.04em', lineHeight: 1.0,
color: '#0a0a0f', margin: '0 0 14px', fontFamily: 'Sora, sans-serif',
}}>

View File

@@ -35,7 +35,7 @@ export default function AIRecommendationsSection() {
<div style={{ marginBottom: 28 }}><span className="fp-kicker fp-rise">AI Intelligence · 05</span></div>
<h2 style={{
fontSize: 'clamp(40px, 5vw, 72px)', fontWeight: 900, letterSpacing: '-0.05em', lineHeight: 0.94,
fontSize: 'clamp(40px, 5vw, 72px)', fontWeight: 800, letterSpacing: '-0.05em', lineHeight: 0.94,
color: '#171512', margin: '0 0 28px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Built for</span></span>

View File

@@ -35,7 +35,7 @@ export default function DiscoverDealsSection() {
<div style={{ marginBottom: 28 }}><span className="fp-kicker fp-rise">Discover · 02</span></div>
<h2 style={{
fontSize: 'clamp(44px, 5.6vw, 88px)', fontWeight: 900, lineHeight: 0.94,
fontSize: 'clamp(44px, 5.6vw, 88px)', fontWeight: 800, lineHeight: 0.94,
letterSpacing: '-0.05em', color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Every</span></span>

View File

@@ -73,7 +73,7 @@ export default function EarnRewardsSection() {
<div style={{ marginBottom: 16 }}><span className="fp-kicker fp-rise">Earn · 03</span></div>
<h2 style={{
fontSize: 'clamp(28px, 3.4vw, 48px)', fontWeight: 900, lineHeight: 1.08,
fontSize: 'clamp(28px, 3.4vw, 48px)', fontWeight: 800, lineHeight: 1.08,
letterSpacing: '-0.04em', color: '#171512', margin: '0 0 20px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Every step.</span></span>

View File

@@ -65,7 +65,7 @@ export default function FinalCTASection() {
{/* Headline */}
<h2 style={{
fontSize: 'clamp(48px, 7.5vw, 112px)', fontWeight: 900, letterSpacing: '-0.06em', lineHeight: 0.92,
fontSize: 'clamp(48px, 7.5vw, 112px)', fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.92,
color: '#171512', margin: '0 0 28px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Your rewards</span></span>

View File

@@ -61,10 +61,6 @@ export default function HeroSection() {
{/* Text column */}
<div style={{ maxWidth: 720 }}>
<div className="fp-content fp-eyebrow" style={{ marginBottom: 26, display: 'flex', justifyContent: 'center' }}>
<span className="fp-kicker">For People · By Loyaly</span>
</div>
<h1 className="fp-headline" style={{
fontSize: 'clamp(40px, 5.4vw, 82px)', fontWeight: 800, lineHeight: 1.02,
letterSpacing: '-0.045em', color: '#fff', margin: '0 0 28px', fontFamily: 'Sora, sans-serif',

View File

@@ -30,7 +30,7 @@ export default function SpendAnywhereSection() {
<div>
<div style={{ marginBottom: 22 }}><span className="fp-kicker fp-rise">Spend · 04</span></div>
<h2 style={{
fontSize: 'clamp(46px, 7vw, 110px)', fontWeight: 900, letterSpacing: '-0.06em', lineHeight: 0.9,
fontSize: 'clamp(46px, 7vw, 110px)', fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.9,
color: '#fff', margin: 0, fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Spend it</span></span>

View File

@@ -105,7 +105,7 @@ export default function WhyLoyalySection() {
</div>
<h2 style={{
fontSize: 'clamp(30px, 3.6vw, 50px)', fontWeight: 900, lineHeight: 1.08,
fontSize: 'clamp(30px, 3.6vw, 50px)', fontWeight: 800, lineHeight: 1.08,
letterSpacing: '-0.04em', color: '#171512', margin: '0 0 20px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Millions shop offline.</span></span>

View File

@@ -234,8 +234,7 @@ export default function ProblemsShift({ isLoaded = true }: ProblemsShiftProps) {
scrollTrigger: { trigger: '.ps-bottom-bar', start: 'top 92%', toggleActions: 'play none none reset' },
});
bottomTl
.fromTo('.ps-bottom-bar', { opacity: 0, y: 36, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 0.8, ease: 'power3.out' })
.fromTo('.ps-feature-item', { opacity: 0, y: 16 }, { opacity: 1, y: 0, stagger: 0.09, duration: 0.5, ease: 'power3.out' }, '-=0.5');
.fromTo('.ps-feature-item', { opacity: 0, y: 36, scale: 0.88 }, { opacity: 1, y: 0, scale: 1, stagger: 0.1, duration: 0.65, ease: 'back.out(1.7)' });
ScrollTrigger.refresh();
}, sectionRef);
@@ -263,7 +262,7 @@ export default function ProblemsShift({ isLoaded = true }: ProblemsShiftProps) {
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />
<span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '0.18em', textTransform: 'uppercase', color: '#C49600', fontFamily: 'Sora, sans-serif' }}>The Opportunity</span>
</div>
<h2 className="ps-title" style={{ color: '#171512', fontWeight: 900, fontSize: 'clamp(2.4rem, 6vw, 4.8rem)', letterSpacing: '-0.05em', lineHeight: 0.94, margin: '0 0 20px', fontFamily: 'Sora, sans-serif' }}>
<h2 className="ps-title" style={{ color: '#171512', fontWeight: 800, fontSize: 'clamp(2.4rem, 6vw, 4.8rem)', letterSpacing: '-0.05em', lineHeight: 0.94, margin: '0 0 20px', fontFamily: 'Sora, sans-serif' }}>
Retail is busy.<br /><span style={{ color: '#f4be28' }}>But blind.</span>
</h2>
<p className="ps-subtitle" style={{ color: '#4f463a', fontSize: 'clamp(1rem, 1.3vw, 1.2rem)', fontWeight: 500, maxWidth: 620, margin: '0 auto', lineHeight: 1.7, fontFamily: 'Sora, sans-serif' }}>
@@ -305,26 +304,43 @@ export default function ProblemsShift({ isLoaded = true }: ProblemsShiftProps) {
</div>
{/* Bottom features bar */}
<div className="ps-bottom-bar relative z-10 mx-auto" style={{ maxWidth: 980, marginTop: 'clamp(40px, 5vw, 72px)', background: '#fff', borderRadius: 20, border: '1px solid rgba(0,0,0,0.05)', boxShadow: '0 20px 50px -30px rgba(23,21,18,0.2)', padding: 'clamp(16px, 1.6vw, 22px) clamp(20px, 3vw, 44px)', display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'center', gap: 16 }}>
{BOTTOM_FEATURES.map((f, i) => (
<React.Fragment key={f.label}>
<div className="ps-feature-item" style={{ flex: '1 1 180px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 11, cursor: 'default' }}>
<span style={{ width: 36, height: 36, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 10, background: '#faf9f5', border: '1px solid rgba(0,0,0,0.05)' }}>
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#171512" strokeWidth="2">{f.icon}</svg>
</span>
<span style={{ color: '#171512', fontWeight: 700, fontSize: '0.95rem', fontFamily: 'Sora, sans-serif' }}>{f.label}</span>
</div>
{i < BOTTOM_FEATURES.length - 1 && <span className="ps-sep" style={{ width: 1, height: 28, background: 'rgba(0,0,0,0.07)' }} />}
</React.Fragment>
<div className="ps-bottom-bar relative z-10 mx-auto" style={{ maxWidth: 980, marginTop: 'clamp(40px, 5vw, 72px)', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))', gap: 'clamp(12px, 1.6vw, 18px)' }}>
{BOTTOM_FEATURES.map((f) => (
<div
key={f.label}
className="ps-feature-item"
style={{
display: 'flex', alignItems: 'center', gap: 14,
background: '#fff', borderRadius: 18,
border: '1px solid rgba(0,0,0,0.06)',
boxShadow: '0 1px 2px rgba(0,0,0,0.03), 0 16px 36px -22px rgba(23,21,18,0.22)',
padding: 'clamp(16px, 1.6vw, 20px) clamp(16px, 2vw, 22px)',
cursor: 'default',
transition: 'transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease',
}}
>
<span style={{
width: 44, height: 44, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
borderRadius: 13, background: 'linear-gradient(145deg,#fff8e8 0%,#fff2c8 100%)',
border: '1px solid rgba(244,190,40,0.25)',
}}>
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#bc7122" strokeWidth="2">{f.icon}</svg>
</span>
<span style={{ color: '#171512', fontWeight: 700, fontSize: '0.95rem', fontFamily: 'Sora, sans-serif', lineHeight: 1.3 }}>{f.label}</span>
</div>
))}
</div>
{/* Responsive: stack cards on narrow screens, rotate connector */}
<style>{`
#problems-shift .ps-feature-item:hover {
transform: translateY(-4px);
box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 24px 48px -22px rgba(23,21,18,0.3);
border-color: rgba(244,190,40,0.35);
}
@media (max-width: 900px) {
#problems-shift .ps-cards-row { flex-direction: column; }
#problems-shift .ps-connector-arrow { transform: rotate(90deg); }
#problems-shift .ps-sep { display: none; }
}
`}</style>
</section>

View File

@@ -275,7 +275,7 @@ function ProductCard({ product, index }: { product: Product; index: number }) {
<h3 style={{
margin: 0,
fontSize: 'clamp(1.7rem, 2.8vw, 3rem)',
fontWeight: 900, lineHeight: 0.98, letterSpacing: '-0.04em',
fontWeight: 800, lineHeight: 0.98, letterSpacing: '-0.04em',
color: '#ffffff', fontFamily: 'Sora, sans-serif',
}}>{product.title}</h3>
@@ -410,7 +410,7 @@ export default function ProductsSection({ isLoaded = false }: ProductsSectionPro
<span style={{ width: 32, height: 2, background: '#f4be28', display: 'block', borderRadius: 2 }} />
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: '#C49600', fontFamily: 'Sora, sans-serif' }}>Platform</span>
</div>
<h2 style={{ margin: 0, fontSize: 'clamp(1.9rem, 3.6vw, 3.2rem)', fontWeight: 900, lineHeight: 0.94, letterSpacing: '-0.04em', color: '#111111', fontFamily: 'Sora, sans-serif' }}>
<h2 style={{ margin: 0, fontSize: 'clamp(1.9rem, 3.6vw, 3.2rem)', fontWeight: 800, lineHeight: 0.94, letterSpacing: '-0.04em', color: '#111111', fontFamily: 'Sora, sans-serif' }}>
Four products.<br />
<span style={{ background: 'linear-gradient(105deg, #f4be28 0%, #FF9800 100%)', backgroundSize: '200% auto', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', animation: 'prodShimmer 3s linear infinite' }}>One ecosystem.</span>
</h2>

View File

@@ -155,7 +155,7 @@ export default function TheLoop() {
<h2 style={{
margin: '0 0 18px',
fontSize: 'clamp(2.6rem, 6vw, 5rem)',
fontWeight: 900, color: '#fff',
fontWeight: 800, color: '#fff',
lineHeight: 0.92, letterSpacing: '-0.05em',
fontFamily: 'Sora, sans-serif',
}}>
@@ -222,6 +222,7 @@ export default function TheLoop() {
style={{
flex: 1,
position: 'relative',
zIndex: 1,
padding: 'clamp(26px, 2.2vw, 38px) clamp(20px, 1.8vw, 28px)',
borderRadius: 26,
background: isActive ? 'rgba(244,190,40,0.07)' : 'rgba(255,255,255,0.025)',
@@ -276,14 +277,15 @@ export default function TheLoop() {
width: 'clamp(28px, 3vw, 56px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
position: 'relative',
zIndex: 0,
}}>
<div style={{
<div className="loop-connector-line" style={{
position: 'absolute', left: 0, right: 0, height: 2, borderRadius: 2,
background: 'linear-gradient(90deg, rgba(244,190,40,0.1), rgba(244,190,40,0.45), rgba(244,190,40,0.1))',
backgroundSize: '200% 100%',
animation: revealed ? 'loopFlow 1.8s linear infinite' : 'none',
}} />
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#f4be28" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ position: 'relative', zIndex: 2, opacity: 0.8 }}>
<svg className="loop-connector-arrow" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#f4be28" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ position: 'relative', zIndex: 2, opacity: 0.8 }}>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</div>
@@ -330,9 +332,25 @@ export default function TheLoop() {
@keyframes loopShimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
@keyframes loopDash { to { stroke-dashoffset: -32; } }
@keyframes loopFlow { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
@keyframes loopFlowVertical { 0%{background-position:0 200%} 100%{background-position:0 -200%} }
@keyframes loopArrowBounceH { 0%,100% { transform: translateX(0); } 50% { transform: translateX(5px); } }
@keyframes loopArrowBounceV { 0%,100% { transform: rotate(90deg) translateX(0); } 50% { transform: rotate(90deg) translateX(5px); } }
.loop-connector-arrow { animation: loopArrowBounceH 1.4s ease-in-out infinite; }
@media (max-width: 880px) {
.loop-row { flex-direction: column; }
.loop-connector { width: 100% !important; height: 40px; transform: rotate(90deg); }
.loop-card { z-index: 1; }
.loop-connector {
width: 100% !important; height: 40px; z-index: 0;
}
.loop-connector-line {
left: 50% !important; right: auto !important; top: 0 !important; bottom: 0 !important;
width: 2px !important; height: 100% !important;
transform: translateX(-50%);
background: linear-gradient(180deg, rgba(244,190,40,0.1), rgba(244,190,40,0.45), rgba(244,190,40,0.1)) !important;
background-size: 100% 200% !important;
animation-name: loopFlowVertical !important;
}
.loop-connector-arrow { animation: loopArrowBounceV 1.4s ease-in-out infinite; }
.loop-arc, .loop-arc-label { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {

View File

@@ -1,6 +1,6 @@
'use client';
import React, { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { DEFAULT_EASE, DEFAULT_DURATION } from '@/utils/animation';
@@ -12,93 +12,204 @@ if (typeof window !== 'undefined') {
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
interface CardProps {
interface Stage {
num: string;
title: string;
description: string;
icon: React.ReactNode;
cardRef: React.RefObject<HTMLDivElement | null>;
orderClass: string;
}
function StageCard({ num, title, description, icon, cardRef, orderClass }: CardProps) {
const STAGES: Stage[] = [
{
num: '01',
title: 'Understand',
description: 'Track store footfall, customer behavior, and real-time engagement with AI — giving merchants the intelligence to run smarter campaigns and targeted offers that actually convert.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
),
},
{
num: '02',
title: 'Engage',
description: 'Customers earn Loyaly Coins through daily login, 5,000-step walking challenges, and interactive games — Maze, Spin Wheel, Scratch Card, and Match Master — keeping them engaged every day.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
),
},
{
num: '03',
title: 'Convert',
description: 'Merchants create game-based discount campaigns where customers play once to reveal their reward — turning product discovery into an exciting, high-conversion in-store experience.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
),
},
{
num: '04',
title: 'Retain',
description: 'Daily rewards, surprise coupons, and game-based offers keep customers returning to earn, play, and redeem — creating a self-reinforcing loyalty loop that compounds value with every visit.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
),
},
];
function StageCard({
stage,
index,
active,
onHover,
}: {
stage: Stage;
index: number;
active: boolean;
onHover: (i: number | null) => void;
}) {
const cardRef = useRef<HTMLDivElement>(null);
const onMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const el = cardRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - 0.5;
const py = (e.clientY - r.top) / r.height - 0.5;
el.style.setProperty('--rx', `${(-py * 6).toFixed(2)}deg`);
el.style.setProperty('--ry', `${(px * 8).toFixed(2)}deg`);
el.style.setProperty('--mx', `${((px + 0.5) * 100).toFixed(1)}%`);
el.style.setProperty('--my', `${((py + 0.5) * 100).toFixed(1)}%`);
};
const onMouseLeave = () => {
const el = cardRef.current;
if (!el) return;
el.style.setProperty('--rx', '0deg');
el.style.setProperty('--ry', '0deg');
onHover(null);
};
return (
<div
ref={cardRef}
className={`wwd-card relative group ${orderClass}`}
data-stage-card={index}
className={`wwd-card relative group ${active ? 'wwd-card-active' : ''}`}
onMouseMove={onMouseMove}
onMouseEnter={() => onHover(index)}
onMouseLeave={onMouseLeave}
style={{
background: 'rgba(255,255,255,0.025)',
backdropFilter: 'blur(24px) saturate(160%)',
WebkitBackdropFilter: 'blur(24px) saturate(160%)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '28px',
padding: 'clamp(30px, 3vw, 48px)',
transition: 'transform 0.5s cubic-bezier(0.22,1,0.36,1), box-shadow 0.5s ease, border-color 0.5s ease',
cursor: 'default',
overflow: 'hidden',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.05)',
}}
onMouseEnter={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(-8px)';
(e.currentTarget as HTMLElement).style.boxShadow = '0 32px 80px rgba(244, 190, 40,0.12), 0 0 0 1px rgba(244, 190, 40,0.25), inset 0 1px 0 rgba(255,255,255,0.08)';
(e.currentTarget as HTMLElement).style.borderColor = 'rgba(244, 190, 40,0.3)';
}}
onMouseLeave={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
(e.currentTarget as HTMLElement).style.boxShadow = 'inset 0 1px 0 rgba(255,255,255,0.05)';
(e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.08)';
position: 'relative',
transform: 'perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateY(var(--cascade, 0px))',
transformStyle: 'preserve-3d',
transition: 'transform 0.45s cubic-bezier(0.22,1,0.36,1), box-shadow 0.45s ease, border-color 0.45s ease',
}}
>
{/* Giant editorial number watermark */}
<div style={{
position: 'absolute', top: '-24px', right: '8px',
fontSize: 'clamp(8rem, 11vw, 13rem)', fontWeight: 900, lineHeight: 1,
color: 'transparent',
WebkitTextStroke: '1px rgba(244, 190, 40,0.06)',
fontFamily: 'Sora, sans-serif',
letterSpacing: '-0.06em',
userSelect: 'none', pointerEvents: 'none',
}}>
{num}
<div
className="wwd-card-inner"
style={{
position: 'relative',
background: 'rgba(255,255,255,0.025)',
backdropFilter: 'blur(24px) saturate(160%)',
WebkitBackdropFilter: 'blur(24px) saturate(160%)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 26,
padding: 'clamp(28px, 2.4vw, 38px)',
overflow: 'hidden',
cursor: 'default',
height: '100%',
boxShadow: active
? '0 32px 70px -20px rgba(244,190,40,0.22), 0 0 0 1px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)'
: 'inset 0 1px 0 rgba(255,255,255,0.05)',
borderColor: active ? 'rgba(244,190,40,0.32)' : 'rgba(255,255,255,0.08)',
}}
>
{/* Cursor-following glow */}
<div
className="wwd-card-glow"
style={{
position: 'absolute', inset: 0, pointerEvents: 'none', opacity: active ? 1 : 0,
background: 'radial-gradient(280px circle at var(--mx,50%) var(--my,50%), rgba(244,190,40,0.12), transparent 70%)',
transition: 'opacity 0.4s ease',
}}
/>
{/* Giant editorial number, bleeding off the top edge */}
<div style={{
position: 'absolute', top: '-0.32em', right: '-0.06em',
fontSize: 'clamp(6rem, 8vw, 9rem)', fontWeight: 900, lineHeight: 1,
color: 'transparent',
WebkitTextStroke: active ? '1px rgba(244,190,40,0.18)' : '1px rgba(244,190,40,0.07)',
fontFamily: 'Sora, sans-serif',
letterSpacing: '-0.06em',
userSelect: 'none', pointerEvents: 'none',
transition: 'all 0.45s ease',
}}>
{stage.num}
</div>
{/* Step rail dot */}
<div style={{
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 22,
position: 'relative', zIndex: 2,
}}>
<span style={{
fontSize: 11, fontWeight: 800, letterSpacing: '0.18em', textTransform: 'uppercase',
color: active ? '#f4be28' : 'rgba(255,255,255,0.32)',
fontFamily: 'Sora, sans-serif', transition: 'color 0.4s ease',
}}>
Stage {stage.num}
</span>
<span style={{ flex: 1, height: 1, background: active ? 'rgba(244,190,40,0.35)' : 'rgba(255,255,255,0.08)', transition: 'background 0.4s ease' }} />
</div>
{/* Icon */}
<div style={{
width: 54, height: 54,
borderRadius: 16,
background: active ? 'rgba(244,190,40,0.16)' : 'rgba(244,190,40,0.1)',
border: `1px solid rgba(244,190,40,${active ? 0.4 : 0.22})`,
color: '#f4be28',
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 28,
position: 'relative', zIndex: 2,
boxShadow: active ? '0 0 28px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)' : 'inset 0 1px 0 rgba(255,255,255,0.08)',
transform: active ? 'rotate(-6deg) scale(1.05)' : 'none',
transition: 'all 0.45s cubic-bezier(0.22,1,0.36,1)',
}}>
{stage.icon}
</div>
{/* Title */}
<h3 style={{
margin: '0 0 14px',
fontSize: 'clamp(1.4rem, 2vw, 1.85rem)',
fontWeight: 800, color: '#ffffff',
lineHeight: 1.1, letterSpacing: '-0.03em',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{stage.title}
</h3>
{/* Description */}
<p style={{
margin: 0,
fontSize: '0.92rem',
color: 'rgba(255,255,255,0.45)',
lineHeight: 1.75,
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{stage.description}
</p>
</div>
{/* Icon */}
<div style={{
width: '56px', height: '56px',
borderRadius: '16px',
background: 'rgba(244, 190, 40,0.1)',
border: '1px solid rgba(244, 190, 40,0.22)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: '32px',
position: 'relative', zIndex: 2,
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.08)',
}}>
{icon}
</div>
{/* Title */}
<h3 style={{
margin: '0 0 16px',
fontSize: 'clamp(1.5rem, 2.2vw, 2rem)',
fontWeight: 800, color: '#ffffff',
lineHeight: 1.1, letterSpacing: '-0.03em',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{title}
</h3>
{/* Description */}
<p style={{
margin: 0,
fontSize: 'clamp(0.9rem, 1vw, 1rem)',
color: 'rgba(255,255,255,0.45)',
lineHeight: 1.75, maxWidth: '360px',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{description}
</p>
</div>
);
}
@@ -109,129 +220,43 @@ interface WhatWeDoProps {
export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
const containerRef = useRef<HTMLDivElement>(null);
const gridContainerRef = useRef<HTMLDivElement>(null);
const card1Ref = useRef<HTMLDivElement>(null);
const card2Ref = useRef<HTMLDivElement>(null);
const card3Ref = useRef<HTMLDivElement>(null);
const card4Ref = useRef<HTMLDivElement>(null);
const [active, setActive] = useState<number | null>(null);
const [points, setPoints] = useState({
p1: { x: 0, y: 0 },
p2: { x: 0, y: 0 },
p3: { x: 0, y: 0 },
p4: { x: 0, y: 0 }
});
const [hasMeasured, setHasMeasured] = useState(false);
const updatePoints = useCallback(() => {
if (!gridContainerRef.current || !card1Ref.current || !card2Ref.current || !card3Ref.current || !card4Ref.current) return;
const containerRect = gridContainerRef.current.getBoundingClientRect();
const r1 = card1Ref.current.getBoundingClientRect();
const r2 = card2Ref.current.getBoundingClientRect();
const r3 = card3Ref.current.getBoundingClientRect();
const r4 = card4Ref.current.getBoundingClientRect();
// Check that width is non-zero to verify the elements have rendered and have dimensions
if (r1.width === 0 || r2.width === 0 || r3.width === 0 || r4.width === 0) return;
setPoints({
p1: {
x: r1.left - containerRect.left + r1.width / 2,
y: r1.top - containerRect.top + r1.height / 2
},
p2: {
x: r2.left - containerRect.left + r2.width / 2,
y: r2.top - containerRect.top + r2.height / 2
},
p3: {
x: r3.left - containerRect.left + r3.width / 2,
y: r3.top - containerRect.top + r3.height / 2
},
p4: {
x: r4.left - containerRect.left + r4.width / 2,
y: r4.top - containerRect.top + r4.height / 2
}
});
setHasMeasured(true);
}, []);
useEffect(() => {
if (typeof window === 'undefined') return;
// Use ResizeObserver for highly responsive layout tracking
const observer = new ResizeObserver(() => {
updatePoints();
});
if (gridContainerRef.current) observer.observe(gridContainerRef.current);
if (card1Ref.current) observer.observe(card1Ref.current);
if (card2Ref.current) observer.observe(card2Ref.current);
if (card3Ref.current) observer.observe(card3Ref.current);
if (card4Ref.current) observer.observe(card4Ref.current);
// Initial calculation
updatePoints();
// Secondary delay to ensure clean alignment post-font/css loading
const timer = setTimeout(updatePoints, 200);
window.addEventListener('resize', updatePoints);
return () => {
observer.disconnect();
window.removeEventListener('resize', updatePoints);
clearTimeout(timer);
};
}, [updatePoints]);
// GSAP animations for stagger entrances on entering viewport
useIsomorphicLayoutEffect(() => {
if (!isLoaded) return;
if (typeof window === 'undefined') return;
const ctx = gsap.context(() => {
// Header Animation
gsap.fromTo(
'.wwd-header-el',
{ opacity: 0, y: 40 },
{
opacity: 1,
y: 0,
duration: DEFAULT_DURATION,
stagger: 0.15,
ease: DEFAULT_EASE,
scrollTrigger: {
trigger: '.wwd-header',
start: 'top 85%',
toggleActions: 'play none none reset'
}
opacity: 1, y: 0, duration: DEFAULT_DURATION, stagger: 0.15, ease: DEFAULT_EASE,
scrollTrigger: { trigger: '.wwd-header', start: 'top 85%', toggleActions: 'play none none reset' },
}
);
// Card Stagger Animation
gsap.fromTo(
'.wwd-card',
{ opacity: 0, y: 60 },
{ opacity: 0, y: 70, scale: 0.94 },
{
opacity: 1,
y: 0,
duration: DEFAULT_DURATION,
stagger: 0.15,
ease: DEFAULT_EASE,
onComplete: updatePoints,
scrollTrigger: {
trigger: '.wwd-grid-container',
start: 'top 80%',
toggleActions: 'play none none reset'
}
opacity: 1, y: 0, scale: 1, duration: 0.9, stagger: 0.12, ease: 'power3.out',
scrollTrigger: { trigger: '.wwd-grid-container', start: 'top 80%', toggleActions: 'play none none reset' },
}
);
gsap.fromTo(
'.wwd-rail-step',
{ opacity: 0, y: 14 },
{
opacity: 1, y: 0, duration: 0.5, stagger: 0.08, ease: 'power3.out',
scrollTrigger: { trigger: '.wwd-rail', start: 'top 85%', toggleActions: 'play none none reset' },
}
);
}, containerRef);
return () => ctx.revert();
}, [isLoaded, updatePoints]);
}, [isLoaded]);
return (
<section
@@ -241,136 +266,83 @@ export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
style={{
width: 'calc(100% - 40px)',
margin: '0 20px 20px',
borderRadius: '32px',
borderRadius: 32,
zIndex: 2,
}}
>
{/* Animated dark background (replaces video) */}
<AnimatedDarkBg variant="amber" />
{/* Header Container */}
<div className="wwd-header relative z-10 text-center max-w-4xl mx-auto mb-24 sm:mb-28 md:mb-32 lg:mb-36 flex flex-col items-center">
{/* Pill Badge */}
{/* Header */}
<div className="wwd-header relative z-10 text-center max-w-4xl mx-auto mb-16 sm:mb-20 flex flex-col items-center">
<div className="wwd-header-el inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-white/10 bg-white/5 text-[#f4be28] text-xs font-bold tracking-wider uppercase mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-[#f4be28] animate-pulse" />
What We Do
</div>
{/* Main Heading */}
<h2 className="wwd-header-el text-white font-black mb-6" style={{ fontSize: 'clamp(2.8rem, 6vw, 5.5rem)', letterSpacing: '-0.05em', lineHeight: 0.9, fontFamily: 'Sora, sans-serif' }}>
Understand.<br />
<span className="text-[#f4be28]">Engage. Convert.</span><br />
Retain.
</h2>
{/* Subtitle */}
<p className="wwd-header-el text-gray-400 text-base sm:text-lg max-w-xl font-medium" style={{ fontFamily: 'Sora, sans-serif', lineHeight: 1.75 }}>
Four stages. One platform. A smarter retail cycle that turns every walk-in into a loyal customer.
</p>
</div>
{/* Grid and Paths Container */}
<div ref={gridContainerRef} className="relative z-10 wwd-grid-container max-w-6xl mx-auto">
{/* 2 x 2 Responsive Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 lg:gap-24 relative z-20 px-2 md:px-0">
{/* Card 1 — Understand */}
<StageCard
num="01"
title="Understand"
description="Track store footfall, customer behavior, and real-time engagement with AI — giving merchants the intelligence to run smarter campaigns and targeted offers that actually convert."
cardRef={card1Ref}
orderClass="order-1 md:order-1"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
}
/>
{/* Card 2 — Engage */}
<StageCard
num="02"
title="Engage"
description="Customers earn Loyaly Coins through daily login, 5,000-step walking challenges, and interactive games — Maze, Spin Wheel, Scratch Card, and Match Master — keeping them engaged every day."
cardRef={card2Ref}
orderClass="order-2 md:order-2"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
}
/>
{/* Card 4 — Retain (positioned bottom-left on desktop via order-3) */}
<StageCard
num="04"
title="Retain"
description="Daily rewards, surprise coupons, and game-based offers keep customers returning to earn, play, and redeem — creating a self-reinforcing loyalty loop that compounds value with every visit."
cardRef={card4Ref}
orderClass="order-4 md:order-3"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
}
/>
{/* Card 3 — Convert (positioned bottom-right on desktop via order-4) */}
<StageCard
num="03"
title="Convert"
description="Merchants create game-based discount campaigns where customers play once to reveal their reward — turning product discovery into an exciting, high-conversion in-store experience."
cardRef={card3Ref}
orderClass="order-3 md:order-4"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
}
/>
</div>
{/* Center Area Ornament */}
{hasMeasured && (
<div
className="absolute z-30 hidden md:flex items-center justify-center"
{/* Step rail — desktop flow indicator synced to card hover, aligned to the card columns below */}
<div className="wwd-rail relative z-10 max-w-6xl mx-auto mb-6 hidden lg:grid lg:grid-cols-4 gap-7">
{STAGES.map((s, i) => (
<button
key={s.num}
type="button"
onMouseEnter={() => setActive(i)}
onMouseLeave={() => setActive(null)}
className="wwd-rail-step"
style={{
pointerEvents: 'none',
left: `${(points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4}px`,
top: `${(points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4}px`,
transform: 'translate(-50%, -50%)'
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
padding: '6px 14px', borderRadius: 100, border: '1px solid',
borderColor: active === i ? 'rgba(244,190,40,0.4)' : 'rgba(255,255,255,0.1)',
background: active === i ? 'rgba(244,190,40,0.1)' : 'rgba(255,255,255,0.03)',
color: active === i ? '#f4be28' : 'rgba(255,255,255,0.4)',
fontSize: 11, fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase',
fontFamily: 'Sora, sans-serif', cursor: 'default',
transition: 'all 0.3s ease',
}}
>
{/* Glowing container */}
<div className="relative w-16 h-16 rounded-full bg-[#0D0D0D] border border-[#f4be28]/35 flex items-center justify-center shadow-[0_0_35px_rgba(244, 190, 40,0.22)]">
{/* Pulsing outer aura */}
<div className="absolute inset-0 rounded-full border border-[#f4be28]/20 animate-ping opacity-60 pointer-events-none" />
{/* Rotating ↻ Icon */}
<svg
className="w-7 h-7 text-[#f4be28] animate-[spin_12s_linear_infinite]"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2.5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</div>
</div>
)}
{s.num} · {s.title}
</button>
))}
</div>
{/* Cascading 4-up grid */}
<div className="relative z-10 wwd-grid-container max-w-6xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-7">
{STAGES.map((stage, i) => (
<div key={stage.num} className="wwd-cascade-item" style={{ ['--cascade-offset' as string]: `${(i % 2 === 1 ? 32 : 0)}px` }}>
<StageCard stage={stage} index={i} active={active === i} onHover={setActive} />
</div>
))}
</div>
</div>
<style>{`
@media (min-width: 1024px) {
.wwd-cascade-item:nth-child(2n) .wwd-card { --cascade: 36px; }
}
.wwd-card-inner::before {
content: '';
position: absolute; inset: 0; border-radius: inherit; padding: 1px;
background: linear-gradient(135deg, rgba(244,190,40,0.5), transparent 40%, transparent 60%, rgba(244,190,40,0.25));
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.wwd-card-active .wwd-card-inner::before { opacity: 1; }
`}</style>
</section>
);
}