Files
Landing/src/sections/Showcase.tsx
T
2026-08-31 19:24:16 -05:00

177 lines
6.0 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
import Loader from "../components/Loader";
import { useProducts } from "../services/products";
import { assetUrl } from "../services/api";
import type { ThemeProps } from "../types";
export const Showcase = ({ isNavyBg }: ThemeProps) => {
const { data, isLoading } = useProducts();
const products = (data ?? []).filter((p) => p.isActive);
const bg = isNavyBg ? "#080c14" : "#f0efec";
const text = isNavyBg ? "#ffffff" : "#0a0a0a";
const sub = isNavyBg ? "rgba(255,255,255,0.45)" : "#6a6866";
const border = isNavyBg ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)";
const tagBg = isNavyBg ? "rgba(59,130,246,0.1)" : "rgba(59,130,246,0.07)";
return (
<section
id="showcase"
aria-labelledby="showcase-heading"
style={{
background: bg,
color: text,
borderBottom: `1px solid ${border}`,
}}
className="px-8 py-28 md:px-14 transition-colors duration-500"
>
<div className="mx-auto max-w-7xl">
<motion.div
className="mb-16"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
>
<span
className="mb-3 block text-xs font-semibold uppercase tracking-[0.14em]"
style={{ color: "#3b82f6" }}
>
Nuestras soluciones
</span>
<h2
id="showcase-heading"
className="text-headline"
style={{ maxWidth: "28ch" }}
>
Software diseñado para tu operación.
</h2>
<p
className="mt-4 max-w-xl text-base leading-relaxed"
style={{ color: sub }}
>
Cada módulo resuelve un problema real. Sin funciones de relleno, sin licencias por usuario, sin límites artificiales.
</p>
</motion.div>
{isLoading ? (
<Loader className="py-20" />
) : products.length > 0 ? (
<div className="grid gap-6 lg:grid-cols-3">
{products.map(({ id, imageUrl, name, description, badges }, i) => (
<motion.article
key={id}
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{
duration: 0.55,
delay: i * 0.1,
ease: [0.22, 1, 0.36, 1],
}}
style={{
borderRadius: 14,
border: `1px solid ${border}`,
background: isNavyBg ? "rgba(255,255,255,0.03)" : "#ffffff",
overflow: "hidden",
}}
>
<div
style={{
background: isNavyBg
? "rgba(255,255,255,0.04)"
: "rgba(0,0,0,0.03)",
borderBottom: `1px solid ${border}`,
aspectRatio: "16/9",
overflow: "hidden",
}}
>
{imageUrl ? (
<img
src={assetUrl(imageUrl)}
alt={name}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
display: "block",
}}
/>
) : (
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke={sub}
strokeWidth="1.5"
>
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="8.5" cy="8.5" r="1.5" />
<path d="m21 15-5-5L5 21" />
</svg>
</div>
)}
</div>
<div style={{ padding: "24px 24px 28px" }}>
<h3
className="font-display font-bold mb-2"
style={{
fontSize: "1.05rem",
color: text,
letterSpacing: "-0.01em",
}}
>
{name}
</h3>
<p
className="text-sm leading-relaxed mb-5"
style={{ color: sub }}
>
{description}
</p>
{badges && badges.length > 0 && (
<div className="flex flex-wrap gap-2">
{badges.map((badge) => (
<span
key={badge}
style={{
fontSize: 10.5,
fontWeight: 600,
padding: "3px 9px",
borderRadius: 99,
background: tagBg,
color: "#3b82f6",
letterSpacing: "0.01em",
}}
>
{badge}
</span>
))}
</div>
)}
</div>
</motion.article>
))}
</div>
) : (
<p className="text-sm" style={{ color: sub }}>
Sin productos registrados.
</p>
)}
</div>
</section>
);
};