13 Commits

9 changed files with 1444 additions and 658 deletions

View File

@@ -2,46 +2,69 @@
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
import { useState } from "react"; import ContactCTA from '@/components/sections/contact/ContactCTA';
import { Eye, EyeOff, Mail, Lock } from 'lucide-react'; import FooterLogoEmphasis from '@/components/sections/footer/FooterLogoEmphasis';
import Input from '@/components/form/Input'; import { Mail, Lock, ArrowRight, AlertCircle } from 'lucide-react';
import { useState } from 'react';
export default function LoginPage() { export default function LoginPage() {
const [email, setEmail] = useState(""); const [formData, setFormData] = useState({
const [password, setPassword] = useState(""); email: '',
const [showPassword, setShowPassword] = useState(false); password: ''
const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); });
const [isSubmitted, setIsSubmitted] = useState(false); const [errors, setErrors] = useState<{ [key: string]: string }>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const validateForm = () => { const validateForm = () => {
const newErrors: { email?: string; password?: string } = {}; const newErrors: { [key: string]: string } = {};
if (!email) { if (!formData.email) {
newErrors.email = "Email é obrigatório"; newErrors.email = 'Email é obrigatório';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = "Email inválido"; newErrors.email = 'Email inválido';
} }
if (!password) { if (!formData.password) {
newErrors.password = "Senha é obrigatória"; newErrors.password = 'Senha é obrigatória';
} else if (password.length < 6) { } else if (formData.password.length < 6) {
newErrors.password = "Senha deve ter no mínimo 6 caracteres"; newErrors.password = 'Senha deve ter pelo menos 6 caracteres';
} }
return newErrors; setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}; };
const handleSubmit = (e: React.FormEvent) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const newErrors = validateForm();
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) { if (!validateForm()) {
setIsSubmitted(true); return;
console.log("Login attempt:", { email, password }); }
setTimeout(() => {
setIsSubmitted(false); setIsSubmitting(true);
}, 2000); try {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Login attempt:', formData);
alert('Login bem-sucedido!');
setFormData({ email: '', password: '' });
} catch (error) {
setErrors({ submit: 'Erro ao fazer login. Tente novamente.' });
} finally {
setIsSubmitting(false);
} }
}; };
@@ -62,99 +85,175 @@ export default function LoginPage() {
<NavbarStyleCentered <NavbarStyleCentered
navItems={[ navItems={[
{ name: "Dashboard", id: "/" }, { name: "Dashboard", id: "/" },
{ name: "Treino", id: "training" }, { name: "Sobre", id: "#hero" },
{ name: "Nutrição", id: "nutrition" }, { name: "Features", id: "#onboarding" },
{ name: "Comunidade", id: "community" }, { name: "Contato", id: "#contact" },
{ name: "Perfil", id: "profile" } { name: "Signup", id: "/signup" }
]} ]}
button={{ text: "Começar Agora", href: "/signup" }} button={{ text: "Fazer Login", href: "/login" }}
brandName="FitFlow Pro" brandName="FitFlow Pro"
/> />
</div> </div>
<div className="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4"> <div id="login" data-section="login" className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md"> <div className="w-full max-w-md space-y-8">
<div className="bg-card rounded-3xl shadow-lg p-8 border border-accent/10"> <div className="text-center space-y-3">
<div className="mb-8"> <h1 className="text-4xl font-extrabold tracking-tight">Bem-vindo de volta</h1>
<h1 className="text-3xl font-extrabold text-foreground mb-2">Bem-vindo de Volta</h1> <p className="text-base text-gray-600 dark:text-gray-400">Faça login na sua conta FitFlow Pro</p>
<p className="text-foreground/70">Faça login para acessar seu progresso</p> </div>
</div>
<div className="bg-card rounded-2xl border border-primary-cta/20 p-8 shadow-lg">
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div> {/* Email Field */}
<label className="block text-sm font-medium text-foreground mb-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <label htmlFor="email" className="block text-sm font-medium text-foreground">
<Mail size={16} /> Email
Email
</div>
</label>
<Input
value={email}
onChange={setEmail}
type="email"
placeholder="seu@email.com"
required
className={errors.email ? "border-red-500" : ""}
/>
{errors.email && (
<p className="text-red-500 text-sm mt-1">{errors.email}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
<div className="flex items-center gap-2">
<Lock size={16} />
Senha
</div>
</label> </label>
<div className="relative"> <div className="relative">
<Input <Mail className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
value={password} <input
onChange={setPassword} id="email"
type={showPassword ? "text" : "password"} name="email"
placeholder="••••••••" type="email"
required value={formData.email}
className={errors.password ? "border-red-500" : "pr-10"} onChange={handleChange}
placeholder="seu@email.com"
className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
errors.email
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/> />
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground/50 hover:text-foreground transition-colors"
aria-label={showPassword ? "Ocultar senha" : "Mostrar senha"}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div> </div>
{errors.password && ( {errors.email && (
<p className="text-red-500 text-sm mt-1">{errors.password}</p> <div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle className="w-4 h-4" />
{errors.email}
</div>
)} )}
</div> </div>
{/* Password Field */}
<div className="space-y-2">
<label htmlFor="password" className="block text-sm font-medium text-foreground">
Senha
</label>
<div className="relative">
<Lock className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
<input
id="password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
placeholder="••••••••"
className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
errors.password
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/>
</div>
{errors.password && (
<div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle className="w-4 h-4" />
{errors.password}
</div>
)}
</div>
{/* Submit Errors */}
{errors.submit && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 flex items-center gap-2 text-red-700 dark:text-red-400 text-sm">
<AlertCircle className="w-4 h-4 flex-shrink-0" />
{errors.submit}
</div>
)}
{/* Submit Button */}
<button <button
type="submit" type="submit"
disabled={isSubmitted} disabled={isSubmitting}
className="w-full py-3 px-4 bg-primary-cta hover:opacity-90 disabled:opacity-50 text-white font-semibold rounded-full transition-all duration-300 transform hover:scale-105" className="w-full bg-primary-cta hover:bg-primary-cta/90 text-white font-semibold py-2.5 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isSubmitted ? "Entrando..." : "Entrar"} {isSubmitting ? 'Entrando...' : 'Fazer Login'}
{!isSubmitting && <ArrowRight className="w-4 h-4" />}
</button> </button>
</form> </form>
<div className="mt-6 text-center text-sm text-foreground/70"> {/* Divider */}
<p> <div className="mt-6 relative">
Não tem conta?{" "} <div className="absolute inset-0 flex items-center">
<a href="/signup" className="text-primary-cta font-semibold hover:underline"> <div className="w-full border-t border-gray-300 dark:border-gray-600"></div>
Criar conta </div>
</a> <div className="relative flex justify-center text-sm">
</p> <span className="px-2 bg-card text-gray-500">ou continue com</span>
</div>
</div> </div>
<div className="mt-6 pt-6 border-t border-accent/10 text-center text-xs text-foreground/50"> {/* Social Login Buttons */}
<p>Teste gratuito por 30 dias. Sem cartão de crédito necessário.</p> <div className="mt-6 space-y-3">
<button className="w-full bg-background hover:bg-gray-50 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-600 text-foreground font-medium py-2.5 rounded-lg transition-all">
Google
</button>
<button className="w-full bg-background hover:bg-gray-50 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-600 text-foreground font-medium py-2.5 rounded-lg transition-all">
Apple
</button>
</div> </div>
</div> </div>
{/* Sign Up Link */}
<p className="text-center text-gray-600 dark:text-gray-400 text-sm">
Não tem conta?{' '}
<a href="/signup" className="text-primary-cta hover:text-primary-cta/80 font-semibold transition-colors">
Criar conta
</a>
</p>
{/* Forgot Password Link */}
<p className="text-center text-gray-600 dark:text-gray-400 text-sm">
<a href="#" className="text-primary-cta hover:text-primary-cta/80 font-semibold transition-colors">
Esqueceu sua senha?
</a>
</p>
</div> </div>
</div> </div>
<div id="footer" data-section="footer">
<FooterLogoEmphasis
columns={[
{
items: [
{ label: "Home", href: "/" },
{ label: "Sobre", href: "/#hero" },
{ label: "Features", href: "/#onboarding" }
]
},
{
items: [
{ label: "Blog", href: "#" },
{ label: "Ajuda", href: "#" },
{ label: "Suporte", href: "#" }
]
},
{
items: [
{ label: "Privacidade", href: "#" },
{ label: "Termos", href: "#" },
{ label: "Contato", href: "#" }
]
},
{
items: [
{ label: "Login", href: "/login" },
{ label: "Signup", href: "/signup" },
{ label: "Dashboard", href: "/" }
]
}
]}
logoText="FitFlow Pro"
/>
</div>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@@ -13,374 +13,381 @@ import SocialProofOne from '@/components/sections/socialProof/SocialProofOne';
import ContactText from '@/components/sections/contact/ContactText'; import ContactText from '@/components/sections/contact/ContactText';
import FooterLogoEmphasis from '@/components/sections/footer/FooterLogoEmphasis'; import FooterLogoEmphasis from '@/components/sections/footer/FooterLogoEmphasis';
import { Activity, Apple, Brain, Dumbbell, Heart, Target, Zap, Users, Star, TrendingDown, TrendingUp } from 'lucide-react'; import { Activity, Apple, Brain, Dumbbell, Heart, Target, Zap, Users, Star, TrendingDown, TrendingUp } from 'lucide-react';
import { WorkoutDataIntegration } from '@/app/components/WorkoutDataIntegration';
export default function LandingPage() { export default function LandingPage() {
const displayMetrics = [
{ id: "1", value: "10.000+", description: "Passos diários rastreados em tempo real com motivação visual de progresso." },
{ id: "2", value: "500 kg", description: "Volume total de peso levantado monitorado com progressão semanal automática." },
{ id: "3", value: "150+ km", description: "Distância corrida mapeada com GPS, ritmo calculado e calorias precisas." },
{ id: "4", value: "42 dias", description: "Sequência de treinos consistentes com badges de dedicação desbloqueados." }
];
const handleCardioInteraction = (data: any) => {
console.log('Cardio interaction:', data);
};
const handleTrainingInteraction = (data: any) => {
console.log('Training interaction:', data);
};
const handleWorkoutMode = (productId: string, productName: string) => {
console.log('Workout mode:', productId, productName);
};
const handleNutritionSelect = (planId: string, planName: string) => {
console.log('Nutrition select:', planId, planName);
};
return ( return (
<WorkoutDataIntegration autoSave={true}> <ThemeProvider
<ThemeProvider defaultButtonVariant="elastic-effect"
defaultButtonVariant="elastic-effect" defaultTextAnimation="entrance-slide"
defaultTextAnimation="entrance-slide" borderRadius="pill"
borderRadius="pill" contentWidth="smallMedium"
contentWidth="smallMedium" sizing="mediumSizeLargeTitles"
sizing="mediumSizeLargeTitles" background="blurBottom"
background="blurBottom" cardStyle="gradient-bordered"
cardStyle="gradient-bordered" primaryButtonStyle="flat"
primaryButtonStyle="flat" secondaryButtonStyle="glass"
secondaryButtonStyle="glass" headingFontWeight="extrabold"
headingFontWeight="extrabold" >
> <div id="nav" data-section="nav">
<div id="nav" data-section="nav"> <NavbarStyleCentered
<NavbarStyleCentered navItems={[
navItems={[ { name: "Dashboard", id: "dashboard" },
{ name: "Dashboard", id: "dashboard" }, { name: "Treino", id: "training" },
{ name: "Treino", id: "training" }, { name: "Nutrição", id: "nutrition" },
{ name: "Nutrição", id: "nutrition" }, { name: "Comunidade", id: "community" },
{ name: "Comunidade", id: "community" }, { name: "Perfil", id: "profile" }
{ name: "Perfil", id: "profile" } ]}
]} button={{ text: "Começar Agora", href: "contact" }}
button={{ text: "Começar Agora", href: "contact" }} brandName="FitFlow Pro"
brandName="FitFlow Pro" />
/> </div>
</div>
<div id="hero" data-section="hero"> <div id="hero" data-section="hero">
<HeroSplitKpi <HeroSplitKpi
title="Seu Corpo, Seu Histórico, Sua Vitória" title="Seu Corpo, Seu Histórico, Sua Vitória"
description="Aplicativo premium de fitness com rastreamento biométrico inteligente, treinos personalizados por IA e cardio com GPS. Transforme seu corpo enquanto acompanha cada segundo da jornada." description="Aplicativo premium de fitness com rastreamento biométrico inteligente, treinos personalizados por IA e cardio com GPS. Transforme seu corpo enquanto acompanha cada segundo da jornada."
tag="Fitness Ultra-Premium" tag="Fitness Ultra-Premium"
tagIcon={Zap} tagIcon={Zap}
background={{ variant: "glowing-orb" }} background={{ variant: "glowing-orb" }}
kpis={[ kpis={[
{ value: "150K+", label: "Usuários Ativos" }, { value: "150K+", label: "Usuários Ativos" },
{ value: "2M+", label: "Treinos Concluídos" }, { value: "2M+", label: "Treinos Concluídos" },
{ value: "4.9★", label: "Avaliação" } { value: "4.9★", label: "Avaliação" }
]} ]}
enableKpiAnimation={true} enableKpiAnimation={true}
imageSrc="https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=1" imageSrc="https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=1"
imageAlt="Dashboard de fitness premium" imageAlt="Dashboard de fitness premium"
imagePosition="right" imagePosition="right"
mediaAnimation="slide-up" mediaAnimation="slide-up"
buttons={[ buttons={[
{ text: "Começar Teste Grátis", href: "contact" }, { text: "Começar Teste Grátis", href: "contact" },
{ text: "Ver Demo", href: "#features" } { text: "Ver Demo", href: "#features" }
]} ]}
avatars={[ avatars={[
{ src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png", alt: "Usuário" }, { src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png", alt: "Usuário" },
{ src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png", alt: "Usuário" }, { src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png", alt: "Usuário" },
{ src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png", alt: "Usuário" } { src: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png", alt: "Usuário" }
]} ]}
avatarText="Join 150K+ athletes" avatarText="Join 150K+ athletes"
/> />
</div> </div>
<div id="onboarding" data-section="onboarding"> <div id="onboarding" data-section="onboarding">
<FeatureCardTwentyFive <FeatureCardTwentyFive
title="Onboarding Biométrico Dinâmico" title="Onboarding Biométrico Dinâmico"
description="Sistema inteligente que adapta-se ao seu corpo. Defina seu objetivo, e o app calcula automaticamente sua timeline de sucesso com precisão científica." description="Sistema inteligente que adapta-se ao seu corpo. Defina seu objetivo, e o app calcula automaticamente sua timeline de sucesso com precisão científica."
tag="Inteligência Adaptativa" tag="Inteligência Adaptativa"
tagIcon={Brain} tagIcon={Brain}
features={[ features={[
{ {
title: "Perfil Biométrico", description: "Coloque seus dados (gênero, altura, peso, idade) e veja o app adaptar todas as demonstrações, avatares e ilustrações anatomicamente.", icon: Target, title: "Perfil Biométrico", description: "Coloque seus dados (gênero, altura, peso, idade) e veja o app adaptar todas as demonstrações, avatares e ilustrações anatomicamente.", icon: Target,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/premium-onboarding-screen-for-fitness-ap-1773256981180-774b293c.png", imageAlt: "Tela de biometria" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/premium-onboarding-screen-for-fitness-ap-1773256981180-774b293c.png", imageAlt: "Tela de biometria"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=2", imageAlt: "Dashboard adaptado" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=2", imageAlt: "Dashboard adaptado"
} }
] ]
}, },
{ {
title: "Metas Inteligentes", description: "Escolha: Perder Peso ou Ganhar Massa. O sistema calcula TMB, projeta planilha de metas e mostra exatamente quando você atingirá seu objetivo.", icon: Zap, title: "Metas Inteligentes", description: "Escolha: Perder Peso ou Ganhar Massa. O sistema calcula TMB, projeta planilha de metas e mostra exatamente quando você atingirá seu objetivo.", icon: Zap,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png", imageAlt: "Plano de nutrição" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png", imageAlt: "Plano de nutrição"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=1", imageAlt: "Métricas de progresso" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=1", imageAlt: "Métricas de progresso"
} }
] ]
} }
]} ]}
animationType="blur-reveal" animationType="blur-reveal"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="cardio" data-section="cardio"> <div id="cardio" data-section="cardio">
<FeatureCardTwentyFive <FeatureCardTwentyFive
title="Hub de Cardio com GPS em Tempo Real" title="Hub de Cardio com GPS em Tempo Real"
description="Rastreie suas corridas e caminhadas com precisão de GPS. Monitore pace, distância, calorias queimadas e vença suas metas diárias com anéis de progresso animados." description="Rastreie suas corridas e caminhadas com precisão de GPS. Monitore pace, distância, calorias queimadas e vença suas metas diárias com anéis de progresso animados."
tag="Cardio Premium" tag="Cardio Premium"
tagIcon={Heart} tagIcon={Heart}
features={[ features={[
{ {
title: "Running Tracker Avançado", description: "GPS ativado em tempo real. Mapeia sua rota, calcula queima de calorias baseado no peso, mostra ritmo ao vivo em painel estilo smartwatch.", icon: Zap, title: "Running Tracker Avançado", description: "GPS ativado em tempo real. Mapeia sua rota, calcula queima de calorias baseado no peso, mostra ritmo ao vivo em painel estilo smartwatch.", icon: Zap,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/gps-running-tracker-interface-with-real--1773256980694-2abe167e.png", imageAlt: "Rastreamento GPS" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/gps-running-tracker-interface-with-real--1773256980694-2abe167e.png", imageAlt: "Rastreamento GPS"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=2", imageAlt: "Métricas de cardio" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=2", imageAlt: "Métricas de cardio"
} }
] ]
}, },
{ {
title: "Pedômetro & Caminhada", description: "Contador de passos embutido com rastreamento de distância e calorias. Vença suas metas de 10.000 passos com visual de anéis de progresso motivacional.", icon: Activity, title: "Pedômetro & Caminhada", description: "Contador de passos embutido com rastreamento de distância e calorias. Vença suas metas de 10.000 passos com visual de anéis de progresso motivacional.", icon: Activity,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=3", imageAlt: "Dashboard de passos" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png?_wi=3", imageAlt: "Dashboard de passos"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=3", imageAlt: "Progresso de atividade" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=3", imageAlt: "Progresso de atividade"
} }
] ]
} }
]} ]}
animationType="depth-3d" animationType="depth-3d"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="training" data-section="training"> <div id="training" data-section="training">
<FeatureCardTwentyFive <FeatureCardTwentyFive
title="Core de Treinamento com IA" title="Core de Treinamento com IA"
description="Algoritmo adaptativo que recomenda treinos baseado em biometria. Escolha entre Casa (calistenia) ou Ginásio (máquinas/pesos) e isole por grupo muscular com modelo anatômico 3D interativo." description="Algoritmo adaptativo que recomenda treinos baseado em biometria. Escolha entre Casa (calistenia) ou Ginásio (máquinas/pesos) e isole por grupo muscular com modelo anatômico 3D interativo."
tag="Recomendação de IA" tag="Recomendação de IA"
tagIcon={Brain} tagIcon={Brain}
features={[ features={[
{ {
title: "Dualidade de Ambientes", description: "Treinos em Casa (peso corporal, calistenia) ou Ginásio (máquinas, pesos livres). Sistema diferencia automaticamente baseado em sua escolha e disponibilidade de equipamento.", icon: Dumbbell, title: "Dualidade de Ambientes", description: "Treinos em Casa (peso corporal, calistenia) ou Ginásio (máquinas, pesos livres). Sistema diferencia automaticamente baseado em sua escolha e disponibilidade de equipamento.", icon: Dumbbell,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png?_wi=1", imageAlt: "Seleção de exercícios" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png?_wi=1", imageAlt: "Seleção de exercícios"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=1", imageAlt: "Execução de treino" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=1", imageAlt: "Execução de treino"
} }
] ]
}, },
{ {
title: "Isolamento por Grupo Muscular", description: "Modelo anatômico 3D/2D interativo. Clique em um músculo (peitoral, glúteo, etc.) e veja lista filtrada de exercícios com foco nesse músculo específico.", icon: Zap, title: "Isolamento por Grupo Muscular", description: "Modelo anatômico 3D/2D interativo. Clique em um músculo (peitoral, glúteo, etc.) e veja lista filtrada de exercícios com foco nesse músculo específico.", icon: Zap,
mediaItems: [ mediaItems: [
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png?_wi=2", imageAlt: "Anatomia interativa" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png?_wi=2", imageAlt: "Anatomia interativa"
}, },
{ {
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=2", imageAlt: "Exercícios filtrados" imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=2", imageAlt: "Exercícios filtrados"
} }
] ]
} }
]} ]}
animationType="scale-rotate" animationType="scale-rotate"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="workout-mode" data-section="workout-mode"> <div id="workout-mode" data-section="workout-mode">
<ProductCardOne <ProductCardOne
title="Modo 'Em Execução' - Tracking de Série" title="Modo 'Em Execução' - Tracking de Série"
description="Interface imersiva que transforma o treino em experiência interativa com cronômetro de descanso, registro de progresso e gráficos de progressão." description="Interface imersiva que transforma o treino em experiência interativa com cronômetro de descanso, registro de progresso e gráficos de progressão."
tag="Focus Mode" tag="Focus Mode"
tagIcon={Target} tagIcon={Target}
products={[ products={[
{ {
id: "1", name: "Iniciar Série", price: "Botão Proeminente", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=3", imageAlt: "Tela de série" id: "1", name: "Iniciar Série", price: "Botão Proeminente", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=3", imageAlt: "Tela de série", onProductClick: () => handleWorkoutMode("1", "Iniciar Série")
}, },
{ {
id: "2", name: "Cronômetro de Descanso", price: "Automático", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=4", imageAlt: "Descanso entre séries" id: "2", name: "Cronômetro de Descanso", price: "Automático", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png?_wi=4", imageAlt: "Descanso entre séries", onProductClick: () => handleWorkoutMode("2", "Cronômetro de Descanso")
}, },
{ {
id: "3", name: "Registrar Progresso", price: "Carga + Reps", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=4", imageAlt: "Progresso salvo" id: "3", name: "Registrar Progresso", price: "Carga + Reps", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png?_wi=4", imageAlt: "Progresso salvo", onProductClick: () => handleWorkoutMode("3", "Registrar Progresso")
} }
]} ]}
animationType="slide-up" animationType="slide-up"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
gridVariant="three-columns-all-equal-width" gridVariant="three-columns-all-equal-width"
/> />
</div> </div>
<div id="nutrition" data-section="nutrition"> <div id="nutrition" data-section="nutrition">
<PricingCardOne <PricingCardOne
title="Nutrição Inteligente & Receituário" title="Nutrição Inteligente & Receituário"
description="Planos alimentares 100% sincronizados com suas metas de peso. Se quer perder peso, déficit calórico. Se quer ganhar massa, superávit inteligente." description="Planos alimentares 100% sincronizados com suas metas de peso. Se quer perder peso, déficit calórico. Se quer ganhar massa, superávit inteligente."
tag="Sincronizado com Timeline" tag="Sincronizado com Timeline"
tagIcon={Apple} tagIcon={Apple}
plans={[ plans={[
{ {
id: "deficit", badge: "Perda de Peso", badgeIcon: TrendingDown, id: "deficit", badge: "Perda de Peso", badgeIcon: TrendingDown,
price: "Déficit Calórico", subtitle: "Receitas otimizadas para queima de calorias", features: [ price: "Déficit Calórico", subtitle: "Receitas otimizadas para queima de calorias", features: [
"Macros calculados automaticamente", "Prep time entre 15-30 min", "Proteína alta, carboidrato estratégico", "Rastreamento integrado", "Sugestões diárias personalizadas" "Macros calculados automaticamente", "Prep time entre 15-30 min", "Proteína alta, carboidrato estratégico", "Rastreamento integrado", "Sugestões diárias personalizadas"
] ]
}, },
{ {
id: "surplus", badge: "Ganho de Massa", badgeIcon: TrendingUp, id: "surplus", badge: "Ganho de Massa", badgeIcon: TrendingUp,
price: "Superávit Estratégico", subtitle: "Nutrição para crescimento muscular", features: [ price: "Superávit Estratégico", subtitle: "Nutrição para crescimento muscular", features: [
"Calorias calculadas para ganho", "Proteína máxima (2g por kg)", "Carbos pré e pós-treino", "Tempo de preparo eficiente", "Sincronizado com treino de força" "Calorias calculadas para ganho", "Proteína máxima (2g por kg)", "Carbos pré e pós-treino", "Tempo de preparo eficiente", "Sincronizado com treino de força"
] ]
} }
]} ]}
animationType="slide-up" animationType="slide-up"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="metrics" data-section="metrics"> <div id="metrics" data-section="metrics">
<MetricCardFourteen <MetricCardFourteen
title="Suas Conquistas Importam. Veja Cada Número." title="Suas Conquistas Importam. Veja Cada Número."
tag="Performance Metrics" tag="Performance Metrics"
tagAnimation="slide-up" tagAnimation="slide-up"
metrics={[ metrics={displayMetrics}
{ metricsAnimation="slide-up"
id: "1", value: "10.000+", description: "Passos diários rastreados em tempo real com motivação visual de progresso." useInvertedBackground={false}
}, />
{ </div>
id: "2", value: "500 kg", description: "Volume total de peso levantado monitorado com progressão semanal automática."
},
{
id: "3", value: "150+ km", description: "Distância corrida mapeada com GPS, ritmo calculado e calorias precisas."
},
{
id: "4", value: "42 dias", description: "Sequência de treinos consistentes com badges de dedicação desbloqueados."
}
]}
metricsAnimation="slide-up"
useInvertedBackground={false}
/>
</div>
<div id="team" data-section="team"> <div id="team" data-section="team">
<TeamCardOne <TeamCardOne
title="Comunidade de Atletas Profissionais" title="Comunidade de Atletas Profissionais"
description="Feed social onde você compartilha rotas de corrida, estatísticas de treino e compete em rankings semanais com outros usuários." description="Feed social onde você compartilha rotas de corrida, estatísticas de treino e compete em rankings semanais com outros usuários."
tag="Gamificação Social" tag="Gamificação Social"
tagIcon={Users} tagIcon={Users}
members={[ members={[
{ {
id: "1", name: "Marcus A.", role: "Elite Runner", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=1", imageAlt: "Marcus A." id: "1", name: "Marcus A.", role: "Elite Runner", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=1", imageAlt: "Marcus A."
}, },
{ {
id: "2", name: "Ana L.", role: "Strength Coach", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=1", imageAlt: "Ana L." id: "2", name: "Ana L.", role: "Strength Coach", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=1", imageAlt: "Ana L."
}, },
{ {
id: "3", name: "Rafael S.", role: "Nutrition Expert", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=1", imageAlt: "Rafael S." id: "3", name: "Rafael S.", role: "Nutrition Expert", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=1", imageAlt: "Rafael S."
}, },
{ {
id: "4", name: "Sofia M.", role: "Fitness Trainer", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=1", imageAlt: "Sofia M." id: "4", name: "Sofia M.", role: "Fitness Trainer", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=1", imageAlt: "Sofia M."
} }
]} ]}
animationType="blur-reveal" animationType="blur-reveal"
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
gridVariant="four-items-2x2-equal-grid" gridVariant="four-items-2x2-equal-grid"
/> />
</div> </div>
<div id="testimonials" data-section="testimonials"> <div id="testimonials" data-section="testimonials">
<TestimonialCardTwelve <TestimonialCardTwelve
testimonials={[ testimonials={[
{ {
id: "1", name: "Carlos M.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=2", imageAlt: "Carlos M." id: "1", name: "Carlos M.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=2", imageAlt: "Carlos M."
}, },
{ {
id: "2", name: "Beatriz R.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=2", imageAlt: "Beatriz R." id: "2", name: "Beatriz R.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=2", imageAlt: "Beatriz R."
}, },
{ {
id: "3", name: "Diego P.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=2", imageAlt: "Diego P." id: "3", name: "Diego P.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=2", imageAlt: "Diego P."
}, },
{ {
id: "4", name: "Juliana T.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=2", imageAlt: "Juliana T." id: "4", name: "Juliana T.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=2", imageAlt: "Juliana T."
}, },
{ {
id: "5", name: "Lucas F.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/young-athlete-male-portrait-energetic-ex-1773256982698-63e4e494.png", imageAlt: "Lucas F." id: "5", name: "Lucas F.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/young-athlete-male-portrait-energetic-ex-1773256982698-63e4e494.png", imageAlt: "Lucas F."
}, },
{ {
id: "6", name: "Marina S.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png", imageAlt: "Marina S." id: "6", name: "Marina S.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png", imageAlt: "Marina S."
} }
]} ]}
cardTitle="Mais de 150 mil atletas já transformaram seu corpo com FitFlow Pro" cardTitle="Mais de 150 mil atletas já transformaram seu corpo com FitFlow Pro"
cardTag="Veja o que eles dizem" cardTag="Veja o que eles dizem"
cardTagIcon={Star} cardTagIcon={Star}
cardAnimation="blur-reveal" cardAnimation="blur-reveal"
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="social-proof" data-section="social-proof"> <div id="social-proof" data-section="social-proof">
<SocialProofOne <SocialProofOne
title="Confiado pelos Maiores Aplicativos e Plataformas de Fitness" title="Confiado pelos Maiores Aplicativos e Plataformas de Fitness"
description="FitFlow Pro integra-se perfeitamente com os ecossistemas de saúde mais populares do mundo." description="FitFlow Pro integra-se perfeitamente com os ecossistemas de saúde mais populares do mundo."
tag="Parcerias Premium" tag="Parcerias Premium"
tagIcon={Zap} tagIcon={Zap}
names={[ logos={[
"Apple Fitness", "Strava", "Fitbit", "Google Fit", "Peloton", "MyFitnessPal", "Garmin", "Nike Training" "http://img.b2bpic.net/free-vector/heart-love-logo_126523-2763.jpg", "http://img.b2bpic.net/free-photo/woman-with-phone-sportswear_1303-8805.jpg", "http://img.b2bpic.net/free-vector/flat-design-fitness-trackers-heart-rate-menu_23-2148515781.jpg", "http://img.b2bpic.net/free-photo/close-up-woman-holding-phone_23-2148889655.jpg", "http://img.b2bpic.net/free-photo/close-up-man-using-phone-while-electric-bike_23-2149098678.jpg", "http://img.b2bpic.net/free-vector/fitness-trackers-concept_23-2148527033.jpg", "http://img.b2bpic.net/free-photo/cropped-view-sexy-bodybuilder-showing-thumbs-up_1153-6281.jpg", "http://img.b2bpic.net/free-photo/phone-muscular-hand-guy-sitting-city-morning-he-holds-bottle-water-headphones_197531-1155.jpg"
]} ]}
logos={[ names={[
"http://img.b2bpic.net/free-vector/heart-love-logo_126523-2763.jpg", "http://img.b2bpic.net/free-photo/woman-with-phone-sportswear_1303-8805.jpg", "http://img.b2bpic.net/free-vector/flat-design-fitness-trackers-heart-rate-menu_23-2148515781.jpg", "http://img.b2bpic.net/free-photo/close-up-woman-holding-phone_23-2148889655.jpg", "http://img.b2bpic.net/free-photo/close-up-man-using-phone-while-electric-bike_23-2149098678.jpg", "http://img.b2bpic.net/free-vector/fitness-trackers-concept_23-2148527033.jpg", "http://img.b2bpic.net/free-photo/cropped-view-sexy-bodybuilder-showing-thumbs-up_1153-6281.jpg", "http://img.b2bpic.net/free-photo/phone-muscular-hand-guy-sitting-city-morning-he-holds-bottle-water-headphones_197531-1155.jpg" "Apple Fitness", "Strava", "Fitbit", "Google Fit", "Peloton", "MyFitnessPal", "Garmin", "Nike Training"
]} ]}
textboxLayout="default" textboxLayout="default"
useInvertedBackground={false} useInvertedBackground={false}
speed={40} speed={40}
showCard={true} showCard={true}
/> />
</div> </div>
<div id="contact" data-section="contact"> <div id="contact" data-section="contact">
<ContactText <ContactText
text="Pronto para transformar seu corpo? Começar seu teste gratuito de 30 dias agora e veja por que 150 mil atletas confiam em FitFlow Pro." text="Pronto para transformar seu corpo? Começar seu teste gratuito de 30 dias agora e veja por que 150 mil atletas confiam em FitFlow Pro."
animationType="entrance-slide" animationType="entrance-slide"
buttons={[ buttons={[
{ text: "Começar Teste Grátis", href: "contact" }, { text: "Começar Teste Grátis", href: "contact" },
{ text: "Conversar com Especialista", href: "#contact" } { text: "Conversar com Especialista", href: "#contact" }
]} ]}
background={{ variant: "sparkles-gradient" }} background={{ variant: "sparkles-gradient" }}
useInvertedBackground={false} useInvertedBackground={false}
/> />
</div> </div>
<div id="footer" data-section="footer"> <div id="footer" data-section="footer">
<FooterLogoEmphasis <FooterLogoEmphasis
columns={[ columns={[
{ {
items: [ items: [
{ label: "Dashboard", href: "#dashboard" }, { label: "Dashboard", href: "#dashboard" },
{ label: "Treino", href: "#training" }, { label: "Treino", href: "#training" },
{ label: "Nutrição", href: "#nutrition" } { label: "Nutrição", href: "#nutrition" }
] ]
}, },
{ {
items: [ items: [
{ label: "Cardio Hub", href: "#cardio" }, { label: "Cardio Hub", href: "#cardio" },
{ label: "Comunidade", href: "#community" }, { label: "Comunidade", href: "#community" },
{ label: "Perfil", href: "#profile" } { label: "Perfil", href: "#profile" }
] ]
}, },
{ {
items: [ items: [
{ label: "Blog", href: "#blog" }, { label: "Blog", href: "#blog" },
{ label: "Ajuda", href: "#help" }, { label: "Ajuda", href: "#help" },
{ label: "Suporte", href: "#support" } { label: "Suporte", href: "#support" }
] ]
}, },
{ {
items: [ items: [
{ label: "Privacidade", href: "#privacy" }, { label: "Privacidade", href: "#privacy" },
{ label: "Termos", href: "#terms" }, { label: "Termos", href: "#terms" },
{ label: "Contato", href: "#contact" } { label: "Contato", href: "#contact" }
] ]
} }
]} ]}
logoText="FitFlow Pro" logoText="FitFlow Pro"
/> />
</div> </div>
</ThemeProvider> </ThemeProvider>
</WorkoutDataIntegration>
); );
} }

