Files
nutriscan/app/sobre/components/StorySection.tsx
T
Evandro Fonseca Junior f98c648311 feat(footer, header, landing): enhance UI components and improve navigation
- Updated Footer component with new links and dynamic year display.
- Refactored Header component to improve mobile navigation and theme toggling.
- Enhanced DonationSection and Features components with improved layout and animations.
- Updated HowItWorks and Impact sections for better user engagement and clarity.
- Improved product page layout and error handling in API interactions.
- General UI refinements across various components for a cohesive user experience.
2026-01-12 19:55:50 -03:00

164 lines
5.8 KiB
TypeScript

"use client";
import React, { useRef } from "react";
import { motion, useScroll, useSpring } from "framer-motion";
import { Calendar, Lightbulb, Heart, Target } from "lucide-react";
import { cn } from "@/lib/utils";
// 1. Definição da Interface de Dados
interface StoryStep {
year: string;
title: string;
description: string;
icon: React.ElementType;
color: string;
side: "left" | "right";
}
const STORY_DATA: StoryStep[] = [
{
year: "2024",
title: "A Pergunta Simples",
description: "O que realmente tem nesse produto? Decifrar tabelas nutricionais era um desafio individual que percebemos ser global.",
icon: Calendar,
color: "blue",
side: "left",
},
{
year: "Descoberta",
title: "Não Estávamos Sozinhos",
description: "Milhares de pessoas buscam transparência alimentar. A necessidade de dados abertos tornou-se uma urgência técnica.",
icon: Lightbulb,
color: "green",
side: "right",
},
{
year: "Solução",
title: "NutriScan Nasce",
description: "Nascido da Engenharia para o mundo. Uma ponte entre o código de barras e a verdade nutricional.",
icon: Heart,
color: "purple",
side: "left",
},
{
year: "Hoje",
title: "Nossa Missão",
description: "Democratizar a informação. Conectar cidadãos à maior base de dados colaborativa do planeta.",
icon: Target,
color: "orange",
side: "right",
},
];
export function StorySection() {
const containerRef = useRef<HTMLDivElement>(null);
// 2. Lógica de Scroll Progressivo (Engenharia de Movimento)
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end end"],
});
const scaleY = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001
});
return (
<section ref={containerRef} className="py-32 bg-white dark:bg-[#030712] relative overflow-hidden">
<div className="max-w-7xl mx-auto px-6">
{/* Header com Animação de Entrada */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
className="text-center mb-32"
>
<h2 className="text-4xl md:text-6xl font-black text-gray-900 dark:text-white mb-6 tracking-tighter">
Nossa <span className="bg-gradient-to-r from-blue-600 to-green-500 bg-clip-text text-transparent">história</span>
</h2>
<p className="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto font-medium">
De uma dúvida individual a uma plataforma de transparência global.
</p>
</motion.div>
<div className="relative">
{/* 3. A Linha do Tempo Reativa */}
<div className="absolute left-1/2 transform -translate-x-1/2 w-[2px] h-full bg-gray-100 dark:bg-gray-800 hidden md:block">
<motion.div
style={{ scaleY }}
className="absolute top-0 w-full h-full bg-gradient-to-b from-blue-500 via-green-500 to-orange-500 origin-top"
/>
</div>
<div className="space-y-24 md:space-y-32">
{STORY_DATA.map((step, index) => (
<TimelineItem key={index} step={step} index={index} />
))}
</div>
</div>
</div>
</section>
);
}
// 4. Sub-componente com Encapsulamento de Animação
function TimelineItem({ step }: { step: StoryStep; index: number }) {
const isLeft = step.side === "left";
const colorMap = {
blue: "bg-blue-500 text-blue-500 border-blue-200 dark:border-blue-900/30",
green: "bg-green-500 text-green-500 border-green-200 dark:border-green-900/30",
purple: "bg-purple-500 text-purple-500 border-purple-200 dark:border-purple-900/30",
orange: "bg-orange-500 text-orange-500 border-orange-200 dark:border-orange-900/30",
};
return (
<div className={cn(
"relative flex flex-col md:flex-row items-center justify-between w-full",
isLeft ? "md:flex-row" : "md:flex-row-reverse"
)}>
{/* Conteúdo */}
<motion.div
initial={{ opacity: 0, x: isLeft ? -50 : 50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6, delay: 0.1 }}
className="w-full md:w-[45%] group"
>
<div className={cn(
"p-8 rounded-[2rem] bg-gray-50 dark:bg-gray-900/50 border border-gray-100 dark:border-gray-800 transition-all duration-500",
"hover:shadow-2xl hover:border-transparent group-hover:bg-white dark:group-hover:bg-gray-900"
)}>
<div className="flex items-center gap-4 mb-6">
<div className={cn("w-12 h-12 rounded-xl flex items-center justify-center text-white shadow-lg", colorMap[step.color as keyof typeof colorMap].split(' ')[0])}>
<step.icon size={24} />
</div>
<div>
<span className={cn("text-xs font-black uppercase tracking-widest", colorMap[step.color as keyof typeof colorMap].split(' ')[1])}>
{step.year}
</span>
<h3 className="text-xl font-bold text-gray-900 dark:text-white leading-tight">
{step.title}
</h3>
</div>
</div>
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
{step.description}
</p>
</div>
</motion.div>
{/* Ponto Central com Efeito de Pulso */}
<div className="absolute left-1/2 transform -translate-x-1/2 w-4 h-4 z-20 hidden md:block">
<motion.div
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
className={cn("w-full h-full rounded-full ring-4 ring-white dark:ring-[#030712] shadow-sm", colorMap[step.color as keyof typeof colorMap].split(' ')[0])}
/>
</div>
<div className="hidden md:block w-[45%]" />
</div>
);
}