Merge pull request #1 from alejandrolaguna20/release/1.1.0

Release/1.1.0
This commit is contained in:
Alejandro Laguna
2025-08-23 13:17:24 +02:00
committed by GitHub
57 changed files with 8244 additions and 3490 deletions

34
.gitignore vendored
View File

@@ -1,24 +1,24 @@
# Logs # build output
logs dist/
*.log
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log*
node_modules # environment variables
dist .env
dist-ssr .env.production
*.local
# Editor directories and files # macOS-specific files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store .DS_Store
*.suo
*.ntvs* # jetbrains setting folder
*.njsproj .idea/
*.sln
*.sw?

View File

@@ -1,15 +1,46 @@
# `create-preact` # Astro Starter Kit: Basics
<h2 align="center"> ```sh
<img height="256" width="256" src="./src/assets/preact.svg"> npm create astro@latest -- --template basics
</h2> ```
<h3 align="center">Get started using Preact and Vite!</h3> > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## Getting Started ## 🚀 Project Structure
- `npm run dev` - Starts a dev server at http://localhost:5173/ Inside of your Astro project, you'll see the following folders and files:
- `npm run build` - Builds for production, emitting to `dist/` ```text
/
├── public/
│ └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── Welcome.astro
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│   └── index.astro
└── package.json
```
- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

18
astro.config.mjs Normal file
View File

@@ -0,0 +1,18 @@
// @ts-check
import { defineConfig } from 'astro/config';
import tailwindcss from "@tailwindcss/vite";
import astroIcon from "astro-icon"
import node from "@astrojs/node";
// https://astro.build/config
export default defineConfig({
vite: { plugins: [tailwindcss()], },
integrations: [astroIcon()],
server: {
host: '0.0.0.0'
},
adapter: node({
mode: "standalone",
}),
});

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon-32x32.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<title>Alejandro Laguna</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

