Update about us, for business pages, and for-people components
This commit is contained in:
@@ -5,16 +5,17 @@ import Image from 'next/image';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { FpTagCard } from './FpCard';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
import { useCountUp, useParallax, useStaggerReveal } from './fpMotion';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
const TAGS = [
|
||||
{ label: 'Earn as you live', icon: '⚡' },
|
||||
{ label: 'Play to win', icon: '🎮' },
|
||||
{ label: 'Spend anywhere', icon: '🛍️' },
|
||||
const TAGS: { label: string; icon: FpIconName }[] = [
|
||||
{ label: 'Earn as you live', icon: 'bolt' },
|
||||
{ label: 'Play to win', icon: 'gamepad' },
|
||||
{ label: 'Spend anywhere', icon: 'bag' },
|
||||
];
|
||||
|
||||
export default function DiscoverDealsSection() {
|
||||
@@ -85,7 +86,7 @@ export default function DiscoverDealsSection() {
|
||||
|
||||
<div ref={tagsRef} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, maxWidth: 460 }}>
|
||||
{TAGS.map((tag) => (
|
||||
<FpTagCard key={tag.label} theme="dark" icon={tag.icon}>{tag.label}</FpTagCard>
|
||||
<FpTagCard key={tag.label} theme="dark" icon={<FpIcon name={tag.icon} />}>{tag.label}</FpTagCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { FpTagCard, FpStatCard } from './FpCard';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
import { useCountUp, useStaggerReveal } from './fpMotion';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
@@ -44,17 +45,17 @@ export default function EarnRewardsSection() {
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
const earnTags = [
|
||||
{ label: 'Walk', icon: '🚶', bg: '#eef2ff', fg: '#4c5fd5' },
|
||||
{ label: 'Visit', icon: '📍', bg: '#fdeeee', fg: '#d5514c' },
|
||||
{ label: 'Selfie', icon: '🤳', bg: '#fef1e8', fg: '#d5824c' },
|
||||
{ label: 'Spin', icon: '🎡', bg: '#f1eefd', fg: '#7a4cd5' },
|
||||
{ label: 'Scratch', icon: '🎟️', bg: '#fdeef7', fg: '#d54ca3' },
|
||||
{ label: 'Challenge', icon: '🏆', bg: '#fff8e1', fg: '#b8860b' },
|
||||
{ label: 'Friend', icon: '🤝', bg: '#eef9f1', fg: '#3f9d5c' },
|
||||
{ label: 'Shop', icon: '🛍️', bg: '#eef7fd', fg: '#2f8fc4' },
|
||||
{ label: 'Event', icon: '🎉', bg: '#fdf1ee', fg: '#c45f2f' },
|
||||
{ label: 'Brand', icon: '🏷️', bg: '#fef7ea', fg: '#c49a2f' },
|
||||
const earnTags: { label: string; icon: FpIconName; bg: string; fg: string }[] = [
|
||||
{ label: 'Walk', icon: 'walk', bg: '#eef2ff', fg: '#4c5fd5' },
|
||||
{ label: 'Visit', icon: 'pin', bg: '#fdeeee', fg: '#d5514c' },
|
||||
{ label: 'Selfie', icon: 'camera', bg: '#fef1e8', fg: '#d5824c' },
|
||||
{ label: 'Spin', icon: 'wheel', bg: '#f1eefd', fg: '#7a4cd5' },
|
||||
{ label: 'Scratch', icon: 'ticket', bg: '#fdeef7', fg: '#d54ca3' },
|
||||
{ label: 'Challenge', icon: 'trophy', bg: '#fff8e1', fg: '#b8860b' },
|
||||
{ label: 'Friend', icon: 'users', bg: '#eef9f1', fg: '#3f9d5c' },
|
||||
{ label: 'Shop', icon: 'bag', bg: '#eef7fd', fg: '#2f8fc4' },
|
||||
{ label: 'Event', icon: 'sparkles', bg: '#fdf1ee', fg: '#c45f2f' },
|
||||
{ label: 'Brand', icon: 'tag', bg: '#fef7ea', fg: '#c49a2f' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -83,7 +84,7 @@ export default function EarnRewardsSection() {
|
||||
style={{
|
||||
width: 300, borderRadius: 42, padding: 10,
|
||||
background: 'linear-gradient(160deg, #2a2a2e 0%, #0a0a0c 100%)',
|
||||
boxShadow: '0 60px 120px -24px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
boxShadow: '0 28px 60px -22px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
@@ -142,7 +143,10 @@ export default function EarnRewardsSection() {
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .erm-phone-shell { animation: none !important; } }
|
||||
@media (max-width: 900px) {
|
||||
.erm-phone-shell { width: 280px !important; --pm-scale: calc(260 / 390); }
|
||||
/* min(...,100%): a hard 280px overflows the ~256px available at
|
||||
320px. The iframe is sized 100%/scale then scaled back, so it
|
||||
still fills the screen exactly at any shell width. */
|
||||
.erm-phone-shell { width: min(280px, 100%) !important; --pm-scale: calc(260 / 390); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
@@ -163,7 +167,7 @@ export default function EarnRewardsSection() {
|
||||
|
||||
<div ref={tagsRef} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 20, maxWidth: 460 }}>
|
||||
{earnTags.map((tag) => (
|
||||
<FpTagCard key={tag.label} theme="light" icon={tag.icon} iconBg={tag.bg}>
|
||||
<FpTagCard key={tag.label} theme="light" icon={<FpIcon name={tag.icon} />} iconBg={tag.bg} iconColor={tag.fg}>
|
||||
{tag.label}
|
||||
</FpTagCard>
|
||||
))}
|
||||
@@ -171,11 +175,11 @@ export default function EarnRewardsSection() {
|
||||
|
||||
{/* Stat strip — same card system as the tags above, distinguished by a filled dark surface */}
|
||||
<div ref={statsRef} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 28, maxWidth: 460 }}>
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon="⚡"
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon={<FpIcon name="bolt" size={20} />}
|
||||
value={<span ref={waysCountRef}>10</span>} label="ways to earn" />
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon="🔥"
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon={<FpIcon name="flame" size={20} />}
|
||||
value="24/7" label="always live" />
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon="🚀"
|
||||
<FpStatCard theme="fill" layout="row" style={{ flex: '1 1 130px', minWidth: 0 }} icon={<FpIcon name="rocket" size={20} />}
|
||||
value="∞" label="no earn cap" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { useMagnetic as useMagneticShared } from './fpMotion';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
@@ -12,11 +13,11 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffec
|
||||
|
||||
|
||||
|
||||
const PROOF = [
|
||||
{ val: '50K+', label: 'Active users', color: '#f4be28', icon: '👥', iconBg: '#fffbeb' },
|
||||
{ val: '10K+', label: 'Partner stores', color: '#16a34a', icon: '🏬', iconBg: '#f0fdf4' },
|
||||
{ val: '₹28L+', label: 'Lyts given', color: '#2563eb', icon: '🎁', iconBg: '#eff6ff' },
|
||||
{ val: '4.9★', label: 'App rating', color: '#9333ea', icon: '⭐', iconBg: '#faf5ff' },
|
||||
const PROOF: { val: string; label: string; color: string; icon: FpIconName; iconBg: string }[] = [
|
||||
{ val: '50K+', label: 'Active users', color: '#f4be28', icon: 'users', iconBg: '#fffbeb' },
|
||||
{ val: '10K+', label: 'Partner stores', color: '#16a34a', icon: 'store', iconBg: '#f0fdf4' },
|
||||
{ val: '₹28L+', label: 'Lyts given', color: '#2563eb', icon: 'gift', iconBg: '#eff6ff' },
|
||||
{ val: '4.9★', label: 'App rating', color: '#9333ea', icon: 'star', iconBg: '#faf5ff' },
|
||||
];
|
||||
|
||||
export default function FinalCTASection() {
|
||||
@@ -262,8 +263,8 @@ export default function FinalCTASection() {
|
||||
<span style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
width: 40, height: 40, borderRadius: 12, background: p.iconBg,
|
||||
fontSize: 18, marginBottom: 12,
|
||||
}}>{p.icon}</span>
|
||||
color: p.color, marginBottom: 12,
|
||||
}}><FpIcon name={p.icon} size={20} /></span>
|
||||
<div style={{ fontSize: 'clamp(20px,2.4vw,26px)', fontWeight: 900, color: p.color, fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em' }}>{p.val}</div>
|
||||
<div style={{ fontSize: 11.5, color: 'rgba(23,21,18,0.5)', fontFamily: 'Sora, sans-serif', marginTop: 5, letterSpacing: '0.02em' }}>{p.label}</div>
|
||||
</div>
|
||||
|
||||
@@ -23,22 +23,25 @@ const DEFAULT_ICON_BG: Record<Theme, string> = {
|
||||
};
|
||||
|
||||
const sora = 'Sora, sans-serif';
|
||||
const GOLD = '#f4be28';
|
||||
|
||||
interface TagCardProps {
|
||||
theme?: Theme;
|
||||
icon: React.ReactNode;
|
||||
/** Icon-badge background; falls back to the theme's gold tint. */
|
||||
iconBg?: string;
|
||||
/** Icon tint — icons draw in `currentColor`. Falls back to the theme's gold. */
|
||||
iconColor?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
/** Compact icon + label pill (radius 12). */
|
||||
export function FpTagCard({ theme = 'light', icon, iconBg, children, className = '', style }: TagCardProps) {
|
||||
export function FpTagCard({ theme = 'light', icon, iconBg, iconColor, children, className = '', style }: TagCardProps) {
|
||||
return (
|
||||
<div className={`fp-card ${themeClass[theme]} ${className}`.trim()} style={style}>
|
||||
<span className="fp-card-icon" style={{ background: iconBg ?? DEFAULT_ICON_BG[theme] }}>{icon}</span>
|
||||
<span className="fp-card-icon" style={{ background: iconBg ?? DEFAULT_ICON_BG[theme], color: iconColor ?? GOLD }}>{icon}</span>
|
||||
<span style={{
|
||||
fontSize: 13, fontWeight: 700, fontFamily: sora, letterSpacing: '-0.01em',
|
||||
whiteSpace: 'nowrap', color: theme === 'light' ? '#171512' : '#fff',
|
||||
@@ -54,6 +57,8 @@ interface StatCardProps {
|
||||
layout?: 'row' | 'column';
|
||||
icon: React.ReactNode;
|
||||
iconBg?: string;
|
||||
/** Icon tint — icons draw in `currentColor`. Falls back to the theme's gold. */
|
||||
iconColor?: string;
|
||||
/** May be a plain node or a count-up <span> ref. */
|
||||
value: React.ReactNode;
|
||||
label: string;
|
||||
@@ -65,7 +70,7 @@ interface StatCardProps {
|
||||
|
||||
/** Larger icon + value + label card (radius 20). */
|
||||
export function FpStatCard({
|
||||
theme = 'light', layout = 'column', icon, iconBg, value, label, color, className = '', style,
|
||||
theme = 'light', layout = 'column', icon, iconBg, iconColor, value, label, color, className = '', style,
|
||||
}: StatCardProps) {
|
||||
const dark = theme !== 'light';
|
||||
const column = layout === 'column';
|
||||
@@ -74,7 +79,7 @@ export function FpStatCard({
|
||||
className={`fp-card fp-card--stat ${themeClass[theme]} ${className}`.trim()}
|
||||
style={{ flexDirection: column ? 'column' : 'row', alignItems: 'center', ...style }}
|
||||
>
|
||||
<span className="fp-card-icon fp-card-icon--lg" style={{ background: iconBg ?? DEFAULT_ICON_BG[theme] }}>{icon}</span>
|
||||
<span className="fp-card-icon fp-card-icon--lg" style={{ background: iconBg ?? DEFAULT_ICON_BG[theme], color: iconColor ?? GOLD }}>{icon}</span>
|
||||
<div style={{ textAlign: column ? 'center' : 'left' }}>
|
||||
<div style={{
|
||||
fontSize: column ? 'clamp(20px, 2.4vw, 26px)' : 18, fontWeight: 900, fontFamily: sora,
|
||||
|
||||
194
src/components/for-people/FpIcons.tsx
Normal file
194
src/components/for-people/FpIcons.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* The icon set for the "For People" page — replaces the emoji that used to sit
|
||||
* in the `.fp-card-icon` badges.
|
||||
*
|
||||
* One cohesive line system: 24px grid, no fill, 1.75 stroke, round caps/joins,
|
||||
* and `currentColor` throughout so a badge only has to set `color` to tint its
|
||||
* icon. Sized in px (not `em`) so a badge's font-size can't rescale the art.
|
||||
*/
|
||||
|
||||
export type FpIconName =
|
||||
| 'walk' | 'compass' | 'users' | 'store' | 'tag' | 'sparkles'
|
||||
| 'bolt' | 'gamepad' | 'bag' | 'gift' | 'star' | 'pin'
|
||||
| 'camera' | 'wheel' | 'ticket' | 'trophy' | 'map' | 'coffee'
|
||||
| 'sneaker' | 'ban' | 'check' | 'flame' | 'rocket';
|
||||
|
||||
const PATHS: Record<FpIconName, React.ReactNode> = {
|
||||
walk: (
|
||||
<>
|
||||
<circle cx="13.2" cy="4.2" r="1.9" />
|
||||
<path d="M10.3 21.5l1.9-5.4-2.4-2.2V9.6l3.4-1.7 2.1 3.1 2.9 1.3" />
|
||||
<path d="M8.4 12.4l1.4-2.5" />
|
||||
<path d="M14.7 15.9l1.9 5.6" />
|
||||
</>
|
||||
),
|
||||
compass: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M15.6 8.4l-2.1 5.1-5.1 2.1 2.1-5.1z" />
|
||||
</>
|
||||
),
|
||||
users: (
|
||||
<>
|
||||
<path d="M16 20.5v-1.9a3.6 3.6 0 00-3.6-3.6H6.6A3.6 3.6 0 003 18.6v1.9" />
|
||||
<circle cx="9.5" cy="7.4" r="3.4" />
|
||||
<path d="M21 20.5v-1.9a3.6 3.6 0 00-2.7-3.5" />
|
||||
<path d="M15.6 4.2a3.6 3.6 0 010 6.9" />
|
||||
</>
|
||||
),
|
||||
store: (
|
||||
<>
|
||||
<path d="M4 10.4V20a1 1 0 001 1h14a1 1 0 001-1v-9.6" />
|
||||
<path d="M2.6 9.6l1.6-5.1a1 1 0 01.95-.7h13.7a1 1 0 01.95.7l1.6 5.1" />
|
||||
<path d="M2.6 9.6a2.35 2.35 0 004.7 0 2.35 2.35 0 004.7 0 2.35 2.35 0 004.7 0 2.35 2.35 0 004.7 0" />
|
||||
<path d="M9.7 21v-5.3h4.6V21" />
|
||||
</>
|
||||
),
|
||||
tag: (
|
||||
<>
|
||||
<path d="M20.6 13.4l-7.2 7.2a1.6 1.6 0 01-2.3 0l-7.2-7.2a1.6 1.6 0 01-.47-1.13V4.6A1.6 1.6 0 015 3h7.67c.42 0 .83.17 1.13.47l6.8 6.8a1.6 1.6 0 010 2.26z" />
|
||||
<circle cx="8.1" cy="8.1" r="1.5" />
|
||||
</>
|
||||
),
|
||||
sparkles: (
|
||||
<>
|
||||
<path d="M11.4 3.2l1.85 4.45 4.45 1.85-4.45 1.85-1.85 4.45-1.85-4.45L5.1 9.5l4.45-1.85z" />
|
||||
<path d="M18.4 14.9l.85 2.05 2.05.85-2.05.85-.85 2.05-.85-2.05-2.05-.85 2.05-.85z" />
|
||||
</>
|
||||
),
|
||||
bolt: <path d="M13.4 2.5L4.6 13.9h6.1L10.6 21.5l8.8-11.4h-6.1z" />,
|
||||
gamepad: (
|
||||
<>
|
||||
<path d="M7.6 8.6h8.8a5 5 0 014.92 4.11l.72 3.95a2.55 2.55 0 01-4.63 1.87l-1.13-1.62a2 2 0 00-1.64-.86H9.36a2 2 0 00-1.64.86l-1.13 1.62a2.55 2.55 0 01-4.63-1.87l.72-3.95A5 5 0 017.6 8.6z" />
|
||||
<path d="M7.9 11.7v3.1M6.35 13.25h3.1" />
|
||||
<path d="M15.7 12.6h.01M17.9 14.8h.01" />
|
||||
</>
|
||||
),
|
||||
bag: (
|
||||
<>
|
||||
<path d="M5.4 7.6h13.2l.95 12.4a1 1 0 01-1 1.08H5.45a1 1 0 01-1-1.08z" />
|
||||
<path d="M8.6 10.2V6.6a3.4 3.4 0 016.8 0v3.6" />
|
||||
</>
|
||||
),
|
||||
gift: (
|
||||
<>
|
||||
<path d="M19.6 12.2v8.3a.5.5 0 01-.5.5H4.9a.5.5 0 01-.5-.5v-8.3" />
|
||||
<rect x="2.8" y="7.6" width="18.4" height="4.6" rx="1" />
|
||||
<path d="M12 7.6v13.4" />
|
||||
<path d="M12 7.6H7.9a2.35 2.35 0 110-4.7C11 2.9 12 7.6 12 7.6z" />
|
||||
<path d="M12 7.6h4.1a2.35 2.35 0 100-4.7C13 2.9 12 7.6 12 7.6z" />
|
||||
</>
|
||||
),
|
||||
star: <path d="M12 2.9l2.9 5.85 6.45.95-4.67 4.55 1.1 6.42L12 17.64l-5.78 3.03 1.1-6.42L2.65 9.7l6.45-.95z" />,
|
||||
pin: (
|
||||
<>
|
||||
<path d="M20 10.4c0 5.5-8 12.1-8 12.1s-8-6.6-8-12.1a8 8 0 1116 0z" />
|
||||
<circle cx="12" cy="10.2" r="2.8" />
|
||||
</>
|
||||
),
|
||||
camera: (
|
||||
<>
|
||||
<path d="M3 8.4h3.3l1.4-2.3a1 1 0 01.86-.5h6.88a1 1 0 01.86.5l1.4 2.3H21a1 1 0 011 1v9.1a1 1 0 01-1 1H3a1 1 0 01-1-1V9.4a1 1 0 011-1z" />
|
||||
<circle cx="12" cy="13.9" r="3.5" />
|
||||
</>
|
||||
),
|
||||
wheel: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<circle cx="12" cy="12" r="2.3" />
|
||||
<path d="M12 3v6.7M12 14.3V21M3 12h6.7M14.3 12H21" />
|
||||
<path d="M5.64 5.64l4.74 4.74M13.62 13.62l4.74 4.74M18.36 5.64l-4.74 4.74M10.38 13.62l-4.74 4.74" />
|
||||
</>
|
||||
),
|
||||
ticket: (
|
||||
<>
|
||||
<path d="M3 9.1V6.6a1 1 0 011-1h16a1 1 0 011 1v2.5a2.6 2.6 0 000 5.2v2.5a1 1 0 01-1 1H4a1 1 0 01-1-1v-2.5a2.6 2.6 0 000-5.2z" />
|
||||
<path d="M9.6 5.9v12.4" strokeDasharray="2 2.4" />
|
||||
</>
|
||||
),
|
||||
trophy: (
|
||||
<>
|
||||
<path d="M7.1 4.4h9.8v5.2a4.9 4.9 0 01-9.8 0z" />
|
||||
<path d="M7.1 6.1H4.9a2.3 2.3 0 000 4.6h2.5" />
|
||||
<path d="M16.9 6.1h2.2a2.3 2.3 0 010 4.6h-2.5" />
|
||||
<path d="M12 14.5v3.2" />
|
||||
<path d="M8.2 21h7.6" />
|
||||
<path d="M9.6 17.7h4.8a1.4 1.4 0 011.4 1.4V21H8.2v-1.9a1.4 1.4 0 011.4-1.4z" />
|
||||
</>
|
||||
),
|
||||
map: (
|
||||
<>
|
||||
<path d="M9.4 4.4L3.4 6.8v12.7l6-2.4 5.2 2.4 6-2.4V4.4l-6 2.4z" />
|
||||
<path d="M9.4 4.4v12.7M14.6 6.8v12.7" />
|
||||
</>
|
||||
),
|
||||
coffee: (
|
||||
<>
|
||||
<path d="M17.1 8.6h1.8a3 3 0 010 6h-1.8" />
|
||||
<path d="M3.1 8.6h14v5.5a5 5 0 01-5 5H8.1a5 5 0 01-5-5z" />
|
||||
<path d="M6.6 2.6v2.9M10.1 2.6v2.9M13.6 2.6v2.9" />
|
||||
</>
|
||||
),
|
||||
sneaker: (
|
||||
<>
|
||||
<path d="M2.6 18.4v-5.6a.8.8 0 01.8-.8h3.2l3.5-2.7 1.7 1.9 4 2.4 5.5 1.85a2 2 0 011.4 1.9v1.04a.9.9 0 01-.9.9H3.5a.9.9 0 01-.9-.9z" />
|
||||
<path d="M10.1 12.5l-1.7 1.6M12.5 13.8l-1.7 1.6M15.1 15.1l-1.7 1.6" />
|
||||
</>
|
||||
),
|
||||
ban: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M5.64 5.64l12.72 12.72" />
|
||||
</>
|
||||
),
|
||||
check: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M7.9 12.2l2.8 2.8 5.4-5.4" />
|
||||
</>
|
||||
),
|
||||
flame: (
|
||||
<path d="M12 22c3.87 0 7-2.98 7-6.66 0-4.55-4.35-6.86-5.56-11.14-2 1.4-3.1 3.4-3.1 5.4 0 1.6-1.3 2.2-2.2 1.2a2.9 2.9 0 01-.68-1.78C6.02 10.2 5 12.6 5 15.34 5 19.02 8.13 22 12 22z" />
|
||||
),
|
||||
rocket: (
|
||||
<>
|
||||
<path d="M4.6 16.6c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 00-2.91-.09z" />
|
||||
<path d="M12 15l-3-3a22 22 0 012-3.95A12.88 12.88 0 0122 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 01-4 2z" />
|
||||
<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" />
|
||||
<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
interface FpIconProps {
|
||||
name: FpIconName;
|
||||
/** Rendered px size. 16 suits the 30px badge, 20 the 40px `--lg` badge. */
|
||||
size?: number;
|
||||
strokeWidth?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function FpIcon({ name, size = 16, strokeWidth = 1.75, className }: FpIconProps) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
style={{ display: 'block', flexShrink: 0 }}
|
||||
>
|
||||
{PATHS[name]}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { PhoneMockupImage } from './PhoneMockup';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
@@ -61,16 +62,16 @@ export default function LiveMoreSection() {
|
||||
</p>
|
||||
|
||||
<div className="fp-rise" style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 28, maxWidth: 460 }}>
|
||||
{[
|
||||
{ label: 'Walk more', icon: '🚶', bg: '#eef2ff' },
|
||||
{ label: 'Explore more', icon: '🧭', bg: '#fdeef7' },
|
||||
{ label: 'Meet friends', icon: '🤝', bg: '#eef9f1' },
|
||||
{ label: 'Support local', icon: '🏪', bg: '#fef1e8' },
|
||||
{ label: 'Discover new brands', icon: '🏷️', bg: '#fef7ea' },
|
||||
{ label: 'Celebrate every day', icon: '🎊', bg: '#fdeeee' },
|
||||
].map((tag) => (
|
||||
{([
|
||||
{ label: 'Walk more', icon: 'walk', bg: '#eef2ff', fg: '#4c5fd5' },
|
||||
{ label: 'Explore more', icon: 'compass', bg: '#fdeef7', fg: '#d54ca3' },
|
||||
{ label: 'Meet friends', icon: 'users', bg: '#eef9f1', fg: '#3f9d5c' },
|
||||
{ label: 'Support local', icon: 'store', bg: '#fef1e8', fg: '#d5824c' },
|
||||
{ label: 'Discover new brands', icon: 'tag', bg: '#fef7ea', fg: '#c49a2f' },
|
||||
{ label: 'Celebrate every day', icon: 'sparkles', bg: '#fdeeee', fg: '#d5514c' },
|
||||
] as { label: string; icon: FpIconName; bg: string; fg: string }[]).map((tag) => (
|
||||
<div key={tag.label} className="fp-card fp-card--light">
|
||||
<span className="fp-card-icon" style={{ background: tag.bg }}>{tag.icon}</span>
|
||||
<span className="fp-card-icon" style={{ background: tag.bg, color: tag.fg }}><FpIcon name={tag.icon} /></span>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, fontFamily: 'Sora, sans-serif', color: '#171512', letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>
|
||||
{tag.label}
|
||||
</span>
|
||||
|
||||
@@ -4,17 +4,18 @@ import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { PhoneMockupImage } from './PhoneMockup';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
const CHALLENGES = [
|
||||
{ label: 'Walk More Challenge', icon: '🚶' },
|
||||
{ label: 'Selfie Quest', icon: '🤳' },
|
||||
{ label: 'Weekend Explorer', icon: '🗺️' },
|
||||
{ label: 'Coffee Week', icon: '☕' },
|
||||
{ label: 'Sneaker Hunt', icon: '👟' },
|
||||
const CHALLENGES: { label: string; icon: FpIconName }[] = [
|
||||
{ label: 'Walk More Challenge', icon: 'walk' },
|
||||
{ label: 'Selfie Quest', icon: 'camera' },
|
||||
{ label: 'Weekend Explorer', icon: 'map' },
|
||||
{ label: 'Coffee Week', icon: 'coffee' },
|
||||
{ label: 'Sneaker Hunt', icon: 'sneaker' },
|
||||
];
|
||||
|
||||
export default function LytsUniverseSection() {
|
||||
@@ -61,7 +62,7 @@ export default function LytsUniverseSection() {
|
||||
<div className="fp-rise" style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 28, maxWidth: 460 }}>
|
||||
{CHALLENGES.map((tag) => (
|
||||
<div key={tag.label} className="fp-card fp-card--dark">
|
||||
<span className="fp-card-icon" style={{ background: 'rgba(244,190,40,0.14)' }}>{tag.icon}</span>
|
||||
<span className="fp-card-icon" style={{ background: 'rgba(244,190,40,0.14)', color: '#f4be28' }}><FpIcon name={tag.icon} /></span>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, fontFamily: 'Sora, sans-serif', color: '#fff', letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>
|
||||
{tag.label}
|
||||
</span>
|
||||
|
||||
@@ -161,7 +161,9 @@ export function PhoneMockupImage({
|
||||
.pm-phone-shell { animation: pmPhoneFloat 7s ease-in-out infinite; }
|
||||
@media (prefers-reduced-motion: reduce) { .pm-phone-shell { animation: none !important; } }
|
||||
@media (max-width: 900px) {
|
||||
.pm-phone-shell { width: ${MOBILE_WIDGET_WIDTH}px !important; }
|
||||
/* min(...,100%) — a hard 280px is wider than the ~256px available at
|
||||
320px, so the widget overflowed its column instead of centring. */
|
||||
.pm-phone-shell { width: min(${MOBILE_WIDGET_WIDTH}px, 100%) !important; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
@@ -188,7 +190,7 @@ export function PhoneFrame({ children, floatClassName = 'pm-phone-float', width
|
||||
style={{
|
||||
width, borderRadius: 42, padding: 10,
|
||||
background: 'linear-gradient(160deg, #2a2a2e 0%, #0a0a0c 100%)',
|
||||
boxShadow: '0 60px 120px -24px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
boxShadow: '0 28px 60px -22px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
@@ -215,7 +217,9 @@ export function PhoneFrame({ children, floatClassName = 'pm-phone-float', width
|
||||
.pm-phone-shell { animation: pmPhoneFloat 7s ease-in-out infinite; }
|
||||
@media (prefers-reduced-motion: reduce) { .pm-phone-shell { animation: none !important; } }
|
||||
@media (max-width: 900px) {
|
||||
.pm-phone-shell { width: ${MOBILE_WIDGET_WIDTH}px !important; }
|
||||
/* min(...,100%) — a hard 280px is wider than the ~256px available at
|
||||
320px, so the widget overflowed its column instead of centring. */
|
||||
.pm-phone-shell { width: min(${MOBILE_WIDGET_WIDTH}px, 100%) !important; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import FpIcon, { type FpIconName } from './FpIcons';
|
||||
|
||||
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
@@ -51,13 +52,13 @@ export default function SpendAnywhereSection() {
|
||||
</p>
|
||||
|
||||
<div className="fp-rise" style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 28, maxWidth: 460 }}>
|
||||
{[
|
||||
{ label: 'No waiting', icon: '⚡' },
|
||||
{ label: 'No cashback', icon: '🚫' },
|
||||
{ label: 'No complicated redemption', icon: '✅' },
|
||||
].map((tag) => (
|
||||
{([
|
||||
{ label: 'No waiting', icon: 'bolt' },
|
||||
{ label: 'No cashback', icon: 'ban' },
|
||||
{ label: 'No complicated redemption', icon: 'check' },
|
||||
] as { label: string; icon: FpIconName }[]).map((tag) => (
|
||||
<div key={tag.label} className="fp-card fp-card--dark">
|
||||
<span className="fp-card-icon" style={{ background: 'rgba(244,190,40,0.14)' }}>{tag.icon}</span>
|
||||
<span className="fp-card-icon" style={{ background: 'rgba(244,190,40,0.14)', color: '#f4be28' }}><FpIcon name={tag.icon} /></span>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, fontFamily: 'Sora, sans-serif', color: '#fff', letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>
|
||||
{tag.label}
|
||||
</span>
|
||||
@@ -83,7 +84,7 @@ export default function SpendAnywhereSection() {
|
||||
style={{
|
||||
width: 300, borderRadius: 42, padding: 10,
|
||||
background: 'linear-gradient(160deg, #2a2a2e 0%, #0a0a0c 100%)',
|
||||
boxShadow: '0 60px 120px -24px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
boxShadow: '0 28px 60px -22px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.06)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
@@ -142,7 +143,9 @@ export default function SpendAnywhereSection() {
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { .spa-phone-shell { animation: none !important; } }
|
||||
@media (max-width: 900px) {
|
||||
.spa-phone-shell { width: 280px !important; --pm-scale: calc(260 / 390); }
|
||||
/* min(...,100%): see EarnRewardsSection — a hard 280px overflows
|
||||
the ~256px available at 320px instead of centring. */
|
||||
.spa-phone-shell { width: min(280px, 100%) !important; --pm-scale: calc(260 / 390); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
|
||||
@@ -332,7 +332,11 @@ export default function VerticalScrollWrapper({ children, sections = [] }: Props
|
||||
}
|
||||
.fp-card--light { background: #fff; border: 1.5px solid var(--carbon); }
|
||||
.fp-card--light:hover { box-shadow: 3px 3px 0 var(--carbon); transform: translate(-2px, -2px); }
|
||||
.fp-card--dark { background: rgba(255,255,255,0.04); border: 1.5px solid rgba(255,255,255,0.18); }
|
||||
/* Opaque, not a translucent wash — these cards can sit over photography
|
||||
(Discover), where a see-through surface wrecks legibility. #12121a is
|
||||
what the old 4% white overlay composited to on the dark panels, so the
|
||||
sections that aren't over art keep their existing weight. */
|
||||
.fp-card--dark { background: #12121a; border: 1.5px solid rgba(255,255,255,0.18); }
|
||||
.fp-card--dark:hover { border-color: var(--gold); box-shadow: 3px 3px 0 rgba(244,190,40,0.35); transform: translate(-2px, -2px); }
|
||||
.fp-card--fill { background: var(--carbon); border: 1.5px solid var(--carbon); }
|
||||
.fp-card--fill:hover { box-shadow: 3px 3px 0 rgba(244,190,40,0.4); transform: translate(-2px, -2px); }
|
||||
@@ -354,13 +358,20 @@ export default function VerticalScrollWrapper({ children, sections = [] }: Props
|
||||
}
|
||||
/* Force single column even when a section sets grid-template-columns inline */
|
||||
.fp-split { grid-template-columns: 1fr !important; gap: 48px !important; }
|
||||
/* A 1fr track still floors at its content's min-content width, so a fixed
|
||||
widget would stretch the column past the viewport. min-width:0 lets it
|
||||
shrink to the real available width so centring resolves correctly. */
|
||||
.fp-split > * { min-width: 0 !important; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.fp-shell { padding: 0 22px; }
|
||||
/* Full-bleed panels on phones (no left/right margin), keep bottom gap */
|
||||
.fp-panel { width: 100%; margin: 0 0 12px; }
|
||||
/* Hero flush to the top on phones */
|
||||
.fp-root { padding-top: 0; }
|
||||
/* Panels keep a 10px left/right margin on phones, plus the bottom gap */
|
||||
.fp-panel {
|
||||
width: calc(100% - (var(--sec-gutter) * 2));
|
||||
margin: 0 var(--sec-gutter) 12px;
|
||||
}
|
||||
/* Hero sits 10px from the top on phones */
|
||||
.fp-root { padding-top: var(--hero-top); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -178,13 +178,13 @@ export default function HeroCarousel({ onReady }: HeroCarouselProps) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Phones — full-bleed hero, flush to top (top margin 0), keep bottom gap */
|
||||
/* Phones — 10px side and top margins, keep bottom gap */
|
||||
@media (max-width: 600px) {
|
||||
.hero-carousel {
|
||||
width: 100%;
|
||||
height: calc(100vh - 12px);
|
||||
height: calc(100svh - 12px);
|
||||
margin: 0 0 12px;
|
||||
width: calc(100% - (var(--sec-gutter) * 2));
|
||||
height: calc(100vh - var(--hero-top) - 12px);
|
||||
height: calc(100svh - var(--hero-top) - 12px);
|
||||
margin: var(--hero-top) var(--sec-gutter) 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -640,7 +640,7 @@ export default function ProductsSection({ isLoaded = false }: ProductsSectionPro
|
||||
reserved space when the pinned element carries its own margin,
|
||||
which shows up as a jump/shake right as the pin engages. */
|
||||
.products-section-wrapper {
|
||||
margin: 20px;
|
||||
margin: 20px var(--sec-gutter);
|
||||
}
|
||||
.products-section-root {
|
||||
--pt: clamp(230px, 28vh, 310px);
|
||||
|
||||
@@ -383,6 +383,14 @@ export default function ShiftSection({ isLoaded = true }: ShiftSectionProps) {
|
||||
#shift .shift-orb { animation: none; }
|
||||
}
|
||||
|
||||
/* Phones — plain white background instead of the cream gradient.
|
||||
!important is needed to override the inline background style. */
|
||||
@media (max-width: 600px) {
|
||||
#shift.shift-section {
|
||||
background: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bento grid styles */
|
||||
.shift-bento-grid {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user