convert-next.js

This commit is contained in:
R-Bharathraj
2026-06-29 18:07:43 +05:30
parent 4760f8e063
commit 0140b0bdf8
1474 changed files with 16523 additions and 9272 deletions

View File

@@ -0,0 +1,523 @@
'use client';
import React, { useEffect, useLayoutEffect, useRef } from 'react';
import Link from 'next/link';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
if (typeof window !== 'undefined') {
gsap.registerPlugin(ScrollTrigger);
}
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
interface Product {
id: number;
href: string;
title: string;
subtitle: string;
description: string;
accent: string;
accentRgb: string;
tags: string[];
}
const PRODUCTS: Product[] = [
{
id: 1,
href: '/solutions/behavision',
title: 'Behavision',
subtitle: 'AI-Powered Footfall Analytics',
description:
'AI-powered footfall analytics and behavioral intelligence. Understand how customers move, dwell, and engage — turning raw foot traffic into rich, actionable insights.',
accent: '#f4be28',
accentRgb: '244, 190, 40',
tags: ['Footfall Tracking', 'Heatmaps', 'Behavior Analysis'],
},
{
id: 2,
href: '/solutions/spendsense',
title: 'SpendSense',
subtitle: 'Customer Spending Intelligence',
description:
'Deep insights into customer spending patterns. Identify high-value segments, predict buying behaviour, and unlock incremental revenue hiding in plain sight.',
accent: '#10B981',
accentRgb: '16, 185, 129',
tags: ['Spending Trends', 'Revenue Analytics', 'Customer Value'],
},
{
id: 3,
href: '/solutions/dyscount',
title: 'Dyscount',
subtitle: 'Smart Discount Engine',
description:
'Smart discount engine that maximises conversions without killing margins. Deliver the right offer to the right customer at exactly the right moment.',
accent: '#EF4444',
accentRgb: '239, 68, 68',
tags: ['Smart Discounts', 'Offer Automation', 'Margin Protection'],
},
{
id: 4,
href: '/solutions/loyaly-app',
title: 'Loyaly App',
subtitle: 'Consumer Loyalty Platform',
description:
'Earn Loyaly Coins through daily login, walking challenges, and interactive games. Unlock surprise coupons, discover nearby store campaigns, and redeem rewards at real offline stores.',
accent: '#f4be28',
accentRgb: '244, 190, 40',
tags: ['Daily Rewards', 'Game-Based Offers', 'Offline Redemption'],
},
];
function ProductIcon({ id, accent }: { id: number; accent: string }) {
if (id === 1) {
return (
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%', height: '100%' }}>
<circle cx="24" cy="24" r="20" fill={accent} opacity="0.15" />
<path d="M14 34 C14 28 18 24 24 24 C30 24 34 28 34 34" stroke={accent} strokeWidth="2.5" strokeLinecap="round" fill="none" />
<circle cx="24" cy="18" r="5" stroke={accent} strokeWidth="2.5" fill="none" />
<circle cx="14" cy="20" r="3.5" stroke={accent} strokeWidth="2" fill="none" />
<circle cx="34" cy="20" r="3.5" stroke={accent} strokeWidth="2" fill="none" />
<path d="M8 34 C8 30 10 27 14 27" stroke={accent} strokeWidth="2" strokeLinecap="round" opacity="0.6" fill="none" />
<path d="M40 34 C40 30 38 27 34 27" stroke={accent} strokeWidth="2" strokeLinecap="round" opacity="0.6" fill="none" />
</svg>
);
}
if (id === 2) {
return (
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%', height: '100%' }}>
<circle cx="24" cy="24" r="20" fill={accent} opacity="0.15" />
<rect x="10" y="28" width="6" height="10" rx="2" fill={accent} opacity="0.5" />
<rect x="19" y="22" width="6" height="16" rx="2" fill={accent} opacity="0.7" />
<rect x="28" y="16" width="6" height="22" rx="2" fill={accent} />
<path d="M12 26 L21 18 L30 12 L36 10" stroke={accent} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" opacity="0.8" fill="none" />
<circle cx="36" cy="10" r="2.5" fill={accent} />
</svg>
);
}
if (id === 3) {
return (
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%', height: '100%' }}>
<circle cx="24" cy="24" r="20" fill={accent} opacity="0.15" />
<path d="M14 14 L34 34" stroke={accent} strokeWidth="2.2" strokeLinecap="round" opacity="0.4" fill="none" />
<circle cx="16" cy="16" r="5" stroke={accent} strokeWidth="2.5" fill="none" />
<circle cx="32" cy="32" r="5" stroke={accent} strokeWidth="2.5" fill="none" />
<path d="M30 14 L34 14 L34 18" stroke={accent} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
<path d="M18 34 L14 34 L14 30" stroke={accent} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
<circle cx="16" cy="16" r="2" fill={accent} />
<circle cx="32" cy="32" r="2" fill={accent} />
</svg>
);
}
return (
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%', height: '100%' }}>
<circle cx="24" cy="24" r="20" fill={accent} opacity="0.15" />
<path d="M24 36 C24 36 10 28 10 19 C10 14.6 13.6 11 18 11 C20.6 11 22.9 12.3 24 14.3 C25.1 12.3 27.4 11 30 11 C34.4 11 38 14.6 38 19 C38 28 24 36 24 36Z" stroke={accent} strokeWidth="2.5" fill={accent} fillOpacity="0.12" strokeLinejoin="round" />
<circle cx="36" cy="34" r="6" fill={accent} />
<path d="M33 34 L35 36 L39 32" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
</svg>
);
}
/* Real product-preview visual per product — a believable mini-UI, not generic decoration */
function ProductVisual({ product }: { product: Product }) {
const { id, accent, accentRgb } = product;
// 1 — Behavision: footfall heatmap + live counter
if (id === 1) {
const cells = Array.from({ length: 48 });
return (
<div style={{ width: '86%', display: 'flex', flexDirection: 'column', gap: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', fontFamily: 'Manrope, sans-serif', fontWeight: 600 }}>Live footfall</span>
<span style={{ fontSize: 11, color: accent, fontFamily: 'Manrope, sans-serif', fontWeight: 700, display: 'flex', alignItems: 'center', gap: 5 }}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#4ade80', display: 'block' }} />2,847
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(8,1fr)', gap: 5 }}>
{cells.map((_, i) => {
const intensity = Math.abs(Math.sin(i * 1.7) * Math.cos(i * 0.6));
const alpha = (0.08 + intensity * 0.7).toFixed(3);
return <div key={i} style={{ aspectRatio: '1 / 1', borderRadius: 5, background: `rgba(${accentRgb}, ${alpha})` }} />;
})}
</div>
<div style={{ display: 'flex', gap: 10 }}>
{[['Peak', '68 PM'], ['Dwell', '14 min'], ['Zones', '12']].map(([k, v]) => (
<div key={k} style={{ flex: 1, padding: '10px 12px', borderRadius: 12, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)' }}>
<div style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)', fontFamily: 'Manrope, sans-serif' }}>{k}</div>
<div style={{ fontSize: 15, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', marginTop: 2 }}>{v}</div>
</div>
))}
</div>
</div>
);
}
// 2 — SpendSense: spend trend chart
if (id === 2) {
const pts = [22, 30, 26, 42, 38, 54, 48, 66, 72];
const max = 80;
const w = 280, h = 120;
const path = pts.map((p, i) => `${(i / (pts.length - 1)) * w},${h - (p / max) * h}`).join(' ');
return (
<div style={{ width: '86%', display: 'flex', flexDirection: 'column', gap: 16 }}>
<div>
<div style={{ fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', fontFamily: 'Manrope, sans-serif', fontWeight: 600 }}>Avg basket value</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginTop: 4 }}>
<span style={{ fontSize: 34, fontWeight: 900, color: '#fff', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em' }}>1,240</span>
<span style={{ fontSize: 13, fontWeight: 700, color: accent, fontFamily: 'Manrope, sans-serif' }}>+18%</span>
</div>
</div>
<svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height: 'auto', overflow: 'visible' }}>
<defs>
<linearGradient id={`sg${id}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={accent} stopOpacity="0.35" />
<stop offset="100%" stopColor={accent} stopOpacity="0" />
</linearGradient>
</defs>
<polygon points={`0,${h} ${path} ${w},${h}`} fill={`url(#sg${id})`} />
<polyline points={path} fill="none" stroke={accent} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
{pts.map((p, i) => <circle key={i} cx={(i / (pts.length - 1)) * w} cy={h - (p / max) * h} r={i === pts.length - 1 ? 4 : 0} fill={accent} />)}
</svg>
</div>
);
}
// 3 — Dyscount: smart offer + conversion uplift
if (id === 3) {
return (
<div style={{ width: '82%', display: 'flex', flexDirection: 'column', gap: 14 }}>
<div style={{ borderRadius: 16, padding: '18px 20px', background: `linear-gradient(135deg, ${accent} 0%, #c0392b 100%)`, position: 'relative', overflow: 'hidden' }}>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.6)', fontFamily: 'Manrope, sans-serif' }}>Smart Offer</div>
<div style={{ fontSize: 30, fontWeight: 900, color: '#fff', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.02em', marginTop: 2 }}>15% OFF</div>
<div style={{ fontSize: 12, color: 'rgba(255,255,255,0.85)', fontFamily: 'Manrope, sans-serif', marginTop: 2 }}>Targeted · high-intent shoppers</div>
</div>
<div style={{ padding: '14px 16px', borderRadius: 14, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.5)', fontFamily: 'Manrope, sans-serif' }}>Conversion uplift</span>
<span style={{ fontSize: 13, fontWeight: 800, color: accent, fontFamily: 'Sora, sans-serif' }}>+41%</span>
</div>
<div style={{ height: 6, borderRadius: 3, background: 'rgba(255,255,255,0.08)', overflow: 'hidden' }}>
<div style={{ height: '100%', width: '72%', borderRadius: 3, background: accent }} />
</div>
<div style={{ fontSize: 11, color: 'rgba(255,255,255,0.35)', fontFamily: 'Manrope, sans-serif', marginTop: 10 }}>Margin protected · 0 waste</div>
</div>
</div>
);
}
// 4 — Loyaly App: rewards card
return (
<div style={{ width: '80%', borderRadius: 22, padding: 22, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.1)', display: 'flex', flexDirection: 'column', gap: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 12, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif' }}>Loyaly </span>
<span style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)', fontFamily: 'Manrope, sans-serif' }}>GOLD MEMBER</span>
</div>
<div style={{ borderRadius: 16, padding: '18px 20px', background: `linear-gradient(135deg, ${accent} 0%, #e09820 100%)` }}>
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.6)', fontFamily: 'Manrope, sans-serif' }}>Reward Points</div>
<div style={{ fontSize: 32, fontWeight: 900, color: '#111', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em', marginTop: 2 }}>284</div>
</div>
{[['Starbucks', '+120'], ['Nike Store', '+85']].map(([s, p]) => (
<div key={s} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', borderRadius: 10, background: 'rgba(255,255,255,0.04)' }}>
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', fontFamily: 'Manrope, sans-serif' }}>{s}</span>
<span style={{ fontSize: 12, fontWeight: 700, color: '#4ade80', fontFamily: 'Manrope, sans-serif' }}>{p}</span>
</div>
))}
</div>
);
}
/* ── Gallery card (horizontal panel) ──────────────────────────────── */
function ProductCard({ product, index }: { product: Product; index: number }) {
return (
<article
className="products-card"
data-index={index}
style={{
borderRadius: '28px',
background: 'linear-gradient(145deg, #0e0e13 0%, #16161d 100%)',
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.05)`,
border: `1px solid rgba(${product.accentRgb}, 0.16)`,
display: 'flex',
flexDirection: 'row',
overflow: 'hidden',
scrollSnapAlign: 'center',
}}
>
{/* ── Left content ── */}
<div className="products-card-content" style={{
flex: '0 0 54%',
padding: 'clamp(22px, 2.6vw, 46px)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
gap: 14,
minWidth: 0,
}}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(11px, 1.5vh, 18px)' }}>
<div style={{
width: 48, height: 48, borderRadius: 14, padding: 10,
background: `rgba(${product.accentRgb}, 0.1)`,
border: `1px solid rgba(${product.accentRgb}, 0.22)`,
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06)',
flexShrink: 0,
}}>
<ProductIcon id={product.id} accent={product.accent} />
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
<span style={{ fontSize: 13, fontWeight: 800, color: product.accent, fontFamily: 'Sora, sans-serif', letterSpacing: '0.05em' }}>0{product.id}</span>
<span style={{ width: 24, height: 1, background: `rgba(${product.accentRgb},0.4)`, display: 'block' }} />
<span style={{ fontSize: '0.68rem', fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: product.accent, fontFamily: 'Manrope, sans-serif' }}>{product.subtitle}</span>
</div>
<h3 style={{
margin: 0,
fontSize: 'clamp(1.7rem, 2.8vw, 3rem)',
fontWeight: 900, lineHeight: 0.98, letterSpacing: '-0.04em',
color: '#ffffff', fontFamily: 'Sora, sans-serif',
}}>{product.title}</h3>
<p style={{
margin: 0,
fontSize: 'clamp(0.82rem, 0.95vw, 0.98rem)',
lineHeight: 1.6, color: 'rgba(255,255,255,0.5)',
maxWidth: '420px', fontFamily: 'Manrope, sans-serif',
}}>{product.description}</p>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 'clamp(11px, 1.5vh, 16px)' }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{product.tags.map((tag) => (
<span key={tag} style={{
padding: '5px 13px', borderRadius: '99px', fontSize: '0.7rem', fontWeight: 600,
color: 'rgba(255,255,255,0.7)', background: 'rgba(255,255,255,0.05)',
border: '1px solid rgba(255,255,255,0.1)', fontFamily: 'Manrope, sans-serif',
}}>{tag}</span>
))}
</div>
<Link
href={product.href}
style={{
display: 'inline-flex', alignItems: 'center', gap: '8px', width: 'max-content',
padding: '11px 22px', borderRadius: '99px', fontSize: '0.85rem', fontWeight: 800,
textDecoration: 'none', color: '#111111', background: product.accent,
fontFamily: 'Manrope, sans-serif', boxShadow: `0 12px 36px rgba(${product.accentRgb},0.25)`,
transition: 'transform 0.3s cubic-bezier(0.34,1.56,0.64,1)',
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLAnchorElement).style.transform = 'scale(1.04)'; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLAnchorElement).style.transform = 'scale(1)'; }}
>
Explore {product.title}
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '15px', height: '15px' }}>
<path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Link>
</div>
</div>
{/* ── Right visual panel ── */}
<div className="products-card-visual" style={{
flex: '0 0 46%',
position: 'relative', overflow: 'hidden',
display: 'flex', alignItems: 'center', justifyContent: 'center',
borderLeft: `1px solid rgba(${product.accentRgb}, 0.1)`,
background: `radial-gradient(ellipse 90% 80% at 60% 40%, rgba(${product.accentRgb},0.08) 0%, transparent 70%)`,
}}>
<div style={{
position: 'absolute', inset: 0,
backgroundImage: `linear-gradient(rgba(${product.accentRgb},0.05) 1px,transparent 1px),linear-gradient(90deg,rgba(${product.accentRgb},0.05) 1px,transparent 1px)`,
backgroundSize: '40px 40px',
maskImage: 'radial-gradient(ellipse 70% 70% at 50% 50%, #000 40%, transparent 85%)',
WebkitMaskImage: 'radial-gradient(ellipse 70% 70% at 50% 50%, #000 40%, transparent 85%)',
}} />
<ProductVisual product={product} />
</div>
</article>
);
}
interface ProductsSectionProps {
isLoaded?: boolean;
}
export default function ProductsSection({ isLoaded = false }: ProductsSectionProps) {
const sectionRef = useRef<HTMLDivElement>(null);
const trackRef = useRef<HTMLDivElement>(null);
const progressRef = useRef<HTMLDivElement>(null);
const counterRef = useRef<HTMLSpanElement>(null);
useIsomorphicLayoutEffect(() => {
if (!isLoaded) return;
if (typeof window === 'undefined') return;
const section = sectionRef.current;
const track = trackRef.current;
if (!section || !track) return;
const mm = gsap.matchMedia();
// Desktop / tablet: pinned horizontal scroll
mm.add('(min-width: 901px)', () => {
const distance = () => Math.max(0, track.scrollWidth - section.clientWidth);
const tween = gsap.to(track, {
x: () => -distance(),
ease: 'none',
scrollTrigger: {
trigger: section,
start: 'top top',
end: () => `+=${distance()}`,
pin: true,
pinType: 'transform',
pinSpacing: true,
anticipatePin: 1,
scrub: 1,
invalidateOnRefresh: true,
refreshPriority: 5,
onUpdate: (self) => {
if (progressRef.current) progressRef.current.style.transform = `scaleX(${self.progress})`;
if (counterRef.current) {
const idx = Math.min(PRODUCTS.length, Math.max(1, Math.round(self.progress * (PRODUCTS.length - 1)) + 1));
counterRef.current.textContent = `0${idx}`;
}
},
},
});
ScrollTrigger.refresh();
return () => { tween.scrollTrigger?.kill(); tween.kill(); gsap.set(track, { clearProps: 'transform' }); };
});
return () => mm.revert();
}, [isLoaded]);
return (
<section
ref={sectionRef}
id="products-section"
className="products-section-root"
style={{ position: 'relative', zIndex: 3 }}
>
{/* ambient glow */}
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 80% 60% at 50% 0%, rgba(244, 190, 40, 0.07) 0%, transparent 70%)', pointerEvents: 'none' }} />
{/* Heading (fixed within the pinned viewport) */}
<div className="products-heading">
<div style={{ maxWidth: 480 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
<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: 'Manrope, 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' }}>
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>
</div>
<div className="products-heading-hint" style={{ maxWidth: 280, textAlign: 'right' }}>
<p style={{ margin: '0 0 14px', fontSize: 'clamp(0.85rem, 1vw, 1rem)', lineHeight: 1.6, color: 'rgba(0,0,0,0.45)', fontFamily: 'Manrope, sans-serif' }}>
Powering every stage of the retail journey with intelligent AI solutions.
</p>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.32)', fontFamily: 'Manrope, sans-serif' }}>
Scroll to explore
<svg viewBox="0 0 24 16" fill="none" width="22" height="14">
<path d="M2 8h18M14 3l5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
{/* Horizontal track */}
<div ref={trackRef} className="products-track">
{PRODUCTS.map((product, index) => (
<ProductCard key={product.id} product={product} index={index} />
))}
</div>
{/* Progress bar + counter */}
<div className="products-progress-wrap">
<span ref={counterRef} style={{ fontSize: 12, fontWeight: 800, color: '#C49600', fontFamily: 'Sora, sans-serif', letterSpacing: '0.08em', minWidth: 22 }}>01</span>
<div style={{ position: 'relative', flex: 1, height: 3, borderRadius: 99, background: 'rgba(0,0,0,0.08)', overflow: 'hidden' }}>
<div ref={progressRef} style={{ position: 'absolute', inset: 0, borderRadius: 99, background: 'linear-gradient(90deg,#f4be28,#FF9800)', transformOrigin: 'left center', transform: 'scaleX(0)' }} />
</div>
<span style={{ fontSize: 12, fontWeight: 800, color: 'rgba(0,0,0,0.3)', fontFamily: 'Sora, sans-serif', letterSpacing: '0.08em' }}>0{PRODUCTS.length}</span>
</div>
<style>{`
@keyframes prodShimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
.products-section-root {
--pt: clamp(230px, 28vh, 310px);
--pb: clamp(64px, 8vh, 96px);
width: calc(100% - 40px);
margin: 20px 20px;
border-radius: 32px;
background: #FAFAFA;
height: 100vh;
overflow: hidden;
}
.products-card {
flex: 0 0 auto;
width: clamp(320px, 74vw, 880px);
/* Fill the band between heading and progress bar, capped on huge screens */
height: min(740px, calc(100vh - var(--pt) - var(--pb)));
}
.products-heading {
position: absolute; top: 0; left: 0; right: 0; z-index: 20;
display: flex; align-items: flex-end; justify-content: space-between;
padding: clamp(32px, 5vh, 56px) clamp(24px, 5vw, 68px) 0;
box-sizing: border-box; pointer-events: none;
}
.products-heading a { pointer-events: auto; }
.products-track {
position: absolute; top: 0; left: 0; height: 100%;
display: flex; align-items: center;
gap: clamp(20px, 2.4vw, 40px);
padding-left: clamp(24px, 5vw, 68px);
padding-right: clamp(24px, 5vw, 68px);
padding-top: var(--pt);
padding-bottom: var(--pb);
box-sizing: border-box;
will-change: transform;
}
.products-progress-wrap {
position: absolute; bottom: clamp(24px, 4vh, 44px);
left: 50%; transform: translateX(-50%); z-index: 20;
width: min(420px, 70vw);
display: flex; align-items: center; gap: 14px;
}
/* Mobile / small tablet: native horizontal swipe, no pin */
@media (max-width: 900px) {
.products-section-root {
height: auto;
overflow: visible;
padding-bottom: 28px;
}
.products-heading {
position: relative;
flex-direction: column; align-items: flex-start; gap: 16px;
padding: clamp(40px, 8vw, 64px) 24px 24px;
}
.products-heading-hint { text-align: left !important; }
.products-track {
position: relative; height: auto;
padding-top: 8px; padding-bottom: 8px;
overflow-x: auto; overflow-y: hidden;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
}
.products-track::-webkit-scrollbar { display: none; }
.products-track { scrollbar-width: none; }
.products-card { height: 560px; width: min(86vw, 460px); }
.products-card { flex-direction: column !important; }
.products-card-content { flex: 1 1 auto !important; }
.products-card-visual { flex: 0 0 200px !important; border-left: none !important; border-top: 1px solid rgba(255,255,255,0.08); }
.products-progress-wrap { position: relative; bottom: auto; transform: none; left: auto; margin: 20px auto 0; }
}
`}</style>
</section>
);
}