Files
nutriscan/app/components/landing/Hero.tsx

183 lines
6.7 KiB
TypeScript

"use client";
import React, { useState, useMemo } from "react";
import { useRouter } from "next/navigation";
import { Search, ScanLine, Activity, Globe } from "lucide-react";
import { useGeolocation } from "@/hooks/use-geolocation";
import { useSearchLogic } from "@/hooks/use-search-logic";
import { cn } from "@/lib/utils";
export function Hero() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const { countryCode, isMounted } = useGeolocation();
const { searchType, isValid, loading, setLoading } = useSearchLogic(searchQuery);
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];
const handleSearch = async (e: React.FormEvent) => {
e.preventDefault();
if (!searchQuery.trim()) return;
setLoading(true);
const target =
searchType === "barcode" && isValid
? `/produto/${searchQuery.trim()}`
: `/resultados?q=${encodeURIComponent(searchQuery.trim())}${
countryCode ? `&country=${countryCode}` : ""
}`;
router.push(target);
};
return (
<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>
{/* ── 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" }}
>
<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
type="text"
placeholder={current.placeholder}
value={searchQuery}
onChange={(e) => setSearchQuery(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"
>
{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>
{/* 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>
);
}
/** Minimal inline arrow icon */
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>
);
}