117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { motion } from 'framer-motion';
|
|
import type { ThemeProps } from '../types';
|
|
|
|
const stackGroups = [
|
|
{
|
|
label: 'FRONTEND',
|
|
items: ['React', 'TypeScript', 'Next.js', 'Tailwind CSS', 'Vite'],
|
|
},
|
|
{
|
|
label: 'BACKEND',
|
|
items: ['Node.js', 'Python', 'Express', 'FastAPI', 'NestJS'],
|
|
},
|
|
{
|
|
label: 'DATOS',
|
|
items: ['PostgreSQL', 'MongoDB', 'Redis', 'Prisma', 'Elasticsearch'],
|
|
},
|
|
{
|
|
label: 'CLOUD & OPS',
|
|
items: ['AWS', 'Docker', 'GitHub Actions', 'Terraform', 'Linux'],
|
|
},
|
|
{
|
|
label: 'INTEGRACIONES',
|
|
items: ['REST', 'GraphQL', 'Stripe', 'OAuth 2.0', 'Webhooks'],
|
|
},
|
|
];
|
|
|
|
export const Stack = ({ isNavyBg }: ThemeProps) => {
|
|
const bg = isNavyBg ? '#0f1520' : '#f9f9f7';
|
|
const text = isNavyBg ? '#eef0f4' : '#0a0a0a';
|
|
const sub = isNavyBg ? 'rgba(255,255,255,0.35)' : '#6a6866';
|
|
const border = isNavyBg ? 'rgba(255,255,255,0.07)' : '#dddbd5';
|
|
const tagBg = isNavyBg ? 'rgba(255,255,255,0.04)' : '#f0efec';
|
|
|
|
return (
|
|
<section
|
|
id="stack"
|
|
aria-labelledby="stack-heading"
|
|
style={{ background: bg, color: text }}
|
|
className="px-8 py-28 md:px-14 transition-colors duration-500"
|
|
>
|
|
<div className="mx-auto max-w-7xl">
|
|
|
|
{/* Header */}
|
|
<div className="mb-16 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
|
<div>
|
|
<div className="mb-4 flex items-center gap-3">
|
|
<span className="h-px w-8 bg-brand-red" />
|
|
<span className="text-mono-label" style={{ color: '#e50914' }}>Las herramientas son el medio</span>
|
|
</div>
|
|
<h2
|
|
id="stack-heading"
|
|
className="text-headline"
|
|
style={{ maxWidth: '26ch' }}
|
|
>
|
|
Las herramientas son el medio.<br className="hidden sm:block" /> El sistema es el fin.
|
|
</h2>
|
|
</div>
|
|
<p className="max-w-xs text-sm leading-relaxed" style={{ color: sub }}>
|
|
Elegimos la tecnología correcta para cada problema. No hay un stack universal.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stack groups */}
|
|
<div
|
|
style={{ borderTop: `1px solid ${border}` }}
|
|
>
|
|
{stackGroups.map((group, i) => (
|
|
<motion.div
|
|
key={group.label}
|
|
initial={{ opacity: 0, x: -8 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.4, delay: i * 0.06 }}
|
|
className="flex flex-col gap-4 py-6 sm:flex-row sm:items-center sm:gap-10"
|
|
style={{ borderBottom: `1px solid ${border}` }}
|
|
>
|
|
<span
|
|
className="text-mono-label shrink-0"
|
|
style={{ color: sub, width: 120 }}
|
|
>
|
|
{group.label}
|
|
</span>
|
|
<div className="flex flex-wrap gap-2">
|
|
{group.items.map(item => (
|
|
<span
|
|
key={item}
|
|
className="text-sm font-medium px-3 py-1 rounded-sm"
|
|
style={{
|
|
background: tagBg,
|
|
border: `1px solid ${border}`,
|
|
color: text,
|
|
}}
|
|
>
|
|
{item}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Footnote */}
|
|
<motion.p
|
|
initial={{ opacity: 0 }}
|
|
whileInView={{ opacity: 1 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
className="mt-10 text-xs leading-relaxed"
|
|
style={{ color: sub, maxWidth: '60ch' }}
|
|
>
|
|
Estas son las herramientas con las que trabajamos actualmente. La elección depende siempre del problema, no de la preferencia.
|
|
</motion.p>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|