Compare commits

...

16 Commits

Author SHA1 Message Date
Clezio Penha d6fef8775f chore: rename project from portfolio-next to portfolio
Deploy Portfolio / deploy (push) Successful in 45s
2026-06-10 16:49:20 -03:00
Clezio Penha 5db9856f9a Add automatic Astro deployment
Deploy Portfolio / deploy (push) Successful in 11s
2026-06-10 16:15:42 -03:00
Clezio Penha c3d748aa29 Last test in workflow
Deploy Portfolio / deploy (push) Successful in 17s
2026-06-10 16:12:37 -03:00
Clezio Penha 6376fc4a1c Test temporary in workflow
Deploy Portfolio / deploy (push) Successful in 11s
2026-06-10 16:06:36 -03:00
Clezio Penha 4c496eff23 Test without risk in workflow
Deploy Portfolio / deploy (push) Successful in 11s
2026-06-10 15:50:17 -03:00
Clezio Penha e70b13a7f4 Test temporary in workflow
Deploy Portfolio / deploy (push) Successful in 11s
2026-06-10 15:46:54 -03:00
Clezio Penha 18ae0deae1 Test repository mount in workflow
Deploy Portfolio / deploy (push) Failing after 11s
2026-06-10 15:44:35 -03:00
Clezio Penha 842c10ac58 Replace test workflow with Astro build
Build Portfolio / deploy (push) Successful in 17s
2026-06-10 15:35:41 -03:00
Clezio Penha 77d30b30f3 Migrate portfolio from HTML/CSS/JS to Astro
Deploy Portfolio / deploy (push) Failing after 1s
2026-06-10 15:18:02 -03:00
clezio ac5d571d28 Test host volume mount
Deploy Portfolio / deploy (push) Failing after 1s
2026-06-08 15:19:56 -03:00
clezio 521c4b02ad Test mounted repo
Deploy Portfolio / deploy (push) Failing after 1s
2026-06-08 15:15:01 -03:00
clezio ffa92bbee1 Check runner filesystem
Deploy Portfolio / deploy (push) Successful in 1s
2026-06-08 15:05:59 -03:00
clezio fbe23f9536 Add first workflow
Deploy Portfolio / deploy (push) Successful in 45s
2026-06-08 15:00:44 -03:00
Clezio Penha ab116e4824 feat(i18n): add multilingual support and interactive features 2026-06-08 14:57:56 -03:00
Clezio Penha 50059bb3a5 feat(ui): implement modern responsive dark theme 2026-06-08 14:57:25 -03:00
Clezio Penha 0e92466fd6 feat(portfolio): redesign portfolio structure and content 2026-06-08 14:57:10 -03:00
27 changed files with 6727 additions and 95 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Deploy Portfolio
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Dependencies
run: npm ci
- name: Build Astro
run: npm run build
- name: Publish Site
run: |
docker exec gitea-runner sh -c 'rm -rf /sites/portfolio/*'
docker cp dist/. gitea-runner:/sites/portfolio/
- name: Verify Deployment
run: |
docker exec gitea-runner ls -la /sites/portfolio
+3
View File
@@ -0,0 +1,3 @@
node_modules/
dist/
.astro/
+43
View File
@@ -0,0 +1,43 @@
# Astro Starter Kit: Minimal
```sh
npm create astro@latest -- --template minimal
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 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).
+13
View File
@@ -0,0 +1,13 @@
// @ts-check
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
i18n: {
locales: ["pt-br", "en", "es"],
defaultLocale: "pt-br",
routing: {
prefixDefaultLocale: false
}
}
});
-21
View File
@@ -1,21 +0,0 @@
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: auto;
padding: 20px;
line-height: 1.6;
}
header {
text-align: center;
margin-bottom: 40px;
}
h1 {
margin-bottom: 0;
}
h2 {
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
}
-74
View File
@@ -1,74 +0,0 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Clezio Penha</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<h1>Clezio Penha</h1>
<p>Backend Developer | C++ | Java | Cloud</p>
</header>
<section>
<h2>Sobre</h2>
```
<p>
Desenvolvedor backend com experiência em C++, Qt e Java.
Atuo com desenvolvimento de aplicações, APIs REST,
Docker, Linux e Cloud.
</p>
```
</section>
<section>
<h2>Tecnologias</h2>
```
<ul>
<li>C++</li>
<li>Qt</li>
<li>Java</li>
<li>Spring Boot</li>
<li>Quarkus</li>
<li>Docker</li>
<li>Linux</li>
<li>Oracle Cloud</li>
</ul>
```
</section>
<section>
<h2>Projetos</h2>
```
<ul>
<li>Infraestrutura Oracle Cloud</li>
<li>Servidor Git Privado com Gitea</li>
<li>APIs Java com Spring Boot</li>
</ul>
```
</section>
<section>
<h2>Contato</h2>
```
<p>Email: clezio.penha@gmail.com</p>
<p>Git: https://git.clezio.com.br</p>
```
</section>
</body>
</html>
+4849
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "portfolio",
"type": "module",
"version": "0.0.1",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^6.4.4"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

+9
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

+30
View File
@@ -0,0 +1,30 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="about" class="section">
<h2>{t.about.title}</h2>
<p>{t.about.description}</p>
</section>
<section class="section">
<h2>{t.about.workTitle}</h2>
<div class="cards-grid">
{
t.workPrinciples.map(work => (
<article class="card">
<h3>{work.title}</h3>
<p>{work.description}</p>
</article>
))
}
</div>
</section>
+29
View File
@@ -0,0 +1,29 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="contact" class="section">
<h2>{t.contact.title}</h2>
<p>{t.contact.description}</p>
<div class="contact-links">
<a href={`mailto:${t.profile.email}`}>
{t.profile.email}
</a>
<a href={t.profile.linkedin} target="_blank" rel="noopener noreferrer">
{t.contact.linkedin}
</a>
</div>
</section>
+28
View File
@@ -0,0 +1,28 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="experience" class="section">
<h2>{t.sections.experience}</h2>
<div class="timeline">
{
t.timeline.map(item => (
<article class="timeline-item">
<h3>{item.title}</h3>
<p>{item.description}</p>
</article>
))
}
</div>
</section>
+43
View File
@@ -0,0 +1,43 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<footer class="footer">
<p>
© 2026 {t.profile.name}
</p>
<p>
{t.profile.footerRole}
</p>
</footer>
<button id="backToTop" aria-label={t.ui.backToTop}>
</button>
<script>
const backToTopButton = document.querySelector("#backToTop");
const toggleBackToTop = () => {
backToTopButton?.classList.toggle("show", window.scrollY > 500);
};
window.addEventListener("scroll", toggleBackToTop, { passive: true });
toggleBackToTop();
backToTopButton?.addEventListener("click", () => {
window.scrollTo({
top: 0,
behavior: "smooth"
});
});
</script>
+166
View File
@@ -0,0 +1,166 @@
---
import { languageLinks, type Locale, type Translation } from "../data/translations";
interface Props {
locale: Locale;
t: Translation;
}
const { locale, t } = Astro.props;
---
<header class="header">
<nav class="navbar">
<div class="logo">
{t.profile.name}
</div>
<ul id="primary-navigation" class="nav-links">
<li><a href="#home">{t.nav.home}</a></li>
<li><a href="#about">{t.nav.about}</a></li>
<li><a href="#specialties">{t.nav.specialties}</a></li>
<li><a href="#experience">{t.nav.experience}</a></li>
<li><a href="#projects">{t.nav.projects}</a></li>
<li><a href="#contact">{t.nav.contact}</a></li>
</ul>
<button
class="menu-toggle"
type="button"
aria-label={t.ui.menuToggle}
aria-expanded="false"
aria-controls="primary-navigation"
>
</button>
<div class="controls">
<button
id="theme-toggle"
type="button"
aria-label={t.ui.themeToggle}
aria-pressed="false"
>◐</button>
<div class="language-switcher" aria-label={t.ui.languageSwitcher}>
{
languageLinks.map(language => (
<a
href={language.href}
aria-label={language.label}
aria-current={language.locale === locale ? "true" : undefined}
class:list={{ active: language.locale === locale }}
>
{language.flag}
</a>
))
}
</div>
</div>
</nav>
</header>
<script>
const themeToggle = document.querySelector("#theme-toggle");
const menuToggle = document.querySelector(".menu-toggle");
const navLinks = document.querySelector(".nav-links");
const navAnchors = document.querySelectorAll(".nav-links a");
const themeStorageKey = "portfolio-theme";
const themeIcons = {
light: "☀️",
dark: "🌙"
};
const getPreferredTheme = () => {
const storedTheme = localStorage.getItem(themeStorageKey);
if (storedTheme === "light" || storedTheme === "dark") {
return storedTheme;
}
return window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
};
const applyTheme = (theme) => {
document.documentElement.dataset.theme = theme;
if (themeToggle instanceof HTMLButtonElement) {
const isLight = theme === "light";
themeToggle.textContent = isLight ? themeIcons.dark : themeIcons.light;
themeToggle.setAttribute("aria-pressed", String(isLight));
}
};
applyTheme(getPreferredTheme());
themeToggle?.addEventListener("click", () => {
const currentTheme = document.documentElement.dataset.theme === "light"
? "light"
: "dark";
const nextTheme = currentTheme === "light" ? "dark" : "light";
localStorage.setItem(themeStorageKey, nextTheme);
applyTheme(nextTheme);
});
const closeMenu = () => {
navLinks?.classList.remove("open");
if (menuToggle instanceof HTMLButtonElement) {
menuToggle.setAttribute("aria-expanded", "false");
menuToggle.textContent = "☰";
}
};
menuToggle?.addEventListener("click", () => {
const isOpen = navLinks?.classList.toggle("open") ?? false;
if (menuToggle instanceof HTMLButtonElement) {
menuToggle.setAttribute("aria-expanded", String(isOpen));
menuToggle.textContent = isOpen ? "×" : "☰";
}
});
navAnchors.forEach((anchor) => {
anchor.addEventListener("click", closeMenu);
});
window.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeMenu();
}
});
const sections = Array.from(navAnchors)
.map((anchor) => document.querySelector(anchor.getAttribute("href") ?? ""))
.filter((section) => section instanceof HTMLElement);
const setActiveLink = (id) => {
navAnchors.forEach((anchor) => {
anchor.classList.toggle("active", anchor.getAttribute("href") === `#${id}`);
});
};
const sectionObserver = new IntersectionObserver(
(entries) => {
const visibleEntry = entries.find((entry) => entry.isIntersecting);
if (visibleEntry?.target instanceof HTMLElement) {
setActiveLink(visibleEntry.target.id);
}
},
{
rootMargin: "-45% 0px -50% 0px",
threshold: 0
}
);
sections.forEach((section) => sectionObserver.observe(section));
</script>
+33
View File
@@ -0,0 +1,33 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="home" class="hero">
<div class="hero-content">
<span class="hero-badge">{t.hero.badge}</span>
<h1>{t.hero.title}</h1>
<p>{t.hero.description}</p>
<div class="hero-actions">
<a href="#projects" class="btn-primary">
{t.hero.primaryButton}
</a>
<a href="#contact" class="btn-secondary">
{t.hero.secondaryButton}
</a>
</div>
</div>
</section>
+22
View File
@@ -0,0 +1,22 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section class="metrics">
{
t.metrics.map(metric => (
<article class="metric-card">
<h3>{metric.value}</h3>
<p>{metric.label}</p>
</article>
))
}
</section>
+47
View File
@@ -0,0 +1,47 @@
---
import MainLayout from "../layouts/MainLayout.astro";
import Header from "./Header.astro";
import Hero from "./Hero.astro";
import Metrics from "./Metrics.astro";
import About from "./About.astro";
import Specialties from "./Specialties.astro";
import Experience from "./Experience.astro";
import Projects from "./Projects.astro";
import Contact from "./Contact.astro";
import Footer from "./Footer.astro";
import { translations, type Locale } from "../data/translations";
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
const t = translations[locale];
---
<MainLayout
title={t.meta.title}
description={t.meta.description}
lang={t.lang}
canonical={t.canonical}
ogTitle={t.meta.ogTitle}
ogDescription={t.meta.ogDescription}
>
<Header locale={locale} t={t} />
<main>
<Hero t={t} />
<Metrics t={t} />
<About t={t} />
<Specialties t={t} />
<Experience t={t} />
<Projects t={t} />
<Contact t={t} />
</main>
<Footer t={t} />
</MainLayout>
+29
View File
@@ -0,0 +1,29 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="projects" class="section">
<h2>{t.sections.projects}</h2>
<div class="cards-grid">
{
t.projects.map(project => (
<article class="project-card">
<h3>{project.title}</h3>
<p>{project.description}</p>
</article>
))
}
</div>
</section>
+43
View File
@@ -0,0 +1,43 @@
---
import type { Translation } from "../data/translations";
interface Props {
t: Translation;
}
const { t } = Astro.props;
---
<section id="specialties" class="section">
<h2>{t.sections.specialties}</h2>
<div class="cards-grid">
{
t.specialties.map(specialty => (
<article class="card">
<h3>{specialty}</h3>
</article>
))
}
</div>
</section>
<section class="section">
<h2>{t.sections.stack}</h2>
<div class="tech-stack">
{
t.stack.map(technology => (
<span>{technology}</span>
))
}
</div>
</section>
+488
View File
@@ -0,0 +1,488 @@
export const defaultLocale = "pt-br";
export const locales = ["pt-br", "en", "es"] as const;
export type Locale = (typeof locales)[number];
export const languageLinks = [
{
locale: "pt-br",
href: "/",
label: "Português",
flag: "🇧🇷"
},
{
locale: "en",
href: "/en/",
label: "English",
flag: "🇺🇸"
},
{
locale: "es",
href: "/es/",
label: "Español",
flag: "🇪🇸"
}
] as const;
export const translations = {
"pt-br": {
lang: "pt-BR",
canonical: "https://clezio.com.br/",
meta: {
title: "Clezio Penha | Senior Backend Engineer",
description:
"Programador Sênior Backend com mais de 15 anos de experiência em Java, C++, APIs REST, Cloud Computing, DevOps e Arquitetura de Software.",
ogTitle: "Clezio Penha | Programador Sênior Backend",
ogDescription: "Mais de 15 anos construindo sistemas robustos, escaláveis e seguros."
},
profile: {
name: "Clezio Penha",
role: "Senior Backend Engineer",
footerRole: "Senior Backend Engineer",
email: "clezio.penha@gmail.com",
linkedin: "https://www.linkedin.com/in/clezio"
},
nav: {
home: "Início",
about: "Sobre",
specialties: "Especialidades",
experience: "Experiência",
projects: "Projetos",
contact: "Contato"
},
hero: {
badge: "Programador Sênior • Backend Engineer • Tech Lead",
title: "Transformando desafios de negócio em soluções robustas, escaláveis e seguras.",
description:
"Programador Sênior com mais de 15 anos de experiência em desenvolvimento backend, arquitetura de software, cloud computing e liderança técnica.",
primaryButton: "Ver Projetos",
secondaryButton: "Entrar em Contato"
},
metrics: [
{
value: "15+",
label: "Anos de Experiência"
},
{
value: "Tech Lead",
label: "Liderança Técnica"
},
{
value: "Cloud",
label: "Oracle Cloud & Azure"
},
{
value: "Backend",
label: "APIs e Sistemas Corporativos"
}
],
about: {
title: "Sobre Mim",
description:
"Ao longo da minha carreira participei da construção e evolução de sistemas corporativos, integrações críticas, automações de processos e plataformas backend voltadas para desempenho, escalabilidade e confiabilidade.",
workTitle: "Como Eu Trabalho"
},
workPrinciples: [
{
title: "Arquitetura",
description: "Planejamento técnico antes da implementação."
},
{
title: "Escalabilidade",
description: "Sistemas preparados para crescimento contínuo."
},
{
title: "Performance",
description: "Eficiência operacional e otimização de recursos."
},
{
title: "Segurança",
description: "Boas práticas desde a concepção."
},
{
title: "Qualidade",
description: "Código limpo e manutenção simplificada."
}
],
sections: {
specialties: "Especialidades",
stack: "Stack Tecnológica",
experience: "Experiência Profissional",
projects: "Projetos em Destaque"
},
specialties: [
"APIs REST",
"Arquitetura de Software",
"Cloud Computing",
"DevOps",
"Bancos de Dados",
"Microsserviços"
],
stack: [
"Java",
"C++",
"Spring Boot",
"Quarkus",
"Docker",
"Kubernetes",
"Jenkins",
"GitLab CI",
"PostgreSQL",
"MySQL",
"SQL Server",
"Oracle Cloud",
"Azure"
],
timeline: [
{
title: "Programador Sênior",
description: "Desenvolvimento de sistemas corporativos e soluções backend."
},
{
title: "Tech Lead",
description: "Liderança técnica e apoio arquitetural."
},
{
title: "Cloud & DevOps",
description: "Modernização de infraestrutura e automação."
}
],
projects: [
{
title: "Sistema de Processamento SPED",
description:
"Plataforma responsável pela geração e processamento automatizado de arquivos fiscais SPED."
},
{
title: "Infraestrutura Oracle Cloud",
description: "Implementação e gerenciamento de infraestrutura cloud."
},
{
title: "Servidor Git Privado com Gitea",
description: "Ambiente Git privado para versionamento e colaboração."
},
{
title: "APIs Corporativas",
description: "Desenvolvimento de APIs REST utilizando Spring Boot."
}
],
contact: {
title: "Vamos Conversar",
description: "Disponível para oportunidades profissionais e novos desafios.",
linkedin: "LinkedIn"
},
ui: {
menuToggle: "Abrir menu",
themeToggle: "Alternar tema",
languageSwitcher: "Selecionar idioma",
backToTop: "Voltar ao topo"
}
},
en: {
lang: "en",
canonical: "https://clezio.com.br/en/",
meta: {
title: "Clezio Penha | Senior Backend Engineer",
description:
"Senior Backend Engineer with 15+ years of experience in Java, C++, REST APIs, cloud computing, DevOps and software architecture.",
ogTitle: "Clezio Penha | Backend Engineer",
ogDescription: "15+ years building robust, scalable and secure systems."
},
profile: {
name: "Clezio Penha",
role: "Senior Backend Engineer",
footerRole: "Senior Backend Engineer",
email: "clezio.penha@gmail.com",
linkedin: "https://www.linkedin.com/in/clezio"
},
nav: {
home: "Home",
about: "About",
specialties: "Specialties",
experience: "Experience",
projects: "Projects",
contact: "Contact"
},
hero: {
badge: "Senior Developer • Backend Engineer • Tech Lead",
title: "Turning business challenges into robust, scalable and secure solutions.",
description:
"Senior developer with 15+ years of experience in backend development, software architecture, cloud computing and technical leadership.",
primaryButton: "View Projects",
secondaryButton: "Get in Touch"
},
metrics: [
{
value: "15+",
label: "Years of Experience"
},
{
value: "Tech Lead",
label: "Technical Leadership"
},
{
value: "Cloud",
label: "Oracle Cloud & Azure"
},
{
value: "Backend",
label: "APIs and Enterprise Systems"
}
],
about: {
title: "About Me",
description:
"Throughout my career, I have helped build and evolve enterprise systems, critical integrations, process automations and backend platforms focused on performance, scalability and reliability.",
workTitle: "How I Work"
},
workPrinciples: [
{
title: "Architecture",
description: "Technical planning before implementation."
},
{
title: "Scalability",
description: "Systems prepared for continuous growth."
},
{
title: "Performance",
description: "Operational efficiency and resource optimization."
},
{
title: "Security",
description: "Good practices from the design phase."
},
{
title: "Quality",
description: "Clean code and simplified maintenance."
}
],
sections: {
specialties: "Specialties",
stack: "Technology Stack",
experience: "Professional Experience",
projects: "Featured Projects"
},
specialties: [
"REST APIs",
"Software Architecture",
"Cloud Computing",
"DevOps",
"Databases",
"Microservices"
],
stack: [
"Java",
"C++",
"Spring Boot",
"Quarkus",
"Docker",
"Kubernetes",
"Jenkins",
"GitLab CI",
"PostgreSQL",
"MySQL",
"SQL Server",
"Oracle Cloud",
"Azure"
],
timeline: [
{
title: "Senior Developer",
description: "Development of enterprise systems and backend solutions."
},
{
title: "Tech Lead",
description: "Technical leadership and architectural support."
},
{
title: "Cloud & DevOps",
description: "Infrastructure modernization and automation."
}
],
projects: [
{
title: "SPED Processing System",
description: "Platform responsible for the automated generation and processing of SPED tax files."
},
{
title: "Oracle Cloud Infrastructure",
description: "Implementation and management of cloud infrastructure."
},
{
title: "Private Git Server with Gitea",
description: "Private Git environment for version control and collaboration."
},
{
title: "Enterprise APIs",
description: "Development of REST APIs using Spring Boot."
}
],
contact: {
title: "Let's Talk",
description: "Available for professional opportunities and new challenges.",
linkedin: "LinkedIn"
},
ui: {
menuToggle: "Open menu",
themeToggle: "Toggle theme",
languageSwitcher: "Select language",
backToTop: "Back to top"
}
},
es: {
lang: "es",
canonical: "https://clezio.com.br/es/",
meta: {
title: "Clezio Penha | Senior Backend Engineer",
description:
"Programador Backend Sénior con más de 15 años de experiencia en Java, C++, APIs REST, cloud computing, DevOps y arquitectura de software.",
ogTitle: "Clezio Penha | Backend Engineer",
ogDescription: "Más de 15 años construyendo sistemas robustos, escalables y seguros."
},
profile: {
name: "Clezio Penha",
role: "Senior Backend Engineer",
footerRole: "Senior Backend Engineer",
email: "clezio.penha@gmail.com",
linkedin: "https://www.linkedin.com/in/clezio"
},
nav: {
home: "Inicio",
about: "Sobre mí",
specialties: "Especialidades",
experience: "Experiencia",
projects: "Proyectos",
contact: "Contacto"
},
hero: {
badge: "Programador Sénior • Backend Engineer • Tech Lead",
title: "Transformando desafíos de negocio en soluciones robustas, escalables y seguras.",
description:
"Programador sénior con más de 15 años de experiencia en desarrollo backend, arquitectura de software, cloud computing y liderazgo técnico.",
primaryButton: "Ver Proyectos",
secondaryButton: "Entrar en Contacto"
},
metrics: [
{
value: "15+",
label: "Años de Experiencia"
},
{
value: "Tech Lead",
label: "Liderazgo Técnico"
},
{
value: "Cloud",
label: "Oracle Cloud y Azure"
},
{
value: "Backend",
label: "APIs y Sistemas Corporativos"
}
],
about: {
title: "Sobre Mí",
description:
"A lo largo de mi carrera he participado en la construcción y evolución de sistemas corporativos, integraciones críticas, automatizaciones de procesos y plataformas backend enfocadas en rendimiento, escalabilidad y confiabilidad.",
workTitle: "Cómo Trabajo"
},
workPrinciples: [
{
title: "Arquitectura",
description: "Planificación técnica antes de la implementación."
},
{
title: "Escalabilidad",
description: "Sistemas preparados para el crecimiento continuo."
},
{
title: "Rendimiento",
description: "Eficiencia operativa y optimización de recursos."
},
{
title: "Seguridad",
description: "Buenas prácticas desde la concepción."
},
{
title: "Calidad",
description: "Código limpio y mantenimiento simplificado."
}
],
sections: {
specialties: "Especialidades",
stack: "Stack Tecnológico",
experience: "Experiencia Profesional",
projects: "Proyectos Destacados"
},
specialties: [
"APIs REST",
"Arquitectura de Software",
"Cloud Computing",
"DevOps",
"Bases de Datos",
"Microservicios"
],
stack: [
"Java",
"C++",
"Spring Boot",
"Quarkus",
"Docker",
"Kubernetes",
"Jenkins",
"GitLab CI",
"PostgreSQL",
"MySQL",
"SQL Server",
"Oracle Cloud",
"Azure"
],
timeline: [
{
title: "Programador Sénior",
description: "Desarrollo de sistemas corporativos y soluciones backend."
},
{
title: "Tech Lead",
description: "Liderazgo técnico y apoyo arquitectónico."
},
{
title: "Cloud & DevOps",
description: "Modernización de infraestructura y automatización."
}
],
projects: [
{
title: "Sistema de Procesamiento SPED",
description: "Plataforma responsable de la generación y procesamiento automatizado de archivos fiscales SPED."
},
{
title: "Infraestructura Oracle Cloud",
description: "Implementación y gestión de infraestructura cloud."
},
{
title: "Servidor Git Privado con Gitea",
description: "Entorno Git privado para versionamiento y colaboración."
},
{
title: "APIs Corporativas",
description: "Desarrollo de APIs REST utilizando Spring Boot."
}
],
contact: {
title: "Hablemos",
description: "Disponible para oportunidades profesionales y nuevos desafíos.",
linkedin: "LinkedIn"
},
ui: {
menuToggle: "Abrir menú",
themeToggle: "Alternar tema",
languageSwitcher: "Seleccionar idioma",
backToTop: "Volver arriba"
}
}
} as const;
export type Translation = (typeof translations)[Locale];
+96
View File
@@ -0,0 +1,96 @@
---
import "../styles/global.css";
import { languageLinks } from "../data/translations";
interface Props {
title: string;
description: string;
lang: "pt" | "en" | "es";
canonical?: string;
ogTitle?: string;
ogDescription?: string;
}
const {
title,
description,
lang,
canonical,
ogTitle = title,
ogDescription = description
} = Astro.props;
const schema = {
"@context": "https://schema.org",
"@type": "Person",
name: "Clezio Penha",
jobTitle: "Senior Software Engineer",
description,
email: "mailto:clezio.penha@gmail.com",
knowsAbout: [
"Java",
"C++",
"Spring Boot",
"Quarkus",
"Docker",
"Kubernetes",
"PostgreSQL",
"Oracle Cloud",
"Azure"
]
};
const localeMap: Record<string, string> = {
"pt": "pt_BR",
"pt-BR": "pt_BR",
"en": "en_US",
"es": "es_ES"
};
---
<!DOCTYPE html>
<html lang={lang}>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<meta name="author" content="Clezio Penha">
<meta name="description" content={description}>
<link rel="canonical" href={canonical}>
<meta name="keywords" content="Java, Spring Boot, Backend, Cloud, Docker, Kubernetes, Oracle Cloud, Azure, APIs REST, DevOps">
<meta property="og:title" content={ogTitle}>
<meta property="og:description" content={ogDescription}>
<meta property="og:type" content="website">
<meta property="og:url" content={canonical}>
<meta property="og:site_name" content="Clezio Penha">
<meta property="og:locale" content={localeMap[lang] ?? "pt_BR"}>
<meta property="og:image" content="https://clezio.com.br/og-image.png"/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Clezio Penha Portfolio" />
{
languageLinks.map(language => (
<link rel="alternate" hreflang={language.locale} href={language.href} />
))
}
<link rel="alternate" hreflang="x-default" href="/">
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
<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@300;400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body>
<slot />
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
---
import PortfolioPage from "../../components/PortfolioPage.astro";
---
<PortfolioPage locale="en" />
+5
View File
@@ -0,0 +1,5 @@
---
import PortfolioPage from "../../components/PortfolioPage.astro";
---
<PortfolioPage locale="es" />
+5
View File
@@ -0,0 +1,5 @@
---
import PortfolioPage from "../components/PortfolioPage.astro";
---
<PortfolioPage locale="pt-br" />
+691
View File
@@ -0,0 +1,691 @@
/* =========================
RESET
========================= */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: "Inter", sans-serif;
background:
radial-gradient(
circle at top,
rgba(59,130,246,.08),
transparent 45%
),
var(--background);
color: var(--text-primary);
line-height: 1.7;
overflow-x: hidden;
transition:
background .3s ease,
color .3s ease;
}
/* =========================
VARIABLES
========================= */
:root {
--background: #09090b;
--surface: #111827;
--surface-hover: #1f2937;
--border: #27272a;
--text-primary: #fafafa;
--text-secondary: #a1a1aa;
--accent: #3b82f6;
--accent-hover: #2563eb;
--header-bg: rgba(9, 9, 11, 0.75);
--header-border: rgba(255, 255, 255, 0.05);
--hero-title-start: #ffffff;
--hero-title-end: #dbeafe;
--container: 1200px;
}
[data-theme="light"] {
--background: #f8fafc;
--surface: #ffffff;
--surface-hover: #f1f5f9;
--border: #e2e8f0;
--text-primary: #0f172a;
--text-secondary: #475569;
--accent: #2563eb;
--accent-hover: #1d4ed8;
--header-bg: rgba(248, 250, 252, 0.82);
--header-border: rgba(15, 23, 42, 0.08);
--hero-title-start: #0f172a;
--hero-title-end: #2563eb;
}
/* =========================
GLOBAL
========================= */
main {
width: 100%;
}
.section {
max-width: var(--container);
margin: 0 auto;
padding: 120px 24px;
}
.section h2 {
font-size: 2.4rem;
margin-bottom: 24px;
font-weight: 700;
}
.section p {
color: var(--text-secondary);
max-width: 800px;
}
/* =========================
HEADER
========================= */
.header {
position: fixed;
top: 0;
width: 100%;
z-index: 1000;
backdrop-filter: blur(18px);
background: var(--header-bg);
border-bottom: 1px solid var(--header-border);
}
.navbar {
max-width: var(--container);
margin: auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 24px;
}
.logo {
font-weight: 700;
font-size: 1.1rem;
}
.nav-links {
display: flex;
gap: 28px;
list-style: none;
}
.nav-links a {
text-decoration: none;
color: var(--text-secondary);
transition: 0.3s ease;
}
.nav-links a:hover {
color: var(--text-primary);
}
/* =========================
HERO
========================= */
.hero {
min-height: 85vh;
display: flex;
align-items: center;
justify-content: center;
padding: 120px 24px 60px;
}
.hero-content {
max-width: 900px;
text-align: center;
}
.hero-badge {
display: inline-block;
margin-bottom: 24px;
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 999px;
background: color-mix(in srgb,
var(--surface) 80%,
transparent);
color: var(--text-secondary);
font-size: 0.9rem;
}
.hero h1 {
font-size: clamp(2.8rem, 6vw, 4.8rem);
background: linear-gradient(180deg,
var(--hero-title-start) 0%,
var(--hero-title-end) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
max-width: 1000px;
line-height: 1.1;
margin: 0 auto 28px;
font-weight: 800;
letter-spacing: -2px;
}
.hero p {
color: var(--text-secondary);
font-size: 1.2rem;
max-width: 700px;
margin: auto;
}
.hero-actions {
margin-top: 40px;
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
/* =========================
BUTTONS
========================= */
.btn-primary,
.btn-secondary {
text-decoration: none;
padding: 14px 26px;
border-radius: 12px;
transition: 0.3s ease;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
transform: translateY(-2px);
}
.btn-secondary {
border: 1px solid var(--border);
color: var(--text-primary);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.05);
}
/* =========================
METRICS
========================= */
.metrics {
max-width: var(--container);
margin: auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
padding: 0 24px 120px;
}
.metric-card {
background: var(--surface);
border: 1px solid var(--border);
padding: 32px;
border-radius: 20px;
text-align: center;
}
.metric-card h3 {
font-size: 2rem;
margin-bottom: 12px;
}
.metric-card p {
color: var(--text-secondary);
}
/* =========================
GRID CARDS
========================= */
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
margin-top: 40px;
}
.card,
.project-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
padding: 28px;
transition: all .3s ease;
}
.card:hover,
.project-card:hover {
transform: translateY(-6px);
border-color: rgba(59, 130, 246, .35);
background: var(--surface-hover);
}
.card h3,
.project-card h3 {
margin-bottom: 12px;
}
.card p,
.project-card p {
color: var(--text-secondary);
}
/* =========================
TECH STACK
========================= */
.tech-stack {
display: flex;
flex-wrap: wrap;
gap: 14px;
margin-top: 40px;
}
.tech-stack span {
padding: 10px 18px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
color: var(--text-secondary);
}
/* =========================
TIMELINE
========================= */
.timeline {
margin-top: 40px;
border-left: 2px solid var(--border);
padding-left: 32px;
}
.timeline-item {
position: relative;
margin-bottom: 50px;
}
.timeline-item::before {
content: "";
position: absolute;
left: -41px;
top: 8px;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--accent);
}
.timeline-item h3 {
margin-bottom: 10px;
}
.timeline-item p {
color: var(--text-secondary);
}
/* =========================
CONTACT
========================= */
.contact-links {
display: flex;
gap: 20px;
margin-top: 30px;
flex-wrap: wrap;
}
.contact-links a {
text-decoration: none;
color: var(--accent);
transition: 0.3s ease;
}
.contact-links a:hover {
color: var(--text-primary);
}
/* =========================
FOOTER
========================= */
.footer {
border-top: 1px solid var(--border);
text-align: center;
padding: 50px 24px;
color: var(--text-secondary);
}
.footer p:first-child {
margin-bottom: 10px;
}
/* ========================= BACK TO TOP ========================= */
#backToTop {
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
border: none;
border-radius: 50%;
background: #3b82f6;
color: white;
font-size: 20px;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 999;
}
#backToTop:hover {
transform: translateY(-3px);
}
#backToTop.show {
opacity: 1;
visibility: visible;
}
/* ========================= SECTION ANIMATION ========================= */
/*
.section,
.metric-card,
.project-card,
.card {
opacity: 0;
transform: translateY(30px);
transition: all 0.7s ease;
}
.show-element {
opacity: 1 !important;
transform: translateY(0) !important;
}
*/
/* ========================= ACTIVE NAV ========================= */
.nav-links a.active {
color: var(--text-primary);
}
.nav-links a.active::after {
content: "";
display: block;
margin-top: 6px;
width: 100%;
height: 2px;
background: #3b82f6;
}
/* =========================
THEME BUTTON
========================= */
.controls {
display: flex;
align-items: center;
gap: 16px;
}
#theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: 1px solid var(--border);
border-radius: 50%;
background: color-mix(in srgb, var(--surface) 72%, transparent);
color: var(--text-primary);
cursor: pointer;
font-size: 1.05rem;
line-height: 1;
transition: .3s ease;
}
#theme-toggle:hover {
background: var(--surface-hover);
transform: rotate(18deg);
}
.language-switcher {
display: flex;
gap: 4px;
padding: 4px;
border: 1px solid var(--border);
border-radius: 999px;
background: color-mix(in srgb, var(--surface) 72%, transparent);
}
.language-switcher a {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 36px;
min-height: 32px;
border: none;
border-radius: 999px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
font: inherit;
font-size: 1.05rem;
line-height: 1;
text-decoration: none;
transition:
background .2s ease,
color .2s ease,
transform .2s ease;
}
.language-switcher a:hover,
.language-switcher a.active {
background: var(--accent);
color: #ffffff;
}
.language-switcher a.active {
transform: scale(1.03);
}
/* =========================
RESPONSIVE
========================= */
@media (max-width: 960px) {
.metrics {
grid-template-columns: repeat(2, 1fr);
}
.nav-links {
display: none;
}
.hero h1 {
font-size: 3rem;
}
.section {
padding: 90px 24px;
}
}
@media (max-width: 640px) {
.metrics {
grid-template-columns: 1fr;
}
.hero {
text-align: center;
}
.hero h1 {
font-size: 2.4rem;
}
.hero p {
font-size: 1rem;
}
.section h2 {
font-size: 2rem;
}
.navbar {
padding: 14px 16px;
}
.controls {
gap: 12px;
}
#theme-toggle {
width: 36px;
height: 36px;
font-size: 1rem;
}
.language-switcher a {
min-width: 32px;
min-height: 30px;
font-size: 1rem;
}
}
.menu-toggle {
display: none;
background: transparent;
border: none;
color: var(--text-primary);
font-size: 1.5rem;
cursor: pointer;
}
.card:hover,
.project-card:hover {
transform: translateY(-6px) scale(1.02);
}
@media (max-width:960px) {
.navbar {
position: relative;
}
.menu-toggle {
display: block;
}
.nav-links.open {
display: flex !important;
position: absolute;
top: calc(100% + 1px);
left: 24px;
right: 24px;
flex-direction: column;
gap: 14px;
padding: 18px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
box-shadow: 0 18px 40px rgba(0, 0, 0, .22);
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}