7462
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,25 @@
{ {
"homepage": "https://alejandrolaguna20.github.io/portfolio/", "name": "portfoliov2",
"private": true,
"type": "module", "type": "module",
"version": "0.0.1",
"engines": {
"node": ">=18.20.8"
},
"scripts": { "scripts": {
"dev": "vite", "dev": "astro dev",
"build": "vite build", "build": "astro build",
"preview": "vite preview", "preview": "astro preview",
"predeploy": "npm run build", "astro": "astro",
"deploy": "gh-pages -d dist" "start": "node ./dist/server/entry.mjs"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.3", "@astrojs/node": "^9.3.3",
"preact": "^10.25.3", "@tailwindcss/vite": "^4.1.11",
"preact-iso": "^2.9.1", "astro": "^5.12.8",
"tailwindcss": "^4.1.3" "astro-icon": "^1.1.5",
}, "lucide-astro": "^0.536.0",
"devDependencies": { "lucide-react": "^0.536.0",
"@preact/preset-vite": "^2.9.3", "serve": "^14.2.4",
"gh-pages": "^6.3.0", "tailwindcss": "^4.1.11"
"typescript": "^5.8.2",
"vite": "^6.0.4"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

1
public/astro.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

1
public/background.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 995 B

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

9
public/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

1
public/site.webmanifest Normal file
View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

1
src/assets/astro.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

9
src/assets/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="27.68" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 296">
<path fill="#673AB8" d="m128 0l128 73.9v147.8l-128 73.9L0 221.7V73.9z"></path>
<path fill="#FFF" d="M34.865 220.478c17.016 21.78 71.095 5.185 122.15-34.704c51.055-39.888 80.24-88.345 63.224-110.126c-17.017-21.78-71.095-5.184-122.15 34.704c-51.055 39.89-80.24 88.346-63.224 110.126Zm7.27-5.68c-5.644-7.222-3.178-21.402 7.573-39.253c11.322-18.797 30.541-39.548 54.06-57.923c23.52-18.375 48.303-32.004 69.281-38.442c19.922-6.113 34.277-5.075 39.92 2.148c5.644 7.223 3.178 21.403-7.573 39.254c-11.322 18.797-30.541 39.547-54.06 57.923c-23.52 18.375-48.304 32.004-69.281 38.441c-19.922 6.114-34.277 5.076-39.92-2.147Z"></path>
<path fill="#FFF" d="M220.239 220.478c17.017-21.78-12.169-70.237-63.224-110.126C105.96 70.464 51.88 53.868 34.865 75.648c-17.017 21.78 12.169 70.238 63.224 110.126c51.055 39.889 105.133 56.485 122.15 34.704Zm-7.27-5.68c-5.643 7.224-19.998 8.262-39.92 2.148c-20.978-6.437-45.761-20.066-69.28-38.441c-23.52-18.376-42.74-39.126-54.06-57.923c-10.752-17.851-13.218-32.03-7.575-39.254c5.644-7.223 19.999-8.261 39.92-2.148c20.978 6.438 45.762 20.067 69.281 38.442c23.52 18.375 42.739 39.126 54.06 57.923c10.752 17.85 13.218 32.03 7.574 39.254Z"></path>
<path fill="#FFF" d="M127.552 167.667c10.827 0 19.603-8.777 19.603-19.604c0-10.826-8.776-19.603-19.603-19.603c-10.827 0-19.604 8.777-19.604 19.603c0 10.827 8.777 19.604 19.604 19.604Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -1,48 +0,0 @@
import { useEffect, useState } from 'react';
import image from '../assets/sketch.png';
const taglines = [
"Amateur Writer",
"Bookworm",
"Star Gazer",
"Physics Enthusiast",
"Lifelong Learner",
"Tea Enthusiast",
"Cosmic Explorer",
"Knowledge Seeker",
];
export default function About({ theme }) {
const [tagline, setTagline] = useState('');
useEffect(() => {
const randomTagline = taglines[Math.floor(Math.random() * taglines.length)];
setTagline(randomTagline);
}, []);
return (
<section id="about" class="pt-8">
<div class="flex flex-col md:flex-row gap-8 items-center">
<div class="flex-1">
<h1 class={`text-4xl md:text-5xl mb-4 ${theme.accent} font-serif font-semibold`}>Alejandro Laguna</h1>
<h2 class="text-xl md:text-2xl mb-6 text-[#b8c4b5]/80 font-medium font-serif">Software Engineer & {tagline}</h2>
<p class="text-lg mb-6 text-[#b8c4b5]/90">
Crafting digital projects that inspire me, while exploring everything from books and tea to space and science. First-year computer science student.
</p>
<div class="flex gap-4">
<a href="#projects" class={`px-6 py-2 rounded-full border ${theme.border} ${theme.hover} transition-all hover:-translate-y-0.5`}>
View Work
</a>
<a href="mailto:alejandro.laguna@epitech.eu" class={`px-6 py-2 rounded-full ${theme.button} transition-all hover:-translate-y-0.5`}>
Contact
</a>
</div>
</div>
<div class="flex-1 flex justify-center">
<img src={image} alt="Alex Clark's Sketch" class="w-64 h-64 rounded-full object-cover" />
</div>
</div>
</section>
);
}

View File

@@ -1,37 +0,0 @@
import EducationCard from './EducationCard';
const education = [
{
institution: "European Institute of Technology (EPITECH)",
degree: "Degree + Master in Computer Science",
duration: "2024 - 2029",
highlights: [
"ONGOING",
"Multiple courses passed with Honors",
"Completed over 30 projects from different fields"
]
},
{
institution: "INS Cendrassos",
degree: "Microcomputer systems & networks",
duration: "2022 - 2024",
highlights: [
"First of the class",
"ERASMUS+ Scholarship",
"Student of the year"
]
}
];
export default function Education({ theme }) {
return (
<section id="education">
<h2 class={`text-3xl mb-8 font-serif font-semibold ${theme.accent}`}>Education</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{education.map((edu, i) => (
<EducationCard key={i} education={edu} theme={theme} />
))}
</div>
</section>
);
}

View File

@@ -1,18 +0,0 @@
export default function EducationCard({ education, theme }) {
return (
<div class={`p-4 rounded-xl border ${theme.border} ${theme.card} transition-all hover:-translate-y-1 group`}>
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-lg">{education.degree}</h3>
<p class="text-[#b8c4b5]/80">{education.institution}</p>
</div>
<span class="text-sm text-[#b8c4b5]/50">{education.duration}</span>
</div>
<ul class="list-disc pl-6 space-y-2 mb-4">
{education.highlights.map((highlight, j) => (
<li key={j} class="text-[#b8c4b5]/90">{highlight}</li>
))}
</ul>
</div>
);
}

View File

@@ -1,39 +0,0 @@
import ExperienceCard from './ExperienceCard';
const experience = [
{
company: "Avantiam Inc - Figueres, Spain",
role: "Full-Stack Developer",
duration: "Sep 2023 - Sep 2024",
technologies: ["Laravel", "Livewire", "AlpineJS", "TailwindCSS", "MariaDB/MySQL"],
highlights: [
"Developed and maintained a comprehensive business management service used by over 100 companies",
"Managed all aspects of the project lifecycle, including server administration, performance tuning, and user experience improvements.",
"Recommendation letter available upon request."
]
},
{
company: "Niblu Group - Maribor, Slovenia",
role: "Backend Developer Intern",
duration: "Summer 2023",
technologies: ["NextJS", "TailwindCSS", "TypeScript", "FastAPI", "AWS S3 Bucket"],
highlights: [
"Developed a secure authentication system and features for managing courses across different user roles.",
"Integrated AWS S3 for seamless storage and retrieval of course materials.",
"Gained valuable personal and professional growth through the ERASMUS experience, adapting to new challenges abroad alone."
]
}
];
export default function Experience({ theme }) {
return (
<section id="experience">
<h2 class={`text-3xl mb-8 ${theme.accent} font-semibold font-serif`}>Work Experience</h2>
<div class="space-y-8">
{experience.map((exp, i) => (
<ExperienceCard key={i} experience={exp} theme={theme} />
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,25 @@
---
export interface Props {
title: string;
company: string;
period: string;
location: string;
achievements: string[];
technologies: string;
}
const { title, company, period, location, achievements, technologies } = Astro.props;
---
<div class="p-4 bg-neutral-50 dark:bg-neutral-800 rounded-2xl border border-neutral-200 dark:border-neutral-700 mx-14">
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100">{title}</h3>
<p class="text-sm text-neutral-600 dark:text-neutral-400">{company}, {period}, {location}</p>
<ul class="list-disc list-inside text-sm text-neutral-700 dark:text-neutral-300 mt-2 space-y-1">
{achievements.map(achievement => (
<li>{achievement}</li>
))}
</ul>
<p class="text-xs text-neutral-500 dark:text-neutral-400 mt-2">
{technologies}
</p>
</div>

View File

@@ -1,36 +0,0 @@
export default function ExperienceCard({ experience, theme }) {
return (
<div class={`p-6 rounded-xl border ${theme.border} ${theme.card} transition-all hover:-translate-y-1 group`}>
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-xl">{experience.company}</h3>
<p class="text-[#b8c4b5]/80">{experience.role}</p>
</div>
<span class="text-sm text-[#b8c4b5]/50">{experience.duration}</span>
</div>
<ul class="list-disc pl-6 space-y-2 mb-4">
{experience.highlights.map((highlight, j) => (
<li key={j} class="text-[#b8c4b5]/90">{highlight}</li>
))}
</ul>
{experience.technologies && (
<div class="flex flex-wrap gap-2 mb-4">
<h4 class="text-lg font-semibold text-[#b8c4b5]/90">Technologies Used:</h4>
{experience.technologies.map((tech, i) => (
<span key={i} class={`px-3 rounded-full ${theme.bg} text-[#b8c4b5]/80`}>{tech}</span>
))}
</div>
)}
{experience.link && (
<a
href={experience.link}
class={`inline-flex items-center text-sm ${theme.accent} ${theme.hover}`}
>
View More
<span class="ml-2 transition-transform group-hover:translate-x-1"></span>
</a>
)}
</div>
);
}

View File

@@ -1,15 +0,0 @@
export default function Footer({ theme }) {
return (
<footer class={`pt-8 border-t border-opacity-20 ${theme.border}`}>
<div class="flex flex-col md:flex-row justify-between items-center">
<p class="text-sm text-[#b8c4b5]/50 mb-4 md:mb-0">© 2025 Alejandro Laguna</p>
<div class="flex gap-6">
<a href="https://github.com/alejandrolaguna20" class={`text-sm ${theme.text} ${theme.hover}`}>GitHub</a>
<a href="https://www.linkedin.com/in/alejandro-laguna-939687278/" class={`text-sm ${theme.text} ${theme.hover}`}>LinkedIn</a>
<a href="/resume_eng.pdf" download="cv_english_alejandro_laguna.pdf" target="_blank" rel="noopener" class={`text-sm ${theme.text} ${theme.hover}`}>CV</a>
</div>
</div>
</footer>
);
}

View File

@@ -1,22 +0,0 @@
export function Header({ theme }) {
return (
<header class="fixed w-full top-0 z-50 backdrop-blur-sm">
<div class="max-w-5xl mx-auto px-4 py-3 flex justify-between items-center">
<h1 class={`text-xl font-bold opacity-50 ${theme.accent} tracking-wider font-serif`}>alejandrolaguna.dev</h1>
<nav class="hidden md:flex gap-6">
{['About', 'Projects', 'Experience'].map((item) => (
<a
href={`#${item.toLowerCase()}`}
class={`text-sm ${theme.text} ${theme.hover} transition-colors`}
>
{item}
</a>
))}
</nav>
</div>
</header>
);
}

View File

@@ -1,10 +0,0 @@
export default function Layout({ children, theme }) {
return (
<div class={`min-h-screen transition-colors duration-300 ${theme.bg} ${theme.text}`}>
<main class="max-w-5xl mx-auto px-4 pt-24 pb-16 space-y-16">
{children}
</main>
</div>
);
}

View File

@@ -0,0 +1,23 @@
---
export interface Props {
href: string;
icon: any;
heading: string;
content: string;
}
const { href, icon: Icon, heading, content } = Astro.props;
---
<a
href={href}
class="nav-card group block p-6 rounded-2xl bg-white dark:bg-neutral-800 border border-neutral-200/80 dark:border-neutral-700 shadow-sm hover:shadow-lg hover:border-neutral-300 dark:hover:border-neutral-600 focus:outline-none focus:ring-2 focus:ring-neutral-900 dark:focus:ring-neutral-100 focus:ring-offset-2 dark:focus:ring-offset-neutral-800 focus:border-neutral-400 dark:focus:border-neutral-500 transition-colors duration-200"
>
<div class="flex flex-col gap-4">
<Icon class="w-7 h-7 text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-neutral-100 transition-colors duration-200" />
<div>
<h3 class="text-2xl font-semibold text-neutral-900 dark:text-neutral-100 mb-2">{heading}</h3>
<p class="text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">{content}</p>
</div>
</div>
</a>

View File

@@ -1,26 +0,0 @@
export default function ProjectCard({ project, theme }) {
return (
<div class={`p-6 rounded-xl border ${theme.border} ${theme.card} transition-all hover:-translate-y-1 group`}>
<div class="flex justify-between items-start mb-4">
<h3 class="text-xl">{project.title}</h3>
<span class="text-sm text-[#b8c4b5]/50">{project.year}</span>
</div>
<p class="mb-4 text-[#b8c4b5]/80">{project.description}</p>
<div class="flex flex-wrap gap-2 mb-4">
{project.tech.map((tech, j) => (
<span key={j} class={`px-3 py-1 rounded-full ${theme.bg} text-[#b8c4b5]/80`}>{tech}</span>
))}
</div>
{project.link && (
<a
href={project.link}
class={`inline-flex items-center text-sm ${theme.accent} ${theme.hover}`}
>
View Details
<span class="ml-2 transition-transform group-hover:translate-x-1"></span>
</a>
)}
</div>
);
}

View File

@@ -1,53 +0,0 @@
import ProjectCard from './ProjectCard';
const projects = [
{
title: "Morph",
description: "Lightweight URL shortening API built in Go with minimal dependencies. First ever project with this language, and I tried to focus on the stdlib and build any needed tools",
tech: ["Go"],
year: "2025",
link: "https://github.com/alejandrolaguna20/morph"
},
{
title: "Runes",
description: "A small, simple, smooth and beautiful flashcard TUI program featuring keyboard navigation and custom study sessions. Built with Go and the Bubbletea framework.",
tech: ["Go", "Bubbletea"],
year: "2025",
link: "https://github.com/alejandrolaguna20/nook.nvim"
},
{
title: "Nook.nvim",
description: "A tiny Neovim plugin created to manage tasks in a way that perfectly fits my personal workflow ",
tech: ["Lua"],
year: "2025",
link: "https://github.com/alejandrolaguna20/nook.nvim"
},
{
title: "Portfolio",
description: "The website you are currently browsing, my little corner on the web.",
tech: ["Typescript", "PreactJS", "TailwindCSS", "Vite"],
year: "2025",
link: "https://github.com/alejandrolaguna20/portfolio"
},
{
title: "Epitech Projects",
description: "A variety of projects I am building in my first year @ Epitech that range from algorithm implementation, graphical and mathematical simulations, and developing from scratch different Linux commands and programs, including a shell.",
tech: ["C", "Bash", "Python"],
year: "2025",
link: "https://www.epitech.eu/programme-grande-ecole-informatique/"
}
];
export default function Projects({ theme }) {
return (
<section id="projects">
<h2 class={`text-3xl font-semibold font-serif mb-8 ${theme.accent}`}>Projects</h2>
<div class="grid gap-6">
{projects.map((project, i) => (
<ProjectCard key={i} project={project} theme={theme} />
))}
</div>
</section>
);
}

16
src/content.config.ts Normal file
View File

@@ -0,0 +1,16 @@
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const workshop = defineCollection({
loader: glob({ base: './src/content/workshop', pattern: '**/*.{md,mdx}' }),
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: image().optional(),
}),
});
export const collections = { workshop };

View File

@@ -0,0 +1,16 @@
---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: '2025-08-03'
tags: []
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -1,44 +0,0 @@
import { render } from 'preact';
import { LocationProvider, Router, Route } from 'preact-iso';
import { Home } from './pages/Home/index.jsx';
import { NotFound } from './pages/_404.jsx';
import './style.css';
import { Header } from './components/Header.js';
export function App() {
const theme = {
bg: 'bg-[#1c2529]',
text: 'text-[#f0e4c8]',
accent: 'text-[#c7dd9a]',
accent2: 'text-[#f0b090]',
border: 'border-[#344146]',
card: 'bg-[#263136]',
hover: 'hover:text-[#fff4d8]',
button: 'bg-[#344146] hover:bg-[#49555a] text-[#f0e4c8]',
bg_hl: 'bg-[#344146]',
gradient: 'from-[#1c2529] via-[#263136] to-[#1c2529]'
}
return (
<LocationProvider>
<Header
theme={theme}
/>
<main>
<Router>
<Route
path="/"
component={() => (
<Home
theme={theme}
/>
)}
/>
<Route default component={() => (<Home theme={theme} />)} />
</Router>
</main>
</LocationProvider>
);
}
render(<App />, document.getElementById('app'));

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

@@ -0,0 +1,70 @@
---
import "../styles/global.css"
import { House, Sun, Moon } from "lucide-astro";
---
<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Alejandro Laguna</title>
<meta name="description" content="A computer science student's digital lab notebook documenting knowledge, building software, and understanding the world." />
<meta name="generator" content={Astro.generator} />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body class="bg-neutral-50 text-neutral-900 dark:bg-neutral-900 dark:text-neutral-100 min-h-screen antialiased transition-colors duration-200">
<div class="fixed inset-0 bg-[linear-gradient(rgba(0,0,0,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(0,0,0,0.03)_1px,transparent_1px)] dark:bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:32px_32px] pointer-events-none" aria-hidden="true"></div>
<header class="relative z-10 w-full px-6 sm:px-8 py-8 border-b border-neutral-200/80 dark:border-neutral-800 bg-white/80 dark:bg-neutral-950/80 backdrop-blur-md">
<div class="max-w-6xl mx-auto flex items-center justify-between">
<div>
<h1 class="text-2xl sm:text-3xl font-bold text-neutral-900 dark:text-neutral-100">Alejandro Laguna</h1>
<p class="text-sm text-neutral-600 dark:text-neutral-400 mt-1 font-medium">My digital corner — science, code, thoughts.</p>
</div>
<div class="flex items-center gap-2">
<a href="/" aria-label="Home" class="text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100 transition-colors bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 p-2 rounded-lg group">
<House class="w-6 h-6 text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-neutral-100 transition-colors duration-200" />
</a>
<button id="theme-toggle" aria-label="Toggle theme" class="text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100 transition-colors bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 p-2 rounded-lg group">
<Sun id="sun-icon" class="w-6 h-6 text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-neutral-100 transition-colors duration-200 dark:hidden" />
<Moon id="moon-icon" class="w-6 h-6 text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-neutral-100 transition-colors duration-200 hidden dark:block" />
</button>
</div>
</div>
</header>
<main class="relative z-10 max-w-6xl mx-auto px-6 sm:px-8 py-16 sm:py-20 min-h-screen">
<slot />
</main>
<footer class="relative z-10 w-full px-6 sm:px-8 py-12 border-t border-neutral-200/80 dark:border-neutral-800 bg-white/80 dark:bg-neutral-950/80 backdrop-blur-md">
<div class="text-center">
<p class="text-sm text-neutral-500 dark:text-neutral-400">
© 2025 Alejandro Laguna. Made with a lot of love.
</p>
</div>
</footer>
<script>
const getThemePreference = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isDark = getThemePreference() === 'dark';
document.documentElement.classList.toggle('dark', isDark);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
document.getElementById('theme-toggle')?.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
if (typeof localStorage !== 'undefined') {
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,18 @@
---
import Layout from "./Layout.astro"
export interface Props {
title: string,
description: string,
pubDate: Date,
}
const {title, description, pubDate} = Astro.props;
---
<Layout>
<h1 class="text-4xl sm:text-5xl font-bold mb-6 text-neutral-900">{title}</h1>
<main class="text-lg text-neutral-600 leading-relaxed text-justify">
<slot />
</main>
</Layout>

View File

@@ -1,28 +0,0 @@
import { useState, useEffect } from 'preact/hooks';
import Layout from '../../components/Layout';
import About from '../../components/About';
import Projects from '../../components/Projects';
import Experience from '../../components/Experience';
import Footer from '../../components/Footer';
import Education from '../../components/Education';
export function Home({ theme }) {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const handleScroll = () => setScrollPosition(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<Layout theme={theme}>
<About theme={theme} />
<Education theme={theme} />
<Experience theme={theme} />
<Projects theme={theme} />
<Footer theme={theme} />
</Layout>
);
}

View File

@@ -1,8 +0,0 @@
export function NotFound() {
return (
<section>
<h1>404: Not Found</h1>
<p>It's gone :(</p>
</section>
);
}

141
src/pages/about.astro Normal file
View File

@@ -0,0 +1,141 @@
---
import "../styles/global.css"
import Layout from "../layouts/Layout.astro"
import NavigationCard from "../components/NavigationCard.astro"
import ExperienceCard from '../components/ExperienceCard.astro';
import { PencilRuler, LibraryBig, NotepadText, GraduationCap, Wrench, Feather, Briefcase } from 'lucide-astro';
const navItems = [
{
href: "/workshop",
icon: PencilRuler,
heading: "Workshop",
content: "From the theory to the implementation. What I build to understand."
},
{
href: "/library",
icon: LibraryBig,
heading: "Library",
content: "Books and papers and resources I use to learn. What I read, and why it matters."
},
{
href: "/notes",
icon: NotepadText,
heading: "Notes",
content: "My explorations into the fundamental nature of everything."
}
];
const experiences = [
{
title: "Software Engineer Intern",
company: "MigaLabs",
period: "July 2025 - December 2025",
location: "Barcelona (Spain)",
achievements: [
"Built monitoring tools to keep tabs on over a terabyte of Ethereum blockchain data - quality control at scale",
"Designed validation systems that could handle different network upgrades, since each fork had its own rules to follow",
"Worked the company's main dashboard using Next.js, turning complex blockchain data into something the team and customers could actually understand",
"Studied deep the Ethereum specifications to figure out what \"correct\" data should look like across different network phases (phase0, Altair, Bellatrix, Capella...)"
],
technologies: "Go • Next.js • Tailwind CSS • Ethereum"
},
{
title: "Full-stack Developer",
company: "Avantiam Inc",
period: "September 2023 - August 2024",
location: "Figueres (Spain)",
achievements: [
"Built a multitenant company management platform from the ground up - one codebase serving over 100 local companies",
"Developed HR tools, task management, and time tracking features that teams actually ended up using",
"Worked with Laravel and Livewire to create real-time interfaces that felt responsive and modern",
"Dealt the challenge of building productivity tools that didn't feel like corporate bloatware"
],
technologies: "Laravel • PHP • JavaScript • Livewire • Alpine.js • Tailwind CSS"
},
{
title: "Backend Developer Intern",
company: "Niblu Group",
period: "Summer 2023",
location: "Maribor (Slovenia)",
achievements: [
"Spent a summer in Slovenia through Erasmus+, working on an e-learning platform while navigating a new country alone at 17",
"Built course delivery systems using FastAPI and Next.js, focusing on making educational content engaging",
"Got hands-on experience with AWS and Docker, learning how to deploy properly instead of just making things work locally",
"Collaborated on tools designed to make learning more accessible. It felt meaningful beyond just writing code"
],
technologies: "Next.js • FastAPI • Python • Tailwind CSS • AWS • Docker"
}
];
---
<Layout>
<section class="max-w-3xl mb-10 sm:mb-14">
<h1 class="text-4xl sm:text-5xl font-bold mb-6 text-neutral-900 dark:text-neutral-100">About</h1>
<p class="text-lg text-neutral-600 dark:text-neutral-400 leading-relaxed text-justify">
I'm a second year computer science student at the European Institute of Technology, still trying to figure out my place but enjoying the journey through systems programming, webdev and other fields. Outside of code, my favorite writer is Isaac Asimov, my favorite piece of music is Spiegel im Spiegel, and my favorite book is The Witches by Roald Dahl. My favorite star is Vega because it was once our north star and will be again in about 12,000 years - something poetic about that cycle. And that's pretty much everything you need to know about me, except maybe my tea preferences, but we'll save that for another time... </p>
</section>
<section class="mb-12 bg-neutral-50 dark:bg-neutral-800 p-6 rounded-2xl border border-neutral-200 dark:border-neutral-700">
<h2 class="flex items-center gap-2 text-2xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
<GraduationCap class="w-6 h-6 text-neutral-500 dark:text-neutral-400" /> Education
</h2>
<ul class="space-y-4">
<li>
<p class="text-base text-neutral-900 dark:text-neutral-100 font-medium">Bachelor + Master's Degree in Computer Science</p>
<p class="text-sm text-neutral-600 dark:text-neutral-400">European Institute of Technology, class of 2029</p>
<ul class="list-['-_'] pl-8 italic text-sm text-neutral-700 dark:text-neutral-300">
<li> In progress </li>
</ul>
</li>
<li>
<p class="text-base text-neutral-900 dark:text-neutral-100 font-medium">Vocational Studies in Networking, Microcomputers and Operative Systems</p>
<p class="text-sm text-neutral-600 dark:text-neutral-400">INS Cendrassos, class of 2024</p>
<ul class="list-['-_'] pl-8 italic text-sm text-neutral-700 dark:text-neutral-300">
<li> Erasmus+ Scholarship </li>
<li> First of the class of 2024 </li>
<li> Student of the year </li>
</ul>
</li>
</ul>
</section>
<section class="mb-12">
<h2 class="flex items-center gap-2 text-2xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
<Briefcase class="w-6 h-6 text-neutral-500 dark:text-neutral-400" /> Experience
</h2>
<div class="grid sm:grid-cols-1 gap-6">
{experiences.map(exp => (
<ExperienceCard {...exp} />
))}
</div>
</section>
<section class="mb-12">
<h2 class="flex items-center gap-2 text-2xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
<Wrench class="w-6 h-6 text-neutral-500 dark:text-neutral-400" /> Tools I Use
</h2>
<code class="block bg-neutral-50 dark:bg-neutral-800 p-4 rounded-xl text-neutral-800 dark:text-neutral-200 overflow-x-auto border border-neutral-200 dark:border-neutral-700">
<b>Languages:</b> C, Go, Python, Javascript/Typescript, PHP<br>
<b>Frameworks & Libraries:</b> Astro, React, Preact, NextJS, Vue, NuxtJS, Express, FastAPI, TailwindCSS <br>
<b>Databases:</b> MongoDB, PostgreSQL, MySQL/MariaDB, ClickHouse <br>
<b>Tools:</b> Git, Bash, Linux, LaTeX, Typst, Neovim<br>
</code>
</section>
<section class="mb-20 sm:mb-28" aria-labelledby="navigation-heading">
<h2 class="flex items-center gap-2 text-2xl font-semibold text-neutral-900 dark:text-neutral-100 mb-4">
<Feather class="w-6 h-6 text-neutral-500 dark:text-neutral-400" /> Projects, posts, thoughts...
</h2>
<div class="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-3 gap-5">
{navItems.map(item => (
<NavigationCard
href={item.href}
icon={item.icon}
heading={item.heading}
content={item.content}
/>
))}
</div>
</section>
</Layout>

57
src/pages/index.astro Normal file
View File

@@ -0,0 +1,57 @@
---
import "../styles/global.css"
import Layout from "../layouts/Layout.astro"
import NavigationCard from '../components/NavigationCard.astro';
import { Fingerprint, PencilRuler, LibraryBig, NotepadText } from 'lucide-astro';
const navItems = [
{
href: "/about",
icon: Fingerprint,
heading: "About",
content: "Who am I, my academic and professional background, what drives this work."
},
{
href: "/workshop",
icon: PencilRuler,
heading: "Workshop",
content: "From the theory to the implementation. What I build to understand."
},
{
href: "/library",
icon: LibraryBig,
heading: "Library",
content: "Books and papers and resources I use to learn. What I read, and why it matters."
},
{
href: "/notes",
icon: NotepadText,
heading: "Notes",
content: "My explorations into the fundamental nature of everything."
}
];
---
<Layout>
<section class="max-w-4xl mb-20 sm:mb-28">
<h2 class="text-4xl sm:text-5xl lg:text-6xl font-bold leading-[1.1] mb-6 text-neutral-900 dark:text-neutral-100">
A <span class="text-neutral-600 dark:text-neutral-400"> notebook </span> for <br>working things out.
</h2>
<p class="text-lg sm:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed max-w-2xl">
Unpolished notes, working prototypes, honest questions. A space for building understanding from the ground up. Trying to understand the world by writing and building.
</p>
</section>
<section class="mb-20 sm:mb-28" aria-labelledby="navigation-heading">
<h2 id="navigation-heading" class="sr-only">Site navigation</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5">
{navItems.map(item => (
<NavigationCard
href={item.href}
icon={item.icon}
heading={item.heading}
content={item.content}
/>
))}
</div>
</section>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import { type CollectionEntry, getCollection, render } from 'astro:content';
import PostLayout from '../../layouts/PostLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('workshop');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'workshop'>;
const post = Astro.props;
const { Content } = await render(post);
---
<PostLayout {...post.data}>
<Content />
</PostLayout>

View File

@@ -0,0 +1,34 @@
---
import "../../styles/global.css"
import Layout from "../../layouts/Layout.astro"
import { getCollection } from 'astro:content';
const posts = (await getCollection('workshop')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<Layout>
<section class="max-w-3xl mb-10 sm:mb-14">
<h1 class="text-4xl sm:text-5xl font-bold mb-6 text-neutral-900 dark:text-neutral-100">Workshop</h1>
<p class="text-lg text-neutral-600 dark:text-neutral-400 leading-relaxed text-justify">
I'm a second year computer science student at the European Institute of Technology, still trying to figure out my place but enjoying the journey through systems programming, webdev and other fields. Outside of code, my favorite writer is Isaac Asimov, my favorite piece of music is Spiegel im Spiegel, and my favorite book is The Witches by Roald Dahl. My favorite star is Vega because it was once our north star and will be again in about 12,000 years - something poetic about that cycle. And that's pretty much everything you need to know about me, except maybe my tea preferences, but we'll save that for another time... </p>
</section>
<ul class="space-y-4">
{posts.map((post) => (
<li class="p-4 rounded-xl border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 hover:shadow-md transition-shadow">
<a href={`/workshop/${post.id}/`} class="block">
<h4 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-300 transition-colors">
{post.data.title}
</h4>
{post.data.pubDate && (
<p class="text-sm text-neutral-500 dark:text-neutral-400 mt-1">
{new Date(post.data.pubDate).toLocaleDateString()}
</p>
)}
</a>
</li>
))}
</ul>
</Layout>

View File

@@ -1,63 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Aleo:ital,wght@0,100..900;1,100..900&family=Sarala:wght@400;700&display=swap');
@import "tailwindcss";
@theme {
--font-serif: "Aleo", "monospace";
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.transition-all {
transition: all 0.3s ease;
}
.hover\:-translate-y-2:hover {
transform: translateY(-8px);
}
.hover\:-translate-y-1:hover {
transform: translateY(-4px);
}
.backdrop-blur-sm {
backdrop-filter: blur(8px);
}
/* Add to style.css */
.group:hover .group-hover\:translate-x-1 {
transform: translateX(0.25rem);
}
.transition-transform {
transition: transform 0.3s ease;
}
.list-disc li::marker {
color: #b8c4b550;
}
.group:hover .group-hover\:translate-x-1 {
transform: translateX(0.25rem);
}
.transition-transform {
transition: transform 0.3s ease;
}
.list-disc li::marker {
color: #b8c4b550;
}
html {
scroll-behavior: smooth;
}

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

@@ -0,0 +1,33 @@
@import url('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..900&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
@import "tailwindcss";
:root {
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-serif: 'Crimson Pro', Georgia, serif;
--font-mono: 'JetBrains Mono', monospace;
}
@custom-variant dark (&:where(.dark, .dark *));
body {
font-family: var(--font-sans);
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
}
h1, h2, h3 {
font-family: var(--font-serif);
letter-spacing: -0.025em;
}
.nav-card {
transform: translateY(0);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-card:hover {
transform: translateY(-2px);
}
.nav-card:focus {
transform: translateY(-1px);
}

View File

@@ -1,20 +1,5 @@
{ {
"compilerOptions": { "extends": "astro/tsconfigs/strict",
"target": "ES2020", "include": [".astro/types.d.ts", "**/*"],
"module": "ESNext", "exclude": ["dist"]
"moduleResolution": "bundler",
"noEmit": true,
"allowJs": true,
"checkJs": true,
/* Preact Config */
"jsx": "react-jsx",
"jsxImportSource": "preact",
"skipLibCheck": true,
"paths": {
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
}
},
"include": ["node_modules/vite/client.d.ts", "**/*"]
} }

View File

@@ -1,11 +0,0 @@
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
import tailwindcss from '@tailwindcss/vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
tailwindcss(),
preact(),
],
});