177 lines
6.0 KiB
TypeScript
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>
|
|
);
|
|
};
|