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
+49
View File
@@ -0,0 +1,49 @@
---
import '../styles/global.css';
export interface Props {
title: string;
description: string;
image?: string;
}
const { title, description, image } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const socialImage = image ? new URL(image, Astro.site) : new URL('/images/og-default.jpg', Astro.site);
---
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title} | David Aragón - Indie Builder</title>
<meta name="description" content={description}>
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={socialImage} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content={canonicalURL} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={socialImage} />
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<!-- 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;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
</head>
<body class="min-h-screen flex flex-col">
<slot />
</body>
</html>
+34
View File
@@ -0,0 +1,34 @@
---
const blogPostJsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "{title}",
"description": "{description}",
"image": "{socialImage}",
"datePublished": "{publishDate}",
"author": {
"@type": "Person",
"name": "David Aragón",
"url": "https://davidaragon.impresion3d.pro"
},
"publisher": {
"@type": "Person",
"name": "David Aragón",
"url": "https://davidaragon.impresion3d.pro"
},
"keywords": "{keywords}",
"articleSection": "{category}",
"inLanguage": "es-ES"
};
---
<!DOCTYPE html>
<html lang="es">
<head>
<!-- JSON-LD Blog Structured Data -->
<script type="application/ld+json" set:html={JSON.stringify(blogPostJsonLd)} />
</head>
<body>
<!-- Blog content -->
</body>
</html>
+95
View File
@@ -0,0 +1,95 @@
---
import BaseLayout from './BaseLayout.astro';
import Header from '../components/layout/Header.astro';
import Footer from '../components/layout/Footer.astro';
import Tag from '../components/ui/Tag.astro';
import { formatDate } from '../utils/dateFormat';
export interface Props {
title: string;
description: string;
url: string;
github?: string;
status: string;
tags: string[];
startDate: Date;
image?: string;
}
const { title, description, url, github, status, tags, startDate, image } = Astro.props;
const statusColors = {
active: 'text-accent-green',
development: 'text-accent-yellow',
completed: 'text-text-tertiary',
};
---
<BaseLayout title={title} description={description} image={image}>
<Header />
<main class="flex-1">
<article class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<header class="mb-12">
<div class="flex items-center gap-3 mb-4">
<span class={`text-sm font-semibold uppercase tracking-wide ${statusColors[status] || 'text-text-tertiary'}`}>
{status}
</span>
<span class="text-text-tertiary">•</span>
<time datetime={startDate.toISOString()} class="text-sm text-text-tertiary">
Desde {formatDate(startDate)}
</time>
</div>
<h1 class="text-4xl md:text-5xl font-bold mb-4">{title}</h1>
<p class="text-xl text-text-secondary mb-6">{description}</p>
<div class="flex flex-wrap gap-3">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="px-6 py-3 bg-primary hover:bg-primary/80 text-background rounded-lg
font-semibold transition-colors"
>
Ver Proyecto →
</a>
{github && (
<a
href={github}
target="_blank"
rel="noopener noreferrer"
class="px-6 py-3 bg-surface hover:bg-surface/80 text-text-primary rounded-lg
font-semibold border border-text-tertiary/20 transition-colors"
>
GitHub →
</a>
)}
</div>
</header>
{image && (
<div class="mb-12 rounded-xl overflow-hidden border border-text-tertiary/20">
<img
src={image}
alt={title}
class="w-full h-auto"
/>
</div>
)}
<div class="prose prose-invert prose-lg max-w-none">
<slot />
</div>
<footer class="mt-12 pt-8 border-t border-text-tertiary/20">
<h3 class="text-lg font-semibold mb-4">Tecnologías</h3>
<div class="flex flex-wrap gap-2">
{tags.map(tag => (
<Tag label={tag} variant="neutral" />
))}
</div>
</footer>
</article>
</main>
<Footer />
</BaseLayout>