Files
fitlabs.dev/docs/superpowers/plans/2026-03-14-fitlabs-landing.md
Felipe Arentsen f11adceceb 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>
2026-03-14 12:26:00 -06:00

1098 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FIT Labs Landing Page — Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build and deploy a single-page landing for fitlabs.dev — FactorIT's AI innovation incubator.
**Architecture:** Astro static site with Tailwind CSS. 6 sections in a single `index.astro` page composed from Astro components. No client-side framework — vanilla JS only for navbar scroll, scroll-reveal animations, and gradient mesh. Deployed via Docker (nginx) on Coolify.
**Tech Stack:** Astro 5.x, Tailwind CSS 4.x, Lucide Icons (SVG inline), Docker + nginx
**Spec:** `docs/superpowers/specs/2026-03-14-fitlabs-landing-design.md`
---
## File Structure
```
fitlabs.dev/
├── public/
│ ├── LogoFIT.png # Logo (already exists at project root)
│ └── favicon.svg # Favicon
├── src/
│ ├── layouts/
│ │ └── Layout.astro # Base HTML: <head> meta/SEO/fonts, <body> slot
│ ├── components/
│ │ ├── Navbar.astro # Sticky navbar with scroll behavior
│ │ ├── Hero.astro # Gradient mesh hero
│ │ ├── About.astro # "Qué es FIT Labs" section
│ │ ├── Capabilities.astro # 5 capabilities list
│ │ ├── Differentiator.astro # Two-column differentiator
│ │ ├── Contact.astro # CTA section
│ │ └── Footer.astro # Footer
│ ├── pages/
│ │ └── index.astro # Composes all sections
│ └── styles/
│ └── global.css # Tailwind imports, custom animations, gradient mesh
├── nginx/
│ └── nginx.conf # Nginx config for static serving
├── Dockerfile # Multi-stage: node build → nginx serve
├── astro.config.mjs # Astro config (static output)
├── package.json
├── tsconfig.json
└── .gitignore
```
---
## Chunk 1: Project Setup & Base Layout
### Task 1: Scaffold Astro Project
**Files:**
- Create: `package.json`, `astro.config.mjs`, `tsconfig.json`, `.gitignore`
- [ ] **Step 1: Initialize Astro project**
```bash
cd /Users/felipearentsen/Claude/code/fitlabs.dev
npm create astro@latest . -- --template minimal --no-install --typescript strict
```
- [ ] **Step 2: Add Tailwind CSS integration**
```bash
npx astro add tailwind -y
```
- [ ] **Step 3: Configure astro.config.mjs for static output**
```javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwindcss from '@astrojs/tailwind';
export default defineConfig({
output: 'static',
integrations: [tailwindcss()],
site: 'https://fitlabs.dev',
});
```
- [ ] **Step 4: Update .gitignore**
Add to `.gitignore`:
```
node_modules/
dist/
.astro/
.superpowers/
```
- [ ] **Step 5: Move logo to public/**
```bash
cp /Users/felipearentsen/Claude/code/fitlabs.dev/LogoFIT.png /Users/felipearentsen/Claude/code/fitlabs.dev/public/LogoFIT.png
```
- [ ] **Step 6: Install dependencies and verify build**
```bash
npm install
npm run build
```
Expected: Build succeeds, `dist/` directory created.
- [ ] **Step 7: Initialize git and commit**
```bash
git init
git add -A
git commit -m "chore: scaffold Astro project with Tailwind CSS"
```
---
### Task 2: Global Styles & Tailwind Config
**Files:**
- Create: `src/styles/global.css`
- Modify: `tailwind.config.mjs`
- [ ] **Step 1: Configure Tailwind with custom colors and fonts**
```javascript
// tailwind.config.mjs
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
colors: {
dark: {
bg: '#0a0a1a',
surface: '#0f172a',
footer: '#06080f',
},
light: {
bg: '#f0f9ff',
surface: '#e0e7ff',
base: '#f8fafc',
},
brand: {
blue: '#0369a1',
violet: '#7c3aed',
cyan: '#06b6d4',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
};
```
- [ ] **Step 2: Create global.css with Tailwind imports and custom animations**
```css
/* src/styles/global.css */
@import 'tailwindcss';
@theme {
--font-sans: 'Inter', system-ui, sans-serif;
}
/* 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;
}
```
- [ ] **Step 3: Verify build**
```bash
npm run build
```
Expected: Build succeeds.
- [ ] **Step 4: Commit**
```bash
git add tailwind.config.mjs src/styles/global.css
git commit -m "feat: add Tailwind config with brand colors and global animations"
```
---
### Task 3: Base Layout with SEO
**Files:**
- Create: `src/layouts/Layout.astro`
- [ ] **Step 1: Create Layout.astro with meta tags and Open Graph**
```astro
---
// src/layouts/Layout.astro
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} />
<!-- Open Graph -->
<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" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- Fonts -->
<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"
/>
<!-- Structured Data -->
<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 />
</body>
</html>
```
- [ ] **Step 2: Create a minimal favicon.svg**
```svg
<!-- public/favicon.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="#0a0a1a"/>
<text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle"
font-family="Inter, sans-serif" font-weight="800" font-size="14" fill="#06b6d4">
FIT
</text>
</svg>
```
- [ ] **Step 3: Create index.astro shell**
```astro
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import '../styles/global.css';
---
<Layout>
<main>
<p class="text-white bg-dark-bg p-8">FIT Labs — Coming soon</p>
</main>
</Layout>
```
- [ ] **Step 4: Verify dev server and build**
```bash
npm run dev -- --port 4321 &
sleep 3
curl -s http://localhost:4321 | head -20
kill %1
npm run build
```
Expected: HTML returned with correct meta tags. Build succeeds.
- [ ] **Step 5: Commit**
```bash
git add src/layouts/Layout.astro src/pages/index.astro public/favicon.svg
git commit -m "feat: add base layout with SEO meta tags and structured data"
```
---
## Chunk 2: Page Sections
### Task 4: Navbar Component
**Files:**
- Create: `src/components/Navbar.astro`
- [ ] **Step 1: Create Navbar.astro**
```astro
---
// src/components/Navbar.astro
---
<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">
<!-- Logo -->
<a href="https://www.factorit.com" target="_blank" rel="noopener noreferrer">
<img src="/LogoFIT.png" alt="FIT Labs" class="h-8" />
</a>
<!-- Desktop Nav -->
<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>
<!-- Mobile Hamburger -->
<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 id="menu-icon" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Mobile Overlay -->
<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>
// Navbar scroll: transparent → solid
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');
}
});
// Mobile menu toggle
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>
```
- [ ] **Step 2: Add Navbar to index.astro**
```astro
---
import Layout from '../layouts/Layout.astro';
import Navbar from '../components/Navbar.astro';
import '../styles/global.css';
---
<Layout>
<Navbar />
<main>
<div class="h-screen bg-dark-bg flex items-center justify-center">
<p class="text-white text-2xl">Hero placeholder</p>
</div>
<div class="h-screen bg-light-bg" id="nosotros"></div>
<div class="h-screen bg-dark-bg" id="capacidades"></div>
<div class="h-screen bg-light-bg"></div>
<div class="h-screen bg-dark-bg" id="contacto"></div>
</main>
</Layout>
```
- [ ] **Step 3: Verify — dev server, scroll nav behavior, mobile menu**
```bash
npm run dev -- --port 4321
```
Open `http://localhost:4321`. Verify: navbar transparent at top, solid on scroll, mobile menu opens/closes.
- [ ] **Step 4: Commit**
```bash
git add src/components/Navbar.astro src/pages/index.astro
git commit -m "feat: add sticky navbar with scroll behavior and mobile menu"
```
---
### Task 5: Hero Section
**Files:**
- Create: `src/components/Hero.astro`
- [ ] **Step 1: Create Hero.astro with gradient mesh**
```astro
---
// src/components/Hero.astro
---
<section id="hero" class="relative min-h-screen flex items-center justify-center bg-dark-bg overflow-hidden">
<!-- Gradient Mesh Blobs -->
<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>
<!-- Content -->
<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 ↓
</a>
</div>
</section>
```
- [ ] **Step 2: Add Hero to index.astro (replace placeholder)**
Update `index.astro` to import and use `<Hero />` instead of the hero placeholder div.
- [ ] **Step 3: Verify — animated gradient blobs drifting, content centered, CTA visible**
```bash
npm run dev -- --port 4321
```
Open `http://localhost:4321`. Verify: blobs animate, text is readable, button links to `#capacidades`.
- [ ] **Step 4: Commit**
```bash
git add src/components/Hero.astro src/pages/index.astro
git commit -m "feat: add hero section with gradient mesh animation"
```
---
### Task 6: About Section
**Files:**
- Create: `src/components/About.astro`
- [ ] **Step 1: Create About.astro**
```astro
---
// src/components/About.astro
---
<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ón de FactorIT
</h2>
<p class="text-slate-500 text-lg leading-relaxed">
FIT Labs es donde exploramos, prototipamos y llevamos a producción soluciones
de inteligencia artificial para empresas. Nacimos del ADN tecnológico de
FactorIT para resolver los desafíos más complejos con IA — no entregamos
demos, entregamos sistemas que funcionan en el mundo real.
</p>
</div>
</section>
```
- [ ] **Step 2: Add About to index.astro**
Import and replace the `#nosotros` placeholder.
- [ ] **Step 3: Verify and commit**
```bash
npm run build
git add src/components/About.astro src/pages/index.astro
git commit -m "feat: add about section"
```
---
### Task 7: Capabilities Section
**Files:**
- Create: `src/components/Capabilities.astro`
- [ ] **Step 1: Create Capabilities.astro**
```astro
---
// src/components/Capabilities.astro
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>
```
- [ ] **Step 2: Add Capabilities to index.astro**
Import and replace the `#capacidades` placeholder.
- [ ] **Step 3: Verify — 5 items with colored borders, icons visible**
```bash
npm run dev -- --port 4321
```
- [ ] **Step 4: Commit**
```bash
git add src/components/Capabilities.astro src/pages/index.astro
git commit -m "feat: add capabilities section with icon list"
```
---
### Task 8: Differentiator Section
**Files:**
- Create: `src/components/Differentiator.astro`
- [ ] **Step 1: Create Differentiator.astro**
```astro
---
// src/components/Differentiator.astro
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">
<!-- Narrative -->
<div class="flex-1">
<h2 class="text-2xl md:text-3xl font-bold text-slate-900 mb-4">
Innovació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á pensado para integrarse en operaciones reales,
soportar carga real, y crecer cuando tu negocio crece.
</p>
</div>
<!-- Values list -->
<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>
```
- [ ] **Step 2: Add to index.astro**
Import and replace the light-bg placeholder.
- [ ] **Step 3: Verify and commit**
```bash
npm run build
git add src/components/Differentiator.astro src/pages/index.astro
git commit -m "feat: add differentiator section with values"
```
---
### Task 9: Contact (CTA) Section
**Files:**
- Create: `src/components/Contact.astro`
- [ ] **Step 1: Create Contact.astro**
```astro
---
// src/components/Contact.astro
---
<section id="contacto" class="relative bg-dark-bg py-24 px-6 overflow-hidden">
<!-- Subtle glow -->
<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">
¿Tienes un desafío en mente?
</h2>
<p class="text-slate-400 text-lg mb-10 leading-relaxed">
Cuéntanos qué problema quieres resolver. Nosotros vemos có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 →
</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>
```
- [ ] **Step 2: Add to index.astro**
Import and replace the `#contacto` placeholder.
- [ ] **Step 3: Verify and commit**
```bash
npm run build
git add src/components/Contact.astro src/pages/index.astro
git commit -m "feat: add CTA contact section"
```
---
### Task 10: Footer
**Files:**
- Create: `src/components/Footer.astro`
- [ ] **Step 1: Create Footer.astro**
```astro
---
// src/components/Footer.astro
---
<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">
<!-- Left: branding -->
<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>
<!-- Right: links -->
<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>
```
- [ ] **Step 2: Add to index.astro**
Import Footer and place after `</main>`.
- [ ] **Step 3: Verify and commit**
```bash
npm run build
git add src/components/Footer.astro src/pages/index.astro
git commit -m "feat: add footer with FactorIT branding"
```
---
## Chunk 3: Animations, Polish & Deployment
### Task 11: Scroll Reveal Animations
**Files:**
- Modify: `src/layouts/Layout.astro` (add script before `</body>`)
- [ ] **Step 1: Add IntersectionObserver script to Layout.astro**
Add before `</body>`:
```html
<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>
```
- [ ] **Step 2: Verify — sections and capabilities animate on scroll**
```bash
npm run dev -- --port 4321
```
Open `http://localhost:4321`. Scroll down. Verify: sections fade in, capability items slide in from left with staggered delay.
- [ ] **Step 3: Commit**
```bash
git add src/layouts/Layout.astro
git commit -m "feat: add scroll reveal animations"
```
---
### Task 12: Final index.astro Assembly
**Files:**
- Modify: `src/pages/index.astro`
- [ ] **Step 1: Ensure index.astro imports all components in order**
```astro
---
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';
---
<Layout>
<Navbar />
<main>
<Hero />
<About />
<Capabilities />
<Differentiator />
<Contact />
</main>
<Footer />
</Layout>
```
- [ ] **Step 2: Full visual check on dev server**
```bash
npm run dev -- --port 4321
```
Check all 6 sections render, navbar works, animations fire, mobile menu works, all links work.
- [ ] **Step 3: Build and verify output**
```bash
npm run build
ls -la dist/
cat dist/index.html | head -30
```
Expected: `dist/index.html` exists with all content.
- [ ] **Step 4: Commit**
```bash
git add src/pages/index.astro
git commit -m "feat: assemble complete landing page"
```
---
### Task 13: Docker + Nginx for Coolify
**Files:**
- Create: `Dockerfile`
- Create: `nginx/nginx.conf`
- [ ] **Step 1: Create nginx.conf**
```nginx
# nginx/nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
include /etc/nginx/mime.types;
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/index.html /index.html;
}
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
}
}
```
- [ ] **Step 2: Create Dockerfile**
```dockerfile
# Dockerfile
FROM node:lts AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine AS runtime
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080
```
- [ ] **Step 3: Test Docker build locally**
```bash
docker build -t fitlabs-dev .
docker run --rm -p 8080:8080 fitlabs-dev &
sleep 2
curl -s http://localhost:8080 | head -20
docker stop $(docker ps -q --filter ancestor=fitlabs-dev)
```
Expected: HTML content returned, site accessible at `http://localhost:8080`.
- [ ] **Step 4: Commit**
```bash
git add Dockerfile nginx/nginx.conf
git commit -m "feat: add Dockerfile and nginx config for Coolify deployment"
```
---
### Task 14: Final Build Verification
- [ ] **Step 1: Clean build**
```bash
rm -rf dist/
npm run build
```
Expected: Build succeeds with no warnings.
- [ ] **Step 2: Check output size**
```bash
du -sh dist/
find dist/ -type f | wc -l
```
Expected: Small output (~100KB500KB), few files.
- [ ] **Step 3: Verify HTML includes all sections**
```bash
grep -c 'id="hero"\|id="nosotros"\|id="capacidades"\|id="contacto"' dist/index.html
```
Expected: 4 matches.
- [ ] **Step 4: Final commit**
```bash
git add -A
git commit -m "chore: final build verification — ready for Coolify deploy"
```
---
## Deployment Notes
To deploy on Coolify:
1. Push repo to a Git remote (GitHub, GitLab, etc.)
2. In Coolify, create a new resource → Docker-based
3. Point to the repo, Coolify will detect the Dockerfile
4. Set port to `8080`
5. Configure domain: `fitlabs.dev`
6. Deploy