updated pages
This commit is contained in:
@@ -1,154 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: '👁',
|
||||
title: 'Turn Footfall into Insight',
|
||||
description:
|
||||
'Real-time visitor tracking and movement analytics reveal exactly how customers move through your store, enabling smarter layout, staffing, and marketing decisions.',
|
||||
},
|
||||
{
|
||||
icon: '🎯',
|
||||
title: 'Personalisation, Perfected',
|
||||
description:
|
||||
'AI-driven profiles let you deliver the right offer to the right customer at precisely the right moment — online, in-store, or both.',
|
||||
},
|
||||
{
|
||||
icon: '⚙️',
|
||||
title: 'One Engine. Total Intelligence.',
|
||||
description:
|
||||
'A single unified platform connects footfall, loyalty, promotions, and revenue data so every team works from the same source of truth.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<section id="about" className="about-section" aria-label="About Loyaly.AI" style={{ margin: '0 20px 20px', borderRadius: '24px', overflow: 'hidden' }}>
|
||||
{/* Two-column intro */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
|
||||
gap: '48px',
|
||||
alignItems: 'center',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '80px 24px 60px',
|
||||
}}
|
||||
>
|
||||
{/* Image */}
|
||||
<div style={{ position: 'relative', minHeight: '340px', borderRadius: '16px', overflow: 'hidden' }}>
|
||||
<Image
|
||||
src="/loyaly_php/assets/images/about.png"
|
||||
alt="Loyaly.AI platform dashboard showing customer intelligence data"
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 12px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
color: '#f4be28',
|
||||
}}
|
||||
>
|
||||
About Us
|
||||
</p>
|
||||
<h2
|
||||
style={{
|
||||
margin: '0 0 20px',
|
||||
fontSize: 'clamp(1.6rem, 3vw, 2.4rem)',
|
||||
fontWeight: 800,
|
||||
lineHeight: 1.2,
|
||||
color: '#171512',
|
||||
}}
|
||||
>
|
||||
AI Powered Customer Intelligence for Retail
|
||||
</h2>
|
||||
<p style={{ margin: '0 0 16px', fontSize: '16px', lineHeight: 1.75, color: '#4f463a' }}>
|
||||
Loyaly.ai is an AI powered customer intelligence platform built for modern retail. We help
|
||||
businesses understand who their customers are, what drives their behaviour, and how to
|
||||
build lasting loyalty at every touchpoint.
|
||||
</p>
|
||||
<p style={{ margin: '0 0 28px', fontSize: '16px', lineHeight: 1.75, color: '#4f463a' }}>
|
||||
From footfall tracking and personalised engagement to smart loyalty programmes and revenue
|
||||
optimisation, Loyaly.AI gives retailers one unified platform to know their customers deeply
|
||||
and act on that knowledge instantly.
|
||||
</p>
|
||||
<Link
|
||||
href="/solutions_page"
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '12px 28px',
|
||||
background: '#f4be28',
|
||||
color: '#171512',
|
||||
borderRadius: '4px',
|
||||
fontWeight: 700,
|
||||
textDecoration: 'none',
|
||||
fontSize: '15px',
|
||||
}}
|
||||
>
|
||||
Explore Our Solutions <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Three-feature grid */}
|
||||
<div
|
||||
style={{
|
||||
background: '#fff2c8',
|
||||
padding: '60px 24px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
|
||||
gap: '32px',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
>
|
||||
{FEATURES.map(({ icon, title, description }) => (
|
||||
<div
|
||||
key={title}
|
||||
style={{
|
||||
background: '#fff8e8',
|
||||
borderRadius: '12px',
|
||||
padding: '32px 28px',
|
||||
boxShadow: '0 2px 12px rgba(23,21,18,0.06)',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '32px', marginBottom: '16px' }} aria-hidden="true">
|
||||
{icon}
|
||||
</div>
|
||||
<h3
|
||||
style={{
|
||||
margin: '0 0 12px',
|
||||
fontSize: '18px',
|
||||
fontWeight: 700,
|
||||
color: '#171512',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p style={{ margin: 0, fontSize: '15px', lineHeight: 1.7, color: '#4f463a' }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
const CASE_STUDIES = [
|
||||
{
|
||||
slug: 'ai-maximises-discount-strategy',
|
||||
title: 'Stop Wasting Discounts: How AI Maximises Every Offer You Give',
|
||||
excerpt:
|
||||
'Most retail discounts go to customers who would have bought anyway. Discover how Loyaly.AI\'s promotion intelligence engine targets the right buyers at the right margin.',
|
||||
tags: ['AI Solutions', 'Retail Intelligence'],
|
||||
image: '/loyaly_php/assets/images/case-study-2-img-960x718.jpeg',
|
||||
imageAlt: 'Data visualisation showing AI-optimised discount targeting across customer segments',
|
||||
readTime: '5 min read',
|
||||
},
|
||||
{
|
||||
slug: 'ai-redefining-retail-loyalty-2026',
|
||||
title: 'Beyond Points: How AI is Redefining Retail Loyalty in 2026',
|
||||
excerpt:
|
||||
'Traditional points schemes are losing ground. Learn how AI-driven loyalty programmes build genuine relationships through personalisation, timing, and context-aware rewards.',
|
||||
tags: ['AI Retail', 'Loyalty Intelligence'],
|
||||
image: '/loyaly_php/assets/images/case-study-1-img-960x718.jpeg',
|
||||
imageAlt: 'Retail customer using a loyalty app powered by Loyaly.AI',
|
||||
readTime: '7 min read',
|
||||
},
|
||||
];
|
||||
|
||||
export default function BlogSection() {
|
||||
return (
|
||||
<section
|
||||
id="case-studies"
|
||||
aria-label="Case studies"
|
||||
style={{ padding: '80px 24px', background: '#fff8e8', margin: '0 20px 20px', borderRadius: '24px', overflow: 'hidden' }}
|
||||
>
|
||||
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{/* Header */}
|
||||
<div style={{ marginBottom: '48px', display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', flexWrap: 'wrap', gap: '16px' }}>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 10px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
color: '#f4be28',
|
||||
}}
|
||||
>
|
||||
Case Studies
|
||||
</p>
|
||||
<h2
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 'clamp(1.6rem, 3vw, 2.4rem)',
|
||||
fontWeight: 800,
|
||||
color: '#171512',
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
Insights from the Loyaly.AI field
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
href="/blog_main"
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '10px 22px',
|
||||
border: '2px solid #f4be28',
|
||||
borderRadius: '4px',
|
||||
color: '#171512',
|
||||
fontWeight: 700,
|
||||
textDecoration: 'none',
|
||||
fontSize: '14px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
All Articles <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Card grid */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '32px',
|
||||
}}
|
||||
>
|
||||
{CASE_STUDIES.map(({ slug, title, excerpt, tags, image, imageAlt, readTime }) => (
|
||||
<article
|
||||
key={slug}
|
||||
style={{
|
||||
background: '#fff',
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 2px 16px rgba(23,21,18,0.07)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'transform 0.25s ease, box-shadow 0.25s ease',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.transform = 'translateY(-4px)';
|
||||
el.style.boxShadow = '0 8px 32px rgba(23,21,18,0.12)';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.transform = 'translateY(0)';
|
||||
el.style.boxShadow = '0 2px 16px rgba(23,21,18,0.07)';
|
||||
}}
|
||||
>
|
||||
{/* Image */}
|
||||
<Link href={`/use_cases/${slug}`} tabIndex={-1} aria-hidden="true">
|
||||
<div style={{ position: 'relative', height: '220px' }}>
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: '28px', flex: 1, display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
{/* Tags */}
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
{tags.map(tag => (
|
||||
<span
|
||||
key={tag}
|
||||
style={{
|
||||
padding: '4px 10px',
|
||||
background: '#fff2c8',
|
||||
color: '#bc7122',
|
||||
borderRadius: '20px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.04em',
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: '18px',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.35,
|
||||
color: '#171512',
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
href={`/use_cases/${slug}`}
|
||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<p style={{ margin: 0, fontSize: '14px', lineHeight: 1.7, color: '#4f463a', flex: 1 }}>
|
||||
{excerpt}
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '8px' }}>
|
||||
<span style={{ fontSize: '13px', color: '#bc7122', fontWeight: 600 }}>{readTime}</span>
|
||||
<Link
|
||||
href={`/use_cases/${slug}`}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
color: '#171512',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
Read more <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -16,9 +16,9 @@ const NAV = [
|
||||
{
|
||||
title: 'Platform',
|
||||
links: [
|
||||
{ label: 'Behavision AI', href: '/solutions_page' },
|
||||
{ label: 'SpendSense AI', href: '/solutions_page' },
|
||||
{ label: 'Dyscount AI', href: '/solutions_page' },
|
||||
{ label: 'Behavision AI', href: '/use_cases' },
|
||||
{ label: 'SpendSense AI', href: '/use_cases' },
|
||||
{ label: 'Dyscount AI', href: '/use_cases' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { useGSAPReveal, RevealAnimation } from '@/hooks/useGSAPReveal';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
delay?: number;
|
||||
animation?: RevealAnimation;
|
||||
duration?: number;
|
||||
ease?: string;
|
||||
start?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
as?: keyof React.JSX.IntrinsicElements;
|
||||
}
|
||||
|
||||
export default function GSAPReveal({
|
||||
children, delay = 0, animation = 'fade-up',
|
||||
duration = 1.0, ease, start, className, style,
|
||||
}: Props) {
|
||||
const ref = useGSAPReveal({ delay, animation, duration, ease, start });
|
||||
return (
|
||||
<div ref={ref} className={className} style={{ willChange: 'transform, opacity', ...style }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
const PARTNERS = [
|
||||
{ name: 'Partner 1', src: '/loyaly_php/assets/images/partners-1.png' },
|
||||
{ name: 'Partner 2', src: '/loyaly_php/assets/images/partners-2.png' },
|
||||
{ name: 'Partner 3', src: '/loyaly_php/assets/images/partners-3.png' },
|
||||
{ name: 'Partner 4', src: '/loyaly_php/assets/images/partners-4.png' },
|
||||
{ name: 'Partner 5', src: '/loyaly_php/assets/images/partners-5.png' },
|
||||
{ name: 'Partner 6', src: '/loyaly_php/assets/images/partners-6.png' },
|
||||
];
|
||||
|
||||
// Duplicate list so the CSS marquee loops seamlessly
|
||||
const TRACK = [...PARTNERS, ...PARTNERS];
|
||||
|
||||
export default function Partners() {
|
||||
return (
|
||||
<section
|
||||
id="partners"
|
||||
aria-label="Our partners"
|
||||
style={{ padding: '60px 0', overflow: 'hidden', background: '#fff8e8', margin: '0 20px 20px', borderRadius: '24px' }}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '0 24px 40px' }}>
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 10px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
color: '#f4be28',
|
||||
}}
|
||||
>
|
||||
Our Partners
|
||||
</p>
|
||||
<h2
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 'clamp(1.2rem, 2.5vw, 1.8rem)',
|
||||
fontWeight: 700,
|
||||
color: '#171512',
|
||||
}}
|
||||
>
|
||||
Our trusted collaborators in progress and success
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Marquee track */}
|
||||
<div
|
||||
style={{ position: 'relative', overflow: 'hidden' }}
|
||||
aria-hidden="true" // purely decorative – screen readers can skip
|
||||
>
|
||||
{/* Fade edges */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '120px',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(to right, #fff8e8, transparent)',
|
||||
zIndex: 1,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: '120px',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(to left, #fff8e8, transparent)',
|
||||
zIndex: 1,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="partners-track"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '56px',
|
||||
width: 'max-content',
|
||||
animation: 'marquee 28s linear infinite',
|
||||
padding: '8px 0',
|
||||
}}
|
||||
>
|
||||
{TRACK.map(({ name, src }, i) => (
|
||||
<div
|
||||
key={`${name}-${i}`}
|
||||
style={{
|
||||
flex: '0 0 auto',
|
||||
width: '140px',
|
||||
height: '64px',
|
||||
position: 'relative',
|
||||
filter: 'grayscale(1)',
|
||||
opacity: 0.6,
|
||||
transition: 'filter 0.3s, opacity 0.3s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
(e.currentTarget as HTMLDivElement).style.filter = 'grayscale(0)';
|
||||
(e.currentTarget as HTMLDivElement).style.opacity = '1';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLDivElement).style.filter = 'grayscale(1)';
|
||||
(e.currentTarget as HTMLDivElement).style.opacity = '0.6';
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={name}
|
||||
fill
|
||||
style={{ objectFit: 'contain' }}
|
||||
sizes="140px"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.partners-track { animation: none; }
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* Accessible static list for screen readers */}
|
||||
<ul
|
||||
aria-label="Partner logos"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
overflow: 'hidden',
|
||||
clip: 'rect(0,0,0,0)',
|
||||
whiteSpace: 'nowrap',
|
||||
listStyle: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{PARTNERS.map(({ name }) => (
|
||||
<li key={name}>{name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useEffect, useCallback } from 'react';
|
||||
|
||||
export default function PhoneWidget() {
|
||||
const stageRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onMove = useCallback((e: MouseEvent) => {
|
||||
const el = stageRef.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 * 8).toFixed(2)}deg`);
|
||||
el.style.setProperty('--ry', `${(px * 10).toFixed(2)}deg`);
|
||||
el.style.setProperty('--tx', `${(px * 18).toFixed(1)}px`);
|
||||
el.style.setProperty('--ty', `${(py * 18).toFixed(1)}px`);
|
||||
}, []);
|
||||
|
||||
const onLeave = useCallback(() => {
|
||||
const el = stageRef.current;
|
||||
if (!el) return;
|
||||
el.style.setProperty('--rx', '0deg');
|
||||
el.style.setProperty('--ry', '0deg');
|
||||
el.style.setProperty('--tx', '0px');
|
||||
el.style.setProperty('--ty', '0px');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const el = stageRef.current;
|
||||
if (!el) return;
|
||||
el.addEventListener('mousemove', onMove);
|
||||
el.addEventListener('mouseleave', onLeave);
|
||||
return () => {
|
||||
el.removeEventListener('mousemove', onMove);
|
||||
el.removeEventListener('mouseleave', onLeave);
|
||||
};
|
||||
}, [onMove, onLeave]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes pwOrbFloat { 0%,100%{transform:translateY(0) scale(1)} 50%{transform:translateY(-22px) scale(1.04)} }
|
||||
@keyframes pwOrb2Float { 0%,100%{transform:translateY(0) scale(1)} 50%{transform:translateY(18px) scale(0.97)} }
|
||||
@keyframes pwGlow { 0%,100%{box-shadow:0 0 30px rgba(244,190,40,0.2)} 50%{box-shadow:0 0 60px rgba(244,190,40,0.5),0 0 120px rgba(244,190,40,0.2)} }
|
||||
@keyframes pwScreenShimmer { 0%{left:-40%} 60%{left:120%} 100%{left:120%} }
|
||||
.pw-hero-grid { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6vw; width: 100%; }
|
||||
@media (max-width: 900px) { .pw-hero-grid { grid-template-columns: 1fr; } .pw-widget-col { display: none; } }
|
||||
`}</style>
|
||||
|
||||
<div
|
||||
ref={stageRef}
|
||||
style={{
|
||||
position: 'relative', display: 'flex',
|
||||
justifyContent: 'center', alignItems: 'center',
|
||||
perspective: 1200, cursor: 'default',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
transformStyle: 'preserve-3d',
|
||||
transform: 'rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translate(var(--tx,0px), var(--ty,0px))',
|
||||
transition: 'transform 0.5s cubic-bezier(0.22,1,0.36,1)',
|
||||
position: 'relative',
|
||||
}}>
|
||||
{/* Glow ring */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: -36, borderRadius: '56px',
|
||||
background: 'radial-gradient(circle at 50% 50%, rgba(244,190,40,0.2) 0%, transparent 70%)',
|
||||
animation: 'pwGlow 3s ease-in-out infinite',
|
||||
}} />
|
||||
|
||||
{/* Phone shell */}
|
||||
<div style={{
|
||||
width: 268, height: 548, borderRadius: '48px',
|
||||
background: 'linear-gradient(160deg, #22223a 0%, #10101c 100%)',
|
||||
border: '1.5px solid rgba(255,255,255,0.1)',
|
||||
boxShadow: '0 70px 130px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.06), inset 0 1px 0 rgba(255,255,255,0.1)',
|
||||
display: 'flex', flexDirection: 'column', overflow: 'hidden',
|
||||
animation: 'pwOrbFloat 6s ease-in-out infinite', position: 'relative',
|
||||
}}>
|
||||
{/* Shimmer sweep */}
|
||||
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 20, overflow: 'hidden', borderRadius: '48px' }}>
|
||||
<div style={{
|
||||
position: 'absolute', top: '-20%', left: 0, width: '40%', height: '140%',
|
||||
background: 'linear-gradient(105deg, transparent 0%, rgba(255,255,255,0.07) 50%, transparent 100%)',
|
||||
animation: 'pwScreenShimmer 4.5s ease-in-out infinite', animationDelay: '1s',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Dynamic island */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 16, paddingBottom: 4 }}>
|
||||
<div style={{ width: 96, height: 28, borderRadius: 20, background: '#080810', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: '50%', background: '#1a1a28', border: '1px solid rgba(255,255,255,0.08)' }} />
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#0d0d18' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div style={{ padding: '8px 22px 12px' }}>
|
||||
<div style={{ fontSize: 10, color: '#f4be28', fontWeight: 700, fontFamily: 'Sora, sans-serif', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Good Morning</div>
|
||||
<div style={{ fontSize: 16, color: '#fff', fontWeight: 800, fontFamily: 'Sora, sans-serif', marginTop: 2 }}>Loyaly ✦</div>
|
||||
</div>
|
||||
|
||||
{/* LYTs balance card */}
|
||||
<div style={{
|
||||
margin: '0 14px', borderRadius: 22,
|
||||
background: 'linear-gradient(135deg, #f4be28 0%, #e09820 100%)',
|
||||
padding: '18px 20px', boxShadow: '0 8px 24px rgba(244,190,40,0.35)',
|
||||
position: 'relative', overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{ position: 'absolute', top: '-30%', right: '-10%', width: 110, height: 110, borderRadius: '50%', background: 'rgba(255,255,255,0.12)' }} />
|
||||
<div style={{ fontSize: 10, color: 'rgba(23,21,18,0.65)', fontWeight: 700, fontFamily: 'Sora, sans-serif', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Total LYTs</div>
|
||||
<div style={{ fontSize: 34, color: '#171512', fontWeight: 900, fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em', marginTop: 2 }}>284</div>
|
||||
<div style={{ fontSize: 10, color: 'rgba(23,21,18,0.6)', fontFamily: 'Sora, sans-serif', marginTop: 4 }}>≈ ₹284 redeemable</div>
|
||||
</div>
|
||||
|
||||
{/* Activity feed */}
|
||||
<div style={{ padding: '14px 12px', display: 'flex', flexDirection: 'column', gap: 7, flex: 1 }}>
|
||||
{[
|
||||
{ label: 'Daily Login', pts: '+50', color: '#4ade80' },
|
||||
{ label: 'Walking 5k Steps', pts: '+80', color: '#4ade80' },
|
||||
{ label: 'Spin Wheel', pts: '-50', color: '#f87171' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
background: 'rgba(255,255,255,0.04)', borderRadius: 12, padding: '10px 13px',
|
||||
border: '1px solid rgba(255,255,255,0.06)',
|
||||
}}>
|
||||
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.78)', fontFamily: 'Sora, sans-serif' }}>{item.label}</span>
|
||||
<span style={{ fontSize: 12, color: item.color, fontWeight: 700, fontFamily: 'Sora, sans-serif' }}>{item.pts}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Nav dots */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: 6, padding: '10px 0 20px' }}>
|
||||
{[1, 0, 0, 0].map((a, i) => (
|
||||
<div key={i} style={{ width: a ? 22 : 6, height: 4, borderRadius: 2, background: a ? '#f4be28' : 'rgba(255,255,255,0.2)' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating chip — top left */}
|
||||
<div style={{
|
||||
position: 'absolute', top: '12%', left: '-22%', borderRadius: 16,
|
||||
padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 10,
|
||||
background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
||||
animation: 'pwOrb2Float 7s ease-in-out infinite',
|
||||
}}>
|
||||
<div style={{ width: 30, height: 30, borderRadius: 9, background: 'rgba(74,222,128,0.16)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#4ade80', fontSize: 14, fontWeight: 800 }}>↑</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#fff', fontFamily: 'Sora, sans-serif' }}>+120 LYTs</div>
|
||||
<div style={{ fontSize: 9, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif' }}>just now</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating chip — bottom right */}
|
||||
<div style={{
|
||||
position: 'absolute', bottom: '14%', right: '-22%', borderRadius: 16,
|
||||
padding: '12px 16px',
|
||||
background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
||||
animation: 'pwOrbFloat 8s ease-in-out infinite',
|
||||
}}>
|
||||
<div style={{ fontSize: 9, color: 'rgba(255,255,255,0.45)', fontFamily: 'Sora, sans-serif', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 3 }}>Redeemable</div>
|
||||
<div style={{ fontSize: 17, fontWeight: 900, color: '#f4be28', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.02em' }}>₹284.00</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
radius: number;
|
||||
alpha: number;
|
||||
type: 'lost' | 'converted' | 'regular';
|
||||
pulseSpeed: number;
|
||||
pulseTime: number;
|
||||
}
|
||||
|
||||
export default function ProblemParticles() {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
let animationFrameId = 0;
|
||||
let cssWidth = canvas.offsetWidth;
|
||||
let cssHeight = canvas.offsetHeight;
|
||||
|
||||
const particles: Particle[] = [];
|
||||
const storeNode = { x: cssWidth / 2, y: cssHeight / 2, radius: 18 };
|
||||
|
||||
const setCanvasSize = () => {
|
||||
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||
cssWidth = canvas.offsetWidth;
|
||||
cssHeight = canvas.offsetHeight;
|
||||
canvas.width = Math.round(cssWidth * dpr);
|
||||
canvas.height = Math.round(cssHeight * dpr);
|
||||
// Map drawing coordinates to CSS pixels
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
storeNode.x = cssWidth / 2;
|
||||
storeNode.y = cssHeight / 2;
|
||||
};
|
||||
|
||||
const createParticle = (isInitial = false): Particle => {
|
||||
const typeRand = Math.random();
|
||||
const type = typeRand < 0.2 ? 'lost' : typeRand < 0.45 ? 'converted' : 'regular';
|
||||
|
||||
let x: number, y: number;
|
||||
if (isInitial) {
|
||||
x = Math.random() * cssWidth;
|
||||
y = Math.random() * cssHeight;
|
||||
} else {
|
||||
// Spawn from edges
|
||||
if (Math.random() < 0.5) {
|
||||
x = Math.random() < 0.5 ? 0 : cssWidth;
|
||||
y = Math.random() * cssHeight;
|
||||
} else {
|
||||
x = Math.random() * cssWidth;
|
||||
y = Math.random() < 0.5 ? 0 : cssHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Vector pointing towards the store node (center)
|
||||
const angle = Math.atan2(storeNode.y - y, storeNode.x - x);
|
||||
const speed = 0.4 + Math.random() * 0.8;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
vx: Math.cos(angle) * speed + (Math.random() - 0.5) * 0.2,
|
||||
vy: Math.sin(angle) * speed + (Math.random() - 0.5) * 0.2,
|
||||
radius: 2 + Math.random() * 4,
|
||||
alpha: 0.1 + Math.random() * 0.7,
|
||||
type,
|
||||
pulseSpeed: 0.05 + Math.random() * 0.05,
|
||||
pulseTime: Math.random() * Math.PI,
|
||||
};
|
||||
};
|
||||
|
||||
// Initialize
|
||||
setCanvasSize();
|
||||
for (let i = 0; i < 45; i++) particles.push(createParticle(true));
|
||||
|
||||
const handleResize = () => {
|
||||
if (!canvas) return;
|
||||
setCanvasSize();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
||||
|
||||
// Background glow
|
||||
const bgGlow = ctx.createRadialGradient(
|
||||
storeNode.x,
|
||||
storeNode.y,
|
||||
10,
|
||||
storeNode.x,
|
||||
storeNode.y,
|
||||
cssWidth * 0.4
|
||||
);
|
||||
bgGlow.addColorStop(0, 'rgba(0, 102, 255, 0.08)');
|
||||
bgGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
||||
ctx.fillStyle = bgGlow;
|
||||
ctx.fillRect(0, 0, cssWidth, cssHeight);
|
||||
|
||||
// Store hub
|
||||
ctx.shadowBlur = 30;
|
||||
ctx.shadowColor = '#0066ff';
|
||||
ctx.fillStyle = 'rgba(0, 102, 255, 0.9)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(storeNode.x, storeNode.y, storeNode.radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(storeNode.x, storeNode.y, storeNode.radius - 6, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Particles
|
||||
particles.forEach((p, idx) => {
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
p.pulseTime += p.pulseSpeed;
|
||||
|
||||
let alpha = p.alpha;
|
||||
let radius = p.radius;
|
||||
|
||||
if (p.type === 'converted') {
|
||||
alpha = Math.max(0.4, p.alpha + Math.sin(p.pulseTime) * 0.3);
|
||||
radius = p.radius + Math.sin(p.pulseTime) * 1.5;
|
||||
} else if (p.type === 'lost') {
|
||||
const dist = Math.hypot(storeNode.x - p.x, storeNode.y - p.y);
|
||||
if (dist < 150) p.alpha -= 0.008;
|
||||
}
|
||||
|
||||
// Connections between particles
|
||||
for (let j = idx + 1; j < particles.length; j++) {
|
||||
const p2 = particles[j];
|
||||
const dist = Math.hypot(p2.x - p.x, p2.y - p.y);
|
||||
if (dist < 80 && p.alpha > 0 && p2.alpha > 0) {
|
||||
const lineAlpha = (1 - dist / 80) * 0.18 * Math.min(alpha, p2.alpha);
|
||||
ctx.strokeStyle = `rgba(0, 102, 255, ${lineAlpha})`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x, p.y);
|
||||
ctx.lineTo(p2.x, p2.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// Connection to store hub
|
||||
const distToStore = Math.hypot(storeNode.x - p.x, storeNode.y - p.y);
|
||||
if (distToStore < 120 && p.alpha > 0) {
|
||||
const lineAlpha = (1 - distToStore / 120) * 0.25 * p.alpha;
|
||||
ctx.strokeStyle = p.type === 'converted' ? `rgba(0, 220, 255, ${lineAlpha})` : `rgba(0, 102, 255, ${lineAlpha})`;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x, p.y);
|
||||
ctx.lineTo(storeNode.x, storeNode.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Respawn check
|
||||
if (distToStore < storeNode.radius || p.x < 0 || p.x > cssWidth || p.y < 0 || p.y > cssHeight || p.alpha <= 0) {
|
||||
particles[idx] = createParticle();
|
||||
} else {
|
||||
ctx.fillStyle = p.type === 'converted' ? `rgba(0, 220, 255, ${alpha})` : p.type === 'lost' ? `rgba(255, 68, 68, ${alpha})` : `rgba(255, 255, 255, ${alpha})`;
|
||||
if (p.type === 'converted') {
|
||||
ctx.shadowBlur = 10;
|
||||
ctx.shadowColor = '#00dcff';
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
}
|
||||
});
|
||||
|
||||
animationFrameId = requestAnimationFrame(draw);
|
||||
};
|
||||
|
||||
draw();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <canvas ref={canvasRef} className="w-full h-full absolute inset-0" />;
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
const SOLUTIONS = [
|
||||
{
|
||||
id: 'footfall',
|
||||
category: 'AI Solution',
|
||||
title: 'Footfall Intelligence',
|
||||
subtitle: 'Understand Customer Movement in Real Time',
|
||||
excerpt:
|
||||
'Track visitor count, movement patterns, and peak hours across your retail spaces. Turn anonymous foot traffic into actionable intelligence that drives layout, staffing, and promotions.',
|
||||
image: '/loyaly_php/assets/images/deepvision-fi-640x640.jpg',
|
||||
imageAlt: 'Footfall Intelligence dashboard showing real-time customer movement heatmaps',
|
||||
href: '/solutions/footfall-intelligence',
|
||||
},
|
||||
{
|
||||
id: 'journey',
|
||||
category: 'AI Solution',
|
||||
title: 'Customer Journey',
|
||||
subtitle: 'Map Every Interaction Seamlessly',
|
||||
excerpt:
|
||||
'Track and understand each step of your customer\'s journey — online and in-store — to identify friction points, improve conversion, and deliver experiences that build loyalty.',
|
||||
image: '/loyaly_php/assets/images/nlpgenius-natural-language-processing-fi-640x640.jpg',
|
||||
imageAlt: 'Customer journey map visualisation inside Loyaly.AI platform',
|
||||
href: '/solutions/customer-journey',
|
||||
},
|
||||
{
|
||||
id: 'engagement',
|
||||
category: 'AI Solution',
|
||||
title: 'Customer Engagement',
|
||||
subtitle: 'Drive Meaningful Interactions',
|
||||
excerpt:
|
||||
'Analyse behaviour and preferences to deliver personalised experiences, timely offers, and targeted messaging that keep customers coming back.',
|
||||
image: '/loyaly_php/assets/images/smarttrader-predictive-stock-market-analysis-fi-640x640.jpg',
|
||||
imageAlt: 'Customer engagement analytics showing personalisation performance metrics',
|
||||
href: '/solutions/customer-engagement',
|
||||
},
|
||||
{
|
||||
id: 'loyalty',
|
||||
category: 'AI Solution',
|
||||
title: 'Loyalty Management',
|
||||
subtitle: 'Build Lasting Relationships',
|
||||
excerpt:
|
||||
'Reward customer loyalty with smart programmes that adapt in real time — from LYTs and tiers to personalised perks that feel genuinely earned.',
|
||||
image: '/loyaly_php/assets/images/cognivision-transformative-ai-for-intelligent-fi-640x640.jpg',
|
||||
imageAlt: 'Loyalty management module showing customer tier performance',
|
||||
href: '/solutions/loyalty-management',
|
||||
},
|
||||
{
|
||||
id: 'revenue',
|
||||
category: 'AI Solution',
|
||||
title: 'Revenue Optimisation',
|
||||
subtitle: 'Maximise Business Performance',
|
||||
excerpt:
|
||||
'Leverage data insights to optimise pricing, promotions, and strategies for higher revenue growth — without sacrificing margin.',
|
||||
image: '/loyaly_php/assets/images/neuraforge-crafting-future-ready-ai-solutions-fi-640x640.jpg',
|
||||
imageAlt: 'Revenue optimisation charts showing margin improvements',
|
||||
href: '/solutions/revenue-optimisation',
|
||||
},
|
||||
];
|
||||
|
||||
export default function Solutions() {
|
||||
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="solutions"
|
||||
aria-label="Our solutions"
|
||||
style={{ padding: '80px 24px', background: '#fff', margin: '0 20px 20px', borderRadius: '24px', overflow: 'hidden' }}
|
||||
>
|
||||
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{/* Header */}
|
||||
<div style={{ marginBottom: '56px', maxWidth: '640px' }}>
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 12px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
color: '#f4be28',
|
||||
}}
|
||||
>
|
||||
Our Solutions
|
||||
</p>
|
||||
<h2
|
||||
style={{
|
||||
margin: '0 0 16px',
|
||||
fontSize: 'clamp(1.6rem, 3vw, 2.4rem)',
|
||||
fontWeight: 800,
|
||||
lineHeight: 1.2,
|
||||
color: '#171512',
|
||||
}}
|
||||
>
|
||||
Exploring groundbreaking AI solutions transforming retail
|
||||
</h2>
|
||||
<p style={{ margin: 0, fontSize: '16px', lineHeight: 1.7, color: '#4f463a' }}>
|
||||
From understanding footfall to optimising revenue, our five AI modules work independently
|
||||
or together as one unified intelligence engine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
|
||||
gap: '28px',
|
||||
marginBottom: '48px',
|
||||
}}
|
||||
>
|
||||
{SOLUTIONS.map(({ id, category, title, subtitle, excerpt, image, imageAlt, href }) => {
|
||||
const isHovered = hoveredId === id;
|
||||
return (
|
||||
<article
|
||||
key={id}
|
||||
onMouseEnter={() => setHoveredId(id)}
|
||||
onMouseLeave={() => setHoveredId(null)}
|
||||
style={{
|
||||
background: '#fff8e8',
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: isHovered
|
||||
? '0 12px 40px rgba(23,21,18,0.14)'
|
||||
: '0 2px 12px rgba(23,21,18,0.06)',
|
||||
transform: isHovered ? 'translateY(-6px)' : 'translateY(0)',
|
||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* Card image */}
|
||||
<Link href={href} tabIndex={-1} aria-hidden="true" style={{ display: 'block' }}>
|
||||
<div style={{ position: 'relative', height: '220px', overflow: 'hidden' }}>
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
|
||||
transition: 'transform 0.4s ease',
|
||||
}}
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Card content */}
|
||||
<div
|
||||
style={{
|
||||
padding: '24px 24px 28px',
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
{/* Category badge */}
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '4px 10px',
|
||||
background: '#fff2c8',
|
||||
color: '#bc7122',
|
||||
borderRadius: '20px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.04em',
|
||||
alignSelf: 'flex-start',
|
||||
}}
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
|
||||
<h3 style={{ margin: 0, fontSize: '19px', fontWeight: 800, color: '#171512', lineHeight: 1.2 }}>
|
||||
<Link href={href} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<p style={{ margin: 0, fontSize: '14px', fontWeight: 600, color: '#bc7122' }}>
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
<p style={{ margin: 0, fontSize: '14px', lineHeight: 1.7, color: '#4f463a', flex: 1 }}>
|
||||
{excerpt}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={href}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
marginTop: '8px',
|
||||
fontWeight: 700,
|
||||
fontSize: '14px',
|
||||
color: '#171512',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
Explore more <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Link
|
||||
href="/solutions_page"
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '14px 36px',
|
||||
background: '#f4be28',
|
||||
color: '#171512',
|
||||
borderRadius: '4px',
|
||||
fontWeight: 700,
|
||||
textDecoration: 'none',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
View all solutions <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
const TESTIMONIALS = [
|
||||
{
|
||||
quote:
|
||||
'Loyaly.AI transformed how we understand our customers. Foot traffic insights alone helped us rearrange our store layout and boost average basket size by 18% in the first quarter.',
|
||||
author: 'Priya Nair',
|
||||
role: 'Head of Retail Operations',
|
||||
company: 'Trendsetter Fashion Stores',
|
||||
avatar: '/loyaly_php/assets/images/partners-1.png',
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'The AI powered loyalty engine learns fast. Within weeks we were delivering personalised offers that felt genuinely relevant — our repeat visit rate climbed 23%.',
|
||||
author: 'Arjun Mehta',
|
||||
role: 'VP of Customer Experience',
|
||||
company: 'FreshMart Supermarkets',
|
||||
avatar: '/loyaly_php/assets/images/partners-2.png',
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"We tried three loyalty platforms before Loyaly.AI. This is the first one that actually connects footfall data, promotions, and CRM in a single dashboard. It's changed how we run campaigns.",
|
||||
author: 'Sarah Connelly',
|
||||
role: 'Chief Marketing Officer',
|
||||
company: 'Urban Living Group',
|
||||
avatar: '/loyaly_php/assets/images/partners-3.png',
|
||||
},
|
||||
];
|
||||
|
||||
export default function Testimonial() {
|
||||
const [active, setActive] = useState(0);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const total = TESTIMONIALS.length;
|
||||
|
||||
const go = useCallback(
|
||||
(index: number) => {
|
||||
setActive((index + total) % total);
|
||||
},
|
||||
[total]
|
||||
);
|
||||
|
||||
// Auto-advance every 6 s
|
||||
useEffect(() => {
|
||||
if (paused) return;
|
||||
timerRef.current = setTimeout(() => go(active + 1), 6000);
|
||||
return () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
};
|
||||
}, [active, paused, go]);
|
||||
|
||||
const { quote, author, role, company } = TESTIMONIALS[active];
|
||||
|
||||
return (
|
||||
<section
|
||||
id="testimonials"
|
||||
aria-label="Customer testimonials"
|
||||
style={{ background: '#171512', color: '#fff', padding: '80px 24px', margin: '0 20px 20px', borderRadius: '24px', overflow: 'hidden' }}
|
||||
onMouseEnter={() => setPaused(true)}
|
||||
onMouseLeave={() => setPaused(false)}
|
||||
>
|
||||
<div style={{ maxWidth: '860px', margin: '0 auto', textAlign: 'center' }}>
|
||||
{/* Section label */}
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 12px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
color: '#f4be28',
|
||||
}}
|
||||
>
|
||||
Customer Stories
|
||||
</p>
|
||||
<h2
|
||||
style={{
|
||||
margin: '0 0 52px',
|
||||
fontSize: 'clamp(1.4rem, 3vw, 2rem)',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.3,
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
See how retailers are transforming customer loyalty with Loyaly.AI
|
||||
</h2>
|
||||
|
||||
{/* Quote card */}
|
||||
<div
|
||||
key={active}
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: '16px',
|
||||
padding: '48px 40px',
|
||||
position: 'relative',
|
||||
animation: 'fadeIn 0.5s ease',
|
||||
}}
|
||||
>
|
||||
{/* Decorative quote mark */}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '24px',
|
||||
left: '32px',
|
||||
fontSize: '80px',
|
||||
lineHeight: 1,
|
||||
color: '#f4be28',
|
||||
opacity: 0.25,
|
||||
fontFamily: 'Sora, serif',
|
||||
}}
|
||||
>
|
||||
"
|
||||
</span>
|
||||
|
||||
<blockquote
|
||||
style={{
|
||||
margin: '0 0 32px',
|
||||
fontSize: 'clamp(1rem, 2.2vw, 1.2rem)',
|
||||
lineHeight: 1.8,
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
fontStyle: 'italic',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{quote}
|
||||
</blockquote>
|
||||
|
||||
<footer style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}>
|
||||
<cite style={{ fontStyle: 'normal', fontWeight: 700, fontSize: '16px', color: '#f4be28' }}>
|
||||
{author}
|
||||
</cite>
|
||||
<span style={{ fontSize: '14px', color: 'rgba(255,255,255,0.6)' }}>
|
||||
{role}, {company}
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{/* Navigation dots */}
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="Testimonial navigation"
|
||||
style={{ display: 'flex', justifyContent: 'center', gap: '10px', marginTop: '32px' }}
|
||||
>
|
||||
{TESTIMONIALS.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
role="tab"
|
||||
aria-selected={i === active}
|
||||
aria-label={`Show testimonial ${i + 1}`}
|
||||
onClick={() => { go(i); setPaused(true); }}
|
||||
style={{
|
||||
width: i === active ? '28px' : '10px',
|
||||
height: '10px',
|
||||
borderRadius: '5px',
|
||||
background: i === active ? '#f4be28' : 'rgba(255,255,255,0.25)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: 0,
|
||||
transition: 'width 0.3s ease, background 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Prev / Next */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '12px', marginTop: '20px' }}>
|
||||
<button
|
||||
onClick={() => { go(active - 1); setPaused(true); }}
|
||||
aria-label="Previous testimonial"
|
||||
style={arrowBtnStyle}
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { go(active + 1); setPaused(true); }}
|
||||
aria-label="Next testimonial"
|
||||
style={arrowBtnStyle}
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const arrowBtnStyle: React.CSSProperties = {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.08)',
|
||||
color: '#fff',
|
||||
border: '1px solid rgba(255,255,255,0.15)',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'background 0.2s',
|
||||
};
|
||||
@@ -1,8 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { PhoneFrame, PhoneBottomNav, BellIcon, PinIcon, ChevronIcon, SearchIcon, MicIcon, CameraIcon, MapIcon, HeartLogoIcon } from './PhoneMockup';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
const deals = [
|
||||
{ store: 'Starbucks', category: 'Coffee & Drinks', offer: '2× LYTs', savings: '₹120 saved', color: '#00704A', dot: '#22c55e', distance: '0.3 km', rating: 4.8 },
|
||||
@@ -14,9 +20,37 @@ const deals = [
|
||||
|
||||
export default function DiscoverDealsSection() {
|
||||
const [hovered, setHovered] = useState<number | null>(null);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const phoneRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const section = panelRef.current;
|
||||
const phone = phoneRef.current;
|
||||
if (!section || !phone) return;
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.fromTo(phone,
|
||||
{ y: 160, opacity: 0 },
|
||||
{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
ease: 'power2.out',
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: 'top 92%',
|
||||
end: 'top 25%',
|
||||
scrub: 1.2,
|
||||
}
|
||||
}
|
||||
);
|
||||
}, section);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="fp-panel" style={{ background: '#08080f' }}>
|
||||
<section ref={panelRef} className="fp-panel" style={{ background: '#08080f' }}>
|
||||
|
||||
{/* Background photo */}
|
||||
<Image src="/images/stock/deals-retail.png" alt="" fill style={{ objectFit: 'cover', opacity: 0.25, zIndex: 0 }} />
|
||||
@@ -63,7 +97,7 @@ export default function DiscoverDealsSection() {
|
||||
</div>
|
||||
|
||||
{/* Phone mockup — white app screen, matches the in-app Discover experience */}
|
||||
<div className="fp-rise" style={{ transitionDelay: '0.1s', display: 'flex', justifyContent: 'center' }}>
|
||||
<div ref={phoneRef} style={{ display: 'flex', justifyContent: 'center', opacity: 0, width: '100%' }}>
|
||||
<PhoneFrame floatClassName="ddm-phone-shell">
|
||||
|
||||
{/* Amber header */}
|
||||
@@ -151,7 +185,7 @@ export default function DiscoverDealsSection() {
|
||||
<ChevronIcon />
|
||||
</div>
|
||||
|
||||
<PhoneBottomNav active="heart" />
|
||||
<PhoneBottomNav active="tag" />
|
||||
</PhoneFrame>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
@@ -8,6 +8,8 @@ import { PhoneFrame, PhoneBottomNav, BellIcon, FlameIcon } from './PhoneMockup';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
const feed = [
|
||||
{ store: 'Daily Login', pts: +50, time: '2m ago', type: 'earn' },
|
||||
{ store: 'Walking 5,000 steps', pts: +80, time: '1h ago', type: 'earn' },
|
||||
@@ -18,10 +20,38 @@ const feed = [
|
||||
export default function EarnRewardsSection() {
|
||||
const [pts, setPts] = useState(0);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const phoneRef = useRef<HTMLDivElement>(null);
|
||||
const rafRef = useRef<number>(0);
|
||||
const rowRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const activeRowRef = useRef(0);
|
||||
|
||||
// Phone mockup rises in from the bottom as the section scrolls into view
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const section = panelRef.current;
|
||||
const phone = phoneRef.current;
|
||||
if (!section || !phone) return;
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.fromTo(phone,
|
||||
{ y: 160, opacity: 0 },
|
||||
{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
ease: 'power2.out',
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: 'top 92%',
|
||||
end: 'top 25%',
|
||||
scrub: 1.2,
|
||||
}
|
||||
}
|
||||
);
|
||||
}, section);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
// Move the highlighted feed card as the section scrolls through the viewport
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
@@ -136,7 +166,7 @@ export default function EarnRewardsSection() {
|
||||
</div>
|
||||
|
||||
{/* Right — phone mockup, matches the in-app Earn / Home experience */}
|
||||
<div className="fp-rise" style={{ transitionDelay: '0.15s', display: 'flex', justifyContent: 'center' }}>
|
||||
<div ref={phoneRef} style={{ display: 'flex', justifyContent: 'center', opacity: 0, width: '100%' }}>
|
||||
<PhoneFrame floatClassName="erm-phone-shell">
|
||||
|
||||
{/* Amber header */}
|
||||
@@ -230,7 +260,7 @@ export default function EarnRewardsSection() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<PhoneBottomNav active="home" />
|
||||
<PhoneBottomNav active="user" />
|
||||
</PhoneFrame>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useLayoutEffect, useEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
const words = [
|
||||
{ text: 'Effortless.', size: 'clamp(58px, 9vw, 134px)', color: '#ffffff' },
|
||||
@@ -14,8 +20,37 @@ const steps = [
|
||||
];
|
||||
|
||||
export default function ExperienceSection() {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const section = panelRef.current;
|
||||
const card = cardRef.current;
|
||||
if (!section || !card) return;
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.fromTo(card,
|
||||
{ x: 200, opacity: 0 },
|
||||
{
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
ease: 'power2.out',
|
||||
scrollTrigger: {
|
||||
trigger: section,
|
||||
start: 'top 92%',
|
||||
end: 'top 25%',
|
||||
scrub: 1.2,
|
||||
}
|
||||
}
|
||||
);
|
||||
}, section);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="fp-panel" style={{ background: '#07070d' }}>
|
||||
<section ref={panelRef} className="fp-panel" style={{ background: '#07070d' }}>
|
||||
<div className="fp-shell">
|
||||
|
||||
<div style={{ marginBottom: 'clamp(36px, 5vh, 60px)' }}>
|
||||
@@ -36,35 +71,41 @@ export default function ExperienceSection() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Steps */}
|
||||
<div>
|
||||
<p className="fp-rise" style={{
|
||||
fontSize: 16, lineHeight: 1.85, color: 'rgba(255,255,255,0.45)', fontFamily: 'Sora, sans-serif', margin: '0 0 44px', maxWidth: 360,
|
||||
<div ref={cardRef} style={{
|
||||
background: '#f4be28',
|
||||
padding: 'clamp(28px, 4.5vw, 44px)',
|
||||
borderRadius: '28px',
|
||||
boxShadow: '0 24px 48px rgba(244, 190, 40, 0.12)',
|
||||
width: '100%',
|
||||
opacity: 0,
|
||||
}}>
|
||||
<p style={{
|
||||
fontSize: 16, lineHeight: 1.85, color: 'rgba(7, 7, 13, 0.75)', fontFamily: 'Sora, sans-serif', margin: '0 0 36px', maxWidth: 360,
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
No plastic cards. No coupon hunting. No app open required at checkout. Loyaly works quietly in the background while you shop the way you always have.
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{steps.map((s, i) => (
|
||||
<div key={i} className="fp-rise" style={{
|
||||
<div key={i} style={{
|
||||
display: 'flex', gap: 20, alignItems: 'center', padding: '20px 0',
|
||||
borderBottom: i < steps.length - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none',
|
||||
transitionDelay: `${0.2 + i * 0.1}s`,
|
||||
borderBottom: i < steps.length - 1 ? '1px solid rgba(7, 7, 13, 0.1)' : 'none',
|
||||
}}>
|
||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#f4be28', fontFamily: 'Sora, sans-serif', letterSpacing: '0.06em', flexShrink: 0, width: 24 }}>{s.n}</span>
|
||||
<span style={{ fontSize: 16, fontWeight: 600, color: 'rgba(255,255,255,0.75)', fontFamily: 'Sora, sans-serif', lineHeight: 1.4 }}>{s.label}</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 800, color: '#07070d', fontFamily: 'Sora, sans-serif', letterSpacing: '0.06em', flexShrink: 0, width: 24 }}>{s.n}</span>
|
||||
<span style={{ fontSize: 16, fontWeight: 700, color: '#07070d', fontFamily: 'Sora, sans-serif', lineHeight: 1.4 }}>{s.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="fp-rise" style={{ marginTop: 40, display: 'flex', alignItems: 'center', gap: 12, transitionDelay: '0.5s' }}>
|
||||
<div style={{ marginTop: 36, display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{['#f4be28', '#4ade80', '#a855f7', '#3b82f6'].map((c, i) => (
|
||||
<div key={i} style={{ width: 30, height: 30, borderRadius: '50%', background: c, border: '2px solid #07070d', marginLeft: i > 0 ? -9 : 0 }} />
|
||||
{['#ff7b39', '#4ade80', '#a855f7', '#3b82f6'].map((c, i) => (
|
||||
<div key={i} style={{ width: 30, height: 30, borderRadius: '50%', background: c, border: '2px solid #f4be28', marginLeft: i > 0 ? -9 : 0 }} />
|
||||
))}
|
||||
</div>
|
||||
<span style={{ fontSize: 13, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif' }}>
|
||||
Joined by <span style={{ color: '#fff', fontWeight: 700 }}>50,000+</span> shoppers
|
||||
<span style={{ fontSize: 13, color: 'rgba(7, 7, 13, 0.65)', fontFamily: 'Sora, sans-serif', fontWeight: 500 }}>
|
||||
Joined by <span style={{ color: '#07070d', fontWeight: 800 }}>50,000+</span> shoppers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,13 +44,9 @@ export default function FinalCTASection() {
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: { trigger: sectionRef.current, start: 'top 80%', toggleActions: 'play none none reset' },
|
||||
});
|
||||
tl.fromTo('.fcta-badge',
|
||||
{ opacity: 0, y: 20, scale: 0.9 },
|
||||
{ opacity: 1, y: 0, scale: 1, duration: 0.6, ease: 'back.out(2)' }
|
||||
)
|
||||
.fromTo('.fcta-line-1',
|
||||
tl.fromTo('.fcta-line-1',
|
||||
{ opacity: 0, y: 50, filter: 'blur(8px)' },
|
||||
{ opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.8, ease: 'expo.out' }, '-=0.2'
|
||||
{ opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.8, ease: 'expo.out' }
|
||||
)
|
||||
.fromTo('.fcta-line-2',
|
||||
{ opacity: 0, y: 50, filter: 'blur(8px)' },
|
||||
@@ -60,9 +56,32 @@ export default function FinalCTASection() {
|
||||
{ opacity: 0, y: 24 },
|
||||
{ opacity: 1, y: 0, duration: 0.65, ease: 'power3.out' }, '-=0.4'
|
||||
)
|
||||
.fromTo('.fcta-hanging-logo',
|
||||
{ opacity: 0, y: -60, rotation: -16 },
|
||||
{ opacity: 1, y: 0, rotation: 0, duration: 1.1, stagger: 0.16, ease: 'back.out(1.5)' },
|
||||
'-=0.45'
|
||||
)
|
||||
.fromTo('.fcta-logo-link',
|
||||
{ rotation: 10 },
|
||||
{ rotation: 0, duration: 1.2, stagger: 0.16, ease: 'back.out(1.6)' },
|
||||
'<'
|
||||
)
|
||||
.call(() => {
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
gsap.utils.toArray<HTMLElement>('.fcta-hanging-logo').forEach((el, i) => {
|
||||
gsap.to(el, {
|
||||
rotation: i % 2 === 0 ? 3.5 : -3.5,
|
||||
duration: 2.4 + i * 0.3,
|
||||
ease: 'sine.inOut',
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
transformOrigin: 'top center',
|
||||
});
|
||||
});
|
||||
})
|
||||
.fromTo('.fcta-btns',
|
||||
{ opacity: 0, y: 20 },
|
||||
{ opacity: 1, y: 0, duration: 0.55, ease: 'power3.out' }, '-=0.3'
|
||||
{ opacity: 1, y: 0, duration: 0.55, ease: 'power3.out' }, '-=0.45'
|
||||
)
|
||||
.fromTo('.fcta-stat',
|
||||
{ opacity: 0, y: 28, scale: 0.88 },
|
||||
@@ -94,30 +113,10 @@ export default function FinalCTASection() {
|
||||
{/* Amber glow orb top-center */}
|
||||
<div style={{ position: 'absolute', top: '-20%', left: '50%', transform: 'translateX(-50%)', width: '70%', height: '60%', borderRadius: '50%', background: 'radial-gradient(ellipse, rgba(244,190,40,0.12) 0%, transparent 65%)', filter: 'blur(50px)', pointerEvents: 'none' }} />
|
||||
|
||||
{/* Ghost wordmark */}
|
||||
<div style={{ position: 'absolute', bottom: -20, left: '50%', transform: 'translateX(-50%)', whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 0 }}>
|
||||
<span style={{
|
||||
fontSize: 'clamp(100px,20vw,280px)', fontWeight: 900,
|
||||
fontFamily: 'Sora, sans-serif', letterSpacing: '-0.06em', lineHeight: 1,
|
||||
color: 'transparent', WebkitTextStroke: '1px rgba(23,21,18,0.05)',
|
||||
display: 'block',
|
||||
}}>LOYALY</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ position: 'relative', zIndex: 10, maxWidth: 820, margin: '0 auto' }}>
|
||||
|
||||
{/* Live badge */}
|
||||
<div className="fcta-badge" style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8, marginBottom: 36,
|
||||
background: '#f0fdf4', border: '1.5px solid rgba(22,163,74,0.35)',
|
||||
borderRadius: 100, padding: '9px 20px',
|
||||
}}>
|
||||
<span style={{ width: 7, height: 7, borderRadius: '50%', background: '#22c55e', display: 'block', animation: 'fctaPulse 1.6s ease-in-out infinite', boxShadow: '0 0 0 3px rgba(34,197,94,0.2)' }} />
|
||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#16a34a', fontFamily: 'Sora, sans-serif', letterSpacing: '0.1em', textTransform: 'uppercase' }}>
|
||||
Now live in Coimbatore
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Headline */}
|
||||
<h2 style={{ margin: '0 0 28px', fontFamily: 'Sora, sans-serif', lineHeight: 0.92, letterSpacing: '-0.055em' }}>
|
||||
@@ -144,13 +143,130 @@ export default function FinalCTASection() {
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className="fcta-sub" style={{
|
||||
position: 'relative',
|
||||
zIndex: 5,
|
||||
fontSize: 'clamp(1rem,1.2vw,1.15rem)', lineHeight: 1.75,
|
||||
color: 'rgba(23,21,18,0.5)', fontFamily: 'Sora, sans-serif',
|
||||
maxWidth: 440, margin: '0 auto 52px',
|
||||
color: '#171512', fontFamily: 'Sora, sans-serif',
|
||||
maxWidth: 520, width: '100%', margin: '0 auto 0px',
|
||||
background: '#f4be28',
|
||||
border: '1.5px solid #f4be28',
|
||||
borderRadius: 100,
|
||||
padding: '14px 28px',
|
||||
display: 'inline-block',
|
||||
boxSizing: 'border-box',
|
||||
}}>
|
||||
Download Loyaly and start earning on your very next purchase. Free. Instant. Yours.
|
||||
</p>
|
||||
|
||||
{/* Hanging Logos */}
|
||||
<div className="fcta-hanging-logos-wrapper" style={{
|
||||
position: 'relative',
|
||||
height: 250,
|
||||
margin: '-34px auto 40px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
gap: 140,
|
||||
width: 'fit-content',
|
||||
zIndex: 1,
|
||||
}}>
|
||||
{/* Play Store Logo (hanging) */}
|
||||
<div className="fcta-hanging-logo play-store" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
transformOrigin: 'top center',
|
||||
position: 'relative',
|
||||
}}>
|
||||
{/* Keyring loop */}
|
||||
<div style={{
|
||||
width: 16, height: 16, borderRadius: '50%',
|
||||
border: '2px solid #f4be28', marginTop: 14, marginBottom: -3,
|
||||
boxShadow: '0 0 10px rgba(244,190,40,0.35)',
|
||||
flexShrink: 0,
|
||||
}} />
|
||||
{/* String */}
|
||||
<div style={{
|
||||
width: 2,
|
||||
height: 62,
|
||||
background: '#f4be28',
|
||||
opacity: 0.7,
|
||||
boxShadow: '0 0 6px rgba(244,190,40,0.25)',
|
||||
}} />
|
||||
{/* Logo Circle */}
|
||||
<Link href="/contacts" className="fcta-logo-link" style={{
|
||||
width: 116,
|
||||
height: 116,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
background: '#000000',
|
||||
boxShadow: '0 18px 40px rgba(0,0,0,0.24), 0 0 0 1px rgba(244,190,40,0.15)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '5px solid #f4be28',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
transformOrigin: 'top center',
|
||||
marginTop: -1,
|
||||
}}>
|
||||
<img src="/logo_icons/images.png" alt="Google Play" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
</Link>
|
||||
{/* Tooltip */}
|
||||
<div className="fcta-tooltip">
|
||||
Download In Playstore
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Apple Logo (hanging) */}
|
||||
<div className="fcta-hanging-logo apple-store" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
transformOrigin: 'top center',
|
||||
position: 'relative',
|
||||
}}>
|
||||
{/* Keyring loop */}
|
||||
<div style={{
|
||||
width: 16, height: 16, borderRadius: '50%',
|
||||
border: '2px solid #f4be28', marginTop: 14, marginBottom: -3,
|
||||
boxShadow: '0 0 10px rgba(244,190,40,0.35)',
|
||||
flexShrink: 0,
|
||||
}} />
|
||||
{/* String */}
|
||||
<div style={{
|
||||
width: 2,
|
||||
height: 92,
|
||||
background: '#f4be28',
|
||||
opacity: 0.7,
|
||||
boxShadow: '0 0 6px rgba(244,190,40,0.25)',
|
||||
}} />
|
||||
{/* Logo Circle */}
|
||||
<Link href="/contacts" className="fcta-logo-link" style={{
|
||||
width: 116,
|
||||
height: 116,
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
background: '#000000',
|
||||
boxShadow: '0 18px 40px rgba(0,0,0,0.24), 0 0 0 1px rgba(244,190,40,0.15)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '5px solid #f4be28',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
transformOrigin: 'top center',
|
||||
marginTop: -1,
|
||||
}}>
|
||||
<img src="/logo_icons/apple.jpg" alt="App Store" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
</Link>
|
||||
{/* Tooltip */}
|
||||
<div className="fcta-tooltip">
|
||||
Download in App store
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTAs */}
|
||||
<div className="fcta-btns" style={{ display: 'flex', gap: 14, justifyContent: 'center', flexWrap: 'wrap', marginBottom: 64 }}>
|
||||
<Link
|
||||
@@ -210,6 +326,58 @@ export default function FinalCTASection() {
|
||||
<style>{`
|
||||
@keyframes fctaPulse { 0%,100%{box-shadow:0 0 0 0 rgba(34,197,94,0.5)} 50%{box-shadow:0 0 0 6px rgba(34,197,94,0)} }
|
||||
@keyframes fctaShimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
|
||||
.fcta-hanging-logo a {
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
.fcta-hanging-logo a:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.fcta-tooltip {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
background: #171512;
|
||||
color: #ffffff;
|
||||
padding: 6px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-family: 'Sora', sans-serif;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.25, 1, 0.22, 1);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 10;
|
||||
}
|
||||
.fcta-hanging-logo:hover .fcta-tooltip {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
.fcta-tooltip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #171512;
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
.fcta-hanging-logos-wrapper {
|
||||
display: none !important;
|
||||
}
|
||||
.fcta-sub {
|
||||
margin-bottom: 40px !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.fcta-btns {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function HeroSection() {
|
||||
<div style={{ maxWidth: 720 }}>
|
||||
<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',
|
||||
letterSpacing: '-0.045em', color: '#fff', margin: '50px 0 28px', fontFamily: 'Sora, sans-serif',
|
||||
}}>
|
||||
<span className="fp-mask"><span>Shop Smart.</span></span>
|
||||
<span className="fp-mask"><span className="fp-shimmer">Live Rewarded.</span></span>
|
||||
@@ -71,7 +71,7 @@ export default function HeroSection() {
|
||||
fontSize: 'clamp(16px, 1.4vw, 19px)', lineHeight: 1.7, fontWeight: 400,
|
||||
color: 'rgba(255,255,255,0.58)', maxWidth: 640, margin: '0 auto 40px', fontFamily: 'Sora, sans-serif',
|
||||
}}>
|
||||
Meet <strong>LYT (Loyaly Token)</strong>: the single currency behind every reward in the app. Earn them through daily tasks, walking challenges, and games, then spend them instantly at partner stores near you with zero expiry.
|
||||
Meet <strong>LYTs (Loyaly Tokens)</strong>: the single currency behind every reward. Earn them through daily challenges and games, then spend instantly at partner stores near you with zero expiry.
|
||||
</p>
|
||||
|
||||
<div className="fp-content fp-cta" style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center' }}>
|
||||
|
||||
@@ -56,9 +56,20 @@ export function MapIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function HeartLogoIcon({ size = 16 }: { size?: number }) {
|
||||
export function HeartLogoIcon({ size = 16, active = false }: { size?: number; active?: boolean }) {
|
||||
return (
|
||||
<Image src="/images/loyalyai-logo.png" alt="" width={32} height={32} style={{ width: size, height: size, objectFit: 'contain' }} />
|
||||
<Image
|
||||
src="/images/loyalyai-logo.png"
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
objectFit: 'contain',
|
||||
filter: active ? 'brightness(0)' : 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export function UpArrowIcon({ color = '#16a34a' }: { color?: string }) {
|
||||
@@ -85,12 +96,12 @@ export function FlameIcon({ color = '#f4be28' }: { color?: string }) {
|
||||
|
||||
/* ── Bottom nav — pass the active tab key to highlight it ── */
|
||||
export function PhoneBottomNav({ active = 'home' }: { active?: string }) {
|
||||
const items: { key: string; icon: React.ReactNode }[] = [
|
||||
{ key: 'home', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 10.5L12 3l9 7.5" /><path d="M5 9.5V21h14V9.5" /></svg> },
|
||||
{ key: 'tag', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20.59 13.41 12 22l-9-9V4h9z" /><circle cx="7.5" cy="8.5" r="1.2" /></svg> },
|
||||
{ key: 'heart', icon: <HeartLogoIcon size={18} /> },
|
||||
{ key: 'shop', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l1.5-5h15L21 9" /><path d="M3 9h18v11H3z" /><path d="M9 20v-6h6v6" /></svg> },
|
||||
{ key: 'user', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4" /><path d="M4 21c1.5-4 5-6 8-6s6.5 2 8 6" /></svg> },
|
||||
const items: { key: string; icon: (isActive: boolean) => React.ReactNode }[] = [
|
||||
{ key: 'home', icon: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 10.5L12 3l9 7.5" /><path d="M5 9.5V21h14V9.5" /></svg> },
|
||||
{ key: 'tag', icon: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20.59 13.41 12 22l-9-9V4h9z" /><circle cx="7.5" cy="8.5" r="1.2" /></svg> },
|
||||
{ key: 'heart', icon: (isActive) => <HeartLogoIcon size={18} active={isActive} /> },
|
||||
{ key: 'shop', icon: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l1.5-5h15L21 9" /><path d="M3 9h18v11H3z" /><path d="M9 20v-6h6v6" /></svg> },
|
||||
{ key: 'user', icon: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4" /><path d="M4 21c1.5-4 5-6 8-6s6.5 2 8 6" /></svg> },
|
||||
];
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-around', padding: '10px 8px', borderTop: '1px solid #f0f0f0' }}>
|
||||
@@ -99,11 +110,11 @@ export function PhoneBottomNav({ active = 'home' }: { active?: string }) {
|
||||
return (
|
||||
<div key={n.key} style={{
|
||||
width: 34, height: 34, borderRadius: 12, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: isActive && n.key !== 'heart' ? '#171512' : '#9ca3af',
|
||||
background: isActive ? '#fff7e0' : 'transparent',
|
||||
color: isActive ? '#171512' : '#9ca3af',
|
||||
background: isActive ? '#f4be28' : 'transparent',
|
||||
border: isActive ? '1.5px solid #f4be28' : 'none',
|
||||
}}>
|
||||
{n.icon}
|
||||
{n.icon(isActive)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
68
src/components/for-people/PillarsSection.tsx
Normal file
68
src/components/for-people/PillarsSection.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const PILLARS = [
|
||||
{
|
||||
n: '01',
|
||||
title: 'Shop Smarter',
|
||||
desc: 'Unlock top deals, discounts, and exclusive offers at stores near you, just by walking in.',
|
||||
color: '#f4be28',
|
||||
},
|
||||
{
|
||||
n: '02',
|
||||
title: 'Earn Rewards',
|
||||
desc: 'Every rupee spent earns LYT points. Zero expiry. Redeem anytime, at any partner store.',
|
||||
color: '#16a34a',
|
||||
},
|
||||
{
|
||||
n: '03',
|
||||
title: 'Make It a Pleasure',
|
||||
desc: 'Shopping should feel good. Loyaly makes every trip more rewarding, more fun, and more personal.',
|
||||
color: '#a78bfa',
|
||||
},
|
||||
];
|
||||
|
||||
export default function PillarsSection() {
|
||||
return (
|
||||
<section className="fp-panel fp-light" style={{ background: '#ffffff' }}>
|
||||
<div className="fp-shell">
|
||||
<div style={{ marginBottom: 'clamp(40px, 6vh, 64px)' }}>
|
||||
<span className="fp-kicker fp-rise">Save More<span className="fp-kicker-num">02</span></span>
|
||||
</div>
|
||||
|
||||
<h2 className="fp-rise" style={{
|
||||
fontSize: 'clamp(30px, 3.6vw, 50px)', fontWeight: 800, lineHeight: 1.08,
|
||||
letterSpacing: '-0.04em', color: '#171512', margin: '0 0 20px', fontFamily: 'Sora, sans-serif', maxWidth: 640,
|
||||
}}>
|
||||
Save more on every in-store visit.
|
||||
</h2>
|
||||
<p className="fp-rise" style={{
|
||||
fontSize: 16, lineHeight: 1.8, color: '#4f463a', margin: '0 0 56px',
|
||||
fontFamily: 'Sora, sans-serif', maxWidth: 560,
|
||||
}}>
|
||||
Earn Loyaly rewards on everyday shopping, from groceries to fashion to home essentials. Every purchase brings you closer to your next reward.
|
||||
</p>
|
||||
|
||||
<div className="fp-pillar-cols" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 24 }}>
|
||||
{PILLARS.map((p, i) => (
|
||||
<div key={p.n} className="fp-rise" style={{
|
||||
transitionDelay: `${0.1 + i * 0.1}s`,
|
||||
padding: 32, borderRadius: 20, background: '#f9f7f2', border: `1px solid ${p.color}30`,
|
||||
}}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: '50%', background: p.color, marginBottom: 20 }} />
|
||||
<h3 style={{ fontSize: 21, fontWeight: 800, color: '#171512', fontFamily: 'Sora, sans-serif', margin: '0 0 12px', letterSpacing: '-0.02em' }}>{p.title}</h3>
|
||||
<p style={{ fontSize: 14.5, lineHeight: 1.7, color: '#4f463a', fontFamily: 'Sora, sans-serif', margin: 0 }}>{p.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@media (max-width: 900px) {
|
||||
.fp-pillar-cols { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -153,6 +153,31 @@ export default function ProblemSection({ isLoaded = true }: ProblemSectionProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* India retail market stats strip */}
|
||||
<div className="relative z-10 mx-auto problem-india-stats" style={{
|
||||
maxWidth: 1200,
|
||||
marginBottom: 'clamp(32px, 4vw, 48px)',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(5, 1fr)',
|
||||
gap: 'clamp(8px, 1.2vw, 14px)',
|
||||
}}>
|
||||
{[
|
||||
{ val: '$2 Tn', label: 'India retail market by 2032, 25% CAGR' },
|
||||
{ val: '5th', label: 'Largest retail destination globally' },
|
||||
{ val: '10%', label: "Of India's GDP from retail" },
|
||||
{ val: '12%', label: 'Of retail is organised, the rest is offline' },
|
||||
{ val: '500M', label: 'Indians shopping online by 2030, up from 220M' },
|
||||
].map((s, i) => (
|
||||
<div key={i} style={{
|
||||
padding: 'clamp(14px, 1.8vw, 20px)', borderRadius: 16,
|
||||
background: '#f9f7f2', border: '1px solid rgba(23,21,18,0.08)',
|
||||
}}>
|
||||
<div style={{ fontSize: 'clamp(1.3rem, 2vw, 1.7rem)', fontWeight: 900, color: '#171512', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em', lineHeight: 1 }}>{s.val}</div>
|
||||
<div style={{ marginTop: 6, fontSize: '0.72rem', color: '#4f463a', fontFamily: 'Sora, sans-serif', lineHeight: 1.4 }}>{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Grid wrapper */}
|
||||
<div className="relative z-10 mx-auto" style={{ maxWidth: 1200 }}>
|
||||
{/* Row 1: Red Stat Cards */}
|
||||
@@ -429,6 +454,7 @@ export default function ProblemSection({ isLoaded = true }: ProblemSectionProps)
|
||||
#problem .problem-stats-row { grid-template-columns: 1fr !important; }
|
||||
#problem .dark-bento-grid { grid-template-columns: 1fr !important; }
|
||||
#problem .dark-bento-card-4 { grid-column: span 1; }
|
||||
#problem .problem-india-stats { grid-template-columns: repeat(2, 1fr) !important; }
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user