787 lines
29 KiB
HTML
787 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Rawand Lorentzen</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Syne:wght@400;700;800&family=Inter:wght@300;400;500&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg: #f0f2f5;
|
||
--bg2: #e8ebef;
|
||
--surface: #ffffff;
|
||
--surface2: #f7f8fa;
|
||
--border: #d8dde6;
|
||
--border2: #c4ccd8;
|
||
--accent: #2563eb;
|
||
--accent-h: #1d4ed8;
|
||
--accent-lt: #eff6ff;
|
||
--text: #1e2530;
|
||
--text-mid: #4a5568;
|
||
--text-dim: #8896aa;
|
||
--mono: 'JetBrains Mono', monospace;
|
||
--sans: 'Syne', sans-serif;
|
||
--body: 'Inter', sans-serif;
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
|
||
body {
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: var(--body);
|
||
overflow-x: hidden;
|
||
cursor: none;
|
||
}
|
||
|
||
.cursor {
|
||
width: 8px; height: 8px;
|
||
background: var(--accent);
|
||
border-radius: 50%;
|
||
position: fixed;
|
||
pointer-events: none;
|
||
z-index: 9999;
|
||
transition: transform 0.15s;
|
||
}
|
||
.cursor-ring {
|
||
width: 30px; height: 30px;
|
||
border: 1.5px solid var(--accent);
|
||
border-radius: 50%;
|
||
position: fixed;
|
||
pointer-events: none;
|
||
z-index: 9998;
|
||
opacity: 0.35;
|
||
transition: all 0.12s ease;
|
||
}
|
||
|
||
nav {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0;
|
||
z-index: 100;
|
||
padding: 0 48px;
|
||
height: 60px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: rgba(240,242,245,0.92);
|
||
backdrop-filter: blur(16px);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.nav-logo {
|
||
font-family: var(--sans);
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
color: var(--text);
|
||
letter-spacing: -0.3px;
|
||
}
|
||
.nav-logo span { color: var(--accent); }
|
||
|
||
.nav-links { display: flex; gap: 4px; list-style: none; }
|
||
|
||
.nav-links a {
|
||
color: var(--text-mid);
|
||
text-decoration: none;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
padding: 6px 14px;
|
||
border-radius: 6px;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.nav-links a:hover { background: var(--accent-lt); color: var(--accent); }
|
||
.nav-links a.active { background: var(--accent); color: white; }
|
||
|
||
/* HERO */
|
||
.hero {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 100px 48px 80px;
|
||
gap: 80px;
|
||
}
|
||
|
||
.hero-content { max-width: 640px; flex: 1; }
|
||
|
||
.hero-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: var(--accent-lt);
|
||
color: var(--accent);
|
||
font-family: var(--mono);
|
||
font-size: 11px;
|
||
padding: 6px 14px;
|
||
border-radius: 20px;
|
||
margin-bottom: 28px;
|
||
border: 1px solid rgba(37,99,235,0.2);
|
||
}
|
||
.hero-badge::before { content: '●'; font-size: 8px; }
|
||
|
||
.hero h1 {
|
||
font-family: var(--sans);
|
||
font-size: clamp(48px, 6vw, 80px);
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
letter-spacing: -2px;
|
||
color: var(--text);
|
||
margin-bottom: 8px;
|
||
}
|
||
.hero h1 .name-accent { color: var(--accent); }
|
||
|
||
.hero-sub {
|
||
font-family: var(--mono);
|
||
font-size: 13px;
|
||
color: var(--text-dim);
|
||
margin-bottom: 24px;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.hero-desc {
|
||
font-size: 16px;
|
||
line-height: 1.8;
|
||
color: var(--text-mid);
|
||
max-width: 480px;
|
||
margin-bottom: 40px;
|
||
font-weight: 300;
|
||
}
|
||
.hero-desc strong { color: var(--text); font-weight: 500; }
|
||
|
||
.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; }
|
||
|
||
.btn {
|
||
padding: 12px 24px;
|
||
font-family: var(--body);
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
border-radius: 8px;
|
||
border: 1.5px solid transparent;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
|
||
.btn-primary:hover { background: var(--accent-h); border-color: var(--accent-h); }
|
||
.btn-ghost { background: var(--surface); color: var(--text-mid); border-color: var(--border); }
|
||
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-lt); }
|
||
|
||
/* Terminal */
|
||
.terminal {
|
||
width: 360px;
|
||
flex-shrink: 0;
|
||
background: var(--text);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
|
||
display: none;
|
||
}
|
||
@media (min-width: 1100px) { .terminal { display: block; } }
|
||
|
||
.terminal-bar {
|
||
padding: 12px 16px;
|
||
background: #2a3240;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
}
|
||
.dot { width: 11px; height: 11px; border-radius: 50%; }
|
||
.dot-r { background: #ff5f57; }
|
||
.dot-y { background: #febc2e; }
|
||
.dot-g { background: #28c840; }
|
||
.terminal-title { font-family: var(--mono); font-size: 11px; color: #8896aa; margin-left: 6px; }
|
||
.terminal-body { padding: 20px 24px; font-family: var(--mono); font-size: 12.5px; line-height: 2; color: #a8b4c8; }
|
||
.t-prompt { color: #4ade80; }
|
||
.t-cmd { color: #93c5fd; }
|
||
.t-out { color: #e2e8f0; }
|
||
.t-accent { color: #60a5fa; }
|
||
.blink { animation: blink 1.2s infinite; color: #4ade80; }
|
||
@keyframes blink { 0%,100%{opacity:1}50%{opacity:0} }
|
||
|
||
/* SECTIONS */
|
||
section { padding: 96px 48px; }
|
||
|
||
.section-header { margin-bottom: 56px; }
|
||
.section-eyebrow {
|
||
font-family: var(--mono);
|
||
font-size: 11px;
|
||
color: var(--accent);
|
||
letter-spacing: 0.15em;
|
||
text-transform: uppercase;
|
||
margin-bottom: 12px;
|
||
}
|
||
.section-title {
|
||
font-family: var(--sans);
|
||
font-size: clamp(28px, 3.5vw, 42px);
|
||
font-weight: 800;
|
||
color: var(--text);
|
||
letter-spacing: -1px;
|
||
}
|
||
.section-desc {
|
||
margin-top: 12px;
|
||
font-size: 15px;
|
||
color: var(--text-mid);
|
||
font-weight: 300;
|
||
max-width: 480px;
|
||
}
|
||
|
||
hr.divider { border: none; border-top: 1px solid var(--border); }
|
||
|
||
/* ABOUT */
|
||
.about-wrap { display: grid; grid-template-columns: 1fr; gap: 40px; }
|
||
|
||
.about-text { font-size: 15px; line-height: 1.9; color: var(--text-mid); font-weight: 300; max-width: 780px; }
|
||
.about-text p { margin-bottom: 18px; }
|
||
.about-text strong { color: var(--text); font-weight: 500; }
|
||
|
||
.about-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 28px; }
|
||
|
||
.chip {
|
||
font-family: var(--mono);
|
||
font-size: 11px;
|
||
padding: 5px 12px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--text-mid);
|
||
transition: all 0.2s;
|
||
}
|
||
.chip:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-lt); }
|
||
|
||
/* REPOS */
|
||
.repos-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.repo-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 24px;
|
||
text-decoration: none;
|
||
display: block;
|
||
transition: all 0.2s;
|
||
}
|
||
.repo-card:hover { border-color: var(--accent); box-shadow: 0 4px 20px rgba(37,99,235,0.08); transform: translateY(-2px); }
|
||
.repo-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; }
|
||
.repo-name { font-family: var(--sans); font-size: 15px; font-weight: 700; color: var(--text); }
|
||
.repo-arrow { color: var(--text-dim); font-size: 16px; transition: color 0.2s, transform 0.2s; }
|
||
.repo-card:hover .repo-arrow { color: var(--accent); transform: translate(2px,-2px); }
|
||
.repo-desc { font-size: 13px; line-height: 1.7; color: var(--text-mid); margin-bottom: 18px; min-height: 38px; }
|
||
.repo-meta { display: flex; gap: 14px; font-family: var(--mono); font-size: 11px; color: var(--text-dim); }
|
||
.repo-lang::before { content: '● '; color: var(--accent); }
|
||
.repo-loading, .repo-empty {
|
||
grid-column: 1/-1; padding: 56px; text-align: center;
|
||
color: var(--text-dim); font-size: 14px;
|
||
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
||
}
|
||
|
||
/* PROJECTS */
|
||
.projects-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 28px;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.project-card:hover { border-color: var(--border2); box-shadow: 0 4px 20px rgba(0,0,0,0.06); transform: translateY(-2px); }
|
||
.project-card.own { border-color: rgba(37,99,235,0.3); background: var(--accent-lt); }
|
||
.project-card.own:hover { border-color: var(--accent); }
|
||
|
||
.project-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
|
||
.project-num { font-family: var(--mono); font-size: 10px; color: var(--accent); letter-spacing: 0.1em; }
|
||
.project-badge {
|
||
font-family: var(--mono);
|
||
font-size: 10px;
|
||
padding: 3px 8px;
|
||
border-radius: 4px;
|
||
background: var(--accent);
|
||
color: white;
|
||
}
|
||
.project-badge.internship { background: var(--bg2); color: var(--text-dim); border: 1px solid var(--border); }
|
||
|
||
.project-title { font-family: var(--sans); font-size: 18px; font-weight: 700; color: var(--text); margin-bottom: 8px; }
|
||
.project-desc { font-size: 13px; line-height: 1.7; color: var(--text-mid); margin-bottom: 20px; flex: 1; }
|
||
.tags { display: flex; flex-wrap: wrap; gap: 7px; }
|
||
.tag { font-family: var(--mono); font-size: 10px; padding: 3px 10px; border-radius: 4px; background: var(--bg2); color: var(--text-dim); border: 1px solid var(--border); }
|
||
.tag-accent { background: var(--accent-lt); color: var(--accent); border-color: rgba(37,99,235,0.2); }
|
||
|
||
/* BLOG */
|
||
.blog-list { display: flex; flex-direction: column; gap: 12px; }
|
||
.blog-item {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 22px 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24px;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
cursor: none;
|
||
}
|
||
.blog-item:hover { border-color: var(--accent); background: var(--accent-lt); transform: translateX(4px); }
|
||
.blog-date { font-family: var(--mono); font-size: 11px; color: var(--text-dim); white-space: nowrap; width: 88px; flex-shrink: 0; }
|
||
.blog-title { font-size: 14px; color: var(--text); flex: 1; font-weight: 500; }
|
||
.blog-arrow { color: var(--text-dim); font-size: 16px; transition: color 0.2s, transform 0.2s; }
|
||
.blog-item:hover .blog-arrow { color: var(--accent); transform: translateX(4px); }
|
||
|
||
/* LINKS */
|
||
.links-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; }
|
||
.link-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 24px;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
display: block;
|
||
}
|
||
.link-card:hover { border-color: var(--accent); box-shadow: 0 4px 20px rgba(37,99,235,0.08); transform: translateY(-2px); }
|
||
.link-icon { font-size: 22px; margin-bottom: 12px; }
|
||
.link-name { font-family: var(--sans); font-size: 15px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
|
||
.link-desc { font-size: 12px; color: var(--text-dim); line-height: 1.6; }
|
||
|
||
footer {
|
||
border-top: 1px solid var(--border);
|
||
padding: 28px 48px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
font-family: var(--mono);
|
||
background: var(--surface);
|
||
}
|
||
|
||
.fade-in { opacity: 0; transform: translateY(16px); transition: opacity 0.6s ease, transform 0.6s ease; }
|
||
.fade-in.visible { opacity: 1; transform: translateY(0); }
|
||
|
||
@media (max-width: 640px) {
|
||
nav { padding: 0 20px; }
|
||
.hero { padding: 90px 24px 60px; flex-direction: column; gap: 40px; }
|
||
section { padding: 64px 24px; }
|
||
footer { padding: 20px 24px; flex-direction: column; gap: 8px; text-align: center; }
|
||
.nav-links { display: none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="cursor" id="cursor"></div>
|
||
<div class="cursor-ring" id="cursorRing"></div>
|
||
|
||
<nav>
|
||
<div class="nav-logo">Rawand<span>.</span></div>
|
||
<ul class="nav-links">
|
||
<li><a href="#about">About</a></li>
|
||
<li><a href="#repos">Git</a></li>
|
||
<li><a href="#projects">Projects</a></li>
|
||
<li><a href="#blog">Blog</a></li>
|
||
<li><a href="/notes">Notes</a></li>
|
||
<li><a href="#links">Links</a></li>
|
||
</ul>
|
||
</nav>
|
||
|
||
<!-- HERO -->
|
||
<section class="hero">
|
||
<div class="hero-content fade-in">
|
||
<div class="hero-badge">IT-Teknolog — Cloud & Infrastructure</div>
|
||
<h1>Rawand <span class="name-accent">Lorentzen</span></h1>
|
||
<div class="hero-sub">// terraform apply && git push</div>
|
||
<p class="hero-desc">
|
||
Cloud engineer with a background in <strong>network protocols</strong>,
|
||
<strong>embedded systems</strong> and <strong>agile project work</strong>.
|
||
Working with Azure infrastructure and IaC in production — documenting everything along the way.
|
||
</p>
|
||
<div class="hero-cta">
|
||
<a href="#projects" class="btn btn-primary">View Projects →</a>
|
||
<a href="/git" class="btn btn-ghost">⌥ Forgejo</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="terminal">
|
||
<div class="terminal-bar">
|
||
<div class="dot dot-r"></div>
|
||
<div class="dot dot-y"></div>
|
||
<div class="dot dot-g"></div>
|
||
<span class="terminal-title">rawand@hetzner ~ $</span>
|
||
</div>
|
||
<div class="terminal-body">
|
||
<div><span class="t-prompt">❯</span> <span class="t-cmd">whoami</span></div>
|
||
<div class="t-out">rawand_lorentzen</div>
|
||
<br>
|
||
<div><span class="t-prompt">❯</span> <span class="t-cmd">cat certs.txt</span></div>
|
||
<div class="t-accent">→ IT-Teknolog (Graduate)</div>
|
||
<div class="t-accent">→ CompTIA Network+</div>
|
||
<div class="t-accent">→ AZ-104</div>
|
||
<div class="t-accent">→ AZ-500 (in progress)</div>
|
||
<br>
|
||
<div><span class="t-prompt">❯</span> <span class="t-cmd">cat stack.txt</span></div>
|
||
<div class="t-out">→ Azure / Terraform / IaC</div>
|
||
<div class="t-out">→ OSPF / BGP / TCP-IP</div>
|
||
<div class="t-out">→ Python / OOP</div>
|
||
<div class="t-out">→ Docker / Kubernetes</div>
|
||
<div class="t-out">→ Databricks / Power BI</div>
|
||
<br>
|
||
<div><span class="t-prompt">❯</span> <span class="blink">█</span></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<hr class="divider">
|
||
|
||
<!-- ABOUT -->
|
||
<section id="about">
|
||
<div class="section-header">
|
||
<div class="section-eyebrow">01 — About</div>
|
||
<h2 class="section-title">Who I am</h2>
|
||
<p class="section-desc">Engineer, problem solver, lifelong learner.</p>
|
||
</div>
|
||
<div class="about-wrap">
|
||
<div class="about-text fade-in">
|
||
<p>
|
||
Rawand Lorentzen is a qualified <strong>IT-Teknolog</strong> with a broad technical foundation spanning cloud infrastructure, network engineering, and software development. His education covered the full stack of modern IT — from low-level routing protocols like <strong>OSPF and BGP</strong>, communication architectures, and TCP/IP networking, to object-oriented <strong>Python programming</strong> in the context of embedded systems.
|
||
</p>
|
||
<p>
|
||
Since transitioning into cloud infrastructure, Rawand has gained hands-on experience with <strong>Microsoft Azure</strong> and <strong>Terraform-based Infrastructure as Code (IaC)</strong> — working in production environments during his internship at CIMT across governance, CI/CD pipelines, Defender for Cloud, and CIS compliance frameworks. He holds the <strong>AZ-104</strong> certification and is currently working toward <strong>AZ-500</strong>.
|
||
</p>
|
||
<p>
|
||
On the data side, he has worked with <strong>Databricks</strong> and <strong>Power BI</strong> — including workspace provisioning, access control, and integrating data platforms into cloud infrastructure. He has also worked with <strong>Docker</strong> and <strong>Kubernetes</strong>, gaining practical experience with containerised workloads in cloud-native environments.
|
||
</p>
|
||
<p>
|
||
Before IT, Rawand spent close to a decade in physical craftsmanship. That background shaped a mindset that carries directly into infrastructure work: methodical, detail-oriented, and always built to last.
|
||
</p>
|
||
<div class="about-chips">
|
||
<span class="chip">Terraform</span>
|
||
<span class="chip">Azure</span>
|
||
<span class="chip">IaC</span>
|
||
<span class="chip">OSPF / BGP</span>
|
||
<span class="chip">TCP/IP</span>
|
||
<span class="chip">Python</span>
|
||
<span class="chip">OOP</span>
|
||
<span class="chip">Embedded Systems</span>
|
||
<span class="chip">Docker</span>
|
||
<span class="chip">Kubernetes</span>
|
||
<span class="chip">Linux</span>
|
||
<span class="chip">CI/CD</span>
|
||
<span class="chip">CIS Controls</span>
|
||
<span class="chip">Agile / Scrum</span>
|
||
<span class="chip">Databricks</span>
|
||
<span class="chip">Power BI</span>
|
||
<span class="chip">AZ-104</span>
|
||
<span class="chip">AZ-500</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<hr class="divider">
|
||
|
||
<!-- REPOS -->
|
||
<section id="repos" style="background: var(--bg2);">
|
||
<div class="section-header">
|
||
<div class="section-eyebrow">02 — Git</div>
|
||
<h2 class="section-title">Public repositories</h2>
|
||
<p class="section-desc">Live from Forgejo — browse and explore.</p>
|
||
</div>
|
||
<div class="repos-grid" id="reposGrid">
|
||
<div class="repo-loading">Loading repositories from Forgejo...</div>
|
||
</div>
|
||
</section>
|
||
|
||
<hr class="divider">
|
||
|
||
<!-- PROJECTS -->
|
||
<section id="projects">
|
||
<div class="section-header">
|
||
<div class="section-eyebrow">03 — Projects</div>
|
||
<h2 class="section-title">What I've worked on</h2>
|
||
<p class="section-desc">A mix of internship work and personal projects.</p>
|
||
</div>
|
||
<div class="projects-grid">
|
||
|
||
<div class="project-card fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">001</div>
|
||
<span class="project-badge internship">Internship — CIMT</span>
|
||
</div>
|
||
<div class="project-title">Azure Landing Zone</div>
|
||
<p class="project-desc">Contributed to a full Landing Zone implementation using Terraform at CIMT. Covering governance, policy, networking and RBAC across Azure environments.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">Terraform</span>
|
||
<span class="tag">Azure</span>
|
||
<span class="tag">Governance</span>
|
||
<span class="tag">RBAC</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">002</div>
|
||
<span class="project-badge internship">Internship — CIMT</span>
|
||
</div>
|
||
<div class="project-title">DAP — Data Access Platform</div>
|
||
<p class="project-desc">Worked on Databricks workspace provisioning with Entra ID group-based access control and ADLS Gen2 integration at CIMT.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">Databricks</span>
|
||
<span class="tag">Entra ID</span>
|
||
<span class="tag">Terraform</span>
|
||
<span class="tag">ADLS Gen2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">003</div>
|
||
<span class="project-badge internship">Internship — CIMT</span>
|
||
</div>
|
||
<div class="project-title">CIS Compliance Checker</div>
|
||
<p class="project-desc">Contributed to a Python static compliance checker for Terraform files, comparing current vs predicted CIS IMP2 scores across Azure projects.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">Python</span>
|
||
<span class="tag">CIS Controls</span>
|
||
<span class="tag">Terraform</span>
|
||
<span class="tag">OOP</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card own fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">004</div>
|
||
<span class="project-badge">Personal</span>
|
||
</div>
|
||
<div class="project-title">Self-Hosted Forge</div>
|
||
<p class="project-desc">Personal portfolio and self-hosted platform built from scratch. Running on Hetzner with Forgejo, Nginx reverse proxy, Let's Encrypt SSL and Docker Compose.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">Forgejo</span>
|
||
<span class="tag">Docker</span>
|
||
<span class="tag">Nginx</span>
|
||
<span class="tag">Hetzner</span>
|
||
<span class="tag">Linux</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card own fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">005</div>
|
||
<span class="project-badge">School Project</span>
|
||
</div>
|
||
<div class="project-title">Fingerprint Pill Dispenser</div>
|
||
<p class="project-desc">A proof of concept exploring embedded systems and access control. Built on an ESP32 microcontroller with an AS608 fingerprint sensor — patients register their fingerprint, which must be verified before a pill dispenser unlocks. The dispenser was a physical enclosure controlled by an MG90S servo motor. Developed in Python with object-oriented design.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">ESP32</span>
|
||
<span class="tag">AS608</span>
|
||
<span class="tag">Python</span>
|
||
<span class="tag">OOP</span>
|
||
<span class="tag">Embedded</span>
|
||
<span class="tag">MG90S Servo</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card own fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">006</div>
|
||
<span class="project-badge">Personal</span>
|
||
</div>
|
||
<div class="project-title">This Website</div>
|
||
<p class="project-desc">Personal portfolio site, self-hosted on Hetzner. Built from scratch with HTML, CSS and JavaScript — no frameworks, no dependencies.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">HTML/CSS</span>
|
||
<span class="tag">JavaScript</span>
|
||
<span class="tag">Nginx</span>
|
||
<span class="tag">Hetzner</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-card own fade-in">
|
||
<div class="project-top">
|
||
<div class="project-num">007</div>
|
||
<span class="project-badge">Personal</span>
|
||
</div>
|
||
<div class="project-title">PostgreSQL Database</div>
|
||
<p class="project-desc">Set up and administered a self-hosted PostgreSQL database as part of personal infrastructure. Includes schema design and integration with hosted services.</p>
|
||
<div class="tags">
|
||
<span class="tag tag-accent">PostgreSQL</span>
|
||
<span class="tag">Linux</span>
|
||
<span class="tag">Docker</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<hr class="divider">
|
||
|
||
<!-- BLOG -->
|
||
<section id="blog" style="background: var(--bg2);">
|
||
<div class="section-header">
|
||
<div class="section-eyebrow">04 — Blog</div>
|
||
<h2 class="section-title">Articles & writeups</h2>
|
||
<p class="section-desc">Technical deep-dives and lessons learned.</p>
|
||
</div>
|
||
<div class="blog-list" id="blogList">
|
||
<div style="padding:40px;text-align:center;color:var(--text-dim);font-size:14px;">Indlæser...</div>
|
||
</div>
|
||
</section>
|
||
|
||
<hr class="divider">
|
||
|
||
<!-- LINKS -->
|
||
<section id="links">
|
||
<div class="section-header">
|
||
<div class="section-eyebrow">05 — Links</div>
|
||
<h2 class="section-title">Find me here</h2>
|
||
</div>
|
||
<div class="links-grid">
|
||
<a href="/git" class="link-card">
|
||
<div class="link-icon">⌥</div>
|
||
<div class="link-name">Forgejo</div>
|
||
<p class="link-desc">Self-hosted git forge. Code, experiments and open source projects.</p>
|
||
</a>
|
||
<a href="/notes" class="link-card">
|
||
<div class="link-icon">◈</div>
|
||
<div class="link-name">Notes</div>
|
||
<p class="link-desc">Personal knowledge base, technical documentation and articles.</p>
|
||
</a>
|
||
<a href="#" class="link-card">
|
||
<div class="link-icon">in</div>
|
||
<div class="link-name">LinkedIn</div>
|
||
<p class="link-desc">Professional profile, certifications and career updates.</p>
|
||
</a>
|
||
<a href="mailto:rawandlorentzen@gmail.com" class="link-card">
|
||
<div class="link-icon">✉</div>
|
||
<div class="link-name">Email</div>
|
||
<p class="link-desc">rawandlorentzen@gmail.com</p>
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
<footer>
|
||
<span>rawandlorentzen.com</span>
|
||
<span>// built with Linux, Docker & caffeine</span>
|
||
<span>© 2026</span>
|
||
</footer>
|
||
|
||
<script>
|
||
// Cursor
|
||
const cursor = document.getElementById('cursor');
|
||
const ring = document.getElementById('cursorRing');
|
||
let mx = 0, my = 0, rx = 0, ry = 0;
|
||
|
||
document.addEventListener('mousemove', e => {
|
||
mx = e.clientX; my = e.clientY;
|
||
cursor.style.left = mx - 4 + 'px';
|
||
cursor.style.top = my - 4 + 'px';
|
||
});
|
||
|
||
(function animRing() {
|
||
rx += (mx - rx) * 0.12;
|
||
ry += (my - ry) * 0.12;
|
||
ring.style.left = rx - 15 + 'px';
|
||
ring.style.top = ry - 15 + 'px';
|
||
requestAnimationFrame(animRing);
|
||
})();
|
||
|
||
document.querySelectorAll('a, .btn').forEach(el => {
|
||
el.addEventListener('mouseenter', () => { cursor.style.transform = 'scale(2.5)'; ring.style.opacity = '0.6'; });
|
||
el.addEventListener('mouseleave', () => { cursor.style.transform = 'scale(1)'; ring.style.opacity = '0.35'; });
|
||
});
|
||
|
||
// Active nav on scroll
|
||
const sections = document.querySelectorAll('section[id]');
|
||
const navLinks = document.querySelectorAll('.nav-links a');
|
||
|
||
window.addEventListener('scroll', () => {
|
||
let current = '';
|
||
sections.forEach(s => { if (window.scrollY >= s.offsetTop - 80) current = s.id; });
|
||
navLinks.forEach(a => {
|
||
a.classList.remove('active');
|
||
if (a.getAttribute('href') === '#' + current) a.classList.add('active');
|
||
});
|
||
});
|
||
|
||
// Fade in
|
||
const observer = new IntersectionObserver(entries => {
|
||
entries.forEach((e, i) => {
|
||
if (e.isIntersecting) setTimeout(() => e.target.classList.add('visible'), i * 100);
|
||
});
|
||
}, { threshold: 0.1 });
|
||
|
||
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
||
|
||
// Forgejo repos
|
||
async function loadRepos() {
|
||
const grid = document.getElementById('reposGrid');
|
||
try {
|
||
const res = await fetch('/git/api/v1/repos/search?limit=8&sort=updated');
|
||
const data = await res.json();
|
||
const repos = (data.data || []).filter(r => !r.private);
|
||
|
||
if (!repos.length) {
|
||
grid.innerHTML = '<div class="repo-empty">No public repositories yet.<br><span style="font-size:12px;opacity:0.6;margin-top:8px;display:block">Push your first repo to Forgejo to see it here.</span></div>';
|
||
return;
|
||
}
|
||
|
||
grid.innerHTML = repos.map(repo => `
|
||
<a href="/git/${repo.full_name}" class="repo-card fade-in">
|
||
<div class="repo-top">
|
||
<div class="repo-name">${repo.name}</div>
|
||
<span class="repo-arrow">↗</span>
|
||
</div>
|
||
<div class="repo-desc">${repo.description || 'No description provided.'}</div>
|
||
<div class="repo-meta">
|
||
${repo.language ? `<span class="repo-lang">${repo.language}</span>` : ''}
|
||
<span>★ ${repo.stars_count}</span>
|
||
<span>${repo.forks_count} forks</span>
|
||
</div>
|
||
</a>
|
||
`).join('');
|
||
|
||
grid.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
||
} catch {
|
||
grid.innerHTML = '<div class="repo-empty">Could not load repositories.<br><span style="font-size:12px;opacity:0.6;margin-top:8px;display:block">Make sure Forgejo is running at /git</span></div>';
|
||
}
|
||
}
|
||
|
||
loadRepos();
|
||
|
||
// Blog posts
|
||
async function loadBlog() {
|
||
const list = document.getElementById('blogList');
|
||
try {
|
||
const res = await fetch('/api/blog');
|
||
const posts = await res.json();
|
||
if (!posts.length) {
|
||
list.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text-dim);font-size:14px;">Ingen indlæg endnu.</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = posts.map(p => `
|
||
<a href="/blog/${p.slug}" class="blog-item">
|
||
<span class="blog-date">${p.date}</span>
|
||
<span class="blog-title">${p.title}</span>
|
||
<span class="blog-arrow">→</span>
|
||
</a>
|
||
`).join('');
|
||
} catch {
|
||
list.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text-dim);font-size:14px;">Kunne ikke hente indlæg.</div>';
|
||
}
|
||
}
|
||
|
||
loadBlog();
|
||
</script>
|
||
</body>
|
||
</html>
|