Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1098188c38 | |||
| 76951b2686 | |||
| 9f6d8f6534 | |||
| 81364f26e1 | |||
| a134d194ba | |||
| 5f41fe61eb | |||
| e7035f4734 | |||
| c84844aaf3 | |||
| 289c7da1c4 | |||
| 0b5e289aa5 | |||
| 40357c2c40 | |||
| 3180013673 | |||
| 093309937b | |||
| 308dee7afe | |||
| f9e55abdf0 | |||
| 88623e3b9b | |||
| d0afbe9b1f |
@@ -1,64 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import { useWorkoutTracking } from '@/app/hooks/useWorkoutTracking';
|
|
||||||
import { WorkoutSession, CardioSession, NutritionLog } from '@/app/lib/storage/workoutStorage';
|
|
||||||
|
|
||||||
export interface WorkoutDataIntegrationProps {
|
|
||||||
onSave?: (data: any) => void;
|
|
||||||
autoSave?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WorkoutDataIntegration component that provides workout tracking context
|
|
||||||
* Use this component to wrap sections that need to save workout data
|
|
||||||
*/
|
|
||||||
export const WorkoutDataIntegration: React.FC<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
} & WorkoutDataIntegrationProps> = ({ children, onSave, autoSave = true }) => {
|
|
||||||
const { metrics, addWorkoutSession, addCardioSession, addNutritionLog, refreshMetrics } =
|
|
||||||
useWorkoutTracking();
|
|
||||||
|
|
||||||
const handleWorkoutSave = useCallback(
|
|
||||||
(session: WorkoutSession) => {
|
|
||||||
addWorkoutSession(session);
|
|
||||||
if (onSave) onSave({ type: 'workout', data: session });
|
|
||||||
},
|
|
||||||
[addWorkoutSession, onSave]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCardioSave = useCallback(
|
|
||||||
(session: CardioSession) => {
|
|
||||||
addCardioSession(session);
|
|
||||||
if (onSave) onSave({ type: 'cardio', data: session });
|
|
||||||
},
|
|
||||||
[addCardioSession, onSave]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNutritionSave = useCallback(
|
|
||||||
(log: NutritionLog) => {
|
|
||||||
addNutritionLog(log);
|
|
||||||
if (onSave) onSave({ type: 'nutrition', data: log });
|
|
||||||
},
|
|
||||||
[addNutritionLog, onSave]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Expose save functions via context or props
|
|
||||||
const contextValue = {
|
|
||||||
metrics,
|
|
||||||
handleWorkoutSave,
|
|
||||||
handleCardioSave,
|
|
||||||
handleNutritionSave,
|
|
||||||
refreshMetrics,
|
|
||||||
autoSave,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clone children and pass context data as props
|
|
||||||
return (
|
|
||||||
<div data-workout-integration="true" data-context={JSON.stringify(contextValue)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkoutDataIntegration;
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
|
||||||
import {
|
|
||||||
saveWorkoutSession,
|
|
||||||
saveCardioSession,
|
|
||||||
saveNutritionLog,
|
|
||||||
getUserMetrics,
|
|
||||||
getWorkoutSessions,
|
|
||||||
getCardioSessions,
|
|
||||||
getNutritionLogs,
|
|
||||||
WorkoutSession,
|
|
||||||
CardioSession,
|
|
||||||
NutritionLog,
|
|
||||||
UserMetrics,
|
|
||||||
} from '@/app/lib/storage/workoutStorage';
|
|
||||||
|
|
||||||
export const useWorkoutTracking = () => {
|
|
||||||
const [metrics, setMetrics] = useState<UserMetrics>(getUserMetrics());
|
|
||||||
const [workouts, setWorkouts] = useState<WorkoutSession[]>(getWorkoutSessions());
|
|
||||||
const [cardioSessions, setCardioSessions] = useState<CardioSession[]>(getCardioSessions());
|
|
||||||
const [nutritionLogs, setNutritionLogs] = useState<NutritionLog[]>(getNutritionLogs());
|
|
||||||
|
|
||||||
const addWorkoutSession = useCallback((session: WorkoutSession) => {
|
|
||||||
saveWorkoutSession(session);
|
|
||||||
setWorkouts(getWorkoutSessions());
|
|
||||||
setMetrics(getUserMetrics());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const addCardioSession = useCallback((session: CardioSession) => {
|
|
||||||
saveCardioSession(session);
|
|
||||||
setCardioSessions(getCardioSessions());
|
|
||||||
setMetrics(getUserMetrics());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const addNutritionLog = useCallback((log: NutritionLog) => {
|
|
||||||
saveNutritionLog(log);
|
|
||||||
setNutritionLogs(getNutritionLogs());
|
|
||||||
setMetrics(getUserMetrics());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refreshMetrics = useCallback(() => {
|
|
||||||
setMetrics(getUserMetrics());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
metrics,
|
|
||||||
workouts,
|
|
||||||
cardioSessions,
|
|
||||||
nutritionLogs,
|
|
||||||
addWorkoutSession,
|
|
||||||
addCardioSession,
|
|
||||||
addNutritionLog,
|
|
||||||
refreshMetrics,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
/**
|
|
||||||
* Workout and metrics data persistence layer
|
|
||||||
* Handles saving and retrieving workout data from localStorage
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface WorkoutSet {
|
|
||||||
reps: number;
|
|
||||||
weight: number;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkoutSession {
|
|
||||||
id: string;
|
|
||||||
exerciseName: string;
|
|
||||||
date: string;
|
|
||||||
sets: WorkoutSet[];
|
|
||||||
duration: number; // in seconds
|
|
||||||
caloriesBurned: number;
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CardioSession {
|
|
||||||
id: string;
|
|
||||||
type: 'running' | 'walking' | 'cycling';
|
|
||||||
date: string;
|
|
||||||
distance: number; // in km
|
|
||||||
duration: number; // in seconds
|
|
||||||
caloriesBurned: number;
|
|
||||||
pace: number; // km/h
|
|
||||||
steps?: number;
|
|
||||||
route?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NutritionLog {
|
|
||||||
id: string;
|
|
||||||
date: string;
|
|
||||||
calories: number;
|
|
||||||
protein: number;
|
|
||||||
carbs: number;
|
|
||||||
fats: number;
|
|
||||||
meals: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserMetrics {
|
|
||||||
totalWorkouts: number;
|
|
||||||
totalCardioDistance: number;
|
|
||||||
totalCaloriesBurned: number;
|
|
||||||
currentStreak: number;
|
|
||||||
personalRecords: Record<string, number>;
|
|
||||||
lastUpdated: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const STORAGE_KEYS = {
|
|
||||||
WORKOUT_SESSIONS: 'fitflow_workout_sessions',
|
|
||||||
CARDIO_SESSIONS: 'fitflow_cardio_sessions',
|
|
||||||
NUTRITION_LOGS: 'fitflow_nutrition_logs',
|
|
||||||
USER_METRICS: 'fitflow_user_metrics',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a workout session to localStorage
|
|
||||||
*/
|
|
||||||
export const saveWorkoutSession = (session: WorkoutSession): void => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sessions = getWorkoutSessions();
|
|
||||||
sessions.push(session);
|
|
||||||
localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(sessions));
|
|
||||||
updateUserMetrics();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving workout session:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all workout sessions from localStorage
|
|
||||||
*/
|
|
||||||
export const getWorkoutSessions = (): WorkoutSession[] => {
|
|
||||||
if (typeof window === 'undefined') return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = localStorage.getItem(STORAGE_KEYS.WORKOUT_SESSIONS);
|
|
||||||
return data ? JSON.parse(data) : [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error retrieving workout sessions:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a cardio session to localStorage
|
|
||||||
*/
|
|
||||||
export const saveCardioSession = (session: CardioSession): void => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sessions = getCardioSessions();
|
|
||||||
sessions.push(session);
|
|
||||||
localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(sessions));
|
|
||||||
updateUserMetrics();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving cardio session:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all cardio sessions from localStorage
|
|
||||||
*/
|
|
||||||
export const getCardioSessions = (): CardioSession[] => {
|
|
||||||
if (typeof window === 'undefined') return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = localStorage.getItem(STORAGE_KEYS.CARDIO_SESSIONS);
|
|
||||||
return data ? JSON.parse(data) : [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error retrieving cardio sessions:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a nutrition log to localStorage
|
|
||||||
*/
|
|
||||||
export const saveNutritionLog = (log: NutritionLog): void => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const logs = getNutritionLogs();
|
|
||||||
logs.push(log);
|
|
||||||
localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(logs));
|
|
||||||
updateUserMetrics();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving nutrition log:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all nutrition logs from localStorage
|
|
||||||
*/
|
|
||||||
export const getNutritionLogs = (): NutritionLog[] => {
|
|
||||||
if (typeof window === 'undefined') return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = localStorage.getItem(STORAGE_KEYS.NUTRITION_LOGS);
|
|
||||||
return data ? JSON.parse(data) : [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error retrieving nutrition logs:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get user metrics from localStorage
|
|
||||||
*/
|
|
||||||
export const getUserMetrics = (): UserMetrics => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return {
|
|
||||||
totalWorkouts: 0,
|
|
||||||
totalCardioDistance: 0,
|
|
||||||
totalCaloriesBurned: 0,
|
|
||||||
currentStreak: 0,
|
|
||||||
personalRecords: {},
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = localStorage.getItem(STORAGE_KEYS.USER_METRICS);
|
|
||||||
return data
|
|
||||||
? JSON.parse(data)
|
|
||||||
: {
|
|
||||||
totalWorkouts: 0,
|
|
||||||
totalCardioDistance: 0,
|
|
||||||
totalCaloriesBurned: 0,
|
|
||||||
currentStreak: 0,
|
|
||||||
personalRecords: {},
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error retrieving user metrics:', error);
|
|
||||||
return {
|
|
||||||
totalWorkouts: 0,
|
|
||||||
totalCardioDistance: 0,
|
|
||||||
totalCaloriesBurned: 0,
|
|
||||||
currentStreak: 0,
|
|
||||||
personalRecords: {},
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update user metrics based on saved sessions
|
|
||||||
*/
|
|
||||||
export const updateUserMetrics = (): void => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const workoutSessions = getWorkoutSessions();
|
|
||||||
const cardioSessions = getCardioSessions();
|
|
||||||
const nutritionLogs = getNutritionLogs();
|
|
||||||
|
|
||||||
let totalWorkouts = workoutSessions.length;
|
|
||||||
let totalCardioDistance = cardioSessions.reduce((sum, session) => sum + session.distance, 0);
|
|
||||||
let totalCaloriesBurned = workoutSessions.reduce((sum, session) => sum + session.caloriesBurned, 0)
|
|
||||||
+ cardioSessions.reduce((sum, session) => sum + session.caloriesBurned, 0);
|
|
||||||
|
|
||||||
// Calculate streak (consecutive days with activity)
|
|
||||||
const currentStreak = calculateStreak([...workoutSessions, ...cardioSessions]);
|
|
||||||
|
|
||||||
// Extract personal records from workouts
|
|
||||||
const personalRecords: Record<string, number> = {};
|
|
||||||
workoutSessions.forEach((session) => {
|
|
||||||
const maxWeight = Math.max(...session.sets.map((s) => s.weight));
|
|
||||||
const key = `${session.exerciseName}_pr`;
|
|
||||||
if (!personalRecords[key] || maxWeight > personalRecords[key]) {
|
|
||||||
personalRecords[key] = maxWeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const metrics: UserMetrics = {
|
|
||||||
totalWorkouts,
|
|
||||||
totalCardioDistance,
|
|
||||||
totalCaloriesBurned,
|
|
||||||
currentStreak,
|
|
||||||
personalRecords,
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(metrics));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating user metrics:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate consecutive days with activity
|
|
||||||
*/
|
|
||||||
const calculateStreak = (sessions: Array<WorkoutSession | CardioSession>): number => {
|
|
||||||
if (sessions.length === 0) return 0;
|
|
||||||
|
|
||||||
const dates = sessions.map((session) => new Date(session.date).toDateString()).filter((date, index, self) => self.indexOf(date) === index).sort((a, b) => new Date(b).getTime() - new Date(a).getTime());
|
|
||||||
|
|
||||||
let streak = 1;
|
|
||||||
for (let i = 1; i < dates.length; i++) {
|
|
||||||
const currentDate = new Date(dates[i]);
|
|
||||||
const previousDate = new Date(dates[i - 1]);
|
|
||||||
const diffTime = Math.abs(previousDate.getTime() - currentDate.getTime());
|
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
if (diffDays === 1) {
|
|
||||||
streak++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return streak;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all stored data
|
|
||||||
*/
|
|
||||||
export const clearAllData = (): void => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
localStorage.removeItem(STORAGE_KEYS.WORKOUT_SESSIONS);
|
|
||||||
localStorage.removeItem(STORAGE_KEYS.CARDIO_SESSIONS);
|
|
||||||
localStorage.removeItem(STORAGE_KEYS.NUTRITION_LOGS);
|
|
||||||
localStorage.removeItem(STORAGE_KEYS.USER_METRICS);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error clearing data:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export data as JSON for backup
|
|
||||||
*/
|
|
||||||
export const exportData = (): string => {
|
|
||||||
const data = {
|
|
||||||
workouts: getWorkoutSessions(),
|
|
||||||
cardio: getCardioSessions(),
|
|
||||||
nutrition: getNutritionLogs(),
|
|
||||||
metrics: getUserMetrics(),
|
|
||||||
exportDate: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
return JSON.stringify(data, null, 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import data from JSON backup
|
|
||||||
*/
|
|
||||||
export const importData = (jsonData: string): boolean => {
|
|
||||||
if (typeof window === 'undefined') return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(jsonData);
|
|
||||||
if (data.workouts) localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(data.workouts));
|
|
||||||
if (data.cardio) localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(data.cardio));
|
|
||||||
if (data.nutrition) localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(data.nutrition));
|
|
||||||
if (data.metrics) localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(data.metrics));
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error importing data:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
|
||||||
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Eye, EyeOff, Mail, Lock } from 'lucide-react';
|
|
||||||
import Input from '@/components/form/Input';
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
const newErrors: { email?: string; password?: string } = {};
|
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
newErrors.email = "Email é obrigatório";
|
|
||||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
||||||
newErrors.email = "Email inválido";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
newErrors.password = "Senha é obrigatória";
|
|
||||||
} else if (password.length < 6) {
|
|
||||||
newErrors.password = "Senha deve ter no mínimo 6 caracteres";
|
|
||||||
}
|
|
||||||
|
|
||||||
return newErrors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const newErrors = validateForm();
|
|
||||||
setErrors(newErrors);
|
|
||||||
|
|
||||||
if (Object.keys(newErrors).length === 0) {
|
|
||||||
setIsSubmitted(true);
|
|
||||||
console.log("Login attempt:", { email, password });
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsSubmitted(false);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProvider
|
|
||||||
defaultButtonVariant="elastic-effect"
|
|
||||||
defaultTextAnimation="entrance-slide"
|
|
||||||
borderRadius="pill"
|
|
||||||
contentWidth="smallMedium"
|
|
||||||
sizing="mediumSizeLargeTitles"
|
|
||||||
background="blurBottom"
|
|
||||||
cardStyle="gradient-bordered"
|
|
||||||
primaryButtonStyle="flat"
|
|
||||||
secondaryButtonStyle="glass"
|
|
||||||
headingFontWeight="extrabold"
|
|
||||||
>
|
|
||||||
<div id="nav" data-section="nav">
|
|
||||||
<NavbarStyleCentered
|
|
||||||
navItems={[
|
|
||||||
{ name: "Dashboard", id: "/" },
|
|
||||||
{ name: "Treino", id: "training" },
|
|
||||||
{ name: "Nutrição", id: "nutrition" },
|
|
||||||
{ name: "Comunidade", id: "community" },
|
|
||||||
{ name: "Perfil", id: "profile" }
|
|
||||||
]}
|
|
||||||
button={{ text: "Começar Agora", href: "/signup" }}
|
|
||||||
brandName="FitFlow Pro"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4">
|
|
||||||
<div className="w-full max-w-md">
|
|
||||||
<div className="bg-card rounded-3xl shadow-lg p-8 border border-accent/10">
|
|
||||||
<div className="mb-8">
|
|
||||||
<h1 className="text-3xl font-extrabold text-foreground mb-2">Bem-vindo de Volta</h1>
|
|
||||||
<p className="text-foreground/70">Faça login para acessar seu progresso</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-foreground mb-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Mail size={16} />
|
|
||||||
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>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
value={password}
|
|
||||||
onChange={setPassword}
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
className={errors.password ? "border-red-500" : "pr-10"}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
{errors.password && (
|
|
||||||
<p className="text-red-500 text-sm mt-1">{errors.password}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitted}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isSubmitted ? "Entrando..." : "Entrar"}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm text-foreground/70">
|
|
||||||
<p>
|
|
||||||
Não tem conta?{" "}
|
|
||||||
<a href="/signup" className="text-primary-cta font-semibold hover:underline">
|
|
||||||
Criar conta
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 pt-6 border-t border-accent/10 text-center text-xs text-foreground/50">
|
|
||||||
<p>Teste gratuito por 30 dias. Sem cartão de crédito necessário.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
742
src/app/page.tsx
742
src/app/page.tsx
@@ -12,375 +12,407 @@ import TestimonialCardTwelve from '@/components/sections/testimonial/Testimonial
|
|||||||
import SocialProofOne from '@/components/sections/socialProof/SocialProofOne';
|
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 BlogCardOne from '@/components/sections/blog/BlogCardOne';
|
||||||
import { WorkoutDataIntegration } from '@/app/components/WorkoutDataIntegration';
|
import { Activity, Apple, Brain, Dumbbell, Heart, Target, Zap, Users, Star, TrendingDown, TrendingUp, ChefHat } from 'lucide-react';
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
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: "Receitas", id: "recipes" },
|
||||||
{ 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?_wi=1", 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?_wi=1", 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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
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="recipes" data-section="recipes">
|
||||||
<PricingCardOne
|
<BlogCardOne
|
||||||
title="Nutrição Inteligente & Receituário"
|
title="Receitas por Objetivo"
|
||||||
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="Explore receitas deliciosas e saudáveis adaptadas ao seu objetivo: perder peso com déficit calórico ou ganhar massa com superávit estratégico."
|
||||||
tag="Sincronizado com Timeline"
|
tag="Culinária Fitness"
|
||||||
tagIcon={Apple}
|
tagIcon={ChefHat}
|
||||||
plans={[
|
blogs={[
|
||||||
{
|
{
|
||||||
id: "deficit", badge: "Perda de Peso", badgeIcon: TrendingDown,
|
id: "1", category: "Perda de Peso", title: "Frango Grelhado com Brócolis e Batata Doce", excerpt: "Receita de 380 kcal com alto teor proteico. Preparo em 25 minutos com macros otimizados para déficit calórico.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png?_wi=2", imageAlt: "Frango grelhado", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png", date: "15 Jan 2025"
|
||||||
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"
|
{
|
||||||
]
|
id: "2", category: "Ganho de Massa", title: "Pasta de Frango com Abacate e Ovos", excerpt: "Refeição de 850 kcal ideal para pós-treino. Alto em proteína (45g) e gorduras saudáveis para crescimento muscular.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png", imageAlt: "Pasta de frango", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png", date: "14 Jan 2025"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "surplus", badge: "Ganho de Massa", badgeIcon: TrendingUp,
|
id: "3", category: "Perda de Peso", title: "Sopa de Legumes com Peito de Frango", excerpt: "Receita saciante com apenas 220 kcal. Fibras e proteína para manter a saciedade e acelerar a perda de peso.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png", imageAlt: "Sopa de legumes", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png", date: "13 Jan 2025"
|
||||||
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"
|
{
|
||||||
]
|
id: "4", category: "Ganho de Massa", title: "Smoothie Pré-Treino: Aveia com Banana e Mantega de Amendoim", excerpt: "Preparação energética com 620 kcal. Carbos de rápida absorção e proteína para potencializar seus treinos.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/gps-running-tracker-interface-with-real--1773256980694-2abe167e.png?_wi=2", imageAlt: "Smoothie", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png", date: "12 Jan 2025"
|
||||||
}
|
},
|
||||||
]}
|
{
|
||||||
animationType="slide-up"
|
id: "5", category: "Perda de Peso", title: "Salada de Atum com Azeite Extra Virgem", excerpt: "Opção leve e rápida com 280 kcal. Ômega-3 e proteína para acelerar metabolismo e queimar calorias.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png", imageAlt: "Salada de atum", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/young-athlete-male-portrait-energetic-ex-1773256982698-63e4e494.png", date: "11 Jan 2025"
|
||||||
textboxLayout="default"
|
},
|
||||||
useInvertedBackground={false}
|
{
|
||||||
/>
|
id: "6", category: "Ganho de Massa", title: "Arroz Integral com Carne Vermelha e Batata Inglesa", excerpt: "Refeição completa com 950 kcal ideal para dias de treino intenso. Ferro, carbos complexos e proteína muscular.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png?_wi=1", imageAlt: "Arroz com carne", authorName: "Chef Nutrição", authorAvatar: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png", date: "10 Jan 2025"
|
||||||
</div>
|
}
|
||||||
|
]}
|
||||||
|
animationType="slide-up"
|
||||||
|
textboxLayout="default"
|
||||||
|
useInvertedBackground={false}
|
||||||
|
carouselMode="buttons"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="metrics" data-section="metrics">
|
<div id="nutrition" data-section="nutrition">
|
||||||
<MetricCardFourteen
|
<PricingCardOne
|
||||||
title="Suas Conquistas Importam. Veja Cada Número."
|
title="Nutrição Inteligente & Receituário"
|
||||||
tag="Performance Metrics"
|
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."
|
||||||
tagAnimation="slide-up"
|
tag="Sincronizado com Timeline"
|
||||||
metrics={[
|
tagIcon={Apple}
|
||||||
{
|
plans={[
|
||||||
id: "1", value: "10.000+", description: "Passos diários rastreados em tempo real com motivação visual de progresso."
|
{
|
||||||
},
|
id: "deficit", badge: "Perda de Peso", badgeIcon: TrendingDown,
|
||||||
{
|
price: "Déficit Calórico", subtitle: "Receitas otimizadas para queima de calorias", features: [
|
||||||
id: "2", value: "500 kg", description: "Volume total de peso levantado monitorado com progressão semanal automática."
|
"Macros calculados automaticamente", "Prep time entre 15-30 min", "Proteína alta, carboidrato estratégico", "Rastreamento integrado", "Sugestões diárias personalizadas"
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
id: "3", value: "150+ km", description: "Distância corrida mapeada com GPS, ritmo calculado e calorias precisas."
|
{
|
||||||
},
|
id: "surplus", badge: "Ganho de Massa", badgeIcon: TrendingUp,
|
||||||
{
|
price: "Superávit Estratégico", subtitle: "Nutrição para crescimento muscular", features: [
|
||||||
id: "4", value: "42 dias", description: "Sequência de treinos consistentes com badges de dedicação desbloqueados."
|
"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"
|
||||||
}
|
]
|
||||||
]}
|
}
|
||||||
metricsAnimation="slide-up"
|
]}
|
||||||
useInvertedBackground={false}
|
animationType="slide-up"
|
||||||
/>
|
textboxLayout="default"
|
||||||
</div>
|
useInvertedBackground={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="team" data-section="team">
|
<div id="metrics" data-section="metrics">
|
||||||
<TeamCardOne
|
<MetricCardFourteen
|
||||||
title="Comunidade de Atletas Profissionais"
|
title="Suas Conquistas Importam. Veja Cada Número."
|
||||||
description="Feed social onde você compartilha rotas de corrida, estatísticas de treino e compete em rankings semanais com outros usuários."
|
tag="Performance Metrics"
|
||||||
tag="Gamificação Social"
|
tagAnimation="slide-up"
|
||||||
tagIcon={Users}
|
metrics={[
|
||||||
members={[
|
{
|
||||||
{
|
id: "1", value: "10.000+", description: "Passos diários rastreados em tempo real com motivação visual de progresso."
|
||||||
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", value: "500 kg", description: "Volume total de peso levantado monitorado com progressão semanal automática."
|
||||||
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", value: "150+ km", description: "Distância corrida mapeada com GPS, ritmo calculado e calorias precisas."
|
||||||
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", value: "42 dias", description: "Sequência de treinos consistentes com badges de dedicação desbloqueados."
|
||||||
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."
|
}
|
||||||
}
|
]}
|
||||||
]}
|
metricsAnimation="slide-up"
|
||||||
animationType="blur-reveal"
|
useInvertedBackground={false}
|
||||||
textboxLayout="default"
|
/>
|
||||||
useInvertedBackground={false}
|
</div>
|
||||||
gridVariant="four-items-2x2-equal-grid"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="testimonials" data-section="testimonials">
|
<div id="team" data-section="team">
|
||||||
<TestimonialCardTwelve
|
<TeamCardOne
|
||||||
testimonials={[
|
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."
|
||||||
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."
|
tag="Gamificação Social"
|
||||||
},
|
tagIcon={Users}
|
||||||
{
|
members={[
|
||||||
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: "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: "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: "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: "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: "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: "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: "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: "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."
|
]}
|
||||||
}
|
animationType="blur-reveal"
|
||||||
]}
|
textboxLayout="default"
|
||||||
cardTitle="Mais de 150 mil atletas já transformaram seu corpo com FitFlow Pro"
|
useInvertedBackground={false}
|
||||||
cardTag="Veja o que eles dizem"
|
gridVariant="four-items-2x2-equal-grid"
|
||||||
cardTagIcon={Star}
|
/>
|
||||||
cardAnimation="blur-reveal"
|
</div>
|
||||||
useInvertedBackground={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="social-proof" data-section="social-proof">
|
<div id="testimonials" data-section="testimonials">
|
||||||
<SocialProofOne
|
<TestimonialCardTwelve
|
||||||
title="Confiado pelos Maiores Aplicativos e Plataformas de Fitness"
|
testimonials={[
|
||||||
description="FitFlow Pro integra-se perfeitamente com os ecossistemas de saúde mais populares do mundo."
|
{
|
||||||
tag="Parcerias Premium"
|
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."
|
||||||
tagIcon={Zap}
|
},
|
||||||
logos={[
|
{
|
||||||
"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"
|
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."
|
||||||
]}
|
},
|
||||||
names={[
|
{
|
||||||
"Apple Fitness", "Strava", "Fitbit", "Google Fit", "Peloton", "MyFitnessPal", "Garmin", "Nike Training"
|
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."
|
||||||
]}
|
},
|
||||||
textboxLayout="default"
|
{
|
||||||
useInvertedBackground={false}
|
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."
|
||||||
speed={40}
|
},
|
||||||
showCard={true}
|
{
|
||||||
/>
|
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."
|
||||||
</div>
|
},
|
||||||
|
{
|
||||||
|
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?_wi=2", imageAlt: "Marina S."
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
cardTitle="Mais de 150 mil atletas já transformaram seu corpo com FitFlow Pro"
|
||||||
|
cardTag="Veja o que eles dizem"
|
||||||
|
cardTagIcon={Star}
|
||||||
|
cardAnimation="blur-reveal"
|
||||||
|
useInvertedBackground={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="contact" data-section="contact">
|
<div id="social-proof" data-section="social-proof">
|
||||||
<ContactText
|
<SocialProofOne
|
||||||
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."
|
title="Confiado pelos Maiores Aplicativos e Plataformas de Fitness"
|
||||||
animationType="entrance-slide"
|
description="FitFlow Pro integra-se perfeitamente com os ecossistemas de saúde mais populares do mundo."
|
||||||
buttons={[
|
tag="Parcerias Premium"
|
||||||
{ text: "Começar Teste Grátis", href: "contact" },
|
tagIcon={Zap}
|
||||||
{ text: "Conversar com Especialista", href: "#contact" }
|
logos={[
|
||||||
]}
|
"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"
|
||||||
background={{ variant: "sparkles-gradient" }}
|
]}
|
||||||
useInvertedBackground={false}
|
names={[
|
||||||
/>
|
"Apple Fitness", "Strava", "Fitbit", "Google Fit", "Peloton", "MyFitnessPal", "Garmin", "Nike Training"
|
||||||
</div>
|
]}
|
||||||
|
textboxLayout="default"
|
||||||
|
useInvertedBackground={false}
|
||||||
|
speed={40}
|
||||||
|
showCard={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="footer" data-section="footer">
|
<div id="contact" data-section="contact">
|
||||||
<FooterLogoEmphasis
|
<ContactText
|
||||||
columns={[
|
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"
|
||||||
items: [
|
buttons={[
|
||||||
{ label: "Dashboard", href: "#dashboard" },
|
{ text: "Começar Teste Grátis", href: "contact" },
|
||||||
{ label: "Treino", href: "#training" },
|
{ text: "Conversar com Especialista", href: "#contact" }
|
||||||
{ label: "Nutrição", href: "#nutrition" }
|
]}
|
||||||
]
|
background={{ variant: "sparkles-gradient" }}
|
||||||
},
|
useInvertedBackground={false}
|
||||||
{
|
/>
|
||||||
items: [
|
</div>
|
||||||
{ label: "Cardio Hub", href: "#cardio" },
|
|
||||||
{ label: "Comunidade", href: "#community" },
|
<div id="footer" data-section="footer">
|
||||||
{ label: "Perfil", href: "#profile" }
|
<FooterLogoEmphasis
|
||||||
]
|
columns={[
|
||||||
},
|
{
|
||||||
{
|
items: [
|
||||||
items: [
|
{ label: "Dashboard", href: "#dashboard" },
|
||||||
{ label: "Blog", href: "#blog" },
|
{ label: "Treino", href: "#training" },
|
||||||
{ label: "Ajuda", href: "#help" },
|
{ label: "Receitas", href: "#recipes" }
|
||||||
{ label: "Suporte", href: "#support" }
|
]
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
items: [
|
||||||
items: [
|
{ label: "Cardio Hub", href: "#cardio" },
|
||||||
{ label: "Privacidade", href: "#privacy" },
|
{ label: "Nutrição", href: "#nutrition" },
|
||||||
{ label: "Termos", href: "#terms" },
|
{ label: "Perfil", href: "#profile" }
|
||||||
{ label: "Contato", href: "#contact" }
|
]
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
]}
|
items: [
|
||||||
logoText="FitFlow Pro"
|
{ label: "Blog", href: "#blog" },
|
||||||
/>
|
{ label: "Ajuda", href: "#help" },
|
||||||
</div>
|
{ label: "Suporte", href: "#support" }
|
||||||
</ThemeProvider>
|
]
|
||||||
</WorkoutDataIntegration>
|
},
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{ label: "Privacidade", href: "#privacy" },
|
||||||
|
{ label: "Termos", href: "#terms" },
|
||||||
|
{ label: "Contato", href: "#contact" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
logoText="FitFlow Pro"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
|
||||||
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Eye, EyeOff, Mail, Lock, User, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
||||||
import Input from '@/components/form/Input';
|
|
||||||
|
|
||||||
export default function SignupPage() {
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: "", email: "", password: "", confirmPassword: ""
|
|
||||||
});
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
||||||
const [errors, setErrors] = useState<{ [key: string]: string }>({});
|
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
const [passwordStrength, setPasswordStrength] = useState<"weak" | "medium" | "strong" | "">("");
|
|
||||||
|
|
||||||
const calculatePasswordStrength = (pwd: string): "weak" | "medium" | "strong" | "" => {
|
|
||||||
if (!pwd) return "";
|
|
||||||
if (pwd.length < 8) return "weak";
|
|
||||||
if (/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8}$/.test(pwd)) return "strong";
|
|
||||||
return "medium";
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (field: string, value: string) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[field]: value
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (field === "password") {
|
|
||||||
setPasswordStrength(calculatePasswordStrength(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
const newErrors: { [key: string]: string } = {};
|
|
||||||
|
|
||||||
if (!formData.name.trim()) {
|
|
||||||
newErrors.name = "Nome é obrigatório";
|
|
||||||
} else if (formData.name.trim().length < 2) {
|
|
||||||
newErrors.name = "Nome deve ter no mínimo 2 caracteres";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formData.email) {
|
|
||||||
newErrors.email = "Email é obrigatório";
|
|
||||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
||||||
newErrors.email = "Email inválido";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formData.password) {
|
|
||||||
newErrors.password = "Senha é obrigatória";
|
|
||||||
} else if (formData.password.length < 8) {
|
|
||||||
newErrors.password = "Senha deve ter no mínimo 8 caracteres";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formData.password !== formData.confirmPassword) {
|
|
||||||
newErrors.confirmPassword = "As senhas não correspondem";
|
|
||||||
}
|
|
||||||
|
|
||||||
return newErrors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const newErrors = validateForm();
|
|
||||||
setErrors(newErrors);
|
|
||||||
|
|
||||||
if (Object.keys(newErrors).length === 0) {
|
|
||||||
setIsSubmitted(true);
|
|
||||||
console.log("Signup attempt:", formData);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsSubmitted(false);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStrengthColor = () => {
|
|
||||||
switch (passwordStrength) {
|
|
||||||
case "weak":
|
|
||||||
return "text-red-500";
|
|
||||||
case "medium":
|
|
||||||
return "text-yellow-500";
|
|
||||||
case "strong":
|
|
||||||
return "text-green-500";
|
|
||||||
default:
|
|
||||||
return "text-foreground/30";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProvider
|
|
||||||
defaultButtonVariant="elastic-effect"
|
|
||||||
defaultTextAnimation="entrance-slide"
|
|
||||||
borderRadius="pill"
|
|
||||||
contentWidth="smallMedium"
|
|
||||||
sizing="mediumSizeLargeTitles"
|
|
||||||
background="blurBottom"
|
|
||||||
cardStyle="gradient-bordered"
|
|
||||||
primaryButtonStyle="flat"
|
|
||||||
secondaryButtonStyle="glass"
|
|
||||||
headingFontWeight="extrabold"
|
|
||||||
>
|
|
||||||
<div id="nav" data-section="nav">
|
|
||||||
<NavbarStyleCentered
|
|
||||||
navItems={[
|
|
||||||
{ name: "Dashboard", id: "/" },
|
|
||||||
{ name: "Treino", id: "training" },
|
|
||||||
{ name: "Nutrição", id: "nutrition" },
|
|
||||||
{ name: "Comunidade", id: "community" },
|
|
||||||
{ name: "Perfil", id: "profile" }
|
|
||||||
]}
|
|
||||||
button={{ text: "Entrar", href: "/login" }}
|
|
||||||
brandName="FitFlow Pro"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4">
|
|
||||||
<div className="w-full max-w-md">
|
|
||||||
<div className="bg-card rounded-3xl shadow-lg p-8 border border-accent/10">
|
|
||||||
<div className="mb-8">
|
|
||||||
<h1 className="text-3xl font-extrabold text-foreground mb-2">Começar Agora</h1>
|
|
||||||
<p className="text-foreground/70">Crie sua conta e inicie sua transformação</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-foreground mb-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<User size={16} />
|
|
||||||
Nome Completo
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(value) => handleInputChange("name", value)}
|
|
||||||
type="text"
|
|
||||||
placeholder="Seu nome"
|
|
||||||
required
|
|
||||||
className={errors.name ? "border-red-500" : ""}
|
|
||||||
/>
|
|
||||||
{errors.name && (
|
|
||||||
<div className="flex items-center gap-1 text-red-500 text-sm mt-1">
|
|
||||||
<AlertCircle size={14} />
|
|
||||||
{errors.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-foreground mb-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Mail size={16} />
|
|
||||||
Email
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={formData.email}
|
|
||||||
onChange={(value) => handleInputChange("email", value)}
|
|
||||||
type="email"
|
|
||||||
placeholder="seu@email.com"
|
|
||||||
required
|
|
||||||
className={errors.email ? "border-red-500" : ""}
|
|
||||||
/>
|
|
||||||
{errors.email && (
|
|
||||||
<div className="flex items-center gap-1 text-red-500 text-sm mt-1">
|
|
||||||
<AlertCircle size={14} />
|
|
||||||
{errors.email}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</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>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
value={formData.password}
|
|
||||||
onChange={(value) => handleInputChange("password", value)}
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
className={errors.password ? "border-red-500" : "pr-10"}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
{passwordStrength && (
|
|
||||||
<div className={`flex items-center gap-1 text-xs mt-2 ${getStrengthColor()}`}>
|
|
||||||
<CheckCircle2 size={12} />
|
|
||||||
Força: {passwordStrength === "weak" ? "Fraca" : passwordStrength === "medium" ? "Média" : "Forte"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{errors.password && (
|
|
||||||
<div className="flex items-center gap-1 text-red-500 text-sm mt-1">
|
|
||||||
<AlertCircle size={14} />
|
|
||||||
{errors.password}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-foreground mb-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Lock size={16} />
|
|
||||||
Confirmar Senha
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
value={formData.confirmPassword}
|
|
||||||
onChange={(value) => handleInputChange("confirmPassword", value)}
|
|
||||||
type={showConfirmPassword ? "text" : "password"}
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
className={errors.confirmPassword ? "border-red-500" : "pr-10"}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
{errors.confirmPassword && (
|
|
||||||
<div className="flex items-center gap-1 text-red-500 text-sm mt-1">
|
|
||||||
<AlertCircle size={14} />
|
|
||||||
{errors.confirmPassword}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitted}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isSubmitted ? "Criando conta..." : "Criar Conta"}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm text-foreground/70">
|
|
||||||
<p>
|
|
||||||
Já tem conta?{" "}
|
|
||||||
<a href="/login" className="text-primary-cta font-semibold hover:underline">
|
|
||||||
Fazer login
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 pt-6 border-t border-accent/10 text-center text-xs text-foreground/50">
|
|
||||||
<p>Teste gratuito por 30 dias. Sem cartão de crédito necessário.</p>
|
|
||||||
<p className="mt-2">Ao criar uma conta, você concorda com nossos Termos de Serviço e Política de Privacidade.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user