Update homepage, use-cases, and people pages; add preloader and new section components
This commit is contained in:
@@ -60,10 +60,10 @@ const PRODUCTS: Product[] = [
|
||||
{
|
||||
id: 4,
|
||||
href: '/solutions/loyaly-app',
|
||||
title: 'Loyaly App',
|
||||
title: 'Identiq',
|
||||
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.',
|
||||
'Earn Loyaly Coins through daily logins and games, then redeem rewards at real offline stores.',
|
||||
accent: '#f4be28',
|
||||
accentRgb: '244, 190, 40',
|
||||
tags: ['Daily Rewards', 'Game Based Offers', 'Offline Redemption'],
|
||||
@@ -132,14 +132,23 @@ function ProductVisual({ product }: { product: Product }) {
|
||||
<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: 'Sora, sans-serif', fontWeight: 600 }}>Live footfall</span>
|
||||
<span style={{ fontSize: 11, color: accent, fontFamily: 'Sora, 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 className="pv-live-dot" 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})` }} />;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="pv-cell"
|
||||
style={{
|
||||
aspectRatio: '1 / 1', borderRadius: 5, background: `rgba(${accentRgb}, ${alpha})`,
|
||||
animationDelay: `${(i % 8) * 0.12 + Math.floor(i / 8) * 0.07}s`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 10 }}>
|
||||
@@ -177,8 +186,17 @@ function ProductVisual({ product }: { product: Product }) {
|
||||
</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} />)}
|
||||
<polyline className="pv-chart-line" points={path} fill="none" stroke={accent} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" pathLength={100} />
|
||||
{pts.map((p, i) => (
|
||||
i === pts.length - 1
|
||||
? (
|
||||
<g key={i}>
|
||||
<circle className="pv-chart-pulse-ring" cx={(i / (pts.length - 1)) * w} cy={h - (p / max) * h} r={4} fill="none" stroke={accent} strokeWidth="2" />
|
||||
<circle cx={(i / (pts.length - 1)) * w} cy={h - (p / max) * h} r={4} fill={accent} />
|
||||
</g>
|
||||
)
|
||||
: null
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
@@ -187,43 +205,115 @@ function ProductVisual({ product }: { product: Product }) {
|
||||
// 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: 'Sora, 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: 'Sora, sans-serif', marginTop: 2 }}>Targeted · high-intent shoppers</div>
|
||||
<div style={{ width: '82%', position: 'relative', display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Live campaign badge, floating on the card edge */}
|
||||
<div className="pv-streak-badge" style={{ position: 'absolute', top: -34, right: 18, display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', borderRadius: 99, background: '#171512', border: `1px solid ${accent}55`, boxShadow: '0 8px 20px -8px rgba(0,0,0,0.6)' }}>
|
||||
<span className="pv-live-dot" style={{ width: 6, height: 6, borderRadius: '50%', background: accent, display: 'block' }} />
|
||||
<span style={{ fontSize: 11, fontWeight: 800, color: accent, fontFamily: 'Sora, sans-serif' }}>Live campaign</span>
|
||||
</div>
|
||||
|
||||
{/* Coupon-style offer card with perforated divider */}
|
||||
<div style={{ borderRadius: 16, background: `linear-gradient(135deg, ${accent} 0%, #c0392b 100%)`, position: 'relative', overflow: 'hidden', display: 'flex' }}>
|
||||
<div className="pv-sheen" />
|
||||
<div style={{ flex: 1, padding: '16px 18px' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.6)', fontFamily: 'Sora, 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: 'Sora, sans-serif', marginTop: 2 }}>Targeted · high-intent shoppers</div>
|
||||
</div>
|
||||
{/* Perforated tear-off edge */}
|
||||
<div style={{ position: 'relative', width: 0, borderLeft: '2px dashed rgba(0,0,0,0.25)' }}>
|
||||
<span style={{ position: 'absolute', top: -8, left: -7, width: 14, height: 14, borderRadius: '50%', background: '#171512' }} />
|
||||
<span style={{ position: 'absolute', bottom: -8, left: -7, width: 14, height: 14, borderRadius: '50%', background: '#171512' }} />
|
||||
</div>
|
||||
<div style={{ width: 64, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: 11, fontWeight: 800, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif', writingMode: 'vertical-rl', letterSpacing: '0.1em' }}>DYSCOUNT</span>
|
||||
</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: 'Sora, sans-serif' }}>Conversion uplift</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 800, color: accent, fontFamily: 'Sora, sans-serif' }}>+41%</span>
|
||||
<span className="pv-uplift-pulse" 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 className="pv-progress-fill" style={{ height: '100%', width: '72%', borderRadius: 3, background: accent }} />
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: 'rgba(255,255,255,0.35)', fontFamily: 'Sora, sans-serif', marginTop: 10 }}>Margin protected · 0 waste</div>
|
||||
</div>
|
||||
|
||||
{/* Mini stat grid */}
|
||||
<div style={{ display: 'flex', gap: 10 }}>
|
||||
{[['Margin protected', '100%'], ['Waste', '0'], ['Redemptions', '1.2k']].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: 'Sora, sans-serif' }}>{k}</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', marginTop: 2 }}>{v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 4 — Loyaly App: rewards card
|
||||
const REDEMPTIONS: [string, string, string][] = [
|
||||
['Starbucks', '+120', '#1e6b52'],
|
||||
['Nike Store', '+85', '#374151'],
|
||||
];
|
||||
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={{ width: '82%', borderRadius: 24, padding: 22, background: 'linear-gradient(165deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.02) 100%)', border: '1px solid rgba(255,255,255,0.1)', boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06)', position: 'relative', display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
{/* Streak badge, floating on the card edge */}
|
||||
<div className="pv-streak-badge" style={{ position: 'absolute', top: -14, right: 18, display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', borderRadius: 99, background: '#171512', border: `1px solid ${accent}55`, boxShadow: '0 8px 20px -8px rgba(0,0,0,0.6)' }}>
|
||||
<span style={{ fontSize: 12 }}>🔥</span>
|
||||
<span style={{ fontSize: 11, fontWeight: 800, color: accent, fontFamily: 'Sora, sans-serif' }}>6-day streak</span>
|
||||
</div>
|
||||
|
||||
{/* Member header */}
|
||||
<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: 'Sora, 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: 'Sora, 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: 'Sora, sans-serif' }}>{s}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#4ade80', fontFamily: 'Sora, sans-serif' }}>{p}</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<span style={{
|
||||
width: 34, height: 34, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
background: `linear-gradient(135deg, ${accent} 0%, #e09820 100%)`, color: '#111', fontWeight: 800, fontSize: 13, fontFamily: 'Sora, sans-serif',
|
||||
}}>A</span>
|
||||
<div>
|
||||
<div style={{ fontSize: 13, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', lineHeight: 1.1 }}>Loyaly</div>
|
||||
<div style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif' }}>Member since 2024</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10, fontWeight: 800, letterSpacing: '0.06em', color: accent, background: `${accent}1a`, border: `1px solid ${accent}40`, padding: '5px 10px', borderRadius: 99, fontFamily: 'Sora, sans-serif' }}>
|
||||
✦ GOLD
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Points card with progress to next tier */}
|
||||
<div style={{ borderRadius: 18, padding: '18px 20px', background: `linear-gradient(135deg, ${accent} 0%, #e09820 100%)`, position: 'relative', overflow: 'hidden' }}>
|
||||
<div className="pv-sheen" />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.6)', fontFamily: 'Sora, 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>
|
||||
<span style={{ width: 34, height: 34, borderRadius: '50%', background: 'rgba(255,255,255,0.35)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 16 }}>🪙</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<div style={{ height: 5, borderRadius: 3, background: 'rgba(0,0,0,0.15)', overflow: 'hidden' }}>
|
||||
<div className="pv-progress-fill" style={{ height: '100%', width: '68%', borderRadius: 3, background: '#111' }} />
|
||||
</div>
|
||||
<div style={{ fontSize: 10, color: 'rgba(0,0,0,0.55)', fontFamily: 'Sora, sans-serif', marginTop: 6, fontWeight: 600 }}>116 pts to Platinum</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent redemptions */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)', fontFamily: 'Sora, sans-serif' }}>Recently redeemed</span>
|
||||
{REDEMPTIONS.map(([s, p, dot], i) => (
|
||||
<div key={s} className="pv-row" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '9px 12px', borderRadius: 12, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)', animationDelay: `${i * 0.9}s` }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
|
||||
<span style={{ width: 8, height: 8, borderRadius: '50%', background: dot, flexShrink: 0 }} />
|
||||
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.75)', fontFamily: 'Sora, sans-serif', fontWeight: 600 }}>{s}</span>
|
||||
</div>
|
||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#4ade80', fontFamily: 'Sora, sans-serif' }}>{p}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -405,10 +495,12 @@ export default function ProductsSection({ isLoaded = false }: ProductsSectionPro
|
||||
|
||||
{/* 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: 'Sora, sans-serif' }}>Platform</span>
|
||||
<div style={{ maxWidth: 520 }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 9, marginBottom: 18, padding: '6px 16px 6px 8px', borderRadius: 100, background: '#ffffff', border: '1px solid rgba(244,190,40,0.3)', boxShadow: '0 1px 2px rgba(0,0,0,0.03), 0 12px 30px -18px rgba(23,21,18,0.2)' }}>
|
||||
<span style={{ width: 24, height: 24, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(244,190,40,0.15)' }}>
|
||||
<img src="/images/loyalyai-logo.png" alt="" width={15} height={15} style={{ display: 'block', objectFit: 'contain' }} />
|
||||
</span>
|
||||
<span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '0.18em', textTransform: 'uppercase', color: '#8a5a14', fontFamily: 'Sora, sans-serif' }}>Platform</span>
|
||||
</div>
|
||||
<h2 style={{ margin: 0, fontSize: 'clamp(1.9rem, 3.6vw, 3.2rem)', fontWeight: 800, lineHeight: 0.94, letterSpacing: '-0.04em', color: '#111111', fontFamily: 'Sora, sans-serif' }}>
|
||||
Four products.<br />
|
||||
@@ -416,15 +508,13 @@ export default function ProductsSection({ isLoaded = false }: ProductsSectionPro
|
||||
</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: 'Sora, sans-serif' }}>
|
||||
<div className="products-heading-hint" style={{ maxWidth: 300, textAlign: 'right', borderRight: '2px solid rgba(244,190,40,0.3)', paddingRight: 'clamp(18px, 2.2vw, 28px)' }}>
|
||||
<p style={{ margin: '0 0 16px', fontSize: 'clamp(0.85rem, 1vw, 1rem)', lineHeight: 1.6, color: 'rgba(0,0,0,0.45)', fontFamily: 'Sora, 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: 'Sora, 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 style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 10 }}>
|
||||
<span style={{ fontFamily: 'Sora, sans-serif', fontWeight: 600, fontSize: '0.85rem', color: '#8a8072', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Built as one connected platform</span>
|
||||
<span style={{ fontFamily: 'Sora, sans-serif', fontWeight: 800, fontSize: '1.6rem', color: '#f4be28', letterSpacing: '-0.02em' }}>04</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -448,6 +538,72 @@ export default function ProductsSection({ isLoaded = false }: ProductsSectionPro
|
||||
<style>{`
|
||||
@keyframes prodShimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
|
||||
|
||||
/* ── Product-visual loop animations ── */
|
||||
.pv-cell { animation: pvCellPulse 3.6s ease-in-out infinite; }
|
||||
@keyframes pvCellPulse {
|
||||
0%, 100% { opacity: 0.55; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.06); }
|
||||
}
|
||||
.pv-live-dot { animation: pvLiveDot 1.8s ease-in-out infinite; }
|
||||
@keyframes pvLiveDot {
|
||||
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74,222,128,0.5); }
|
||||
50% { opacity: 0.6; box-shadow: 0 0 0 4px rgba(74,222,128,0); }
|
||||
}
|
||||
.pv-chart-line {
|
||||
stroke-dasharray: 100;
|
||||
stroke-dashoffset: 100;
|
||||
animation: pvDrawLine 3s ease-out infinite;
|
||||
}
|
||||
@keyframes pvDrawLine {
|
||||
0% { stroke-dashoffset: 100; }
|
||||
55%, 100% { stroke-dashoffset: 0; }
|
||||
}
|
||||
.pv-chart-pulse-ring {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
animation: pvRingPulse 2.4s ease-out infinite;
|
||||
}
|
||||
@keyframes pvRingPulse {
|
||||
0% { opacity: 0.9; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(2.6); }
|
||||
}
|
||||
.pv-uplift-pulse { display: inline-block; animation: pvUpliftPulse 2.6s ease-in-out infinite; }
|
||||
@keyframes pvUpliftPulse {
|
||||
0%, 100% { opacity: 0.75; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
.pv-progress-fill { position: relative; animation: pvProgressGrow 3.2s ease-in-out infinite; transform-origin: left center; }
|
||||
@keyframes pvProgressGrow {
|
||||
0%, 100% { transform: scaleX(0.94); }
|
||||
50% { transform: scaleX(1); }
|
||||
}
|
||||
.pv-sheen {
|
||||
position: absolute; inset: 0;
|
||||
background: linear-gradient(115deg, transparent 30%, rgba(255,255,255,0.35) 48%, transparent 66%);
|
||||
background-size: 200% 100%;
|
||||
animation: pvSheenSweep 4.5s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes pvSheenSweep {
|
||||
0% { background-position: 160% 0; }
|
||||
60%, 100% { background-position: -60% 0; }
|
||||
}
|
||||
.pv-row { animation: pvRowGlow 3.6s ease-in-out infinite; }
|
||||
@keyframes pvRowGlow {
|
||||
0%, 100% { background: rgba(255,255,255,0.04); }
|
||||
50% { background: rgba(74,222,128,0.08); }
|
||||
}
|
||||
.pv-streak-badge { animation: pvStreakFloat 2.8s ease-in-out infinite; }
|
||||
@keyframes pvStreakFloat {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-3px); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.pv-cell, .pv-live-dot, .pv-chart-line, .pv-chart-pulse-ring, .pv-uplift-pulse, .pv-progress-fill, .pv-sheen, .pv-row, .pv-streak-badge {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.products-section-root {
|
||||
--pt: clamp(230px, 28vh, 310px);
|
||||
--pb: clamp(64px, 8vh, 96px);
|
||||
|
||||
Reference in New Issue
Block a user