feat: complete FIT Labs landing page

- Astro static site with Tailwind CSS v4
- 6 sections: Hero (gradient mesh), About, Capabilities, Differentiator, CTA, Footer
- Sticky navbar with mobile menu
- Scroll reveal animations
- SEO meta tags and structured data
- Docker + nginx config for Coolify deployment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 12:26:00 -06:00
parent f165d005ce
commit f11adceceb
19 changed files with 7047 additions and 27 deletions

View File

@@ -0,0 +1,19 @@
---
---
<section id="nosotros" class="bg-light-bg py-24 px-6">
<div class="max-w-3xl mx-auto text-center reveal">
<p class="text-brand-blue text-xs tracking-[0.2em] uppercase font-semibold mb-3">
Sobre nosotros
</p>
<h2 class="text-3xl md:text-4xl font-bold text-slate-900 mb-6">
La incubadora de innovaci&oacute;n de FactorIT
</h2>
<p class="text-slate-500 text-lg leading-relaxed">
FIT Labs es donde exploramos, prototipamos y llevamos a producci&oacute;n soluciones
de inteligencia artificial para empresas. Nacimos del ADN tecnol&oacute;gico de
FactorIT para resolver los desaf&iacute;os m&aacute;s complejos con IA &mdash; no entregamos
demos, entregamos sistemas que funcionan en el mundo real.
</p>
</div>
</section>

View File

