feat: add tools section with MarkItDown, update Caddyfile for md subdomain

This commit is contained in:
2026-05-28 11:53:06 +00:00
parent 53e2e7a818
commit b7dc08dc2f
3 changed files with 132 additions and 21 deletions
+35 -21
View File
@@ -74,27 +74,6 @@ portainer.carloselugo.com {
} }
} }
# ── Umami Analytics ───────────────────────────────────────────────────────────
analytics.carloselugo.com {
import security_headers
encode gzip
log {
output file /var/log/caddy/analytics.log
format json
}
@public path /script.js /api/send
handle @public {
reverse_proxy umami:3000
}
handle {
@blocked not remote_ip 24.139.235.46/32 192.168.68.0/24 2605:ba00:3108::/48 74.115.203.2/32 165.225.192.0/18
abort @blocked
reverse_proxy umami:3000
}
}
# ── ntfy (notificaciones push) ──────────────────────────────────────────────── # ── ntfy (notificaciones push) ────────────────────────────────────────────────
ntfy.carloselugo.com { ntfy.carloselugo.com {
reverse_proxy ntfy:80 { reverse_proxy ntfy:80 {
@@ -126,3 +105,38 @@ logs.carloselugo.com {
format json format json
} }
} }
# ── NI Quiz — evento público ───────────────────────────────────────────────────
ni-quiz.carloselugo.com {
import security_headers
reverse_proxy ni-quiz:8000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
log {
output file /var/log/caddy/ni-quiz.log
format json
}
}
# ── MarkItDown ────────────────────────────────────────────────────────────────
md.carloselugo.com {
import security_headers
reverse_proxy markitdown:8000
log {
output file /var/log/caddy/markitdown.log
format json
}
}
# ── NI Quiz — evento público ───────────────────────────────────────────────────
route-rush.carloselugo.com {
import security_headers
reverse_proxy route-rush:8001 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
log {
output file /var/log/caddy/ni-quiz.log
format json
}
}
+95
View File
@@ -0,0 +1,95 @@
const tools = [
{
id: '01',
title: 'MarkItDown',
desc: 'Convert any document to Markdown. Drop a PDF, DOCX, PPTX, XLSX, HTML, or CSV and get clean Markdown output instantly.',
tags: ['python', 'fastapi', 'markitdown'],
url: 'https://md.carloselugo.com',
status: 'live',
},
]
const statusLabel = {
live: { text: 'live', color: '#c8ff00' },
wip: { text: 'wip', color: '#ff6b35' },
}
export function renderTools() {
const cards = tools.map(t => {
const s = statusLabel[t.status]
return `
<a class="tool-card reveal" href="${t.url}" target="_blank" rel="noopener">
<div class="card-top">
<span class="card-id mono">${t.id}</span>
<span class="card-status mono" style="color:${s.color}">● ${s.text}</span>
</div>
<h3 class="card-title">${t.title}</h3>
<p class="card-desc">${t.desc}</p>
<div class="tool-bottom">
<div class="card-tags">
${t.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
<span class="tool-link mono">open ↗</span>
</div>
</a>
`
}).join('')
return `
<section class="tools" id="tools">
<div class="container">
<div class="section-head reveal">
<div class="accent-line"></div>
<h2 class="section-title">Tools</h2>
<p class="section-sub">Self-hosted utilities running on the homelab.</p>
</div>
<div class="tool-grid">
${cards}
</div>
</div>
</section>
<style>
.tools { padding: 0 0 100px; }
.section-sub {
font-size: 0.88rem;
color: var(--muted);
margin-top: 8px;
}
.tool-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin-top: 48px;
}
.tool-card {
background: var(--bg-card);
padding: 32px;
transition: background 0.2s;
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
gap: 0;
}
.tool-card:hover { background: #161616; }
.tool-card:hover .tool-link { color: var(--accent); letter-spacing: 0.12em; }
.tool-bottom {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-top: 20px;
gap: 12px;
}
.tool-link {
font-size: 0.72rem;
color: var(--muted);
white-space: nowrap;
transition: color 0.2s, letter-spacing 0.2s;
flex-shrink: 0;
}
</style>
`
}
+2
View File
@@ -2,6 +2,7 @@ import './style.css'
import { renderHeader } from './components/header.js' import { renderHeader } from './components/header.js'
import { renderHero } from './components/hero.js' import { renderHero } from './components/hero.js'
import { renderProjects } from './components/projects.js' import { renderProjects } from './components/projects.js'
import { renderTools } from './components/tools.js'
import { renderBlog, initBlog } from './components/blog.js' import { renderBlog, initBlog } from './components/blog.js'
import { renderFooter } from './components/footer.js' import { renderFooter } from './components/footer.js'
@@ -12,6 +13,7 @@ app.innerHTML = `
<main> <main>
${renderHero()} ${renderHero()}
${renderProjects()} ${renderProjects()}
${renderTools()}
${renderBlog()} ${renderBlog()}
</main> </main>
${renderFooter()} ${renderFooter()}