Files
homelab/index.html
2026-05-24 21:31:52 +00:00

586 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mathias — Personal AI Platform</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #07090f;
--surface: #0d1120;
--surface-2: #121929;
--border: rgba(255,255,255,0.06);
--border-2: rgba(255,255,255,0.10);
--text: #dde3ee;
--text-muted: #526080;
--text-dim: #253248;
--accent: #0ea5e9;
--accent-soft: rgba(14,165,233,0.10);
--accent-glow: rgba(14,165,233,0.06);
--green: #22c55e;
--green-soft: rgba(34,197,94,0.10);
--radius: 14px;
--radius-sm: 8px;
}
html { scroll-behavior: smooth; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
font-size: 15px;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
radial-gradient(circle at 50% 0%, rgba(14,165,233,0.05) 0%, transparent 55%),
linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px);
background-size: 100% 100%, 44px 44px, 44px 44px;
pointer-events: none;
z-index: 0;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 0 28px;
position: relative;
z-index: 1;
}
.reveal {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.6s cubic-bezier(0.16,1,0.3,1), transform 0.6s cubic-bezier(0.16,1,0.3,1);
}
.reveal.visible { opacity: 1; transform: none; }
.reveal-delay-1 { transition-delay: 0.08s; }
.reveal-delay-2 { transition-delay: 0.16s; }
.reveal-delay-3 { transition-delay: 0.24s; }
.reveal-delay-4 { transition-delay: 0.32s; }
.reveal-delay-5 { transition-delay: 0.40s; }
.reveal-delay-6 { transition-delay: 0.48s; }
.hero {
padding: 130px 0 110px;
border-bottom: 1px solid var(--border);
}
.hero-eyebrow {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 36px;
opacity: 0;
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.1s forwards;
}
.hero-eyebrow::before {
content: '';
width: 24px;
height: 1px;
background: var(--accent);
flex-shrink: 0;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: none; }
}
.hero-name {
font-size: clamp(64px, 9vw, 108px);
font-weight: 700;
letter-spacing: -0.04em;
line-height: 0.95;
background: linear-gradient(145deg, #e8edf5 20%, #3d5a80 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 24px;
opacity: 0;
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.2s forwards;
}
.hero-role {
font-size: 15px;
color: var(--text-muted);
margin-bottom: 32px;
letter-spacing: 0.01em;
opacity: 0;
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.3s forwards;
}
.hero-desc {
font-size: 18px;
line-height: 1.75;
color: var(--text);
max-width: 540px;
margin-bottom: 44px;
opacity: 0;
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.4s forwards;
}
.hero-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
opacity: 0;
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.5s forwards;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 7px 16px;
border: 1px solid rgba(34,197,94,0.22);
border-radius: 100px;
font-size: 12.5px;
font-weight: 500;
color: var(--green);
background: var(--green-soft);
letter-spacing: 0.01em;
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--green);
flex-shrink: 0;
animation: statusPulse 2.8s ease-in-out infinite;
}
@keyframes statusPulse {
0%,100% { opacity: 1; box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }
50% { opacity: 0.7; box-shadow: 0 0 0 5px rgba(34,197,94,0); }
}
.chip {
padding: 7px 16px;
border: 1px solid var(--border-2);
border-radius: 100px;
font-size: 12.5px;
color: var(--text-muted);
letter-spacing: 0.01em;
}
section { padding: 96px 0; }
section + section { border-top: 1px solid var(--border); }
.section-header { margin-bottom: 52px; }
.section-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 14px;
}
.section-heading {
font-size: clamp(26px, 4vw, 38px);
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.15;
color: var(--text);
}
.use-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
background: var(--border);
gap: 1px;
}
.use-card {
background: var(--surface);
padding: 36px 32px;
transition: background 0.18s ease;
}
.use-card:hover { background: var(--surface-2); }
.use-num {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--text-dim);
margin-bottom: 22px;
font-variant-numeric: tabular-nums;
}
.use-title {
font-size: 16px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--text);
margin-bottom: 10px;
line-height: 1.3;
}
.use-desc {
font-size: 13.5px;
line-height: 1.65;
color: var(--text-muted);
}
.arch-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.arch-col {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.arch-col-head {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
background: var(--surface);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 8px;
}
.arch-col-head::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--text-dim);
flex-shrink: 0;
}
.arch-col.accent-col .arch-col-head::before { background: var(--accent); }
.arch-col.accent-col { border-color: rgba(14,165,233,0.15); }
.arch-body { background: var(--surface); }
.arch-row {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.arch-row:last-child { border-bottom: none; }
.arch-row:hover { background: var(--surface-2); }
.arch-row-name {
font-size: 13.5px;
font-weight: 600;
color: var(--text);
margin-bottom: 3px;
letter-spacing: -0.005em;
}
.arch-row-spec {
font-size: 12px;
color: var(--text-muted);
line-height: 1.45;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
background: var(--border);
gap: 1px;
}
.stat {
background: var(--surface);
padding: 36px 24px;
transition: background 0.18s;
}
.stat:hover { background: var(--surface-2); }
.stat-value {
font-size: 38px;
font-weight: 700;
letter-spacing: -0.04em;
color: var(--text);
line-height: 1;
margin-bottom: 10px;
}
.stat-value sup {
font-size: 18px;
font-weight: 600;
color: var(--accent);
vertical-align: super;
letter-spacing: 0;
}
.stat-value .unit {
font-size: 20px;
font-weight: 600;
color: var(--text-muted);
letter-spacing: 0;
}
.stat-label {
font-size: 12.5px;
color: var(--text-muted);
line-height: 1.4;
}
.footer {
padding: 36px 0;
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.footer-left {
font-size: 13.5px;
font-weight: 600;
color: var(--text);
}
.footer-right {
font-size: 13px;
color: var(--text-muted);
}
@media (max-width: 900px) {
.use-grid { grid-template-columns: repeat(2, 1fr); }
.arch-grid { grid-template-columns: 1fr; gap: 14px; }
.stats-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 600px) {
.use-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.hero { padding: 90px 0 80px; }
}
</style>
</head>
<body>
<div class="page">
<section class="hero">
<div class="hero-eyebrow">Personal AI Platform</div>
<h1 class="hero-name">Mathias</h1>
<p class="hero-role">Digital Product Manager &amp; Technology Consultant &nbsp;·&nbsp; Sweden</p>
<p class="hero-desc">A self-hosted AI development environment — from bare metal to running agents. Built for privacy, production quality, and speed of iteration.</p>
<div class="hero-meta">
<span class="status-pill"><span class="status-dot"></span>All systems operational</span>
<span class="chip">Open source tooling</span>
<span class="chip">Gitea-native</span>
<span class="chip">No cloud lock-in</span>
</div>
</section>
<section>
<div class="section-header reveal">
<div class="section-label">What it enables</div>
<h2 class="section-heading">Six capabilities.<br>One platform.</h2>
</div>
<div class="use-grid">
<div class="use-card reveal reveal-delay-1">
<div class="use-num">01</div>
<div class="use-title">Private AI Inference</div>
<div class="use-desc">GPU-accelerated local LLM serving with 27+ model routes. Data never leaves the network. Cloud APIs available as optional tier.</div>
</div>
<div class="use-card reveal reveal-delay-2">
<div class="use-num">02</div>
<div class="use-title">Agent Orchestration</div>
<div class="use-desc">Executor + reviewer loops for autonomous coding tasks. Risk-tiered tool middleware. Flat peer-to-peer architecture, not hierarchical.</div>
</div>
<div class="use-card reveal reveal-delay-3">
<div class="use-num">03</div>
<div class="use-title">Financial Automation</div>
<div class="use-desc">Invoice extraction, validation, and ISO 20022 payment generation. Fortnox integration with full audit trail and multi-tenant isolation.</div>
</div>
<div class="use-card reveal reveal-delay-4">
<div class="use-num">04</div>
<div class="use-title">Knowledge Management</div>
<div class="use-desc">Self-hosted brain with BM25 + vector hybrid retrieval. Persistent memory across sessions, machines, and AI harnesses.</div>
</div>
<div class="use-card reveal reveal-delay-5">
<div class="use-num">05</div>
<div class="use-title">Private Git &amp; CI/CD</div>
<div class="use-desc">Gitea as canonical remote. Flux GitOps, Gitea Actions, buildah OCI builds. Full code workflow without GitHub dependency.</div>
</div>
<div class="use-card reveal reveal-delay-6">
<div class="use-num">06</div>
<div class="use-title">Full-Stack Development</div>
<div class="use-desc">Go + HTMX services from local dev to production Kubernetes pods — same day. Templates, scaffolding, and one-command deploys.</div>
</div>
</div>
</section>
<section>
<div class="section-header reveal">
<div class="section-label">How it's built</div>
<h2 class="section-heading">Hardware to protocol.</h2>
</div>
<div class="arch-grid">
<div class="arch-col reveal reveal-delay-1">
<div class="arch-col-head">Hardware</div>
<div class="arch-body">
<div class="arch-row">
<div class="arch-row-name">koala</div>
<div class="arch-row-spec">RTX 5070 · k3s cluster node · GPU inference · Tailscale hub</div>
</div>
<div class="arch-row">
<div class="arch-row-name">iguana</div>
<div class="arch-row-spec">M2 Ultra Mac · Services · Builds · Ollama models</div>
</div>
<div class="arch-row">
<div class="arch-row-name">flamingo</div>
<div class="arch-row-spec">Mac mini · Daily driver · Edge node · Claude Code</div>
</div>
<div class="arch-row">
<div class="arch-row-name">piguard</div>
<div class="arch-row-spec">Raspberry Pi · Network gateway · LiteLLM proxy</div>
</div>
<div class="arch-row">
<div class="arch-row-name">piblock</div>
<div class="arch-row-spec">Raspberry Pi · NAS · Restic off-host backup target</div>
</div>
</div>
</div>
<div class="arch-col reveal reveal-delay-2">
<div class="arch-col-head">Platform</div>
<div class="arch-body">
<div class="arch-row">
<div class="arch-row-name">k3s + Flux</div>
<div class="arch-row-spec">Kubernetes on koala · GitOps reconciliation · all manifests in git</div>
</div>
<div class="arch-row">
<div class="arch-row-name">Tailscale mesh</div>
<div class="arch-row-spec">Zero-trust networking across all five machines · no VPN config</div>
</div>
<div class="arch-row">
<div class="arch-row-name">Observability</div>
<div class="arch-row-spec">Prometheus · Grafana · Jaeger distributed tracing · Loki</div>
</div>
<div class="arch-row">
<div class="arch-row-name">Identity &amp; Secrets</div>
<div class="arch-row-spec">Dex OIDC · SOPS-encrypted manifests · k8s secretKeyRef</div>
</div>
<div class="arch-row">
<div class="arch-row-name">CI/CD</div>
<div class="arch-row-spec">Gitea Actions · buildah OCI builds · local registry · Flux apply</div>
</div>
</div>
</div>
<div class="arch-col accent-col reveal reveal-delay-3">
<div class="arch-col-head">AI Stack</div>
<div class="arch-body">
<div class="arch-row">
<div class="arch-row-name">LiteLLM proxy</div>
<div class="arch-row-spec">27+ model routes · local + cloud · single unified API</div>
</div>
<div class="arch-row">
<div class="arch-row-name">llama-swap</div>
<div class="arch-row-spec">GPU model manager · hot-swap between slots · RTX 5070 offload</div>
</div>
<div class="arch-row">
<div class="arch-row-name">MCP protocol</div>
<div class="arch-row-spec">brain · gitea · infra · routing — Dex-authenticated servers</div>
</div>
<div class="arch-row">
<div class="arch-row-name">Brain KB</div>
<div class="arch-row-spec">BM25 + pgvector hybrid retrieval · LLM synthesis · 100+ entries</div>
</div>
<div class="arch-row">
<div class="arch-row-name">ADK agents</div>
<div class="arch-row-spec">Go ADK · kimi-k2.6 executor · gemma4-31b reviewer · OTLP traces</div>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="section-header reveal">
<div class="section-label">By the numbers</div>
<h2 class="section-heading">What's running.</h2>
</div>
<div class="stats-grid">
<div class="stat reveal reveal-delay-1">
<div class="stat-value">5</div>
<div class="stat-label">Machines on<br>Tailscale mesh</div>
</div>
<div class="stat reveal reveal-delay-2">
<div class="stat-value">27<sup>+</sup></div>
<div class="stat-label">LLM routes<br>local + cloud</div>
</div>
<div class="stat reveal reveal-delay-3">
<div class="stat-value">4</div>
<div class="stat-label">MCP servers<br>in production</div>
</div>
<div class="stat reveal reveal-delay-4">
<div class="stat-value">12<span class="unit">GB</span></div>
<div class="stat-label">GPU VRAM for<br>local inference</div>
</div>
<div class="stat reveal reveal-delay-5">
<div class="stat-value">100<sup>+</sup></div>
<div class="stat-label">Brain knowledge<br>entries</div>
</div>
<div class="stat reveal reveal-delay-6">
<div class="stat-value">1<span class="unit">day</span></div>
<div class="stat-label">Idea to<br>production deploy</div>
</div>
</div>
</section>
<footer class="footer reveal">
<div class="footer-left">Mathias &nbsp;·&nbsp; Sweden</div>
<div class="footer-right">All source on Gitea &nbsp;·&nbsp; Built with Go, Kubernetes &amp; curiosity</div>
</footer>
</div>
<script>
(function() {
var els = document.querySelectorAll('.reveal');
var io = new IntersectionObserver(function(entries) {
entries.forEach(function(e) {
if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); }
});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
els.forEach(function(el) { io.observe(el); });
})();
</script>
</body>
</html>