first version

This commit is contained in:
aferrer committed 2025-10-30 21:58:47 +01:00
commit 33bf3d1e5d
39 files changed
+5197

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
---
import Section from "./ui/Section.astro";
const { aboutMe } = Astro.props;
---
<Section id="about-me">
<h3 class="text-3xl font-bold text-white text-center mb-10">
Acerca de mí
</h3>
{aboutMe.split("\n").map((line: string) => (
line.trim() !== "" ? (
<p class="text-gray-400 mb-4 text-wrap">{line}</p>
) : null
))}
</Section>
+43
View File
@@ -0,0 +1,43 @@
---
import Section from "./ui/Section.astro";
const { experience } = Astro.props;
interface ExperienceProps {
role: string;
company: string;
duration: string;
description: string;
}
---
<Section id="experience">
<h3 class="text-3xl font-bold text-white text-center mb-10">
Experiencia
</h3>
<div class="space-y-12">
{experience.map((exp: ExperienceProps) => (
<div class="flex gap-x-3 relative group rounded-lg">
<div class="relative last:after:hidden after:absolute after:top-0 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200 dark:after:bg-neutral-700 dark:group-hover:after:bg-neutral-600">
<div class="relative z-10 size-7 flex justify-center items-center">
<div class="size-2 rounded-full bg-white border-2 border-gray-300 group-hover:border-gray-600 dark:bg-neutral-800 dark:border-neutral-600 dark:group-hover:border-neutral-600"></div>
</div>
</div>
<div class="section-card overflow-hidden md:flex">
<div class="p-8 flex-1">
<span class="text-sm text-sky-400 mb-4 block">
{exp.duration}
</span>
<h4 class="text-xl font-bold text-white mb-2">
{exp.role}
</h4>
<h5 class="text-md font-medium text-gray-500 mb-4">
{exp.company}
</h5>
<p class="text-gray-400 text-wrap">{exp.description}</p>
</div>
</div>
</div>
))}
</div>
</Section>
+10
View File
@@ -0,0 +1,10 @@
---
const { name } = Astro.props;
---
<footer class="text-center py-8">
<p class="text-gray-500">
© {new Date().getFullYear()}
{name}. Built with Astro & Tailwind CSS.
</p>
</footer>
+130
View File
@@ -0,0 +1,130 @@
---
import { navItems } from "@data/index";
---
<header
id="header-nav"
class="fixed top-0 left-0 right-0 z-50 transition-all duration-300 bg-background/95 ease-in-out"
>
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
{/* Logo */}
<a href="#home" class="text-xl font-bold text-foreground"> {"<Aferrer />"} </a>
{/* Desktop Navigation */}
<nav class="hidden md:flex items-center gap-8">
{
navItems.map((item) => (
<a
href={item.href}
class="text-sm font-medium text-foreground/80 hover:text-foreground transition-colors"
>
{item.label}
</a>
))
}
</nav>
{/* Mobile Navigation */}
<div class="md:hidden">
<button
id="menu-toggle-button"
class="p-2 rounded-md text-foreground hover:bg-foreground/10 transition-colors z-50 relative"
>
<span id="menu-icon" class="text-2xl">☰</span>
</button>
<nav
id="mobile-menu"
class="
fixed top-0 left-0 right-0 h-dvh w-full
flex flex-col items-center justify-center
bg-background/95
shadow-md
z-40
-translate-y-full
transition-transform duration-500 ease-in-out
bg-[#111827]/95
backdrop-blur-md
"
>
<div class="flex flex-col items-center space-y-8">
{
navItems.map((item) => (
<a
href={item.href}
class="text-xl font-medium text-foreground/80 hover:text-foreground transition-colors"
>
{item.label}
</a>
))
}
</div>
</nav>
</div>
</div>
</div>
</header>
<script>
let scrolled = false;
let isMobileMenuOpen = false; // Variable de estado
const header = document.getElementById("header-nav");
const mobileMenu = document.getElementById("mobile-menu");
const body = document.body;
// --- Function to update header style based on scroll and menu state ---
function updateHeaderStyle() {
scrolled = window.scrollY > 0;
// The header maintains the blur if:
// 1. The page is scrolled OR
// 2. The mobile menu is open
if (scrolled || isMobileMenuOpen) {
header?.classList.add("shadow-md", "backdrop-blur-md");
} else {
// The blur is only removed if there is NO scroll AND the menu is closed
header?.classList.remove("shadow-md", "backdrop-blur-md");
}
}
// Add client-side scroll event listener
if (typeof window !== "undefined") {
window.addEventListener("scroll", updateHeaderStyle);
updateHeaderStyle();
}
// Mobile menu toggle
document.addEventListener("DOMContentLoaded", () => {
const menuButton = document.getElementById("menu-toggle-button");
const menuIcon = document.getElementById("menu-icon");
const mobileLinks = document.querySelectorAll("#mobile-menu a");
if (!menuButton || !menuIcon || !mobileMenu) return;
const closeMobileMenu = () => {
mobileMenu.classList.add("-translate-y-full");
mobileMenu.classList.remove("translate-y-0");
body.classList.remove("overflow-hidden");
menuIcon.textContent = "☰";
updateHeaderStyle();
};
menuButton?.addEventListener("click", () => {
isMobileMenuOpen = !isMobileMenuOpen;
if (isMobileMenuOpen) {
mobileMenu.classList.remove("-translate-y-full");
mobileMenu.classList.add("translate-y-0");
body.classList.add("overflow-hidden");
menuIcon.textContent = "✕";
} else {
closeMobileMenu();
}
updateHeaderStyle();
});
mobileLinks.forEach(link => {
link.addEventListener("click", closeMobileMenu);
});
});
</script>
+88
View File
@@ -0,0 +1,88 @@
---
import { Icon } from 'astro-icon/components'
const { name, title, bio, github, linkedin } = Astro.props;
---
<section id="hero" class="py-20 md:py-32 flex flex-col items-center justify-center min-h-[90vh]">
<div class="flex flex-col md:flex-row items-center md:items-center text-center md:text-left max-w-4xl w-full">
<div class="mb-6 md:mb-0 md:mr-12 flex-shrink-0 relative group">
<img
src="/avatar.webp"
alt={`${name} Avatar`}
class="w-32 h-32 md:w-48 md:h-48 rounded-full mx-auto border-4 border-slate-600 md:border-slate-300 relative z-20"
/>
<div class="absolute -inset-1 rounded-full blur-md bg-gradient-to-br from-slate-500 via-cyan-500 to-indigo-500 z-10 glow"></div>
</div>
<div class="flex-1 min-w-0">
<h1
class="text-4xl md:text-6xl font-extrabold tracking-tight bg-gradient-to-r from-slate-300 to-slate-500 bg-clip-text text-transparent mb-4 text-balance"
>
{name}
</h1>
<h2
class="text-2xl md:text-3xl font-semibold tracking-tight bg-gradient-to-r from-cyan-400 to-indigo-400 bg-clip-text text-transparent mb-6"
>
{title}
</h2>
<p class="text-lg text-gray-400 max-w-full text-wrap">{bio}</p>
</div>
</div>
<div class="mt-8 flex space-x-6 items-center justify-center max-w-4xl w-full">
<a
href={github}
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-gray-200 transition-colors hover:scale-110 duration-500"
aria-label="GitHub"
>
<Icon name="gitea" class="w-8 h-8" />
</a>
<a
href={linkedin}
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-gray-200 transition-colors hover:scale-110 duration-500"
aria-label="LinkedIn"
>
<Icon name="linkedin" class="w-8 h-8" />
</a>
</div>
</section>
<style>
/* Optional: Add any additional styles for the Hero component here */
#header-nav {
animation: blur linear both;
animation-timeline: scroll();
animation-range: 0 500px;
/* height: 80px; */
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(0px);
}
/* Glow siempre visible */
.glow {
opacity: 1;
transform: scale(1);
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}
/* Efecto pulsante al hacer hover */
.group:hover .glow {
animation: pulse 1.5s infinite;
}
/* Animación de pulso */
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.6;
}
50% {
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.6;
}
}
</style>
+69
View File
@@ -0,0 +1,69 @@
---
import Section from "./ui/Section.astro";
const { projects } = Astro.props;
interface project {
title: string;
description: string;
tags: string[];
liveUrl?: string;
repoUrl: string;
}
---
<Section id="projects">
<h3 class="text-3xl font-bold text-white text-center mb-10">
Proyectos
</h3>
<div class="space-y-12">
{
projects.map((project: project) => (
<div class="section-card overflow-hidden md:flex">
<div class="p-8 flex-1">
<h4 class="text-xl font-bold text-white mb-2">
{project.title}
</h4>
<p class="text-gray-400 mb-4">{project.description}</p>
<div class="flex flex-wrap gap-2 mb-6">
{project.tags.map((tag) => (
<span class="bg-gray-700 text-sky-300 text-xs font-semibold px-2.5 py-1 rounded-full">
{tag}
</span>
))}
</div>
<div class="flex items-center gap-4">
{project.liveUrl && (
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-sky-400 hover:text-sky-300 transition-colors"
>
<span
class="iconify"
data-icon="mdi:open-in-new"
data-inline="false"
/>
Live Demo
</a>
)}
<a
href={project.repoUrl}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-sky-400 hover:text-sky-300 transition-colors"
>
<span
class="iconify"
data-icon="mdi:github"
data-inline="false"
/>
Source Code
</a>
</div>
</div>
</div>
))
}
</div>
</Section>
+41
View File
@@ -0,0 +1,41 @@
---
import { Icon } from "astro-icon/components";
import Section from "./ui/Section.astro";
const { skills } = Astro.props;
interface SkillProps {
name: string;
icon: string;
}
---
<Section id="skills">
<h3 class="text-3xl font-bold text-white text-center mb-10">
Habilidades
</h3>
<div class="flex items-center justify-between flex-wrap gap-6">
{skills.map((skill:SkillProps) => (
<div class="flex flex-col items-center justify-center p-6 text-center">
<div class="relative group">
<!-- Icono -->
<Icon
name={skill.icon}
class="text-4xl mb-3 cursor-pointer"
data-inline="false"
/>
<!-- Tooltip con flecha -->
<div class="absolute top-full left-1/2 mt-3 -translate-x-1/2 hidden group-hover:flex flex-col items-center">
<!-- Cuerpo del tooltip -->
<div class="bg-gray-800 text-white text-sm px-2 py-1 rounded shadow-lg whitespace-nowrap relative">
{skill.name}
<!-- Flechita -->
<div class="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-gray-800 rotate-45"></div>
</div>
</div>
</div>
</div>
))}
</div>
</Section>
+3
View File
@@ -0,0 +1,3 @@
<section class="container mx-auto max-w-7xl px-6 py-12 md:py-20" id={Astro.props.id}>
<slot />
</section>