Update homepage, use-cases, and people pages; add preloader and new section components
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import React, { useLayoutEffect, useEffect, useRef, useState } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { DEFAULT_EASE, DEFAULT_DURATION } from '@/utils/animation';
|
||||
@@ -13,17 +13,17 @@ if (typeof window !== 'undefined') {
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
interface Stage {
|
||||
num: string;
|
||||
title: string;
|
||||
description: string;
|
||||
tagline: string;
|
||||
detail: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const STAGES: Stage[] = [
|
||||
{
|
||||
num: '01',
|
||||
title: 'Understand',
|
||||
description: 'Track store footfall, customer behavior, and real-time engagement with AI, giving merchants the intelligence to run smarter campaigns and targeted offers that actually convert.',
|
||||
tagline: 'See exactly who walks in, when, and why they buy.',
|
||||
detail: 'Real-time footfall & behavior insights',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
@@ -32,9 +32,9 @@ const STAGES: Stage[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
num: '02',
|
||||
title: 'Engage',
|
||||
description: 'Customers earn Loyaly Coins through daily login, 5,000-step walking challenges, and interactive games, including Maze, Spin Wheel, Scratch Card, and Match Master, keeping them engaged every day.',
|
||||
tagline: 'Playable daily rewards turn one visit into a habit.',
|
||||
detail: 'Daily streaks, challenges & mini games',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
||||
@@ -42,9 +42,9 @@ const STAGES: Stage[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
num: '03',
|
||||
title: 'Convert',
|
||||
description: 'Merchants create game based discount campaigns where customers play once to reveal their reward, turning product discovery into an exciting, high-conversion in-store experience.',
|
||||
tagline: 'Customers play to unlock the deal, then buy on the spot.',
|
||||
detail: 'Higher-converting, game-based offers',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
@@ -52,9 +52,9 @@ const STAGES: Stage[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
num: '04',
|
||||
title: 'Retain',
|
||||
description: 'Daily rewards, surprise coupons, and game based offers keep customers returning to earn, play, and redeem, creating a self reinforcing loyalty loop that compounds value with every visit.',
|
||||
tagline: 'Every redemption resets the loop for the next visit.',
|
||||
detail: 'More repeat visits, compounding loyalty',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
@@ -75,6 +75,7 @@ function StageCard({
|
||||
onHover: (i: number | null) => void;
|
||||
}) {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const iconRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const el = cardRef.current;
|
||||
@@ -82,133 +83,50 @@ function StageCard({
|
||||
const r = el.getBoundingClientRect();
|
||||
const px = (e.clientX - r.left) / r.width - 0.5;
|
||||
const py = (e.clientY - r.top) / r.height - 0.5;
|
||||
el.style.setProperty('--rx', `${(-py * 6).toFixed(2)}deg`);
|
||||
el.style.setProperty('--ry', `${(px * 8).toFixed(2)}deg`);
|
||||
el.style.setProperty('--mx', `${((px + 0.5) * 100).toFixed(1)}%`);
|
||||
el.style.setProperty('--my', `${((py + 0.5) * 100).toFixed(1)}%`);
|
||||
gsap.to(el, { '--mx': `${((px + 0.5) * 100).toFixed(1)}%`, '--my': `${((py + 0.5) * 100).toFixed(1)}%`, duration: 0.3, overwrite: 'auto' } as gsap.TweenVars);
|
||||
};
|
||||
|
||||
const onMouseEnter = () => {
|
||||
onHover(index);
|
||||
if (iconRef.current) gsap.to(iconRef.current, { rotate: -8, scale: 1.08, duration: 0.4, ease: 'back.out(2)' });
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
const el = cardRef.current;
|
||||
if (!el) return;
|
||||
el.style.setProperty('--rx', '0deg');
|
||||
el.style.setProperty('--ry', '0deg');
|
||||
onHover(null);
|
||||
if (iconRef.current) gsap.to(iconRef.current, { rotate: 0, scale: 1, duration: 0.4, ease: 'power3.out' });
|
||||
};
|
||||
|
||||
const isYellow = index % 2 === 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
data-stage-card={index}
|
||||
className={`wwd-card relative group ${active ? 'wwd-card-active' : ''}`}
|
||||
className={`wwd-card relative group ${isYellow ? 'wwd-card-yellow' : 'wwd-card-white'} ${active ? 'wwd-card-active' : ''}`}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseEnter={() => onHover(index)}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
style={{
|
||||
position: 'relative',
|
||||
transform: 'perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateY(var(--cascade, 0px))',
|
||||
transformStyle: 'preserve-3d',
|
||||
transition: 'transform 0.45s cubic-bezier(0.22,1,0.36,1), box-shadow 0.45s ease, border-color 0.45s ease',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="wwd-card-inner"
|
||||
style={{
|
||||
position: 'relative',
|
||||
background: 'rgba(255,255,255,0.025)',
|
||||
backdropFilter: 'blur(24px) saturate(160%)',
|
||||
WebkitBackdropFilter: 'blur(24px) saturate(160%)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius: 26,
|
||||
padding: 'clamp(28px, 2.4vw, 38px)',
|
||||
overflow: 'hidden',
|
||||
cursor: 'default',
|
||||
height: '100%',
|
||||
boxShadow: active
|
||||
? '0 32px 70px -20px rgba(244,190,40,0.22), 0 0 0 1px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)'
|
||||
: 'inset 0 1px 0 rgba(255,255,255,0.05)',
|
||||
borderColor: active ? 'rgba(244,190,40,0.32)' : 'rgba(255,255,255,0.08)',
|
||||
}}
|
||||
>
|
||||
<div className="wwd-card-inner">
|
||||
{/* Cursor-following glow */}
|
||||
<div
|
||||
className="wwd-card-glow"
|
||||
style={{
|
||||
position: 'absolute', inset: 0, pointerEvents: 'none', opacity: active ? 1 : 0,
|
||||
background: 'radial-gradient(280px circle at var(--mx,50%) var(--my,50%), rgba(244,190,40,0.12), transparent 70%)',
|
||||
transition: 'opacity 0.4s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Giant editorial number, bleeding off the top edge */}
|
||||
<div style={{
|
||||
position: 'absolute', top: '-0.32em', right: '-0.06em',
|
||||
fontSize: 'clamp(6rem, 8vw, 9rem)', fontWeight: 900, lineHeight: 1,
|
||||
color: 'transparent',
|
||||
WebkitTextStroke: active ? '1px rgba(244,190,40,0.18)' : '1px rgba(244,190,40,0.07)',
|
||||
fontFamily: 'Sora, sans-serif',
|
||||
letterSpacing: '-0.06em',
|
||||
userSelect: 'none', pointerEvents: 'none',
|
||||
transition: 'all 0.45s ease',
|
||||
}}>
|
||||
{stage.num}
|
||||
</div>
|
||||
|
||||
{/* Step rail dot */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 22,
|
||||
position: 'relative', zIndex: 2,
|
||||
}}>
|
||||
<span style={{
|
||||
fontSize: 11, fontWeight: 800, letterSpacing: '0.18em', textTransform: 'uppercase',
|
||||
color: active ? '#f4be28' : 'rgba(255,255,255,0.32)',
|
||||
fontFamily: 'Sora, sans-serif', transition: 'color 0.4s ease',
|
||||
}}>
|
||||
Stage {stage.num}
|
||||
</span>
|
||||
<span style={{ flex: 1, height: 1, background: active ? 'rgba(244,190,40,0.35)' : 'rgba(255,255,255,0.08)', transition: 'background 0.4s ease' }} />
|
||||
</div>
|
||||
<div className="wwd-card-glow" style={{ opacity: active ? 1 : 0 }} />
|
||||
|
||||
{/* Icon */}
|
||||
<div style={{
|
||||
width: 54, height: 54,
|
||||
borderRadius: 16,
|
||||
background: active ? 'rgba(244,190,40,0.16)' : 'rgba(244,190,40,0.1)',
|
||||
border: `1px solid rgba(244,190,40,${active ? 0.4 : 0.22})`,
|
||||
color: '#f4be28',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
marginBottom: 28,
|
||||
position: 'relative', zIndex: 2,
|
||||
boxShadow: active ? '0 0 28px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)' : 'inset 0 1px 0 rgba(255,255,255,0.08)',
|
||||
transform: active ? 'rotate(-6deg) scale(1.05)' : 'none',
|
||||
transition: 'all 0.45s cubic-bezier(0.22,1,0.36,1)',
|
||||
}}>
|
||||
<div ref={iconRef} className="wwd-icon">
|
||||
{stage.icon}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 style={{
|
||||
margin: '0 0 14px',
|
||||
fontSize: 'clamp(1.4rem, 2vw, 1.85rem)',
|
||||
fontWeight: 800, color: '#ffffff',
|
||||
lineHeight: 1.1, letterSpacing: '-0.03em',
|
||||
fontFamily: 'Sora, sans-serif',
|
||||
position: 'relative', zIndex: 2,
|
||||
}}>
|
||||
{stage.title}
|
||||
</h3>
|
||||
<h3 className="wwd-title">{stage.title}</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p style={{
|
||||
margin: 0,
|
||||
fontSize: '0.92rem',
|
||||
color: 'rgba(255,255,255,0.45)',
|
||||
lineHeight: 1.75,
|
||||
fontFamily: 'Sora, sans-serif',
|
||||
position: 'relative', zIndex: 2,
|
||||
}}>
|
||||
{stage.description}
|
||||
</p>
|
||||
{/* Tagline — single concise line */}
|
||||
<p className="wwd-tagline">{stage.tagline}</p>
|
||||
|
||||
{/* Detail pill — quick supporting fact */}
|
||||
<div className="wwd-detail">
|
||||
<span className="wwd-detail-dot" />
|
||||
{stage.detail}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -236,23 +154,41 @@ export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
'.wwd-card',
|
||||
{ opacity: 0, y: 70, scale: 0.94 },
|
||||
{
|
||||
opacity: 1, y: 0, scale: 1, duration: 0.9, stagger: 0.12, ease: 'power3.out',
|
||||
scrollTrigger: { trigger: '.wwd-grid-container', start: 'top 80%', toggleActions: 'play none none reset' },
|
||||
}
|
||||
);
|
||||
// Per-card reveal: each card gets its own scrubbed-in timeline
|
||||
// (clip-path wipe + rise), staggered across the row, with the
|
||||
// icon and copy cascading in right after.
|
||||
gsap.utils.toArray<HTMLElement>('.wwd-card').forEach((card, i) => {
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: { trigger: card, start: 'top 85%', toggleActions: 'play none none reset' },
|
||||
delay: i * 0.1,
|
||||
});
|
||||
|
||||
gsap.fromTo(
|
||||
'.wwd-rail-step',
|
||||
{ opacity: 0, y: 14 },
|
||||
{
|
||||
opacity: 1, y: 0, duration: 0.5, stagger: 0.08, ease: 'power3.out',
|
||||
scrollTrigger: { trigger: '.wwd-rail', start: 'top 85%', toggleActions: 'play none none reset' },
|
||||
}
|
||||
);
|
||||
tl.fromTo(card,
|
||||
{ opacity: 0, y: 70, scale: 0.94, clipPath: 'inset(0% 0% 100% 0% round 22px)' },
|
||||
{ opacity: 1, y: 0, scale: 1, clipPath: 'inset(0% 0% 0% 0% round 22px)', duration: 0.75, ease: 'power3.out' },
|
||||
0
|
||||
)
|
||||
.fromTo(card.querySelector('.wwd-icon'),
|
||||
{ opacity: 0, scale: 0.4, rotate: -20 },
|
||||
{ opacity: 1, scale: 1, rotate: 0, duration: 0.55, ease: 'back.out(2.2)' },
|
||||
0.3
|
||||
)
|
||||
.fromTo(card.querySelector('.wwd-title'),
|
||||
{ opacity: 0, y: 16 },
|
||||
{ opacity: 1, y: 0, duration: 0.45, ease: 'power2.out' },
|
||||
0.5
|
||||
)
|
||||
.fromTo(card.querySelector('.wwd-tagline'),
|
||||
{ opacity: 0, y: 12 },
|
||||
{ opacity: 1, y: 0, duration: 0.45, ease: 'power2.out' },
|
||||
0.58
|
||||
)
|
||||
.fromTo(card.querySelector('.wwd-detail'),
|
||||
{ opacity: 0, y: 10 },
|
||||
{ opacity: 1, y: 0, duration: 0.4, ease: 'power2.out' },
|
||||
0.66
|
||||
);
|
||||
});
|
||||
}, containerRef);
|
||||
|
||||
return () => ctx.revert();
|
||||
@@ -274,74 +210,98 @@ export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
|
||||
|
||||
{/* Header */}
|
||||
<div className="wwd-header relative z-10 text-center max-w-4xl mx-auto mb-16 sm:mb-20 flex flex-col items-center">
|
||||
<div className="wwd-header-el inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-white/10 bg-white/5 text-[#f4be28] text-xs font-bold tracking-wider uppercase mb-5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#f4be28] animate-pulse" />
|
||||
What We Do
|
||||
</div>
|
||||
<span className="wwd-header-el" style={{
|
||||
fontSize: 11, fontWeight: 800, letterSpacing: '0.2em', textTransform: 'uppercase',
|
||||
color: 'rgba(244,190,40,0.7)', fontFamily: 'Sora, sans-serif', marginBottom: 18,
|
||||
}}>
|
||||
One Platform
|
||||
</span>
|
||||
|
||||
<h2 className="wwd-header-el text-white font-black mb-6" style={{ fontSize: 'clamp(2.8rem, 6vw, 5.5rem)', letterSpacing: '-0.05em', lineHeight: 0.9, fontFamily: 'Sora, sans-serif' }}>
|
||||
Understand.<br />
|
||||
<span className="text-[#f4be28]">Engage. Convert.</span><br />
|
||||
Retain.
|
||||
<h2 className="wwd-header-el" style={{
|
||||
margin: 0,
|
||||
fontSize: 'clamp(2.6rem, 6vw, 5rem)',
|
||||
fontWeight: 800, color: '#fff',
|
||||
lineHeight: 0.98, letterSpacing: '-0.04em',
|
||||
fontFamily: 'Sora, sans-serif',
|
||||
}}>
|
||||
Every visit,<br />
|
||||
a <span className="wwd-shimmer">loyal customer.</span>
|
||||
</h2>
|
||||
|
||||
<p className="wwd-header-el text-gray-400 text-base sm:text-lg max-w-xl font-medium" style={{ fontFamily: 'Sora, sans-serif', lineHeight: 1.75 }}>
|
||||
Four stages. One platform. A smarter retail cycle that turns every walk-in into a loyal customer.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Step rail — desktop flow indicator synced to card hover, aligned to the card columns below */}
|
||||
<div className="wwd-rail relative z-10 max-w-6xl mx-auto mb-6 hidden lg:grid lg:grid-cols-4 gap-7">
|
||||
{STAGES.map((s, i) => (
|
||||
<button
|
||||
key={s.num}
|
||||
type="button"
|
||||
onMouseEnter={() => setActive(i)}
|
||||
onMouseLeave={() => setActive(null)}
|
||||
className="wwd-rail-step"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||
padding: '6px 14px', borderRadius: 100, border: '1px solid',
|
||||
borderColor: active === i ? 'rgba(244,190,40,0.4)' : 'rgba(255,255,255,0.1)',
|
||||
background: active === i ? 'rgba(244,190,40,0.1)' : 'rgba(255,255,255,0.03)',
|
||||
color: active === i ? '#f4be28' : 'rgba(255,255,255,0.4)',
|
||||
fontSize: 11, fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||
fontFamily: 'Sora, sans-serif', cursor: 'default',
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{s.num} · {s.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Cascading 4-up grid */}
|
||||
{/* 4-up grid */}
|
||||
<div className="relative z-10 wwd-grid-container max-w-6xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-7">
|
||||
{STAGES.map((stage, i) => (
|
||||
<div key={stage.num} className="wwd-cascade-item" style={{ ['--cascade-offset' as string]: `${(i % 2 === 1 ? 32 : 0)}px` }}>
|
||||
<StageCard stage={stage} index={i} active={active === i} onHover={setActive} />
|
||||
</div>
|
||||
<StageCard key={stage.title} stage={stage} index={i} active={active === i} onHover={setActive} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@media (min-width: 1024px) {
|
||||
.wwd-cascade-item:nth-child(2n) .wwd-card { --cascade: 36px; }
|
||||
.wwd-card {
|
||||
position: relative;
|
||||
transform: perspective(1100px) rotateX(0deg) rotateY(0deg);
|
||||
transition: transform 0.45s cubic-bezier(0.22,1,0.36,1);
|
||||
}
|
||||
.wwd-card-inner::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0; border-radius: inherit; padding: 1px;
|
||||
background: linear-gradient(135deg, rgba(244,190,40,0.5), transparent 40%, transparent 60%, rgba(244,190,40,0.25));
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
opacity: 0;
|
||||
.wwd-card-inner {
|
||||
position: relative;
|
||||
border-radius: 22px;
|
||||
padding: clamp(24px, 2vw, 30px);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
transition: transform 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s ease;
|
||||
}
|
||||
.wwd-card-white .wwd-card-inner { background: #ffffff; }
|
||||
.wwd-card-yellow .wwd-card-inner { background: #F4C21A; }
|
||||
.wwd-card-active .wwd-card-inner {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 24px 50px -18px rgba(0,0,0,0.35);
|
||||
}
|
||||
.wwd-card-glow {
|
||||
position: absolute; inset: 0; pointer-events: none;
|
||||
background: radial-gradient(260px circle at var(--mx,50%) var(--my,50%), rgba(0,0,0,0.06), transparent 70%);
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.wwd-card-active .wwd-card-inner::before { opacity: 1; }
|
||||
.wwd-icon {
|
||||
width: 50px; height: 50px; border-radius: 14px;
|
||||
background: #0D0D0D; color: #F4C21A;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 22px; position: relative; z-index: 2;
|
||||
}
|
||||
.wwd-title {
|
||||
margin: 0 0 10px; font-size: clamp(1.25rem, 1.8vw, 1.5rem);
|
||||
font-weight: 800; color: #0D0D0D; letter-spacing: -0.02em;
|
||||
font-family: 'Sora', sans-serif; position: relative; z-index: 2;
|
||||
}
|
||||
.wwd-tagline {
|
||||
margin: 0 0 16px; font-size: 0.9rem; color: rgba(13,13,13,0.62);
|
||||
line-height: 1.55; font-family: 'Sora', sans-serif;
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.wwd-detail {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-size: 0.76rem; font-weight: 700; letter-spacing: 0.02em;
|
||||
color: rgba(13,13,13,0.75); font-family: 'Sora', sans-serif;
|
||||
padding-top: 14px; border-top: 1px solid rgba(13,13,13,0.1);
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.wwd-detail-dot {
|
||||
width: 6px; height: 6px; border-radius: 50%; background: #0D0D0D;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.wwd-shimmer {
|
||||
background: linear-gradient(105deg,#f4be28 0%,#fff8e0 48%,#f4be28 62%,#ff9500 100%);
|
||||
background-size: 200% auto;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: wwdShimmer 3s linear infinite;
|
||||
}
|
||||
@keyframes wwdShimmer { 0% { background-position: 200% center; } 100% { background-position: -200% center; } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wwd-shimmer { animation: none; }
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user