Project_Horus/index.html

787 lines
29 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>