feat: recover portfolio pages from scratch workspaces

- Add base layouts (BaseLayout, BlogLayout, ProjectLayout)
- Add UI components (Header, Footer, Navigation, Card, Tag)
- Add About page with personal story
- Add Projects pages (index, detail)
- Add homepage content
- Add SEO files (robots.txt, webmanifest)

Work was done by agents in isolated workspaces.
Consolidated into main repo for proper git tracking.
This commit is contained in:
wh-leader
2026-05-11 07:41:22 +02:00
parent 85143c0b05
commit 05036766e4
16 changed files with 638 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
---
import { getCollection } from 'astro:content';
import ProjectLayout from '../../layouts/ProjectLayout.astro';
export async function getStaticPaths() {
const projects = await getCollection('projects');
return projects
.filter(p => !p.data.draft)
.map(project => ({
params: { slug: project.slug },
props: { project },
}));
}
const { project } = Astro.props;
const { Content } = await project.render();
---
<ProjectLayout {...project.data}>
<Content />
</ProjectLayout>
+127
View File
@@ -0,0 +1,127 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import Header from '../../components/layout/Header.astro';
import Footer from '../../components/layout/Footer.astro';
import Card from '../../components/ui/Card.astro';
import Tag from '../../components/ui/Tag.astro';
import { getCollection } from 'astro:content';
const allProjects = (await getCollection('projects'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.startDate.getTime() - a.data.startDate.getTime());
const featured = allProjects.filter(p => p.data.featured);
const active = allProjects.filter(p => p.data.status === 'active' && !p.data.featured);
const others = allProjects.filter(p => p.data.status !== 'active' && !p.data.featured);
---
<BaseLayout
title="Proyectos"
description="Portfolio de productos SaaS: WarrantyHub, Garage61 API, y más proyectos en desarrollo"
>
<Header />
<main class="flex-1 max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<header class="mb-12">
<h1 class="text-4xl font-bold mb-4">Proyectos</h1>
<p class="text-xl text-text-secondary max-w-3xl">
Portfolio de productos SaaS construidos en público. Desde ideas hasta
productos escalables generando revenue recurrente.
</p>
</header>
{featured.length > 0 && (
<section class="mb-16">
<h2 class="text-2xl font-semibold mb-6 text-primary">🌟 Proyectos Destacados</h2>
<div class="grid md:grid-cols-2 gap-6">
{featured.map(project => (
<Card href={`/projects/${project.slug}`} class="flex flex-col">
{project.data.image && (
<img
src={project.data.image}
alt={project.data.title}
class="w-full h-48 object-cover rounded-t-lg -m-6 mb-4"
/>
)}
<h3 class="text-2xl font-semibold mb-2 text-text-primary">
{project.data.title}
</h3>
<p class="text-text-secondary mb-4 flex-1">
{project.data.description}
</p>
<div class="flex flex-wrap gap-2 mb-4">
{project.data.tags.slice(0, 4).map(tag => (
<Tag label={tag} variant="primary" />
))}
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-accent-green font-medium capitalize">
{project.data.status}
</span>
<span class="text-primary text-sm font-medium">
Ver proyecto →
</span>
</div>
</Card>
))}
</div>
</section>
)}
{active.length > 0 && (
<section class="mb-16">
<h2 class="text-2xl font-semibold mb-6">Proyectos Activos</h2>
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{active.map(project => (
<Card href={`/projects/${project.slug}`}>
<h3 class="text-xl font-semibold mb-2 text-text-primary">
{project.data.title}
</h3>
<p class="text-text-secondary mb-4">
{project.data.description}
</p>
<div class="flex flex-wrap gap-2 mb-4">
{project.data.tags.slice(0, 3).map(tag => (
<Tag label={tag} variant="neutral" />
))}
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-accent-green font-medium">Activo</span>
<span class="text-primary text-sm font-medium">Ver →</span>
</div>
</Card>
))}
</div>
</section>
)}
{others.length > 0 && (
<section>
<h2 class="text-2xl font-semibold mb-6">Otros Proyectos</h2>
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{others.map(project => (
<Card href={`/projects/${project.slug}`}>
<h3 class="text-xl font-semibold mb-2 text-text-primary">
{project.data.title}
</h3>
<p class="text-text-secondary mb-4">
{project.data.description}
</p>
<div class="flex flex-wrap gap-2 mb-4">
{project.data.tags.slice(0, 3).map(tag => (
<Tag label={tag} variant="neutral" />
))}
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-text-tertiary capitalize">
{project.data.status}
</span>
<span class="text-primary text-sm font-medium">Ver →</span>
</div>
</Card>
))}
</div>
</section>
)}
</main>
<Footer />
</BaseLayout>