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

+79 -65
View File
@@ -1,97 +1,111 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { Heart, Coffee, ShieldCheck, Server, Globe, Code2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Heart, Server, ShieldCheck, Globe } from "lucide-react";
export function DonationSection() {
return (
<section className="py-24 bg-white dark:bg-[#030712] relative overflow-hidden">
{/* Background Decor sutil */}
<div className="absolute top-0 right-0 w-1/3 h-1/3 bg-blue-500/5 blur-[120px] rounded-full pointer-events-none" />
<section className="bg-base section-py">
<div className="container-editorial">
<div className="max-w-5xl mx-auto px-6 relative z-10">
<div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Lado Esquerdo: A Missão (Copywriting Técnico) */}
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
>
<h2 className="text-4xl md:text-5xl font-black text-gray-900 dark:text-white mb-6 tracking-tighter leading-tight">
Mantenha a <span className="text-blue-600">Engenharia</span> Livre de Interesses.
{/* ── Section header ───────────────────────────────────────────── */}
<div className="mb-16">
<span className="label text-[var(--accent)] block mb-4">Apoie o Projeto</span>
<hr className="hairline mb-10" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-end">
<h2 className="font-display text-display-lg text-[var(--fg-primary)]">
Mantenha a informação{" "}
<em className="font-display-i text-[var(--accent)]">livre.</em>
</h2>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8 leading-relaxed">
O NutriScan é um projeto de código aberto nascido em Garuva, SC. Operamos sem anúncios e sem venda de dados. Nosso único combustível é o apoio da comunidade que acredita na democratização da saúde.
<p className="text-[var(--fg-secondary)] text-lg leading-relaxed max-w-md">
O NutriScan opera sem anúncios e sem venda de dados. Nosso único
combustível é o apoio de quem acredita na democratização da saúde.
</p>
</div>
</div>
<div className="space-y-4">
{/* ── Content grid ─────────────────────────────────────────────── */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-20 items-start">
{/* Left: What your support funds */}
<div className="lg:col-span-6">
<div className="divide-y divide-[var(--border-subtle)]">
{[
{ icon: Server, text: "Manutenção de Servidores e API de Alta Latência" },
{ icon: ShieldCheck, text: "Segurança de Dados e Auditoria Open Source" },
{ icon: Globe, text: "Expansão da Base de Dados Internacional" },
].map((item, i) => (
<div key={i} className="flex items-center gap-3 text-sm font-medium text-gray-700 dark:text-gray-300">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<item.icon className="w-4 h-4 text-blue-600" />
{
icon: Server,
title: "Infraestrutura",
desc: "Servidores, CDN e API de alta disponibilidade para garantir acesso global sem interrupções.",
},
{
icon: ShieldCheck,
title: "Segurança & Auditoria",
desc: "Revisões de código open source, análise de dependências e manutenção da integridade dos dados.",
},
{
icon: Globe,
title: "Expansão da Base",
desc: "Integração com novos mercados e suporte a mais países, línguas e categorias de produtos.",
},
].map(({ icon: Icon, title, desc }, i) => (
<div key={i} className="flex gap-6 py-8 group">
<div className="shrink-0 mt-0.5">
<Icon
className="w-5 h-5 text-[var(--fg-muted)] group-hover:text-[var(--accent)] transition-colors duration-300"
strokeWidth={1.5}
/>
</div>
<div>
<h4 className="font-sans font-semibold text-[var(--fg-primary)] mb-1.5">
{title}
</h4>
<p className="text-[var(--fg-secondary)] text-sm leading-relaxed">
{desc}
</p>
</div>
{item.text}
</div>
))}
</div>
</motion.div>
</div>
{/* Lado Direito: O Card de Doação (Psychological Anchoring) */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="relative"
>
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-100 dark:border-gray-800 p-8 rounded-[2.5rem] shadow-2xl backdrop-blur-sm">
<div className="flex items-center gap-4 mb-8">
<div className="w-12 h-12 bg-gradient-to-br from-yellow-400 to-orange-500 rounded-2xl flex items-center justify-center shadow-lg shadow-orange-500/20">
<Coffee className="w-6 h-6 text-white" />
</div>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">Apoie com um Café</h4>
<p className="text-xs text-gray-500 uppercase tracking-widest font-mono">Status: Sistema Online</p>
</div>
</div>
{/* Right: Donation card */}
<div className="lg:col-span-6">
<div className="card-warm p-10 rounded-xl">
<blockquote className="italic text-gray-600 dark:text-gray-400 text-sm border-l-2 border-blue-500 pl-4 mb-8">
&quot;Como desenvolvedor independente, cada contribuição me permite dedicar mais horas à refatoração e novas features para o NutriScan.&quot;
{/* Eyebrow */}
<span className="label text-[var(--fg-muted)] block mb-8">
Desenvolvido em Garuva, SC · Brasil
</span>
{/* Quote */}
<blockquote className="border-l-2 border-[var(--accent)] pl-5 mb-10">
<p className="font-display text-display-sm text-[var(--fg-primary)] leading-snug mb-3">
&ldquo;Cada contribuição me permite dedicar mais horas ao{" "}
<em className="font-display-i">NutriScan</em>.&rdquo;
</p>
<cite className="label text-[var(--fg-muted)] not-italic">
— Evandro, desenvolvedor
</cite>
</blockquote>
{/* CTA */}
<a
href="https://buymeacoffee.com/evandrotruuta"
target="_blank"
rel="noopener noreferrer"
className="block group"
className="btn-accent w-full justify-center py-4 text-base"
>
<Button className="w-full h-16 bg-blue-600 hover:bg-blue-700 text-white rounded-2xl font-bold text-lg transition-all group-hover:scale-[1.02] active:scale-95 shadow-xl shadow-blue-500/25">
<Heart className="w-5 h-5 mr-3 fill-white group-hover:animate-pulse" />
Contribuir agora
</Button>
<Heart className="w-4 h-4" strokeWidth={1.5} />
Contribuir agora
</a>
<div className="mt-6 flex justify-center gap-4">
<div className="flex items-center gap-1.5 text-[10px] font-bold text-gray-400 uppercase tracking-tighter">
<Code2 size={12} /> Engenharia Brasileira
</div>
</div>
<p className="mt-4 label text-center text-[var(--fg-faint)]">
Qualquer valor ajuda a manter o NutriScan gratuito.
</p>
</div>
{/* Elemento Decorativo: Floating Badge */}
<div className="absolute -bottom-6 -right-6 bg-white dark:bg-gray-800 p-4 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 animate-bounce">
<p className="text-[10px] font-black text-blue-600 uppercase">Qualquer valor ajuda!</p>
</div>
</motion.div>
</div>
</div>
</div>
</section>
);
}
}
+96 -118
View File
@@ -1,147 +1,125 @@
"use client";
import React from "react";
import { motion } from "framer-motion"; // ESSENCIAL: Evita erros de referência
import { Shield, Zap, Sparkles, Heart, CheckCircle } from "lucide-react";
import { cn } from "@/lib/utils";
// Definição de Tipos para garantir integridade no TS
interface Feature {
icon: React.ElementType;
number: string;
title: string;
description: string;
color: string;
bgColor: string;
iconBg: string;
benefit: string;
detail: string;
}
const features: Feature[] = [
{
icon: Shield,
title: "Dados da Comunidade",
description: "Informações fornecidas pela Open Food Facts, a maior base colaborativa global mantida por voluntários.",
color: "text-blue-600 dark:text-blue-400",
bgColor: "bg-blue-50 dark:bg-blue-900/20",
iconBg: "bg-blue-100 dark:bg-blue-900/40",
benefit: "Base colaborativa mundial",
number: "01",
title: "Dados Colaborativos",
description:
"Informações fornecidas pelo Open Food Facts, a maior base colaborativa global — a Wikipedia dos alimentos, mantida por voluntários em todo o mundo.",
detail: "2 M+ produtos",
},
{
icon: Zap,
title: "Atualização Contínua",
description: "Novos produtos e fórmulas atualizados a cada minuto pela comunidade. Dados sempre frescos no seu dispositivo.",
color: "text-green-600 dark:text-green-400",
bgColor: "bg-green-50 dark:bg-green-900/20",
iconBg: "bg-green-100 dark:bg-green-900/40",
benefit: "Atualizado em tempo real",
number: "02",
title: "Sempre Atualizado",
description:
"Novos produtos e reformulações são adicionados a cada minuto pela comunidade. Conectamos você direto à fonte, sem intermediários.",
detail: "Atualização em tempo real",
},
{
icon: Sparkles,
title: "Velocidade de Resposta",
description: "Interface otimizada para decisões rápidas. Escaneie e obtenha o diagnóstico nutricional em poucos segundos.",
color: "text-purple-600 dark:text-purple-400",
bgColor: "bg-purple-50 dark:bg-purple-900/20",
iconBg: "bg-purple-100 dark:bg-purple-900/40",
benefit: "Resultados em <10s",
number: "03",
title: "Decisão em Segundos",
description:
"Interface projetada para clareza imediata. Nutri-Score, NOVA e alérgenos visíveis à primeira vista — sem ruído visual.",
detail: "Resultados em < 10 s",
},
{
icon: Heart,
title: "Filosofia Open Source",
description: "O NutriScan é e sempre será gratuito. Conhecimento sobre saúde deve ser um direito universal.",
color: "text-orange-600 dark:text-orange-400",
bgColor: "bg-orange-50 dark:bg-orange-900/20",
iconBg: "bg-orange-100 dark:bg-orange-900/40",
benefit: "Gratuito para sempre",
number: "04",
title: "Open Source, Sempre",
description:
"O NutriScan é e sempre será gratuito. Conhecimento sobre saúde deve ser um direito universal, sem anúncios ou venda de dados.",
detail: "Licença MIT",
},
];
// Variantes de animação para o container pai (Stagger effect)
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.15 },
},
};
// Variantes para cada card individual
const cardVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { type: "spring", stiffness: 100 },
},
};
export function Features() {
return (
<section className="py-24 bg-white dark:bg-[#030712] relative overflow-hidden">
{/* Background Decor: Sutil gradiente para profundidade */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-blue-500/5 via-transparent to-transparent pointer-events-none" />
<section className="bg-base section-py">
<div className="container-editorial">
<div className="max-w-7xl mx-auto px-6 relative z-10">
{/* Header com Motion */}
<motion.div
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-20"
>
<h2 className="text-4xl md:text-5xl font-black text-gray-900 dark:text-white mb-6 tracking-tighter">
Por que confiar no <span className="text-blue-600">NutriScan</span>
</h2>
<p className="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto font-medium">
Engenharia de dados a serviço da sua saúde, com transparência total e zero custo.
</p>
</motion.div>
{/* ── Section header ───────────────────────────────────────────── */}
<div className="mb-16">
<span className="label text-[var(--accent)] block mb-4">Recursos</span>
<hr className="hairline mb-10" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-end">
<h2 className="font-display text-display-lg text-[var(--fg-primary)]">
Por que confiar no{" "}
<em className="font-display-i text-[var(--accent)]">NutriScan</em>
</h2>
<p className="text-[var(--fg-secondary)] text-lg leading-relaxed max-w-md">
Engenharia de dados a serviço da sua saúde, com transparência
total e zero custo.
</p>
</div>
</div>
{/* Features Grid com Stagger */}
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
className="grid grid-cols-1 md:grid-cols-2 gap-8"
>
{features.map((feature, index) => (
<motion.div
key={index}
variants={cardVariants}
whileHover={{ y: -5, transition: { duration: 0.2 } }}
className="group relative bg-gray-50 dark:bg-gray-900/50 rounded-3xl p-8 border border-gray-100 dark:border-gray-800 hover:border-blue-500/30 transition-colors"
>
{/* Icon Container */}
<div
className={cn(
feature.iconBg,
"w-14 h-14 rounded-2xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
)}
>
<feature.icon className={cn("w-7 h-7", feature.color)} />
</div>
{/* Text Content */}
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-4 tracking-tight">
{feature.title}
</h3>
<p className="text-gray-600 dark:text-gray-400 mb-8 leading-relaxed">
{feature.description}
</p>
{/* Benefit Badge */}
<div className={cn(
"inline-flex items-center px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest",
feature.bgColor,
feature.color
)}>
<CheckCircle className="w-3.5 h-3.5 mr-2" />
<span>{feature.benefit}</span>
</div>
</motion.div>
{/* ── Feature rows ─────────────────────────────────────────────── */}
<div className="divide-y divide-[var(--border-subtle)]">
{features.map((f, i) => (
<FeatureRow key={f.number} feature={f} index={i} />
))}
</motion.div>
</div>
{/* ── Bottom rule ──────────────────────────────────────────────── */}
<hr className="hairline mt-0" />
</div>
</section>
);
}
}
function FeatureRow({
feature,
index,
}: {
feature: Feature;
index: number;
}) {
return (
<div
className={cn(
"group grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-12 py-10 md:py-12",
"transition-colors duration-300 hover:bg-[var(--bg-surface)]",
"cursor-default -mx-4 px-4 md:-mx-6 md:px-6"
)}
style={{ animationDelay: `${index * 0.1}s` }}
>
{/* Step number */}
<div className="md:col-span-1 flex items-start">
<span className="label text-[var(--fg-faint)] group-hover:text-[var(--accent)] transition-colors duration-300">
{feature.number}
</span>
</div>
{/* Title */}
<div className="md:col-span-3 flex items-start">
<h3 className="font-display text-display-sm text-[var(--fg-primary)] leading-tight">
{feature.title}
</h3>
</div>
{/* Description */}
<div className="md:col-span-6 flex items-start">
<p className="text-[var(--fg-secondary)] leading-relaxed text-base">
{feature.description}
</p>
</div>
{/* Detail pill */}
<div className="md:col-span-2 flex items-start md:justify-end">
<span className="inline-flex items-center gap-1.5 text-[var(--accent)] label">
<span className="w-1.5 h-1.5 rounded-full bg-[var(--accent)] shrink-0" />
{feature.detail}
</span>
</div>
</div>
);
}
+135 -214
View File
@@ -2,48 +2,40 @@
import React, { useState, useMemo } from "react";
import { useRouter } from "next/navigation";
import { Search, ScanLine, Globe, Activity, Database, Zap, Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Search, ScanLine, Activity, Globe } from "lucide-react";
import { useGeolocation } from "@/hooks/use-geolocation";
import { useSearchLogic } from "@/hooks/use-search-logic";
import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "framer-motion";
export function Hero() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const { countryCode, isMounted } = useGeolocation();
const { searchType, isValid, loading, setLoading } = useSearchLogic(searchQuery);
const { theme } = useTheme();
const searchConfig = useMemo(() => ({
barcode: {
placeholder: "Insira o código EAN/UPC (ex: 789...)",
button: "Analisar Produto",
icon: <ScanLine className="w-5 h-5" />,
subtext: "Protocolo: Detecção de Código de Barras",
color: "blue",
gradient: "from-blue-600 via-cyan-500 to-blue-400"
},
name: {
placeholder: "Qual alimento deseja investigar?",
button: "Consultar Banco",
icon: <Search className="w-5 h-5" />,
subtext: "Protocolo: Pesquisa em Base Colaborativa",
color: "emerald",
gradient: "from-emerald-600 via-teal-500 to-green-400"
},
auto: {
placeholder: "Digite o nome ou aponte o código...",
button: "Buscar",
icon: <Activity className="w-5 h-5" />,
subtext: "Aguardando input do operador...",
color: "slate",
gradient: "from-slate-600 via-blue-500 to-slate-400"
}
}), []);
const searchConfig = useMemo(
() => ({
barcode: {
placeholder: "Cole o código EAN/UPC do produto…",
button: "Analisar",
icon: <ScanLine className="w-4 h-4" />,
label: "Código de barras detectado",
},
name: {
placeholder: "Nome do alimento ou marca…",
button: "Buscar",
icon: <Search className="w-4 h-4" />,
label: "Pesquisa por nome",
},
auto: {
placeholder: "Nome do produto ou código de barras…",
button: "Buscar",
icon: <Activity className="w-4 h-4" />,
label: "Detecção automática",
},
}),
[]
);
const current = searchConfig[searchType];
@@ -51,211 +43,140 @@ export function Hero() {
e.preventDefault();
if (!searchQuery.trim()) return;
setLoading(true);
const target = searchType === "barcode" && isValid
? `/produto/${searchQuery.trim()}`
: `/resultados?q=${encodeURIComponent(searchQuery.trim())}${countryCode ? `&country=${countryCode}` : ""}`;
const target =
searchType === "barcode" && isValid
? `/produto/${searchQuery.trim()}`
: `/resultados?q=${encodeURIComponent(searchQuery.trim())}${
countryCode ? `&country=${countryCode}` : ""
}`;
router.push(target);
};
return (
<section className="relative min-h-[90vh] flex flex-col items-center justify-center px-6 py-20 overflow-hidden bg-white dark:bg-[#030712] transition-colors duration-500">
{/* 1. INFRAESTRUTURA VISUAL: Background de Engenharia */}
<div className="absolute inset-0 z-0">
{/* Grid de Coordenadas */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#e2e8f0_1px,transparent_1px),linear-gradient(to_bottom,#e2e8f0_1px,transparent_1px)] dark:bg-[linear-gradient(to_right,#1e293b_1px,transparent_1px),linear-gradient(to_bottom,#1e293b_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)] opacity-20 dark:opacity-20 transition-opacity duration-500" />
{/* Glow de Fundo Reativo */}
<motion.div
animate={{
scale: searchQuery ? 1.2 : 1,
opacity: searchQuery ? (theme === "dark" ? 0.15 : 0.08) : (theme === "dark" ? 0.05 : 0.02)
}}
className={cn(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[500px] rounded-full blur-[120px] transition-colors duration-700",
searchType === "barcode" ? "bg-blue-600 dark:bg-blue-600" : searchType === "name" ? "bg-emerald-600 dark:bg-emerald-600" : "bg-blue-400 dark:bg-blue-900"
<section className="relative bg-base overflow-hidden">
{/* ── Subtle grain texture ──────────────────────────────────────── */}
<div
className="pointer-events-none absolute inset-0 opacity-[0.025]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
backgroundSize: "200px 200px",
}}
/>
<div className="container-editorial relative z-10">
{/* ── Upper label row ─────────────────────────────────────────── */}
<div className="pt-24 pb-6 flex items-center justify-between">
<span className="label text-[var(--fg-muted)]">
Open Food Facts · Base Colaborativa
</span>
{isMounted && countryCode && (
<span className="label text-[var(--fg-muted)] flex items-center gap-1.5">
<Globe className="w-3 h-3" />
{countryCode.toUpperCase()}
</span>
)}
/>
</div>
{/* Elemento Decorativo Lua/Sol */}
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 1, delay: 0.5 }}
className="absolute top-16 right-8 md:top-24 md:right-16 lg:top-32 lg:right-24 z-0 pointer-events-none"
{/* ── Hairline ────────────────────────────────────────────────── */}
<hr className="hairline" />
{/* ── Hero headline ───────────────────────────────────────────── */}
<div className="py-16 md:py-24 max-w-4xl">
<h1
className="font-display text-display-xl text-[var(--fg-primary)] leading-[0.88] tracking-[-0.03em] mb-8 animate-fade-up"
style={{ animationFillMode: "both" }}
>
A verdade sobre{" "}
<em className="not-italic font-display-i text-[var(--accent)]">
o que você come.
</em>
</h1>
<p
className="text-lg md:text-xl text-[var(--fg-secondary)] max-w-xl leading-relaxed animate-fade-up anim-delay-1"
style={{ animationFillMode: "both" }}
>
Dados nutricionais completos de 2 milhões de produtos —
Nutri-Score, NOVA, ingredientes e alérgenos. Gratuito, sempre.
</p>
</div>
{/* ── Search form ─────────────────────────────────────────────── */}
<div
className="pb-20 animate-fade-up anim-delay-2"
style={{ animationFillMode: "both" }}
>
<AnimatePresence mode="wait">
{theme === "dark" ? (
<motion.div
key="moon"
initial={{ opacity: 0, rotate: -90 }}
animate={{ opacity: 1, rotate: 0 }}
exit={{ opacity: 0, rotate: 90 }}
transition={{ duration: 0.5 }}
>
<Moon className="w-24 h-24 md:w-32 md:h-32 lg:w-40 lg:h-40 text-blue-400/20 transition-colors duration-500" />
</motion.div>
) : (
<motion.div
key="sun"
initial={{ opacity: 0, rotate: 90 }}
animate={{ opacity: 1, rotate: 0 }}
exit={{ opacity: 0, rotate: -90 }}
transition={{ duration: 0.5 }}
>
<Sun className="w-24 h-24 md:w-32 md:h-32 lg:w-40 lg:h-40 text-yellow-400/30 transition-colors duration-500" />
</motion.div>
)}
</AnimatePresence>
</motion.div>
</div>
<div className="relative z-10 max-w-5xl mx-auto text-center">
{/* 2. BADGE DE STATUS DO SISTEMA */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="inline-flex items-center gap-3 px-4 py-2 rounded-full bg-gray-100/80 dark:bg-white/5 border border-gray-200 dark:border-white/10 backdrop-blur-md mb-12 transition-colors duration-500"
>
<div className="flex -space-x-2">
<div className="w-6 h-6 rounded-full border-2 border-white dark:border-[#030712] bg-blue-500 flex items-center justify-center">
<Database size={10} className="text-white" />
</div>
<div className="w-6 h-6 rounded-full border-2 border-white dark:border-[#030712] bg-emerald-500 flex items-center justify-center">
<Zap size={10} className="text-white" />
</div>
</div>
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-600 dark:text-slate-400 transition-colors duration-500">
NutriScan Protocol <span className="text-blue-600 dark:text-blue-500">v2.1</span> • {isMounted ? countryCode?.toUpperCase() : "INTL"}
</span>
</motion.div>
{/* 3. HEADLINE DE ALTA DENSIDADE */}
<h1 className="text-5xl md:text-8xl font-black text-gray-900 dark:text-white mb-8 tracking-tighter leading-[0.85] perspective-1000 transition-colors duration-500">
A verdade sobre <br />
<span className={cn(
"text-transparent bg-clip-text bg-gradient-to-r transition-all duration-700",
current.gradient
)}>
seus alimentos.
</span>
</h1>
<p className="text-gray-600 dark:text-slate-400 text-lg md:text-xl max-w-2xl mx-auto mb-16 leading-relaxed font-medium transition-colors duration-500">
Engenharia de dados aplicada à transparência nutricional.
Descriptografamos rótulos para que você tome decisões soberanas.
</p>
{/* 4. O TERMINAL DE BUSCA (A PEÇA CENTRAL) */}
<form onSubmit={handleSearch} className="relative max-w-3xl mx-auto group">
{/* Efeito de Borda Neon Reativa */}
<div className={cn(
"absolute -inset-[2px] rounded-[2rem] blur-sm opacity-20 group-focus-within:opacity-100 transition-all duration-500 bg-gradient-to-r",
current.gradient
)} />
<div className="relative flex items-center bg-white/90 dark:bg-slate-900/90 backdrop-blur-2xl rounded-[1.8rem] border border-gray-200 dark:border-white/10 p-2 shadow-2xl transition-colors duration-500">
<div className="flex-1 relative flex items-center">
{/* Ícone de Estado Animado */}
<div className="absolute left-6">
<AnimatePresence mode="wait">
<motion.div
key={searchType}
initial={{ rotate: -90, opacity: 0, scale: 0.5 }}
animate={{ rotate: 0, opacity: 1, scale: 1 }}
exit={{ rotate: 90, opacity: 0, scale: 0.5 }}
className={cn(
"transition-colors duration-500",
searchType === "barcode" ? "text-blue-600 dark:text-blue-400" : searchType === "name" ? "text-emerald-600 dark:text-emerald-400" : "text-gray-400 dark:text-slate-500"
)}
>
{current.icon}
</motion.div>
</AnimatePresence>
<form onSubmit={handleSearch} className="max-w-2xl">
<div className="search-editorial">
{/* Mode icon */}
<div className="text-[var(--fg-muted)] shrink-0">
{current.icon}
</div>
<Input
<input
type="text"
placeholder={current.placeholder}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-16 pr-4 py-9 text-xl border-0 bg-transparent focus-visible:ring-0 w-full text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-slate-600 font-medium tracking-tight transition-colors duration-500"
autoComplete="off"
spellCheck={false}
className="flex-1 bg-transparent border-none outline-none py-[1.125rem] text-[var(--fg-primary)] placeholder-[var(--fg-faint)] text-base font-sans"
/>
<button
type="submit"
disabled={loading}
className="btn-accent shrink-0 py-2.5 px-5 text-sm"
>
{loading ? (
<span className="flex items-center gap-2">
<span className="w-3.5 h-3.5 border-2 border-white/40 border-t-white rounded-full animate-spin" />
Buscando…
</span>
) : (
<span className="flex items-center gap-1.5">
{current.button}
<ArrowRight />
</span>
)}
</button>
</div>
<Button
type="submit"
disabled={loading}
className={cn(
"h-14 px-10 rounded-[1.2rem] font-black uppercase tracking-widest text-xs transition-all duration-500 active:scale-95 shadow-xl",
searchType === "barcode" ? "bg-blue-600 hover:bg-blue-500 shadow-blue-500/20" : "bg-emerald-600 hover:bg-emerald-500 shadow-emerald-500/20"
)}
>
{loading ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<div className="flex items-center gap-2">
<span>{current.button}</span>
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
</div>
)}
</Button>
</div>
{/* 5. METADADOS DA OPERAÇÃO */}
<div className="flex items-center justify-between px-6 mt-6">
<div className={cn(
"flex items-center gap-2 font-mono text-[10px] font-bold uppercase tracking-[0.2em] transition-colors duration-500",
searchType === "barcode" ? "text-blue-500" : searchType === "name" ? "text-emerald-500" : "text-slate-600"
)}>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-current opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-current"></span>
</span>
{current.subtext}
</div>
{isMounted && countryCode && (
<div className="flex items-center gap-2 text-gray-600 dark:text-slate-500 bg-gray-100 dark:bg-white/5 px-3 py-1 rounded-full border border-gray-200 dark:border-white/10 transition-colors duration-500">
<Globe size={12} />
<span className="text-[10px] font-bold uppercase">Region: {countryCode.toUpperCase()}</span>
</div>
)}
</div>
</form>
</div>
{/* 6. INDICADOR DE MÉTRICAS (Bottom Shelf) */}
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 w-full max-w-4xl px-6 hidden md:block">
<div className="grid grid-cols-3 gap-12 border-t border-gray-200 dark:border-white/5 pt-10 opacity-60 dark:opacity-40 grayscale hover:grayscale-0 transition-all duration-700">
<div className="flex flex-col items-center gap-1">
<span className="text-2xl font-mono font-bold text-gray-900 dark:text-white tracking-tighter transition-colors duration-500">2M+</span>
<span className="text-[9px] uppercase tracking-[0.3em] font-black text-gray-500 dark:text-slate-500 transition-colors duration-500">Dataset Entries</span>
</div>
<div className="flex flex-col items-center gap-1 border-x border-gray-200 dark:border-white/5">
<span className="text-2xl font-mono font-bold text-gray-900 dark:text-white tracking-tighter transition-colors duration-500">100%</span>
<span className="text-[9px] uppercase tracking-[0.3em] font-black text-gray-500 dark:text-slate-500 transition-colors duration-500">Open Source</span>
</div>
<div className="flex flex-col items-center gap-1">
<span className="text-2xl font-mono font-bold text-gray-900 dark:text-white tracking-tighter transition-colors duration-500">&lt; 10s</span>
<span className="text-[9px] uppercase tracking-[0.3em] font-black text-gray-500 dark:text-slate-500 transition-colors duration-500">Analysis Speed</span>
</div>
{/* Detection status */}
<p className="mt-3 label text-[var(--fg-muted)] flex items-center gap-1.5">
<span
className={cn(
"w-1.5 h-1.5 rounded-full inline-block transition-colors duration-300",
searchType !== "auto"
? "bg-[var(--accent)]"
: "bg-[var(--fg-faint)]"
)}
/>
{current.label}
</p>
</form>
</div>
</div>
</section>
);
}
// Sub-componente auxiliar para ícone
function ArrowRight({ size, className }: { size: number, className?: string }) {
/** Minimal inline arrow icon */
function ArrowRight() {
return (
<svg
width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" className={className}
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14m-7-7 7 7-7 7"/>
<path d="M5 12h14m-7-7 7 7-7 7" />
</svg>
);
}
}
+111 -131
View File
@@ -1,9 +1,7 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { ScanLine, Search, BarChart3, CheckCircle, Globe, Zap } from "lucide-react";
import Image from "next/image";
import { ScanLine, Search, BarChart3, CheckCircle } from "lucide-react";
import { cn } from "@/lib/utils";
interface Step {
@@ -11,8 +9,6 @@ interface Step {
icon: React.ElementType;
title: string;
description: string;
color: string;
glow: string;
}
const steps: Step[] = [
@@ -20,161 +16,145 @@ const steps: Step[] = [
number: "01",
icon: ScanLine,
title: "Digitalize",
description: "Reconhecimento instantâneo de códigos em mais de 100 países com tecnologia de baixa latência.",
color: "text-blue-500",
glow: "shadow-blue-500/20",
description:
"Insira o código EAN/UPC ou o nome do produto. Detecção automática em mais de 100 países.",
},
{
number: "02",
icon: Search,
title: "Consulte",
description: "Conexão direta ao Open Food Facts, a base de dados colaborativa global (Wikipedia dos alimentos).",
color: "text-green-500",
glow: "shadow-green-500/20",
description:
"Conexão direta ao Open Food Facts — a maior base colaborativa de alimentos do planeta.",
},
{
number: "03",
icon: BarChart3,
title: "Analise",
description: "Processamento de Nutri-Score, graus de processamento NOVA e alertas de alérgenos críticos.",
color: "text-purple-500",
glow: "shadow-purple-500/20",
description:
"Nutri-Score, classificação NOVA, macronutrientes e alertas de alérgenos em uma tela.",
},
{
number: "04",
icon: CheckCircle,
title: "Decida",
description: "Informação técnica pura e sem vieses comerciais para decisões alinhadas à sua saúde.",
color: "text-orange-500",
glow: "shadow-orange-500/20",
description:
"Informação técnica pura, sem vieses comerciais, para escolhas alinhadas à sua saúde.",
},
];
export function HowItWorks() {
return (
<section className="py-24 bg-white dark:bg-[#030712] transition-colors duration-500">
<div className="max-w-7xl mx-auto px-6">
{/* Header - Foco em Valor Técnico */}
<div className="text-center mb-24">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
className="text-4xl md:text-5xl font-black text-gray-900 dark:text-white mb-6 tracking-tighter"
>
Pipeline de <span className="text-blue-600">Consciência Alimentar</span>
</motion.h2>
<p className="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto font-medium">
Quatro estágios de processamento de dados para transformar um código de barras em inteligência nutricional.
</p>
<section className="bg-[var(--bg-surface)] section-py">
<div className="container-editorial">
{/* ── Section header ───────────────────────────────────────────── */}
<div className="mb-16">
<span className="label text-[var(--accent)] block mb-4">
Como funciona
</span>
<hr className="hairline mb-10" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-end">
<h2 className="font-display text-display-lg text-[var(--fg-primary)]">
Quatro etapas,{" "}
<em className="font-display-i text-[var(--accent)]">
clareza total.
</em>
</h2>
<p className="text-[var(--fg-secondary)] text-lg leading-relaxed max-w-md">
De um simples código de barras à inteligência nutricional
completa — em poucos segundos.
</p>
</div>
</div>
{/* Steps Grid - O "Motor" da Seção */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 relative">
{steps.map((step, index) => (
<motion.div
key={step.number}
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.15 }}
className="relative group"
>
{/* Conector Visual (Desktop) */}
{index < steps.length - 1 && (
<div className="hidden lg:block absolute top-12 -right-8 w-16 h-[2px] bg-gradient-to-r from-gray-200 to-transparent dark:from-gray-800 z-0" />
)}
<div className={cn(
"relative z-10 p-8 rounded-3xl bg-gray-50 dark:bg-gray-900/50 border border-gray-100 dark:border-gray-800 transition-all duration-500 hover:border-blue-500/30",
"group-hover:translate-y-[-8px] group-hover:shadow-2xl",
step.glow
)}>
{/* Badge de Número em Estilo Industrial */}
<span className="absolute top-4 right-6 text-sm font-black font-mono text-gray-300 dark:text-gray-700">
STEP_{step.number}
</span>
<div className={cn(
"w-14 h-14 rounded-2xl flex items-center justify-center mb-8 bg-white dark:bg-gray-800 shadow-sm transition-transform duration-500 group-hover:rotate-[10deg]",
step.color
)}>
<step.icon className="w-7 h-7" />
</div>
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-4 tracking-tight">
{step.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed font-medium">
{step.description}
</p>
</div>
</motion.div>
{/* ── Steps grid ───────────────────────────────────────────────── */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-px bg-[var(--border-subtle)]">
{steps.map((step, i) => (
<StepCard key={step.number} step={step} index={i} />
))}
</div>
{/* Technical Datasheet (O que você vai encontrar) */}
<div className="mt-32 mb-12 flex justify-center">
<div className="relative w-full max-w-2xl h-64 rounded-2xl overflow-hidden shadow-2xl">
<Image
src="/images/nut-facts.webp"
alt="Lista de ingredientes e informações nutricionais de produto alimentar"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
/>
</div>
</div>
<div className="mt-32 p-10 rounded-[2.5rem] bg-slate-900 text-white relative overflow-hidden shadow-3xl">
<div className="absolute top-0 right-0 p-10 opacity-10">
<BarChart3 size={200} />
{/* ── Data spec callout ─────────────────────────────────────────── */}
<div className="mt-20 grid grid-cols-1 md:grid-cols-2 gap-px bg-[var(--border-subtle)] rounded-xl overflow-hidden">
<div className="bg-[var(--bg-elevated)] p-10">
<span className="label text-[var(--accent)] block mb-6">
Bio-Markers &amp; Macros
</span>
<ul className="space-y-4">
{[
"Calorias (kcal / 100 g)",
"Perfil lipídico e glicêmico",
"Proteínas e fibras",
"Micronutrientes",
].map((item) => (
<li
key={item}
className="flex items-center gap-3 text-[var(--fg-secondary)] text-sm"
>
<span className="w-1 h-1 rounded-full bg-[var(--accent)] shrink-0" />
{item}
</li>
))}
</ul>
</div>
<div className="relative z-10 grid grid-cols-1 lg:grid-cols-3 gap-12">
<div className="lg:col-span-1">
<h3 className="text-3xl font-black mb-4 tracking-tighter">Data Specification</h3>
<p className="text-slate-400 text-sm leading-relaxed mb-6">
Extração de metadados via Open Food Facts API v2. Suporte a protocolos Nutri-Score e classificações NOVA.
</p>
<div className="flex items-center gap-4 text-xs font-mono text-blue-400 uppercase tracking-widest">
<Globe size={14} /> Open Source Database
</div>
</div>
<div className="lg:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Lista de Features Refinada */}
<div className="space-y-4">
<h4 className="text-blue-500 font-bold text-xs uppercase tracking-widest">Bio-Markers & Macros</h4>
<ul className="space-y-3">
{["Calorias (Kcal/100g)", "Perfil Lipídico e Glicêmico", "Micronutrientes & Fibras"].map(item => (
<li key={item} className="flex items-center gap-3 text-sm text-slate-300">
<Zap size={12} className="text-blue-500" /> {item}
</li>
))}
</ul>
</div>
<div className="relative w-full h-48 rounded-xl overflow-hidden">
<Image
src="/images/nutscore.webp"
alt="Sistema de classificação Nutri-Score"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
<div className="space-y-4">
<h4 className="text-green-500 font-bold text-xs uppercase tracking-widest">Compliance & Alerta</h4>
<ul className="space-y-3">
{["Identificação de Alérgenos", "Aditivos e Conservantes", "Classificação de Processamento"].map(item => (
<li key={item} className="flex items-center gap-3 text-sm text-slate-300">
<CheckCircle size={12} className="text-green-500" /> {item}
</li>
))}
</ul>
</div>
</div>
<div className="bg-[var(--bg-elevated)] p-10">
<span className="label text-[var(--fg-muted)] block mb-6">
Compliance &amp; Alertas
</span>
<ul className="space-y-4">
{[
"Identificação de alérgenos",
"Aditivos e conservantes",
"Classificação NOVA de processamento",
"Nutri-Score A–E",
].map((item) => (
<li
key={item}
className="flex items-center gap-3 text-[var(--fg-secondary)] text-sm"
>
<span className="w-1 h-1 rounded-full bg-[var(--border-strong)] shrink-0" />
{item}
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
}
}
function StepCard({ step, index }: { step: Step; index: number }) {
const Icon = step.icon;
return (
<div
className={cn(
"bg-[var(--bg-elevated)] p-8 md:p-10 group",
"transition-colors duration-300 hover:bg-[var(--bg-base)]"
)}
style={{ animationDelay: `${index * 0.1}s` }}
>
{/* Step number — large serif */}
<div className="step-number mb-6 group-hover:text-[var(--accent)] transition-colors duration-300">
{step.number}
</div>
{/* Monochrome icon */}
<div className="mb-6">
<Icon
className="w-5 h-5 text-[var(--fg-muted)] group-hover:text-[var(--fg-secondary)] transition-colors duration-300"
strokeWidth={1.5}
/>
</div>
<h3 className="font-display text-display-sm text-[var(--fg-primary)] mb-3">
{step.title}
</h3>
<p className="text-[var(--fg-secondary)] text-sm leading-relaxed">
{step.description}
</p>
</div>
);
}
+121 -104
View File
@@ -1,130 +1,147 @@
"use client";
import { useState, useEffect } from "react";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Search, ScanLine, Activity } from "lucide-react";
import { useSearchLogic } from "@/hooks/use-search-logic";
import { cn } from "@/lib/utils";
export function PersuasiveCTA() {
const [query, setQuery] = useState("");
const [searchType, setSearchType] = useState<"barcode" | "name" | null>(null);
const router = useRouter();
const [query, setQuery] = useState("");
const { searchType, isValid, loading, setLoading } = useSearchLogic(query);
// Detectar tipo de busca em tempo real
useEffect(() => {
const cleaned = query.replace(/\D/g, ""); // Remove tudo que não é número
if (cleaned.length >= 8 && cleaned.length <= 13) {
setSearchType("barcode");
} else if (query.trim()) {
setSearchType("name");
} else {
setSearchType(null);
}
}, [query]);
const handleSearch = () => {
const handleSearch = async (e: React.FormEvent) => {
e.preventDefault();
if (!query.trim()) return;
const type = detectSearchType(query);
if (type === "barcode") {
// Limpar código de barras (remover espaços/caracteres)
const cleanBarcode = query.replace(/\D/g, "");
if (isValidBarcode(cleanBarcode)) {
// Redirecionar para página do produto
router.push(`/produto/${cleanBarcode}`);
} else {
// Mostrar erro de código inválido
alert("Código de barras inválido. Deve ter entre 8 e 13 dígitos.");
}
} else {
// Busca normal por nome
router.push(`/resultados?q=${encodeURIComponent(query.trim())}`);
}
setLoading(true);
const target =
searchType === "barcode" && isValid
? `/produto/${query.trim()}`
: `/resultados?q=${encodeURIComponent(query.trim())}`;
router.push(target);
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
handleSearch();
}
};
const icon =
searchType === "barcode" ? (
<ScanLine className="w-4 h-4" />
) : searchType === "name" ? (
<Search className="w-4 h-4" />
) : (
<Activity className="w-4 h-4" />
);
// Funções auxiliares (copiadas do SearchBar)
const detectSearchType = (input: string): "barcode" | "name" => {
const cleaned = input.replace(/\D/g, "");
return cleaned.length >= 8 && cleaned.length <= 13 ? "barcode" : "name";
};
const isValidBarcode = (barcode: string): boolean => {
return barcode.length >= 8 && barcode.length <= 13 && /^\d+$/.test(barcode);
};
const label =
searchType === "barcode"
? "Código de barras detectado"
: searchType === "name"
? "Pesquisa por nome"
: "Detecção automática";
return (
<section className="py-20 bg-gradient-to-br from-blue-600 to-green-600 text-white">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-3xl md:text-5xl font-bold mb-6">
Pronto para descobrir a verdade sobre seus alimentos?
</h2>
<p className="text-xl mb-8 opacity-90">
Junte-se a milhares de pessoas que já tomam decisões alimentares mais
conscientes com informações transparentes e confiáveis.
</p>
<section className="bg-[var(--bg-surface)] section-py border-t border-[var(--border-subtle)]">
<div className="container-editorial">
{/* Contador Animado de Usuários */}
<div className="mb-8">
<div className="text-2xl font-semibold">
<span className="animate-pulse">12.847</span> pessoas já buscaram
hoje
{/* ── Section header ───────────────────────────────────────────── */}
<div className="mb-16">
<span className="label text-[var(--accent)] block mb-4">
Comece Agora
</span>
<hr className="hairline mb-10" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-end">
<h2 className="font-display text-display-lg text-[var(--fg-primary)]">
Pronto para descobrir a{" "}
<em className="font-display-i text-[var(--accent)]">
verdade sobre seus alimentos?
</em>
</h2>
<p className="text-[var(--fg-secondary)] text-lg leading-relaxed max-w-md">
Sem cadastro, sem anúncios, sem venda de dados. Resultados
nutricionais completos em segundos.
</p>
</div>
</div>
{/* Search Bar Destacado */}
<div className="max-w-2xl mx-auto bg-white rounded-2xl p-6 shadow-2xl">
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
type="text"
placeholder="Digite o código de barras de qualquer produto..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={handleKeyPress}
className="pl-10 pr-4 h-12 text-gray-900"
/>
{/* ── Search form ──────────────────────────────────────────────── */}
<form onSubmit={handleSearch} className="max-w-2xl">
<div className="search-editorial">
<div className="text-[var(--fg-muted)] shrink-0">{icon}</div>
{/* Indicador visual do tipo de busca */}
{searchType && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
{searchType === "barcode" ? "Código de barras" : "Nome"}
</span>
</div>
)}
</div>
<Button
onClick={handleSearch}
disabled={!query.trim()}
className="h-12 px-6 bg-gradient-to-r from-blue-600 to-green-600 hover:from-blue-700 hover:to-green-700"
<input
type="text"
placeholder="Nome do produto ou código de barras…"
value={query}
onChange={(e) => setQuery(e.target.value)}
autoComplete="off"
spellCheck={false}
className="flex-1 bg-transparent border-none outline-none py-[1.125rem] text-[var(--fg-primary)] placeholder-[var(--fg-faint)] text-base font-sans"
/>
<button
type="submit"
disabled={loading}
className="btn-accent shrink-0 py-2.5 px-5 text-sm"
>
<Search className="h-4 w-4 mr-2" />
Buscar
</Button>
{loading ? (
<span className="flex items-center gap-2">
<span className="w-3.5 h-3.5 border-2 border-white/40 border-t-white rounded-full animate-spin" />
Buscando…
</span>
) : (
<span className="flex items-center gap-1.5">
Buscar
<ArrowRight />
</span>
)}
</button>
</div>
<p className="text-center mt-3 text-sm text-gray-500">
💡 Dica: O código de barras está na parte de trás da embalagem
</p>
</div>
{/* Trust Indicators */}
<div className="mt-8 flex justify-center gap-6 text-sm opacity-75">
<span>✓ Sem cadastro necessário</span>
<span>✓ Resultados instantâneos</span>
<span>✓ 100% gratuito</span>
{/* Detection status */}
<p className="mt-3 label text-[var(--fg-muted)] flex items-center gap-1.5">
<span
className={cn(
"w-1.5 h-1.5 rounded-full inline-block",
searchType !== "auto"
? "bg-[var(--accent)]"
: "bg-[var(--fg-faint)]"
)}
/>
{label}
</p>
</form>
{/* ── Trust indicators ─────────────────────────────────────────── */}
<div className="mt-12 flex flex-wrap gap-x-10 gap-y-3">
{["Sem cadastro necessário", "Resultados instantâneos", "100% gratuito e open source"].map(
(item) => (
<span
key={item}
className="label text-[var(--fg-muted)] flex items-center gap-2"
>
<span className="w-1 h-1 rounded-full bg-[var(--accent)]" />
{item}
</span>
)
)}
</div>
</div>
</section>
);
}
function ArrowRight() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14m-7-7 7 7-7 7" />
</svg>
);
}
+34 -44
View File
@@ -1,51 +1,41 @@
import { Database, Globe, Zap, Code } from "lucide-react";
const stats = [
{
value: "2 M+",
label: "Produtos no Open Food Facts",
},
{
value: "100+",
label: "Países cobertos",
},
{
value: "99.9%",
label: "Uptime garantido",
},
{
value: "100%",
label: "Open Source",
},
];
export function TrustBadges() {
return (
<section className="py-12 bg-gray-50 dark:bg-gray-800 border-t border-b">
<div className="max-w-7xl mx-auto px-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
<div className="animate-fadeIn">
<div className="w-16 h-16 mx-auto mb-3 bg-blue-100 dark:bg-blue-900/40 rounded-full flex items-center justify-center">
<Database className="w-8 h-8 text-blue-600" />
<section className="bg-[var(--bg-surface)] border-y border-[var(--border-subtle)]">
<div className="container-editorial">
<div className="grid grid-cols-2 md:grid-cols-4 md:divide-x divide-[var(--border-subtle)]">
{stats.map(({ value, label }, i) => (
<div
key={i}
className="flex flex-col items-center gap-2 px-6 py-8"
>
<span className="font-display text-2xl text-[var(--fg-primary)] leading-none">
{value}
</span>
<span className="label text-center text-[var(--fg-muted)]">
{label}
</span>
</div>
<div className="text-2xl font-bold text-blue-600">
2M+
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Produtos no Open Food Facts
</div>
</div>
<div className="animate-fadeIn" style={{animationDelay: '0.1s'}}>
<div className="w-16 h-16 mx-auto mb-3 bg-green-100 dark:bg-green-900/40 rounded-full flex items-center justify-center">
<Globe className="w-8 h-8 text-green-600" />
</div>
<div className="text-2xl font-bold text-green-600">100+</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Países cobertos
</div>
</div>
<div className="animate-fadeIn" style={{animationDelay: '0.2s'}}>
<div className="w-16 h-16 mx-auto mb-3 bg-purple-100 dark:bg-purple-900/40 rounded-full flex items-center justify-center">
<Zap className="w-8 h-8 text-purple-600" />
</div>
<div className="text-2xl font-bold text-purple-600">99.9%</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Uptime garantido
</div>
</div>
<div className="animate-fadeIn" style={{animationDelay: '0.3s'}}>
<div className="w-16 h-16 mx-auto mb-3 bg-orange-100 dark:bg-orange-900/40 rounded-full flex items-center justify-center animate-pulse-slow">
<Code className="w-8 h-8 text-orange-600" />
</div>
<div className="text-xl font-bold text-orange-600">100%</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Open Source
</div>
</div>
))}
</div>
</div>
</section>