View File

@@ -2,91 +2,139 @@
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
import { useState } from "react"; import ContactCTA from '@/components/sections/contact/ContactCTA';
import { Eye, EyeOff, Mail, Lock, User, CheckCircle2, AlertCircle } from 'lucide-react'; import FooterLogoEmphasis from '@/components/sections/footer/FooterLogoEmphasis';
import Input from '@/components/form/Input'; import { Mail, Lock, User, AlertCircle, Check, ArrowRight } from 'lucide-react';
import { useState } from 'react';
interface FormErrors {
[key: string]: string;
}
interface SignupFormData {
name: string;
email: string;
password: string;
confirmPassword: string;
agreedToTerms: boolean;
}
export default function SignupPage() { export default function SignupPage() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState<SignupFormData>({
name: "", email: "", password: "", confirmPassword: "" name: '',
email: '',
password: '',
confirmPassword: '',
agreedToTerms: false
}); });
const [showPassword, setShowPassword] = useState(false); const [errors, setErrors] = useState<FormErrors>({});
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [passwordStrength, setPasswordStrength] = useState(0);
const [isSubmitted, setIsSubmitted] = useState(false);
const [passwordStrength, setPasswordStrength] = useState<"weak" | "medium" | "strong" | "">("");
const calculatePasswordStrength = (pwd: string): "weak" | "medium" | "strong" | "" => { const calculatePasswordStrength = (password: string) => {
if (!pwd) return ""; let strength = 0;
if (pwd.length < 8) return "weak"; if (password.length >= 8) strength++;
if (/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8}$/.test(pwd)) return "strong"; if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
return "medium"; if (/\d/.test(password)) strength++;
}; if (/[^a-zA-Z\d]/.test(password)) strength++;
setPasswordStrength(strength);
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({
...prev,
[field]: value
}));
if (field === "password") {
setPasswordStrength(calculatePasswordStrength(value));
}
}; };
const validateForm = () => { const validateForm = () => {
const newErrors: { [key: string]: string } = {}; const newErrors: FormErrors = {};
if (!formData.name.trim()) { if (!formData.name.trim()) {
newErrors.name = "Nome é obrigatório"; newErrors.name = 'Nome é obrigatório';
} else if (formData.name.trim().length < 2) { } else if (formData.name.length < 2) {
newErrors.name = "Nome deve ter no mínimo 2 caracteres"; newErrors.name = 'Nome deve ter pelo menos 2 caracteres';
} }
if (!formData.email) { if (!formData.email) {
newErrors.email = "Email é obrigatório"; newErrors.email = 'Email é obrigatório';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = "Email inválido"; newErrors.email = 'Email inválido';
} }
if (!formData.password) { if (!formData.password) {
newErrors.password = "Senha é obrigatória"; newErrors.password = 'Senha é obrigatória';
} else if (formData.password.length < 8) { } else if (formData.password.length < 8) {
newErrors.password = "Senha deve ter no mínimo 8 caracteres"; newErrors.password = 'Senha deve ter pelo menos 8 caracteres';
} }
if (formData.password !== formData.confirmPassword) { if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = "As senhas não correspondem"; newErrors.confirmPassword = 'As senhas não correspondem';
}
if (!formData.agreedToTerms) {
newErrors.agreedToTerms = 'Você deve aceitar os termos e condições';
} }
return newErrors;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const newErrors = validateForm();
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target;
const newValue = type === 'checkbox' ? checked : value;
if (Object.keys(newErrors).length === 0) { setFormData(prev => ({
setIsSubmitted(true); ...prev,
console.log("Signup attempt:", formData); [name]: newValue
setTimeout(() => { }));
setIsSubmitted(false);
}, 2000); if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
if (name === 'password') {
calculatePasswordStrength(value);
} }
}; };
const getStrengthColor = () => { const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
switch (passwordStrength) { e.preventDefault();
case "weak":
return "text-red-500"; if (!validateForm()) {
case "medium": return;
return "text-yellow-500";
case "strong":
return "text-green-500";
default:
return "text-foreground/30";
} }
setIsSubmitting(true);
try {
await new Promise(resolve => setTimeout(resolve, 1500));
console.log('Signup attempt:', formData);
alert('Conta criada com sucesso! Faça login para continuar.');
setFormData({
name: '',
email: '',
password: '',
confirmPassword: '',
agreedToTerms: false
});
setPasswordStrength(0);
} catch (error) {
setErrors({ submit: 'Erro ao criar conta. Tente novamente.' });
} finally {
setIsSubmitting(false);
}
};
const getPasswordStrengthColor = () => {
if (passwordStrength === 0) return 'bg-gray-300';
if (passwordStrength === 1) return 'bg-red-500';
if (passwordStrength === 2) return 'bg-yellow-500';
if (passwordStrength === 3) return 'bg-blue-500';
return 'bg-green-500';
};
const getPasswordStrengthLabel = () => {
if (passwordStrength === 0) return '';
if (passwordStrength === 1) return 'Fraca';
if (passwordStrength === 2) return 'Média';
if (passwordStrength === 3) return 'Forte';
return 'Muito Forte';
}; };
return ( return (
@@ -106,168 +154,275 @@ export default function SignupPage() {
<NavbarStyleCentered <NavbarStyleCentered
navItems={[ navItems={[
{ name: "Dashboard", id: "/" }, { name: "Dashboard", id: "/" },
{ name: "Treino", id: "training" }, { name: "Sobre", id: "#hero" },
{ name: "Nutrição", id: "nutrition" }, { name: "Features", id: "#onboarding" },
{ name: "Comunidade", id: "community" }, { name: "Contato", id: "#contact" },
{ name: "Perfil", id: "profile" } { name: "Login", id: "/login" }
]} ]}
button={{ text: "Entrar", href: "/login" }} button={{ text: "Criar Conta", href: "/signup" }}
brandName="FitFlow Pro" brandName="FitFlow Pro"
/> />
</div> </div>
<div className="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4"> <div id="signup" data-section="signup" className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md"> <div className="w-full max-w-md space-y-8">
<div className="bg-card rounded-3xl shadow-lg p-8 border border-accent/10"> <div className="text-center space-y-3">
<div className="mb-8"> <h1 className="text-4xl font-extrabold tracking-tight">Comece sua jornada</h1>
<h1 className="text-3xl font-extrabold text-foreground mb-2">Começar Agora</h1> <p className="text-base text-gray-600 dark:text-gray-400">Crie sua conta FitFlow Pro e transforme seu corpo</p>
<p className="text-foreground/70">Crie sua conta e inicie sua transformação</p> </div>
</div>
<form onSubmit={handleSubmit} className="space-y-6"> <div className="bg-card rounded-2xl border border-primary-cta/20 p-8 shadow-lg">
<div> <form onSubmit={handleSubmit} className="space-y-5">
<label className="block text-sm font-medium text-foreground mb-2"> {/* Name Field */}
<div className="flex items-center gap-2"> <div className="space-y-2">
<User size={16} /> <label htmlFor="name" className="block text-sm font-medium text-foreground">
Nome Completo Nome Completo
</div>
</label> </label>
<Input <div className="relative">
value={formData.name} <User className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
onChange={(value) => handleInputChange("name", value)} <input
type="text" id="name"
placeholder="Seu nome" name="name"
required type="text"
className={errors.name ? "border-red-500" : ""} value={formData.name}
/> onChange={handleChange}
placeholder="Seu nome"
className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
errors.name
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/>
</div>
{errors.name && ( {errors.name && (
<div className="flex items-center gap-1 text-red-500 text-sm mt-1"> <div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle size={14} /> <AlertCircle className="w-4 h-4" />
{errors.name} {errors.name}
</div> </div>
)} )}
</div> </div>
<div> {/* Email Field */}
<label className="block text-sm font-medium text-foreground mb-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <label htmlFor="email" className="block text-sm font-medium text-foreground">
<Mail size={16} /> Email
Email
</div>
</label> </label>
<Input <div className="relative">
value={formData.email} <Mail className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
onChange={(value) => handleInputChange("email", value)} <input
type="email" id="email"
placeholder="seu@email.com" name="email"
required type="email"
className={errors.email ? "border-red-500" : ""} value={formData.email}
/> onChange={handleChange}
placeholder="seu@email.com"
className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
errors.email
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/>
</div>
{errors.email && ( {errors.email && (
<div className="flex items-center gap-1 text-red-500 text-sm mt-1"> <div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle size={14} /> <AlertCircle className="w-4 h-4" />
{errors.email} {errors.email}
</div> </div>
)} )}
</div> </div>
<div> {/* Password Field */}
<label className="block text-sm font-medium text-foreground mb-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <label htmlFor="password" className="block text-sm font-medium text-foreground">
<Lock size={16} /> Senha
Senha
</div>
</label> </label>
<div className="relative"> <div className="relative">
<Input <Lock className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
<input
id="password"
name="password"
type="password"
value={formData.password} value={formData.password}
onChange={(value) => handleInputChange("password", value)} onChange={handleChange}
type={showPassword ? "text" : "password"} placeholder="••••••••••"
placeholder="••••••••" className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
required errors.password
className={errors.password ? "border-red-500" : "pr-10"} ? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/> />
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground/50 hover:text-foreground transition-colors"
aria-label={showPassword ? "Ocultar senha" : "Mostrar senha"}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div> </div>
{passwordStrength && ( {formData.password && (
<div className={`flex items-center gap-1 text-xs mt-2 ${getStrengthColor()}`}> <div className="space-y-2">
<CheckCircle2 size={12} /> <div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5 overflow-hidden">
Força: {passwordStrength === "weak" ? "Fraca" : passwordStrength === "medium" ? "Média" : "Forte"} <div
className={`h-full transition-all ${getPasswordStrengthColor()}`}
style={{ width: `${(passwordStrength / 4) * 100}%` }}
></div>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
Força: <span className="font-semibold">{getPasswordStrengthLabel()}</span>
</p>
</div> </div>
)} )}
{errors.password && ( {errors.password && (
<div className="flex items-center gap-1 text-red-500 text-sm mt-1"> <div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle size={14} /> <AlertCircle className="w-4 h-4" />
{errors.password} {errors.password}
</div> </div>
)} )}
</div> </div>
<div> {/* Confirm Password Field */}
<label className="block text-sm font-medium text-foreground mb-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <label htmlFor="confirmPassword" className="block text-sm font-medium text-foreground">
<Lock size={16} /> Confirmar Senha
Confirmar Senha
</div>
</label> </label>
<div className="relative"> <div className="relative">
<Input <Lock className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" />
<input
id="confirmPassword"
name="confirmPassword"
type="password"
value={formData.confirmPassword} value={formData.confirmPassword}
onChange={(value) => handleInputChange("confirmPassword", value)} onChange={handleChange}
type={showConfirmPassword ? "text" : "password"} placeholder="••••••••••"
placeholder="••••••••" className={`w-full pl-10 pr-4 py-2.5 bg-background border rounded-lg focus:outline-none focus:ring-2 transition-all ${
required errors.confirmPassword
className={errors.confirmPassword ? "border-red-500" : "pr-10"} ? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-primary-cta'
}`}
/> />
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground/50 hover:text-foreground transition-colors"
aria-label={showConfirmPassword ? "Ocultar senha" : "Mostrar senha"}
>
{showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div> </div>
{formData.confirmPassword && formData.password === formData.confirmPassword && !errors.confirmPassword && (
<div className="flex items-center gap-2 text-green-500 text-sm">
<Check className="w-4 h-4" />
Senhas correspondem
</div>
)}
{errors.confirmPassword && ( {errors.confirmPassword && (
<div className="flex items-center gap-1 text-red-500 text-sm mt-1"> <div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle size={14} /> <AlertCircle className="w-4 h-4" />
{errors.confirmPassword} {errors.confirmPassword}
</div> </div>
)} )}
</div> </div>
{/* Terms Checkbox */}
<div className="space-y-2">
<div className="flex items-start gap-3">
<input
id="agreedToTerms"
name="agreedToTerms"
type="checkbox"
checked={formData.agreedToTerms}
onChange={handleChange}
className="w-5 h-5 mt-0.5 rounded border-gray-300 accent-primary-cta cursor-pointer"
/>
<label htmlFor="agreedToTerms" className="text-sm text-gray-600 dark:text-gray-400 cursor-pointer">
Concordo com os{' '}
<a href="#" className="text-primary-cta hover:underline font-semibold">
termos de serviço
</a>
{' '}e{' '}
<a href="#" className="text-primary-cta hover:underline font-semibold">
política de privacidade
</a>
</label>
</div>
{errors.agreedToTerms && (
<div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle className="w-4 h-4" />
{errors.agreedToTerms}
</div>
)}
</div>
{/* Submit Errors */}
{errors.submit && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 flex items-center gap-2 text-red-700 dark:text-red-400 text-sm">
<AlertCircle className="w-4 h-4 flex-shrink-0" />
{errors.submit}
</div>
)}
{/* Submit Button */}
<button <button
type="submit" type="submit"
disabled={isSubmitted} disabled={isSubmitting}
className="w-full py-3 px-4 bg-primary-cta hover:opacity-90 disabled:opacity-50 text-white font-semibold rounded-full transition-all duration-300 transform hover:scale-105" className="w-full bg-primary-cta hover:bg-primary-cta/90 text-white font-semibold py-2.5 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isSubmitted ? "Criando conta..." : "Criar Conta"} {isSubmitting ? 'Criando conta...' : 'Criar Conta'}
{!isSubmitting && <ArrowRight className="w-4 h-4" />}
</button> </button>
</form> </form>
<div className="mt-6 text-center text-sm text-foreground/70"> {/* Divider */}
<p> <div className="mt-6 relative">
tem conta?{" "} <div className="absolute inset-0 flex items-center">
<a href="/login" className="text-primary-cta font-semibold hover:underline"> <div className="w-full border-t border-gray-300 dark:border-gray-600"></div>
Fazer login </div>
</a> <div className="relative flex justify-center text-sm">
</p> <span className="px-2 bg-card text-gray-500">ou continue com</span>
</div>
</div> </div>
<div className="mt-6 pt-6 border-t border-accent/10 text-center text-xs text-foreground/50"> {/* Social Signup Buttons */}
<p>Teste gratuito por 30 dias. Sem cartão de crédito necessário.</p> <div className="mt-6 space-y-3">
<p className="mt-2">Ao criar uma conta, você concorda com nossos Termos de Serviço e Política de Privacidade.</p> <button className="w-full bg-background hover:bg-gray-50 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-600 text-foreground font-medium py-2.5 rounded-lg transition-all">
Google
</button>
<button className="w-full bg-background hover:bg-gray-50 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-600 text-foreground font-medium py-2.5 rounded-lg transition-all">
Apple
</button>
</div> </div>
</div> </div>
{/* Login Link */}
<p className="text-center text-gray-600 dark:text-gray-400 text-sm">
tem conta?{' '}
<a href="/login" className="text-primary-cta hover:text-primary-cta/80 font-semibold transition-colors">
Fazer login
</a>
</p>
</div> </div>
</div> </div>
<div id="footer" data-section="footer">
<FooterLogoEmphasis
columns={[
{
items: [
{ label: "Home", href: "/" },
{ label: "Sobre", href: "/#hero" },
{ label: "Features", href: "/#onboarding" }
]
},
{
items: [
{ label: "Blog", href: "#" },
{ label: "Ajuda", href: "#" },
{ label: "Suporte", href: "#" }
]
},
{
items: [
{ label: "Privacidade", href: "#" },
{ label: "Termos", href: "#" },
{ label: "Contato", href: "#" }
]
},
{
items: [
{ label: "Login", href: "/login" },
{ label: "Signup", href: "/signup" },
{ label: "Dashboard", href: "/" }
]
}
]}
logoText="FitFlow Pro"
/>
</div>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@@ -0,0 +1,53 @@
'use client';
import FeatureCardTwentyFive from './FeatureCardTwentyFive';
interface FeatureCardTwentyFiveWithSavingProps {
title: string;
description: string;
tag?: string;
tagIcon?: any;
features: Array<{
title: string;
description: string;
icon: any;
mediaItems: Array<{ imageSrc: string; imageAlt: string }>;
}>;
animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal' | 'depth-3d';
textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image';
useInvertedBackground: boolean;
onFeatureInteraction?: (featureData: any) => void;
workoutType?: 'cardio' | 'training';
}
export function FeatureCardTwentyFiveWithSaving({
title,
description,
tag,
tagIcon,
features,
animationType,
textboxLayout,
useInvertedBackground,
onFeatureInteraction,
workoutType,
}: FeatureCardTwentyFiveWithSavingProps) {
const handleFeatureInteraction = (featureIndex: number, data?: any) => {
onFeatureInteraction?.(data);
};
return (
<FeatureCardTwentyFive
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
features={features}
animationType={animationType}
textboxLayout={textboxLayout}
useInvertedBackground={useInvertedBackground}
/>
);
}
export default FeatureCardTwentyFiveWithSaving;

View File

@@ -0,0 +1,70 @@
'use client';
import { useWorkoutStorage } from '@/hooks/useWorkoutStorage';
import MetricCardFourteen from './MetricCardFourteen';
import { useEffect, useState } from 'react';
interface MetricCardFourteenWithSavingProps {
title: string;
tag: string;
tagAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
metrics: Array<{
id: string;
value: string;
description: string;
}>;
metricsAnimation: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
useInvertedBackground: boolean;
}
export function MetricCardFourteenWithSaving({
title,
tag,
tagAnimation,
metrics,
metricsAnimation,
useInvertedBackground,
}: MetricCardFourteenWithSavingProps) {
const { metrics: userMetrics, updateMetrics } = useWorkoutStorage();
const [displayMetrics, setDisplayMetrics] = useState(metrics);
useEffect(() => {
if (userMetrics) {
// Dynamically update metrics based on saved data
const updatedMetrics = metrics.map(metric => {
let value = metric.value;
if (metric.id === '1' && userMetrics.totalDistance > 0) {
// Update steps/distance metric
value = `${Math.round(userMetrics.totalDistance).toLocaleString()}+`;
} else if (metric.id === '2' && userMetrics.totalWeight > 0) {
// Update volume metric
value = `${Math.round(userMetrics.totalWeight)} kg`;
} else if (metric.id === '3' && userMetrics.totalDistance > 0) {
// Update distance metric
value = `${Math.round(userMetrics.totalDistance)}+ km`;
} else if (metric.id === '4' && userMetrics.consistency > 0) {
// Update consistency metric
value = `${userMetrics.consistency} days`;
}
return { ...metric, value };
});
setDisplayMetrics(updatedMetrics);
}
}, [userMetrics, metrics]);
return (
<MetricCardFourteen
title={title}
tag={tag}
tagAnimation={tagAnimation}
metrics={displayMetrics}
metricsAnimation={metricsAnimation}
useInvertedBackground={useInvertedBackground}
/>
);
}
export default MetricCardFourteenWithSaving;

View File

@@ -0,0 +1,68 @@
'use client';
import PricingCardOne from './PricingCardOne';
interface NutritionPlan {
id: string;
badge: string;
badgeIcon?: any;
price: string;
subtitle: string;
features: string[];
}
interface PricingCardOneWithSavingProps {
title: string;
description: string;
tag?: string;
tagIcon?: any;
plans: NutritionPlan[];
animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal' | 'depth-3d';
textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image';
useInvertedBackground: boolean;
onNutritionPlanSelected?: (planData: any) => void;
}
export function PricingCardOneWithSaving({
title,
description,
tag,
tagIcon,
plans,
animationType,
textboxLayout,
useInvertedBackground,
onNutritionPlanSelected,
}: PricingCardOneWithSavingProps) {
const handlePlanSelection = (planId: string, planBadge: string) => {
const nutritionData = {
type: 'nutrition' as const,
date: new Date().toISOString().split('T')[0],
data: {
plan: planBadge,
planId,
selectedAt: Date.now(),
},
};
onNutritionPlanSelected?.(nutritionData);
};
const enhancedPlans = plans.map(plan => ({
...plan,
}));
return (
<PricingCardOne
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
plans={enhancedPlans as any}
animationType={animationType}
textboxLayout={textboxLayout}
useInvertedBackground={useInvertedBackground}
/>
);
}
export default PricingCardOneWithSaving;

View File

@@ -0,0 +1,83 @@
'use client';
import ProductCardOne from './ProductCardOne';
interface ProductCardOneWithSavingProps {
title: string;
description: string;
tag?: string;
tagIcon?: any;
products: Array<{
id: string;
name: string;
price: string;
imageSrc: string;
imageAlt?: string;
onFavorite?: () => void;
onProductClick?: () => void;
isFavorited?: boolean;
}>;
animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image';
useInvertedBackground: boolean;
gridVariant:
| 'uniform-all-items-equal'
| 'bento-grid'
| 'bento-grid-inverted'
| 'two-columns-alternating-heights'
| 'asymmetric-60-wide-40-narrow'
| 'three-columns-all-equal-width'
| 'four-items-2x2-equal-grid'
| 'one-large-right-three-stacked-left'
| 'items-top-row-full-width-bottom'
| 'full-width-top-items-bottom-row'
| 'one-large-left-three-stacked-right';
onWorkoutStart?: (workoutData: any) => void;
}
export function ProductCardOneWithSaving({
title,
description,
tag,
tagIcon,
products,
animationType,
textboxLayout,
useInvertedBackground,
gridVariant,
onWorkoutStart,
}: ProductCardOneWithSavingProps) {
const handleProductClick = (productId: string, productName: string) => {
const workoutData = {
type: 'training' as const,
date: new Date().toISOString().split('T')[0],
data: {
workout: productName,
startTime: Date.now(),
productId,
},
};
onWorkoutStart?.(workoutData);
};
const enhancedProducts = products.map(product => ({
...product,
onProductClick: () => handleProductClick(product.id, product.name),
}));
return (
<ProductCardOne
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
products={enhancedProducts}
animationType={animationType}
textboxLayout={textboxLayout}
useInvertedBackground={useInvertedBackground}
gridVariant={gridVariant}
/>
);
}
export default ProductCardOneWithSaving;

View File

@@ -0,0 +1,120 @@
'use client';
import { useState, useCallback, useEffect } from 'react';
import { WorkoutStorageService, WorkoutSession, UserMetrics } from '@/lib/storage/workoutStorage';
export function useWorkoutStorage() {
const [workouts, setWorkouts] = useState<WorkoutSession[]>([]);
const [metrics, setMetrics] = useState<UserMetrics | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Load initial data
useEffect(() => {
setIsLoading(true);
try {
const loadedWorkouts = WorkoutStorageService.getWorkoutSessions();
const loadedMetrics = WorkoutStorageService.getMetrics();
setWorkouts(loadedWorkouts);
setMetrics(loadedMetrics);
} finally {
setIsLoading(false);
}
}, []);
const saveWorkout = useCallback((session: Omit<WorkoutSession, 'id' | 'createdAt'>) => {
try {
const saved = WorkoutStorageService.saveWorkoutSession(session);
setWorkouts(prev => [...prev, saved]);
// Recalculate metrics
const updated = WorkoutStorageService.calculateMetricsFromSessions();
setMetrics(updated);
return saved;
} catch (error) {
console.error('Error saving workout:', error);
return null;
}
}, []);
const updateWorkout = useCallback((id: string, updates: Partial<WorkoutSession>) => {
try {
const updated = WorkoutStorageService.updateWorkoutSession(id, updates);
if (updated) {
setWorkouts(prev => prev.map(w => w.id === id ? updated : w));
// Recalculate metrics
const newMetrics = WorkoutStorageService.calculateMetricsFromSessions();
setMetrics(newMetrics);
}
return updated;
} catch (error) {
console.error('Error updating workout:', error);
return null;
}
}, []);
const deleteWorkout = useCallback((id: string) => {
try {
const success = WorkoutStorageService.deleteWorkoutSession(id);
if (success) {
setWorkouts(prev => prev.filter(w => w.id !== id));
// Recalculate metrics
const updated = WorkoutStorageService.calculateMetricsFromSessions();
setMetrics(updated);
}
return success;
} catch (error) {
console.error('Error deleting workout:', error);
return false;
}
}, []);
const getWorkoutsByType = useCallback((type: WorkoutSession['type']) => {
return workouts.filter(w => w.type === type);
}, [workouts]);
const getWorkoutsByDate = useCallback((date: string) => {
return workouts.filter(w => w.date === date);
}, [workouts]);
const updateMetrics = useCallback((updates: Partial<UserMetrics>) => {
try {
const updated = WorkoutStorageService.updateMetrics(updates);
setMetrics(updated);
return updated;
} catch (error) {
console.error('Error updating metrics:', error);
return metrics;
}
}, [metrics]);
const clearAllData = useCallback(() => {
try {
WorkoutStorageService.clearAllData();
setWorkouts([]);
setMetrics({
totalWorkouts: 0,
totalDistance: 0,
totalCalories: 0,
totalWeight: 0,
consistency: 0,
lastUpdated: Date.now(),
});
} catch (error) {
console.error('Error clearing data:', error);
}
}, []);
return {
workouts,
metrics,
isLoading,
saveWorkout,
updateWorkout,
deleteWorkout,
getWorkoutsByType,
getWorkoutsByDate,
updateMetrics,
clearAllData,
};
}
export type UseWorkoutStorageReturn = ReturnType<typeof useWorkoutStorage>;

View File

@@ -1,54 +1,185 @@
export interface WorkoutSession { // Workout and metrics persistence layer
interface WorkoutSession {
id: string; id: string;
type: 'cardio' | 'training' | 'nutrition';
date: string; date: string;
type: 'cardio' | 'strength' | 'flexibility' | 'nutrition'; data: Record<string, any>;
duration: number; createdAt: number;
calories?: number;
notes?: string;
} }
export interface UserMetrics { interface UserMetrics {
totalWorkouts: number; totalWorkouts: number;
totalDistance: number;
totalCalories: number; totalCalories: number;
averageDuration: number; totalWeight: number;
consistency: number; // days in a row
lastUpdated: number;
} }
export async function saveWorkoutSession(session: Omit<WorkoutSession, 'id'>): Promise<WorkoutSession> { const STORAGE_KEYS = {
return { id: '1', ...session }; WORKOUTS: 'fitflow_workouts',
METRICS: 'fitflow_metrics',
USER_PROFILE: 'fitflow_user_profile',
} as const;
export class WorkoutStorageService {
// Workout Session Management
static saveWorkoutSession(session: Omit<WorkoutSession, 'id' | 'createdAt'>): WorkoutSession {
try {
const sessions = this.getWorkoutSessions();
const newSession: WorkoutSession = {
id: `workout_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
...session,
createdAt: Date.now(),
};
sessions.push(newSession);
localStorage.setItem(STORAGE_KEYS.WORKOUTS, JSON.stringify(sessions));
return newSession;
} catch (error) {
console.error('Error saving workout session:', error);
throw error;
}
}
static getWorkoutSessions(): WorkoutSession[] {
try {
const data = localStorage.getItem(STORAGE_KEYS.WORKOUTS);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('Error retrieving workout sessions:', error);
return [];
}
}
static getWorkoutSessionsByType(type: WorkoutSession['type']): WorkoutSession[] {
const sessions = this.getWorkoutSessions();
return sessions.filter(session => session.type === type);
}
static getWorkoutSessionsByDate(date: string): WorkoutSession[] {
const sessions = this.getWorkoutSessions();
return sessions.filter(session => session.date === date);
}
static updateWorkoutSession(id: string, updates: Partial<WorkoutSession>): WorkoutSession | null {
try {
const sessions = this.getWorkoutSessions();
const index = sessions.findIndex(s => s.id === id);
if (index === -1) return null;
const updated = { ...sessions[index], ...updates, id: sessions[index].id, createdAt: sessions[index].createdAt };
sessions[index] = updated;
localStorage.setItem(STORAGE_KEYS.WORKOUTS, JSON.stringify(sessions));
return updated;
} catch (error) {
console.error('Error updating workout session:', error);
return null;
}
}
static deleteWorkoutSession(id: string): boolean {
try {
const sessions = this.getWorkoutSessions();
const filtered = sessions.filter(s => s.id !== id);
localStorage.setItem(STORAGE_KEYS.WORKOUTS, JSON.stringify(filtered));
return filtered.length < sessions.length;
} catch (error) {
console.error('Error deleting workout session:', error);
return false;
}
}
// Metrics Management
static saveMetrics(metrics: UserMetrics): void {
try {
localStorage.setItem(STORAGE_KEYS.METRICS, JSON.stringify(metrics));
} catch (error) {
console.error('Error saving metrics:', error);
}
}
static getMetrics(): UserMetrics {
try {
const data = localStorage.getItem(STORAGE_KEYS.METRICS);
if (!data) {
return {
totalWorkouts: 0,
totalDistance: 0,
totalCalories: 0,
totalWeight: 0,
consistency: 0,
lastUpdated: Date.now(),
};
}
return JSON.parse(data);
} catch (error) {
console.error('Error retrieving metrics:', error);
return {
totalWorkouts: 0,
totalDistance: 0,
totalCalories: 0,
totalWeight: 0,
consistency: 0,
lastUpdated: Date.now(),
};
}
}
static updateMetrics(updates: Partial<UserMetrics>): UserMetrics {
const current = this.getMetrics();
const updated: UserMetrics = { ...current, ...updates, lastUpdated: Date.now() };
this.saveMetrics(updated);
return updated;
}
// Calculated Metrics
static calculateMetricsFromSessions(): UserMetrics {
const sessions = this.getWorkoutSessions();
let totalDistance = 0;
let totalCalories = 0;
let totalWeight = 0;
sessions.forEach(session => {
if (session.data.distance) totalDistance += Number(session.data.distance) || 0;
if (session.data.calories) totalCalories += Number(session.data.calories) || 0;
if (session.data.weight) totalWeight += Number(session.data.weight) || 0;
});
return this.updateMetrics({
totalWorkouts: sessions.length,
totalDistance,
totalCalories,
totalWeight,
});
}
// Batch Operations
static clearAllData(): void {
try {
localStorage.removeItem(STORAGE_KEYS.WORKOUTS);
localStorage.removeItem(STORAGE_KEYS.METRICS);
localStorage.removeItem(STORAGE_KEYS.USER_PROFILE);
} catch (error) {
console.error('Error clearing data:', error);
}
}
static exportData(): { workouts: WorkoutSession[]; metrics: UserMetrics } {
return {
workouts: this.getWorkoutSessions(),
metrics: this.getMetrics(),
};
}
static importData(data: { workouts: WorkoutSession[]; metrics: UserMetrics }): void {
try {
localStorage.setItem(STORAGE_KEYS.WORKOUTS, JSON.stringify(data.workouts));
this.saveMetrics(data.metrics);
} catch (error) {
console.error('Error importing data:', error);
}
}
} }
export async function getWorkoutSessions(): Promise<WorkoutSession[]> { export type { WorkoutSession, UserMetrics };
return [];
}
export async function updateWorkoutSession(id: string, session: Partial<WorkoutSession>): Promise<WorkoutSession> {
return { id, date: '', type: 'cardio', duration: 0, ...session };
}
export async function deleteWorkoutSession(id: string): Promise<void> {
// Delete implementation
}
export async function getWorkoutsByType(type: string): Promise<WorkoutSession[]> {
return [];
}
export async function getWorkoutsByDateRange(startDate: string, endDate: string): Promise<WorkoutSession[]> {
return [];
}
export async function saveUserMetrics(metrics: UserMetrics): Promise<void> {
// Save implementation
}
export async function getUserMetrics(): Promise<UserMetrics> {
return { totalWorkouts: 0, totalCalories: 0, averageDuration: 0 };
}
export async function updateUserMetrics(metrics: Partial<UserMetrics>): Promise<void> {
// Update implementation
}
export async function calculateMetricsFromSessions(sessions: WorkoutSession[]): Promise<UserMetrics> {
return { totalWorkouts: sessions.length, totalCalories: 0, averageDuration: 0 };
}