Add hit counter service and expand frontend content sections

- New hit-counter FastAPI service (Prometheus metrics, SQLite storage), wired into Caddy (hits.carloselugo.com) and docker-compose
- Scrape hit-counter in Prometheus every 5m
- Add articles, about, contact, experience, and expertise sections to the frontend, with per-article hit tracking

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 20:31:52 +00:00
co-authored by Claude Sonnet 5
parent fd961a0668
commit 44d6c1e5a9
21 changed files with 959 additions and 320 deletions
+77
View File
@@ -0,0 +1,77 @@
export function renderAbout() {
return `
<hr class="hr" />
<section class="about" id="about">
<div class="container about-inner">
<div class="about-photo reveal" id="about-photo">
<img src="/images/profile_rev2.png" alt="Carlos Lugo" onerror="this.parentElement.classList.add('about-photo--empty'); this.remove();" />
</div>
<div class="about-copy reveal">
<span class="section-kicker">About</span>
<h2 class="about-title">From the field to the dashboard.</h2>
<p class="about-p">
I'm Carlos Lugo, based in Puerto Rico. My career started operating ISP, MAN, and MPLS networks
on the ground, and over two decades it's grown into leading capacity planning, technology
architecture, and now network intelligence for one of the region's largest operators.
</p>
<p class="about-p">
Today I build the analytics platforms and AI-driven models that guide infrastructure decisions
at scale — and I take on select consulting engagements for teams who need that same translation
from telemetry to strategy.
</p>
<div class="about-tags">
<span class="tag tag-neutral">Cisco IOS-XR</span>
<span class="tag tag-neutral">Juniper JunOS</span>
<span class="tag tag-neutral">Kafka / ClickHouse</span>
<span class="tag tag-neutral">Python / Flask</span>
<span class="tag tag-neutral">Fortigate / Palo Alto</span>
</div>
<p class="about-meta">
A.S. Instrumentation Technology, UPR Bayamón · Former Cisco CCNP · Spanish (native), English (professional)
</p>
</div>
</div>
</section>
<hr class="hr" />
<style>
.about { padding: 64px 0; }
.about-inner {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: 48px;
align-items: center;
}
.about-photo {
width: 100%;
max-width: 340px;
aspect-ratio: 1 / 1;
border-radius: 50%;
overflow: hidden;
background: var(--bg-card);
}
.about-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.about-photo--empty {
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed var(--border-strong);
}
.about-photo--empty::after {
content: 'CL';
font-family: var(--font-sans);
font-weight: 800;
font-size: 3rem;
color: var(--accent);
}
.about-title { font-size: 2rem; margin-bottom: 20px; }
.about-p { font-size: 1rem; line-height: 1.7; color: var(--muted-2); max-width: 56ch; margin-bottom: 16px; }
.about-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 24px; }
.about-meta { font-size: 0.8rem; color: var(--muted); margin-top: 24px; }
@media (max-width: 780px) {
.about-inner { grid-template-columns: 1fr; }
.about-photo { max-width: 220px; margin: 0 auto; }
}
</style>
`
}