feat: complete NutriScan redesign
This commit is contained in:
1 parent
c1fe4af997
commit
f71cd167b3
163 files changed
+30802
-14169
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user