@@ -0,0 +1,62 @@
---
const capabilities = [
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>`,
title: 'Documentos Inteligentes',
description: 'OCR, extracción y clasificación automática',
borderColor: 'border-brand-cyan',
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>`,
title: 'Agentes de IA',
description: 'Conversacionales y autónomos',
borderColor: 'border-brand-violet',
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>`,
title: 'Automatización con IA',
description: 'Procesos end-to-end inteligentes',
borderColor: 'border-brand-blue',
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="m19 9-5 5-4-4-3 3"/></svg>`,
title: 'Análisis de Datos',
description: 'Insights e inteligencia de negocio',
borderColor: 'border-brand-cyan',
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7 5V8l-7 5V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/></svg>`,
title: 'Soluciones Industriales',
description: 'IA aplicada a tu vertical',
borderColor: 'border-brand-violet',
},
];
---
<section id="capacidades" class="bg-dark-bg py-24 px-6">
<div class="max-w-3xl mx-auto">
<div class="text-center mb-12 reveal">
<p class="text-brand-cyan text-xs tracking-[0.2em] uppercase font-semibold mb-3">
Capacidades
</p>
<h2 class="text-3xl md:text-4xl font-bold text-white">
Lo que construimos
</h2>
</div>
<div class="flex flex-col gap-4">
{capabilities.map((cap, i) => (
<div
class={`flex items-center gap-5 bg-dark-surface border-l-[3px] ${cap.borderColor} rounded-r-lg px-6 py-5 slide-in hover:bg-dark-surface/80 hover:shadow-[0_0_20px_rgba(6,182,212,0.05)] transition-all duration-300`}
style={`transition-delay: ${i * 100}ms;`}
>
<div class="text-brand-cyan shrink-0" set:html={cap.icon} />
<div>
<h3 class="text-white font-semibold">{cap.title}</h3>
<p class="text-slate-400 text-sm mt-1">{cap.description}</p>
</div>
</div>
))}
</div>
</div>
</section>

View File

@@ -0,0 +1,29 @@
---
---
<section id="contacto" class="relative bg-dark-bg py-24 px-6 overflow-hidden">
<div
class="absolute top-[-30%] left-1/2 -translate-x-1/2 w-[500px] h-[300px] rounded-full opacity-40"
style="background: radial-gradient(circle, rgba(124,58,237,0.15) 0%, transparent 70%); filter: blur(60px);"
></div>
<div class="relative z-10 max-w-2xl mx-auto text-center reveal">
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
&iquest;Tienes un desaf&iacute;o en mente?
</h2>
<p class="text-slate-400 text-lg mb-10 leading-relaxed">
Cu&eacute;ntanos qu&eacute; problema quieres resolver. Nosotros vemos c&oacute;mo la IA puede ayudarte.
</p>
<a
href="mailto:contacto@fitlabs.dev"
class="inline-block px-10 py-4 text-base font-semibold text-white rounded-lg bg-gradient-to-br from-brand-blue to-brand-violet hover:scale-105 hover:shadow-[0_0_30px_rgba(124,58,237,0.3)] transition-all duration-300"
>
Conversemos &rarr;
</a>
<p class="mt-5 text-sm text-slate-500">
<a href="mailto:contacto@fitlabs.dev" class="hover:text-brand-cyan transition-colors">
contacto@fitlabs.dev
</a>
</p>
</div>
</section>

View File

@@ -0,0 +1,36 @@
---
const values = [
{ text: 'Arquitectura para escalar', borderColor: 'border-brand-blue' },
{ text: 'Performance en producción', borderColor: 'border-brand-violet' },
{ text: 'Integración con tu ecosistema', borderColor: 'border-brand-cyan' },
];
---
<section class="bg-light-bg py-24 px-6">
<div class="max-w-4xl mx-auto reveal">
<p class="text-brand-blue text-xs tracking-[0.2em] uppercase font-semibold mb-8">
Nuestro enfoque
</p>
<div class="flex flex-col md:flex-row gap-12 md:gap-16">
<div class="flex-1">
<h2 class="text-2xl md:text-3xl font-bold text-slate-900 mb-4">
Innovaci&oacute;n con los pies en la tierra
</h2>
<p class="text-slate-500 leading-relaxed">
Nos apasiona la IA, pero nos obsesiona que funcione. Cada proyecto que
sale de FIT Labs est&aacute; pensado para integrarse en operaciones reales,
soportar carga real, y crecer cuando tu negocio crece.
</p>
</div>
<div class="flex-1 flex flex-col gap-3">
{values.map((val) => (
<div class={`bg-white border-l-[3px] ${val.borderColor} rounded-lg px-5 py-4 shadow-sm`}>
<p class="text-slate-900 font-semibold text-sm">{val.text}</p>
</div>
))}
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,19 @@
---
---
<footer class="bg-dark-footer py-8 px-6 border-t border-white/5">
<div class="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
<div>
<a href="https://www.factorit.com" target="_blank" rel="noopener noreferrer" class="text-white font-bold text-sm hover:text-brand-cyan transition-colors">
FIT <span class="text-brand-cyan">Labs</span>
</a>
<p class="text-slate-500 text-xs mt-1">by FactorIT</p>
</div>
<div class="flex items-center gap-6 text-sm text-slate-500">
<a href="https://www.linkedin.com/company/factorit" target="_blank" rel="noopener noreferrer" class="hover:text-white transition-colors">LinkedIn</a>
<a href="https://github.com/factorit" target="_blank" rel="noopener noreferrer" class="hover:text-white transition-colors">GitHub</a>
<a href="https://www.factorit.com" target="_blank" rel="noopener noreferrer" class="hover:text-white transition-colors">factorit.com</a>
</div>
</div>
</footer>

37
src/components/Hero.astro Normal file
View File

@@ -0,0 +1,37 @@
---
---
<section id="hero" class="relative min-h-screen flex items-center justify-center bg-dark-bg overflow-hidden">
<div class="absolute inset-0 overflow-hidden">
<div
class="absolute top-[-10%] right-[-5%] w-[500px] h-[500px] rounded-full opacity-60"
style="background: radial-gradient(circle, rgba(124,58,237,0.3) 0%, transparent 70%); filter: blur(80px); animation: mesh-drift-1 20s ease-in-out infinite;"
></div>
<div
class="absolute bottom-[-15%] left-[-10%] w-[600px] h-[600px] rounded-full opacity-50"
style="background: radial-gradient(circle, rgba(6,182,212,0.25) 0%, transparent 70%); filter: blur(80px); animation: mesh-drift-2 20s ease-in-out infinite;"
></div>
<div
class="absolute top-[40%] left-[40%] -translate-x-1/2 -translate-y-1/2 w-[550px] h-[550px] rounded-full opacity-40"
style="background: radial-gradient(circle, rgba(3,105,161,0.2) 0%, transparent 70%); filter: blur(80px); animation: mesh-drift-3 20s ease-in-out infinite;"
></div>
</div>
<div class="relative z-10 text-center px-6 max-w-2xl mx-auto">
<p class="text-brand-cyan text-xs tracking-[0.2em] uppercase mb-4 font-semibold">
FactorIT Innovation Lab
</p>
<h1 class="text-5xl md:text-7xl font-extrabold text-white mb-6">
FIT Labs
</h1>
<p class="text-lg md:text-xl text-slate-400 mb-10 leading-relaxed">
Donde la inteligencia artificial se convierte en soluciones enterprise que escalan
</p>
<a
href="#capacidades"
class="inline-block px-8 py-3 text-base font-semibold text-white rounded-lg bg-gradient-to-br from-brand-blue to-brand-violet hover:scale-105 hover:shadow-[0_0_30px_rgba(124,58,237,0.3)] transition-all duration-300"
>
Explorar capacidades &darr;
</a>
</div>
</section>

View File

@@ -0,0 +1,67 @@
---
---
<nav id="navbar" class="fixed top-0 left-0 right-0 z-50 transition-all duration-300">
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<a href="https://www.factorit.com" target="_blank" rel="noopener noreferrer">
<img src="/LogoFIT.png" alt="FIT Labs" class="h-8" />
</a>
<div class="hidden md:flex items-center gap-8">
<a href="#capacidades" class="text-sm text-slate-300 hover:text-white transition-colors">Capacidades</a>
<a href="#nosotros" class="text-sm text-slate-300 hover:text-white transition-colors">Nosotros</a>
<a href="#contacto" class="px-4 py-2 text-sm font-semibold text-white rounded-lg bg-gradient-to-br from-brand-blue to-brand-violet hover:scale-105 transition-transform">
Contacto
</a>
</div>
<button id="menu-toggle" class="md:hidden text-white" aria-label="Abrir menú">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<div id="mobile-menu" class="fixed inset-0 bg-dark-bg/95 backdrop-blur-sm flex flex-col items-center justify-center gap-8 hidden z-40">
<button id="menu-close" class="absolute top-6 right-6 text-white" aria-label="Cerrar menú">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<a href="#capacidades" class="text-2xl text-white hover:text-brand-cyan transition-colors mobile-link">Capacidades</a>
<a href="#nosotros" class="text-2xl text-white hover:text-brand-cyan transition-colors mobile-link">Nosotros</a>
<a href="#contacto" class="text-2xl text-white hover:text-brand-cyan transition-colors mobile-link">Contacto</a>
</div>
</nav>
<script>
const navbar = document.getElementById('navbar');
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar?.classList.add('bg-dark-bg/90', 'backdrop-blur-md', 'shadow-lg');
} else {
navbar?.classList.remove('bg-dark-bg/90', 'backdrop-blur-md', 'shadow-lg');
}
});
const toggle = document.getElementById('menu-toggle');
const menu = document.getElementById('mobile-menu');
const close = document.getElementById('menu-close');
const links = document.querySelectorAll('.mobile-link');
function openMenu() {
menu?.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function closeMenu() {
menu?.classList.add('hidden');
document.body.style.overflow = '';
}
toggle?.addEventListener('click', openMenu);
close?.addEventListener('click', closeMenu);
links.forEach(link => link.addEventListener('click', closeMenu));
menu?.addEventListener('click', (e) => {
if (e.target === menu) closeMenu();
});
</script>

74
src/layouts/Layout.astro Normal file
View File

@@ -0,0 +1,74 @@
---
interface Props {
title?: string;
description?: string;
}
const {
title = 'FIT Labs — Innovación con IA by FactorIT',
description = 'Soluciones de inteligencia artificial enterprise que escalan. La incubadora de innovación de FactorIT.',
} = Astro.props;
---
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://fitlabs.dev" />
<meta property="og:site_name" content="FIT Labs" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap"
rel="stylesheet"
/>
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
"name": "FIT Labs",
"description": description,
"url": "https://fitlabs.dev",
"parentOrganization": {
"@type": "Organization",
"name": "FactorIT",
"url": "https://www.factorit.com"
}
})} />
<title>{title}</title>
</head>
<body class="font-sans antialiased">
<slot />
<script>
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
},
{ threshold: 0.1 }
);
document.querySelectorAll('.reveal, .slide-in').forEach((el) => {
observer.observe(el);
});
</script>
</body>
</html>

View File

@@ -1,17 +1,23 @@
---
import Layout from '../layouts/Layout.astro';
import Navbar from '../components/Navbar.astro';
import Hero from '../components/Hero.astro';
import About from '../components/About.astro';
import Capabilities from '../components/Capabilities.astro';
import Differentiator from '../components/Differentiator.astro';
import Contact from '../components/Contact.astro';
import Footer from '../components/Footer.astro';
import '../styles/global.css';
---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
</body>
</html>
<Layout>
<Navbar />
<main>
<Hero />
<About />
<Capabilities />
<Differentiator />
<Contact />
</main>
<Footer />
</Layout>

68
src/styles/global.css Normal file
View File

@@ -0,0 +1,68 @@
@import "tailwindcss";
@theme {
--font-sans: 'Inter', system-ui, sans-serif;
--color-dark-bg: #0a0a1a;
--color-dark-surface: #0f172a;
--color-dark-footer: #06080f;
--color-light-bg: #f0f9ff;
--color-light-surface: #e0e7ff;
--color-light-base: #f8fafc;
--color-brand-blue: #0369a1;
--color-brand-violet: #7c3aed;
--color-brand-cyan: #06b6d4;
}
/* Gradient mesh animation for hero */
@keyframes mesh-drift-1 {
0%, 100% { transform: translate(0, 0); }
33% { transform: translate(30px, -20px); }
66% { transform: translate(-20px, 15px); }
}
@keyframes mesh-drift-2 {
0%, 100% { transform: translate(0, 0); }
33% { transform: translate(-25px, 20px); }
66% { transform: translate(15px, -30px); }
}
@keyframes mesh-drift-3 {
0%, 100% { transform: translate(0, 0); }
33% { transform: translate(20px, 25px); }
66% { transform: translate(-30px, -10px); }
}
/* Scroll reveal */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
/* Capability list slide-in */
.slide-in {
opacity: 0;
transform: translateX(-20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.slide-in.visible {
opacity: 1;
transform: translateX(0);
}
/* Gradient text utility */
.gradient-text {
background: linear-gradient(135deg, #0369a1, #7c3aed);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}