feat: complete NutriScan redesign

This commit is contained in:
evandrodevbr committed 2026-07-16 16:31:50 -03:00
1 parent c1fe4af997
commit f71cd167b3
163 files changed
+30802 -14169

No files matched your search

+43 -123
View File
@@ -1,164 +1,84 @@
"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",
description:
"O que realmente tem nesse produto? Decifrar tabelas nutricionais era um desafio individual que percebemos ser global.",
},
{
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",
description:
"Milhares de pessoas buscam transparência alimentar. A necessidade de dados abertos tornou-se uma urgência coletiva.",
},
{
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",
description:
"Uma ponte entre o código de barras e a verdade nutricional. Engenharia a serviço de uma escolha consciente.",
},
{
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",
description:
"Democratizar a informação. Conectar cidadãos à maior base de dados colaborativa de alimentos do planeta.",
},
];
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">
<section className="bg-base section-py">
<div className="container-editorial">
{/* Section header */}
<div className="section-header">
<span className="eyebrow">Nossa História</span>
<h2 className="font-display">
De uma dúvida individual a uma plataforma de transparência global.
</p>
</motion.div>
</h2>
</div>
{/* Timeline — clean editorial list with hairlines */}
<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>
{/* Vertical accent line */}
<div className="absolute left-[5.75rem] top-0 bottom-0 w-px bg-[var(--border-subtle)] hidden md:block" />
<div className="space-y-24 md:space-y-32">
<div className="space-y-0">
{STORY_DATA.map((step, index) => (
<TimelineItem key={index} step={step} index={index} />
<div
key={index}
className="group relative grid grid-cols-1 md:grid-cols-[6rem_1fr] gap-0 border-b border-[var(--border-subtle)] last:border-b-0 py-10 hover-lift cursor-default"
>
{/* Year label */}
<div className="md:pr-8 mb-4 md:mb-0 flex md:flex-col items-center md:items-start md:pt-1">
<span className="label text-accent">{step.year}</span>
{/* Timeline dot */}
<div className="hidden md:block mt-4 ml-px w-2 h-2 rounded-full bg-[var(--border-strong)] group-hover:bg-[var(--accent)] transition-colors duration-300" />
</div>
{/* Content */}
<div className="md:pl-12">
<h3 className="font-display text-display-sm text-[var(--fg-primary)] mb-3">
{step.title}
</h3>
<p className="text-[var(--fg-secondary)] leading-relaxed max-w-xl">
{step.description}
</p>
</div>
</div>
))}
</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>
);
}