i seriously vibe coded the hell out of this LOL. I'm not a web guy. I'll

look this over to make sure its not TOO slop-heavy later on.
This commit is contained in:
lordtet 2026-03-23 22:49:10 -04:00
parent 9113eb8b0a
commit 955a330ad9
26 changed files with 4379 additions and 0 deletions

9
_data/site.json Normal file
View file

@ -0,0 +1,9 @@
{
"handle": "lordtet",
"author": "lordtet",
"tagline": "developer · tinkerer · human",
"bio": "I build things that are useful, interesting, or just plain fun. Fascinated by systems programming, the open web, and old hardware.",
"email": "you@example.com",
"github": "https://github.com/lordtet",
"pgp_fingerprint": "DEAD BEEF 1337 C0DE F00D CAFE BABE 0000 1234 5678 9ABC DEF0"
}

38
_includes/base.njk Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title or "~/home" }} — {{ site.handle }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=VT323&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/phosphor.css">
</head>
<body>
{# CRT overlay effects — purely decorative #}
<div class="scanlines" aria-hidden="true"></div>
<div class="crt-vignette" aria-hidden="true"></div>
<div class="desktop" id="desktop">
{# ── Header — dot pattern strip with centered clock ── #}
<div class="desktop-header" aria-label="System header">
<div class="header-clock" id="clock" aria-live="off"></div>
<a class="boring-link" href="/boring/" title="Plain text navigation">click if boring</a>
</div>
{# ── Main content (injected by each page) ─── #}
<div class="desktop-area">
{{ content | safe }}
</div>
</div>{# /desktop #}
<script src="/assets/js/wm.js"></script>
<script src="/assets/js/images.js"></script>
<script src="/assets/js/clock.js"></script>
</body>
</html>

31
_includes/plain.njk Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title or "plain" }} — {{ site.handle }}</title>
<style>
body {
font-family: 'Courier New', monospace;
font-size: 15px;
line-height: 1.6;
max-width: 680px;
margin: 40px auto;
padding: 0 20px;
background: #fff;
color: #111;
}
h1 { font-size: 1.4em; margin-bottom: 0.3em; }
h2 { font-size: 1.1em; margin: 1.5em 0 0.4em; border-bottom: 1px solid #ccc; padding-bottom: 3px; }
a { color: #333; }
a:hover { text-decoration: none; background: #111; color: #fff; }
ul { padding-left: 1.2em; }
li { margin: 0.25em 0; }
.back { font-size: 0.85em; margin-top: 2em; }
hr { border: none; border-top: 1px solid #ccc; margin: 2em 0; }
</style>
</head>
<body>
{{ content | safe }}
</body>
</html>

11
_includes/post-plain.njk Normal file
View file

@ -0,0 +1,11 @@
---
layout: plain.njk
---
<h1>{{ title }}</h1>
<p style="font-size:0.85em; color:#555;">
{{ date | readableDate }}{% if tags %} &middot; {{ tags | join(', ') }}{% endif %}
</p>
<hr>
{{ content | safe }}
<hr>
<p class="back"><a href="/boring/blog/">← back to blog</a></p>

22
_includes/post.njk Normal file
View file

@ -0,0 +1,22 @@
---
layout: base.njk
---
<div class="window page-window focused"
data-wid="page-window"
data-title="{{ title }}"
data-close="back">
<div class="window-content">
{% if date %}
<p style="color:var(--p-dim); font-size:13px; margin-bottom:16px;">
{{ date | readableDate }}
{% if tags %} · {{ tags | join(', ') }}{% endif %}
</p>
{% endif %}
{{ content | safe }}
<hr style="border:none; border-top:1px solid var(--p-dim); margin:24px 0 16px">
<p><a href="/blog/">← back to posts</a></p>
</div>
</div>

240
_site/index.html Normal file
View file

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>~/home — lordtet</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=VT323&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/phosphor.css">
</head>
<body>
<div class="scanlines" aria-hidden="true"></div>
<div class="crt-vignette" aria-hidden="true"></div>
<div class="desktop" id="desktop">
<div class="desktop-header" aria-label="System header">
<div class="header-clock" id="clock" aria-live="off"></div>
<a class="boring-link" href="/boring/" title="Plain text navigation">click if boring</a>
</div>
<div class="desktop-area">
<div class="desktop-icons" aria-label="Desktop icons">
<div class="desktop-icon" data-opens="win-about" role="button" tabindex="0" aria-label="Open about">
<div class="icon-art">[i]</div>
<div class="icon-label">about</div>
</div>
<div class="desktop-icon" data-opens="win-blog" role="button" tabindex="0" aria-label="Open blog">
<div class="icon-art">[/]</div>
<div class="icon-label">blog/</div>
</div>
<div class="desktop-icon" data-opens="win-projects" role="button" tabindex="0" aria-label="Open projects">
<div class="icon-art">[*]</div>
<div class="icon-label">projects</div>
</div>
<div class="desktop-icon" data-opens="win-contact" role="button" tabindex="0" aria-label="Open contact">
<div class="icon-art">[@]</div>
<div class="icon-label">contact</div>
</div>
</div>
<div class="window hidden"
id="win-about"
data-wid="win-about"
data-title="about.sh"
style="top:50px; left:120px; width:440px;">
<div class="window-content">
<h1>$ ./about.sh</h1>
<p>Hi, I'm <strong>lordtet</strong>.</p>
<p>I build things that are useful, interesting, or just plain fun. Fascinated by systems programming, the open web, and old hardware.</p>
<h2>Skills</h2>
<ul>
<li>Systems &amp; low-level programming</li>
<li>Web development (front + back)</li>
<li>Open source / UNIX tooling</li>
</ul>
<h2>Contact</h2>
<p>
<a href="https://github.com/lordtet" target="_blank" rel="noopener">github</a> ·
<a href="mailto:you@example.com">email</a>
</p>
<p><a href="/boring/about/">→ plaintext version</a></p>
</div>
</div>
<div class="window hidden"
id="win-blog"
data-wid="win-blog"
data-title="blog/"
style="top:60px; left:400px; width:500px; height:460px;">
<div class="window-content" id="blog-window-content">
<div id="blog-listing">
<div class="blog-tags">
<button class="blog-tag active" data-tag="*">all <span class="tag-count">1</span></button>
<button class="blog-tag" data-tag="tag1">tag1 <span class="tag-count">1</span></button>
<button class="blog-tag" data-tag="tag2">tag2 <span class="tag-count">1</span></button>
</div>
<ul class="post-list" id="blog-post-list">
<li class="post-list-item"
data-tags="tag1 tag2">
<a href="#"
class="blog-open-post"
data-post-idx="0">Post Title</a>
<span class="post-date">2026-01-01</span>
</li>
</ul>
<p style="margin-top:14px; border-top:1px solid var(--p-dim); padding-top:10px;">
<a href="/boring/blog/">→ plaintext version</a>
</p>
</div>
<div class="blog-post-panel hidden" id="blog-post-0">
<button class="blog-back">← back</button>
<h2 style="margin-top:10px;">Post Title</h2>
<p class="post-meta">
2026-01-01
· tag1, tag2
</p>
<hr class="post-rule">
<p>Post body goes here. Markdown is supported.</p>
<h2>Section Heading</h2>
<p>Paragraph text. Inline <code>code</code> looks like this.</p>
<pre><code class="language-lang">code block
</code></pre>
<ul>
<li>list item</li>
<li>list item</li>
</ul>
<p><img src="/assets/img/example.png" alt="alt text"></p>
</div>
</div>
</div>
<div class="window hidden"
id="win-projects"
data-wid="win-projects"
data-title="projects.sh"
style="top:140px; left:120px; width:560px; height:380px;">
<div class="window-content">
<h2>Projects</h2>
<div class="project-grid">
<div class="project-card">
<h3>project-alpha</h3>
<p>Sample project. Replace with your actual work.</p>
<div class="tags">
<span class="tag">rust</span>
<span class="tag">cli</span>
</div>
</div>
<div class="project-card">
<h3>project-beta</h3>
<p>Another interesting thing you built.</p>
<div class="tags">
<span class="tag">typescript</span>
<span class="tag">web</span>
</div>
</div>
<div class="project-card">
<h3>project-gamma</h3>
<p>Open source tooling for fun and profit.</p>
<div class="tags">
<span class="tag">python</span>
<span class="tag">oss</span>
</div>
</div>
</div>
<p style="margin-top:14px"><a href="/boring/projects/">→ plaintext version</a></p>
</div>
</div>
<div class="window hidden"
id="win-contact"
data-wid="win-contact"
data-title="contact"
style="top:120px; left:240px; width:480px;">
<div class="window-content">
<h2>Get in touch</h2>
<h3>Mail</h3>
<p><a href="mailto:you@example.com">you@example.com</a></p>
<h3>GitHub</h3>
<p><a href="https://github.com/lordtet" target="_blank" rel="noopener">https://github.com/lordtet</a></p>
<h3>PGP</h3>
<p style="color:var(--p-dim); font-size:13px; margin-bottom:6px;">
Fingerprint:
</p>
<pre>DEAD BEEF 1337 C0DE F00D CAFE BABE 0000 1234 5678 9ABC DEF0</pre>
<p style="color:var(--p-dim); font-size:13px;">
Key available on
<a href="https://keys.openpgp.org" target="_blank" rel="noopener">keys.openpgp.org</a>
or on request.
</p>
<p style="margin-top:14px"><a href="/boring/contact/">→ plaintext version</a></p>
</div>
</div>
<script src="/assets/js/blog.js"></script>
</div>
</div>
<script src="/assets/js/wm.js"></script>
<script src="/assets/js/images.js"></script>
<script src="/assets/js/clock.js"></script>
</body>
</html>

30
about.md Normal file
View file

@ -0,0 +1,30 @@
---
permalink: false
---
## $ ./about.sh
Hi, I'm **{{ site.author }}**.
{{ site.bio }}
## Skills
- Systems & low-level programming
- Web development (front + back)
- Open source / UNIX tooling
- Whatever interesting problem is in front of me
## Setup
```
OS: Gentoo Linux
Shell: zsh
Editor: neovim
WM: dwm
```
## Contact
- **GitHub:** [{{ site.github }}]({{ site.github }})
- **Email:** [{{ site.email }}](mailto:{{ site.email }})

586
assets/css/phosphor.css Normal file
View file

@ -0,0 +1,586 @@
/*
AT&T 3B1 / UNIX PC · Personal Site Stylesheet
Orange phosphor, reverse-video title bars, 1984
*/
/* ── Variables ──────────────────────────────────── */
:root {
/* Orange phosphor — warm amber-orange, like the 3B1 monitor */
--p: #e07820; /* primary phosphor orange */
--p-dim: #7a3e0a; /* dim: borders, secondary text */
--p-bright: #ffb05c; /* bright: highlights, headings */
--p-glow: rgba(224,120,32,0.38);
--p-faint: rgba(224,120,32,0.06);
/* Backgrounds — near-black with a breath of warmth */
--bg: #060300;
--win-bg: rgba(6, 3, 0, 0.98);
--bar-bg: rgba(4, 2, 0, 0.99);
/* Shadows */
--glow-text: 0 0 5px var(--p), 0 0 14px var(--p-glow);
--glow-box: 0 0 0 1px var(--p-dim), 0 0 18px rgba(224,120,32,0.10);
/* Typography — VT323 is the star here, body for readability */
--font-display: 'VT323', 'Courier New', monospace;
--font-body: 'Share Tech Mono', 'Courier New', monospace;
}
/* ── Reset ──────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: var(--bg);
color: var(--p);
font-family: var(--font-body);
font-size: 15px;
line-height: 1.55;
}
a { color: var(--p-bright); text-decoration: none; }
/* ── CRT Overlay Effects ────────────────────────── */
/* Scanlines the 3B1 had a fairly sharp display;
keep these subtle so they read as "screen" not "noise" */
.scanlines {
position: fixed; inset: 0; z-index: 9999; pointer-events: none;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 3px,
rgba(0, 0, 0, 0.07) 3px,
rgba(0, 0, 0, 0.07) 4px
);
}
/* Vignette — slightly asymmetric like a real CRT bezel */
.crt-vignette {
position: fixed; inset: 0; z-index: 9998; pointer-events: none;
background: radial-gradient(ellipse 90% 80% at 50% 50%,
transparent 50%,
rgba(0, 0, 0, 0.65) 100%
);
}
/* Very subtle phosphor afterglow / flicker */
@keyframes flicker {
0%, 94%, 100% { opacity: 1; }
95% { opacity: 0.975; }
97% { opacity: 0.99; }
}
body { animation: flicker 12s infinite; }
/* ── Desktop Shell ──────────────────────────────── */
.desktop {
position: relative;
width: 100vw; height: 100vh;
display: flex; flex-direction: column;
overflow: hidden;
}
/* Barely-visible dot matrix — evokes a phosphor screen surface */
.desktop::before {
content: '';
position: absolute; inset: 0; pointer-events: none;
background-image: radial-gradient(circle, rgba(224,120,32,0.055) 1px, transparent 1px);
background-size: 4px 4px;
}
/* ── Header — dense dot pattern with centred clock ── */
/*
* The 3B1 desktop had a characteristic halftone dot field
* spanning the full width of the screen, with the system
* clock displayed as a bright cutout in the centre.
*/
.desktop-header {
position: relative; z-index: 500;
height: 26px;
flex-shrink: 0;
border-bottom: 2px solid var(--p-dim);
/* Dense dot matrix — the signature 3B1 desktop pattern */
background-color: var(--bg);
background-image: radial-gradient(circle, var(--p-dim) 1px, transparent 1px);
background-size: 4px 4px;
display: flex;
align-items: center;
justify-content: center;
}
/* Clock — sits centred, punched out of the dot field */
.header-clock {
font-family: var(--font-display);
font-size: 17px;
letter-spacing: 3px;
color: var(--p);
text-shadow: 0 0 6px var(--p-glow); /* subtle warmth, not a blast */
/* Opaque background clears the dots behind the text */
background: var(--bg);
padding: 0 14px;
border-left: 1px solid var(--p-dim);
border-right: 1px solid var(--p-dim);
height: 100%;
display: flex; align-items: center;
}
/* "i'm boring" — accessibility escape hatch, tucked in the right corner */
.boring-link {
position: absolute;
right: 0; top: 0; bottom: 0;
padding: 0 10px;
font-family: var(--font-body);
font-size: 11px;
color: var(--p-dim);
display: flex; align-items: center;
border-left: 1px solid var(--p-dim);
background: var(--bg); /* clear the dots behind it */
transition: color 0.06s, background 0.06s;
white-space: nowrap;
}
.boring-link:hover {
color: var(--bg);
background: var(--p-dim);
}
/* ── Desktop Area ───────────────────────────────── */
.desktop-area {
flex: 1;
position: relative;
overflow: hidden;
}
/* ── Desktop Icons ──────────────────────────────── */
.desktop-icons {
position: absolute;
top: 14px; left: 14px;
display: flex; flex-direction: column;
gap: 6px;
z-index: 50;
}
.desktop-icon {
display: flex; flex-direction: column;
align-items: center; gap: 5px;
cursor: pointer;
padding: 10px 8px;
border: 1px solid transparent;
width: 88px;
text-align: center;
transition: all 0.06s;
user-select: none;
}
.desktop-icon:hover { border-color: var(--p-dim); background: var(--p-faint); }
.desktop-icon.active { background: var(--p); border-color: var(--p); }
.desktop-icon.active .icon-art,
.desktop-icon.active .icon-label { color: var(--bg); text-shadow: none; }
.icon-art {
font-family: var(--font-display);
font-size: 36px;
line-height: 1;
color: var(--p);
}
.icon-label {
font-size: 12px;
color: var(--p-dim);
letter-spacing: 0.5px;
}
/* ── Windows ────────────────────────────────────── */
/*
* 3B1 window anatomy:
*
* [] TITLE
* thick border throughout
* content
*
* [×][]
*
* Focused: amber fill on title + footer, black text, amber border.
* Unfocused: dark decorations, amber text, black border + thin outline.
*/
/* ── Unfocused (default) ──────────────────────── */
.window {
position: absolute;
min-width: 280px; min-height: 160px;
background: var(--win-bg);
border: 4px solid var(--p);
border-radius: 0;
display: flex; flex-direction: column;
}
.hidden { display: none; }
/* Title bar — dark bg, amber text */
.window-titlebar {
display: flex; align-items: stretch; gap: 0;
border-bottom: 3px solid var(--p-dim);
background: var(--win-bg);
cursor: move;
user-select: none;
flex-shrink: 0;
transition: background 0.08s, border-color 0.08s;
}
/* Base rule — strip UA button styling */
.win-btn {
appearance: none;
-webkit-appearance: none;
color: var(--p-dim);
}
.win-btn.maximize {
width: 28px;
border: none;
border-right: 3px solid var(--p-dim);
background: transparent;
font-family: var(--font-display);
font-size: 16px;
cursor: pointer; padding: 0;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
transition: background 0.06s, color 0.06s;
}
.win-btn.maximize:hover { background: var(--p-faint); color: var(--p); }
.window-title {
flex: 1;
font-family: var(--font-display);
font-size: 18px;
letter-spacing: 1px;
color: var(--p); /* amber text when unfocused */
padding: 2px 10px;
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
line-height: 1;
display: flex; align-items: center;
transition: color 0.08s;
}
/* Content pane */
.window-content {
flex: 1;
padding: 14px 18px;
overflow-y: auto; overflow-x: hidden;
}
/* Footer bar — dark bg, amber-tinted buttons */
.window-footer {
display: flex; align-items: stretch; gap: 0;
border-top: 3px solid var(--p-dim);
background: var(--win-bg);
flex-shrink: 0;
transition: background 0.08s, border-color 0.08s;
}
/* Close button — bottom-left */
.win-btn.close {
width: 28px; height: 20px;
border: none;
border-right: 3px solid var(--p-dim);
background: transparent;
color: var(--p-dim);
font-family: var(--font-display);
font-size: 20px;
line-height: 1;
cursor: pointer; padding: 0;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
transition: all 0.06s;
}
.win-btn.close:hover { background: var(--p-faint); color: var(--p); border-right-color: var(--p); }
.win-footer-spacer { flex: 1; }
/* Resize handle — bottom-right */
.win-resize {
width: 28px; height: 20px;
border: none;
border-left: 3px solid var(--p-dim);
background: transparent;
color: var(--p-dim);
font-size: 11px;
cursor: se-resize;
user-select: none;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
transition: all 0.06s;
}
.win-resize:hover { background: var(--p-faint); color: var(--p); border-left-color: var(--p); }
/* ── Focused — amber fill on decorations, black text ── */
.window.focused {
/* border is already var(--p) — decoration fill is what signals focus */
}
.window.focused .window-titlebar {
background: var(--p);
border-bottom-color: var(--p);
}
.window.focused .window-footer {
background: var(--p);
border-top-color: var(--p);
}
/* Children all have explicit color values so inheritance won't reach them */
.window.focused .window-title,
.window.focused .win-btn,
.window.focused .win-resize {
color: #000;
}
/* Internal dividers become dark on amber */
.window.focused .win-btn.maximize { border-right-color: rgba(0,0,0,0.25); }
.window.focused .win-btn.close { border-right-color: rgba(0,0,0,0.25); }
.window.focused .win-resize { border-left-color: rgba(0,0,0,0.25); }
/* Hover: darken slightly */
.window.focused .win-btn:hover,
.window.focused .win-resize:hover {
background: rgba(0,0,0,0.18);
}
/* ── Scrollbar ──────────────────────────────────── */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track {
background: transparent;
border-left: 2px solid var(--p-dim);
}
::-webkit-scrollbar-thumb {
background: var(--p-dim);
border-left: 2px solid var(--p-dim);
}
::-webkit-scrollbar-thumb:hover { background: var(--p); }
/* ── Content Typography ─────────────────────────── */
.window-content h1,
.window-content h2,
.window-content h3 {
font-family: var(--font-display);
color: var(--p-bright);
text-shadow: none;
letter-spacing: 1px;
margin: 0 0 10px;
}
.window-content h1 { font-size: 38px; }
.window-content h2 { font-size: 28px; border-bottom: 1px solid var(--p-dim); padding-bottom: 4px; margin-bottom: 14px; }
.window-content h3 { font-size: 22px; }
.window-content p { margin: 0 0 12px; }
.window-content ul, .window-content ol { margin: 0 0 12px 20px; }
.window-content li { margin-bottom: 4px; }
.window-content a {
color: var(--p-bright);
border-bottom: 1px solid var(--p-dim);
transition: all 0.06s;
}
.window-content a:hover {
color: var(--bg);
background: var(--p);
border-bottom-color: var(--p);
text-shadow: none;
}
.window-content code {
background: rgba(224,120,32,0.08);
border: 1px solid var(--p-dim);
padding: 1px 5px;
font-family: var(--font-body);
font-size: 0.93em;
}
.window-content pre {
background: rgba(0,0,0,0.6);
border: 2px solid var(--p-dim);
border-left: 4px solid var(--p-dim);
padding: 12px 16px;
overflow-x: auto;
margin: 0 0 14px;
}
.window-content pre code { background: none; border: none; padding: 0; }
/* ── Terminal Helpers ───────────────────────────── */
.prompt { padding-left: 0; }
.prompt::before { content: '$ '; color: var(--p-bright); }
.prompt-output { color: var(--p-dim); margin-bottom: 8px; padding-left: 14px; }
@keyframes blink { 0%,49%{opacity:1} 50%,100%{opacity:0} }
.cursor::after { content: '█'; animation: blink 1s step-end infinite; }
/* ASCII/pre art blocks */
.window-content pre.ascii {
border: none; border-left: none;
background: none;
color: var(--p);
text-shadow: 0 0 8px var(--p-glow); /* kept faint — ascii art earns a little glow */
padding: 0;
line-height: 1.15;
margin-bottom: 16px;
}
/* ── Blog window in-UI browser ─────────────────── */
/* Tag filter strip */
.blog-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-bottom: 14px;
}
.blog-tag {
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--p-dim);
background: transparent;
color: var(--p-dim);
font-family: var(--font-body);
font-size: 14px;
letter-spacing: 0.5px;
padding: 4px 12px;
cursor: pointer;
transition: all 0.06s;
}
.blog-tag:hover { border-color: var(--p); color: var(--p); }
.blog-tag.active { background: var(--p); border-color: var(--p); color: #000; }
.tag-count { opacity: 0.65; font-size: 10px; }
/* Back button inside post view */
.blog-back {
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--p-dim);
background: transparent;
color: var(--p);
font-family: var(--font-body);
font-size: 13px;
padding: 2px 12px;
cursor: pointer;
transition: all 0.06s;
display: inline-block;
margin-bottom: 4px;
}
.blog-back:hover { background: var(--p); color: #000; border-color: var(--p); }
/* Post metadata line */
.post-meta {
font-size: 12px;
color: var(--p-dim);
margin: 4px 0 0;
}
/* Divider between meta and body */
.post-rule {
border: none;
border-top: 1px solid var(--p-dim);
margin: 12px 0 16px;
}
/* ── Blog Post List ─────────────────────────────── */
.post-list { list-style: none; margin: 0; padding: 0; }
.post-list li {
display: flex; justify-content: space-between; align-items: baseline;
border-bottom: 1px solid var(--p-dim);
padding: 7px 0; gap: 16px;
}
.post-list a { border: none; color: var(--p); }
.post-list a:hover { background: none; color: var(--p-bright); text-shadow: var(--glow-text); }
.post-date { color: var(--p-dim); font-size: 12px; white-space: nowrap; flex-shrink: 0; }
/* ── Project Cards ──────────────────────────────── */
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
gap: 10px;
}
.project-card {
border: 2px solid var(--p-dim);
padding: 10px 12px;
transition: all 0.1s;
}
.project-card:hover { border-color: var(--p); background: var(--p-faint); }
.project-card h3 { font-size: 17px; margin-bottom: 6px; }
.project-card p { font-size: 13px; color: var(--p-dim); margin: 0 0 10px; }
.tags { display: flex; flex-wrap: wrap; gap: 4px; }
.tag { font-size: 11px; border: 1px solid var(--p-dim); padding: 1px 6px; color: var(--p-dim); }
/* ── Single-page window (blog posts, /about, etc) ── */
.page-window {
top: 30px;
left: 50%;
transform: translateX(-50%);
width: min(760px, 90vw);
max-height: calc(100vh - 70px);
}
/* ── Image Revealer ─────────────────────────────── */
.img-revealer {
border: 1px solid var(--p-dim);
margin: 12px 0;
}
.img-revealer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 8px;
background: var(--p-faint);
border-bottom: 1px solid var(--p-dim);
}
.img-revealer-label {
font-size: 12px;
color: var(--p-dim);
font-style: italic;
}
.img-reveal-btn {
appearance: none;
-webkit-appearance: none;
background: none;
border: 1px solid var(--p-dim);
color: var(--p);
font-family: inherit;
font-size: 11px;
padding: 1px 8px;
cursor: pointer;
letter-spacing: 0.05em;
}
.img-reveal-btn:hover {
background: var(--p);
color: #000;
}
/* Image hidden by default; shown when revealed */
.img-revealer img {
display: none;
max-width: 100%;
padding: 8px;
filter: sepia(1) hue-rotate(-10deg) saturate(3) brightness(0.85);
}
.img-revealer.revealed img {
display: block;
}

40
assets/js/blog.js Normal file
View file

@ -0,0 +1,40 @@
(function () {
var listing = document.getElementById('blog-listing');
var content = document.getElementById('blog-window-content');
var panels = document.querySelectorAll('.blog-post-panel');
var tagBtns = document.querySelectorAll('.blog-tag');
var items = document.querySelectorAll('.post-list-item');
// ── Tag filtering ──────────────────────────────
tagBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
tagBtns.forEach(function (b) { b.classList.remove('active'); });
btn.classList.add('active');
var tag = btn.dataset.tag;
items.forEach(function (li) {
var tags = li.dataset.tags ? li.dataset.tags.split(' ') : [];
li.style.display = (tag === '*' || tags.indexOf(tag) !== -1) ? '' : 'none';
});
});
});
// ── Open a post ────────────────────────────────
document.querySelectorAll('.blog-open-post').forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
listing.classList.add('hidden');
panels.forEach(function (p) { p.classList.add('hidden'); });
var panel = document.getElementById('blog-post-' + link.dataset.postIdx);
if (panel) { panel.classList.remove('hidden'); content.scrollTop = 0; }
});
});
// ── Back to listing ────────────────────────────
document.querySelectorAll('.blog-back').forEach(function (btn) {
btn.addEventListener('click', function () {
panels.forEach(function (p) { p.classList.add('hidden'); });
listing.classList.remove('hidden');
content.scrollTop = 0;
});
});
}());

16
assets/js/clock.js Normal file
View file

@ -0,0 +1,16 @@
(function () {
var clk = document.getElementById('clock');
var days = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
var months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
function tick() {
var d = new Date();
var date = days[d.getDay()] + ' ' + months[d.getMonth()] + ' '
+ String(d.getDate()).padStart(2, '0');
var time = String(d.getHours()).padStart(2,'0') + ':'
+ String(d.getMinutes()).padStart(2,'0') + ':'
+ String(d.getSeconds()).padStart(2,'0');
clk.textContent = date + ' ' + time;
}
tick();
setInterval(tick, 1000);
}());

45
assets/js/images.js Normal file
View file

@ -0,0 +1,45 @@
/*
Image revealer wraps every <img> inside a
.window-content with a click-to-reveal widget.
*/
(function () {
"use strict";
function wrapImage(img) {
var alt = (img.getAttribute('alt') || '').trim() || 'image';
// Container
var revealer = document.createElement('div');
revealer.className = 'img-revealer';
// Header row: label + button
var header = document.createElement('div');
header.className = 'img-revealer-header';
var label = document.createElement('span');
label.className = 'img-revealer-label';
label.textContent = alt;
var btn = document.createElement('button');
btn.className = 'img-reveal-btn';
btn.textContent = 'reveal';
header.appendChild(label);
header.appendChild(btn);
revealer.appendChild(header);
// Move the img into the revealer, hidden by default
img.parentNode.insertBefore(revealer, img);
revealer.appendChild(img);
btn.addEventListener('click', function () {
var revealed = revealer.classList.toggle('revealed');
btn.textContent = revealed ? 'hide' : 'reveal';
});
}
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.window-content img').forEach(wrapImage);
});
}());

234
assets/js/wm.js Normal file
View file

@ -0,0 +1,234 @@
/*
Phosphor Terminal Window Manager (wm.js)
*/
const WM = (() => {
"use strict";
let topZ = 100;
const state = new Map(); // wid → { el, hidden, maximized, savedStyle }
let drag = null;
let resize = null;
/* ── Init ──────────────────────────────── */
function init() {
document.querySelectorAll(".window").forEach(register);
setupIcons();
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
// Focus topmost visible window on load
const visible = [...state.values()].filter(s => !s.hidden);
if (visible.length) focusEl(visible[visible.length - 1].el);
}
/* ── Build and inject window chrome ────── */
function buildChrome(win) {
const title = win.dataset.title || win.dataset.wid || "";
const bar = document.createElement("div");
bar.className = "window-titlebar";
const maxBtn = document.createElement("button");
maxBtn.className = "win-btn maximize";
maxBtn.title = "Full screen";
maxBtn.textContent = "⤢";
const titleSpan = document.createElement("span");
titleSpan.className = "window-title";
titleSpan.textContent = title;
bar.appendChild(maxBtn);
bar.appendChild(titleSpan);
win.insertBefore(bar, win.firstChild);
const footer = document.createElement("div");
footer.className = "window-footer";
const closeBtn = document.createElement("button");
closeBtn.className = "win-btn close";
closeBtn.title = "Close";
closeBtn.textContent = "×";
const spacer = document.createElement("div");
spacer.className = "win-footer-spacer";
const resizeHandle = document.createElement("div");
resizeHandle.className = "win-resize";
resizeHandle.title = "Drag to resize";
resizeHandle.textContent = "◢";
footer.appendChild(closeBtn);
footer.appendChild(spacer);
footer.appendChild(resizeHandle);
win.appendChild(footer);
}
/* ── Register a window element ─────────── */
function register(win) {
const id = win.dataset.wid;
if (!id || state.has(id)) return;
buildChrome(win);
const hidden = win.classList.contains("hidden");
state.set(id, { el: win, hidden, maximized: false, savedStyle: null });
// Draggable title bar
const bar = win.querySelector(".window-titlebar");
bar.addEventListener("mousedown", e => {
if (e.button !== 0 || e.target.closest(".win-btn")) return;
startDrag(e, win);
});
// Focus on any click inside window
win.addEventListener("mousedown", () => {
if (!state.get(id)?.hidden) focusEl(win);
});
// Window buttons
const q = s => win.querySelector(s);
const closeAction = win.dataset.close;
q(".win-btn.close") ?.addEventListener("click", () => closeAction === "back" ? history.back() : hide(id));
q(".win-btn.maximize")?.addEventListener("click", () => toggleMax(id));
// Resize handle
q(".win-resize")?.addEventListener("mousedown", e => {
if (e.button !== 0) return;
e.preventDefault(); e.stopPropagation();
const r = win.getBoundingClientRect();
resize = { el: win, x0: e.clientX, y0: e.clientY, w0: r.width, h0: r.height };
});
}
/* ── Focus ─────────────────────────────── */
function focusEl(win) {
document.querySelectorAll(".window.focused")
.forEach(w => w.classList.remove("focused"));
win.classList.add("focused");
win.style.zIndex = ++topZ;
}
function focus(id) {
const s = state.get(id);
if (s && !s.hidden) focusEl(s.el);
}
/* ── Show / Hide ───────────────────────── */
function show(id) {
const s = state.get(id);
if (!s) return;
s.el.classList.remove("hidden");
s.hidden = false;
focusEl(s.el);
}
function hide(id) {
const s = state.get(id);
if (!s) return;
s.el.classList.add("hidden");
s.hidden = true;
}
function toggle(id) {
const s = state.get(id);
if (!s) return;
if (s.hidden) {
show(id);
} else if (s.el.classList.contains("focused")) {
hide(id);
} else {
focusEl(s.el);
}
}
/* ── Maximize / Restore ────────────────── */
function toggleMax(id) {
const s = state.get(id);
if (!s) return;
const el = s.el;
const area = document.querySelector(".desktop-area");
if (!s.maximized) {
s.savedStyle = {
top: el.style.top, left: el.style.left,
width: el.style.width, height: el.style.height,
transform: el.style.transform,
};
el.style.transform = "none";
el.style.top = "0";
el.style.left = "0";
el.style.width = area.clientWidth + "px";
el.style.height = area.clientHeight + "px";
s.maximized = true;
} else {
const sv = s.savedStyle;
el.style.top = sv.top;
el.style.left = sv.left;
el.style.width = sv.width;
el.style.height = sv.height;
el.style.transform = sv.transform;
s.maximized = false;
}
focusEl(el);
}
/* ── Drag ──────────────────────────────── */
function startDrag(e, win) {
e.preventDefault();
focusEl(win);
// Freeze any CSS transform into explicit top/left so dragging is consistent
const wr = win.getBoundingClientRect();
const pr = win.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 };
const left0 = wr.left - pr.left;
const top0 = wr.top - pr.top;
win.style.transform = "none";
win.style.left = left0 + "px";
win.style.top = top0 + "px";
drag = { el: win, x0: e.clientX, y0: e.clientY, left0, top0 };
document.body.style.cursor = "move";
}
/* ── Mouse events ──────────────────────── */
function onMove(e) {
if (drag) {
const dx = e.clientX - drag.x0;
const dy = e.clientY - drag.y0;
drag.el.style.left = (drag.left0 + dx) + "px";
drag.el.style.top = (drag.top0 + dy) + "px";
}
if (resize) {
const dx = e.clientX - resize.x0;
const dy = e.clientY - resize.y0;
resize.el.style.width = Math.max(280, resize.w0 + dx) + "px";
resize.el.style.height = Math.max(140, resize.h0 + dy) + "px";
}
}
function onUp() {
drag = null;
resize = null;
document.body.style.cursor = "";
}
/* ── Desktop icons ─────────────────────── */
function setupIcons() {
document.querySelectorAll(".desktop-icon[data-opens]").forEach(icon => {
icon.addEventListener("click", () => {
document.querySelectorAll(".desktop-icon.active")
.forEach(i => i.classList.remove("active"));
icon.classList.add("active");
show(icon.dataset.opens);
});
});
}
/* ── Boot: hide icons for hidden windows ─ */
document.addEventListener("DOMContentLoaded", init);
return { show, hide, toggle, focus };
})();

18
blog/index.njk Normal file
View file

@ -0,0 +1,18 @@
---
permalink: false
---
## $ ls -lt blog/
{% if collections.posts.length %}
<ul class="post-list">
{% for post in collections.posts %}
<li>
<a href="{{ post.url }}">{{ post.data.title }}</a>
<span class="post-date">{{ post.date | readableDate }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p style="color:var(--p-dim)">No posts yet. Run: <code>touch blog/posts/first-post.md</code></p>
{% endif %}

23
blog/posts/_template.md Normal file
View file

@ -0,0 +1,23 @@
---
permalink: false
title: "Post Title"
date: 2026-01-01
tags:
- tag1
- tag2
---
Post body goes here. Markdown is supported.
## Section Heading
Paragraph text. Inline `code` looks like this.
```lang
code block
```
- list item
- list item
![alt text](/assets/img/example.png)

View file

@ -0,0 +1,3 @@
{
"layout": "post-plain.njk"
}

30
boring.md Normal file
View file

@ -0,0 +1,30 @@
---
layout: plain.njk
title: "plain index"
permalink: /boring/
---
# {{ site.author }}
{{ site.tagline }}
---
## Navigation
- [About](/boring/about/)
- [Projects](/boring/projects/)
- [Blog](/boring/blog/)
## Recent posts
{% for post in collections.posts %}
- [{{ post.data.title }}]({{ post.url }}) — {{ post.date | readableDate }}
{% endfor %}
{% if not collections.posts.length %}
- *(no posts yet)*
{% endif %}
---
[← back to the interesting version](/)

35
boring/about.md Normal file
View file

@ -0,0 +1,35 @@
---
layout: plain.njk
title: "about"
permalink: /boring/about/
---
# {{ site.author }}
{{ site.bio }}
## Skills
- Systems & low-level programming
- Web development (front + back)
- Open source / UNIX tooling
- Whatever interesting problem is in front of me
## Setup
```
OS: Gentoo Linux
Shell: zsh
Editor: neovim
WM: dwm
```
## Contact
- **GitHub:** [{{ site.github }}]({{ site.github }})
- **Email:** [{{ site.email }}](mailto:{{ site.email }})
- **PGP:** `{{ site.pgp_fingerprint }}`
---
[← back](/boring/)

19
boring/blog.md Normal file
View file

@ -0,0 +1,19 @@
---
layout: plain.njk
title: "blog"
permalink: /boring/blog/
---
# Blog
{% if collections.posts.length %}
{% for post in collections.posts %}
- [{{ post.data.title }}]({{ post.url }}) — {{ post.date | readableDate }}{% if post.data.tags %} `{{ post.data.tags | join(', ') }}`{% endif %}
{% endfor %}
{% else %}
*(no posts yet)*
{% endif %}
---
[← back](/boring/)

17
boring/blog.njk Normal file
View file

@ -0,0 +1,17 @@
---
permalink: false
---
# Blog
{% if collections.posts.length %}
{% for post in collections.posts %}
- [{{ post.data.title }}]({{ post.url }}) — {{ post.date | readableDate }}{% if post.data.tags %} `{{ post.data.tags | join(', ') }}`{% endif %}
{% endfor %}
{% else %}
*(no posts yet)*
{% endif %}
---
[← back](/boring/)

25
boring/contact.md Normal file
View file

@ -0,0 +1,25 @@
---
layout: plain.njk
title: "contact"
permalink: /boring/contact/
---
# Contact
## Mail
[{{ site.email }}](mailto:{{ site.email }})
## GitHub
[{{ site.github }}]({{ site.github }})
## PGP
Fingerprint: `{{ site.pgp_fingerprint }}`
Key available on [keys.openpgp.org](https://keys.openpgp.org) or on request.
---
[← back](/boring/)

40
boring/projects.md Normal file
View file

@ -0,0 +1,40 @@
---
layout: plain.njk
title: "projects"
permalink: /boring/projects/
---
# Projects
A selection of things I've built or am currently building.
---
### project-alpha
A CLI tool for doing X. Written in Rust.
**Tags:** `rust` `cli`
**Links:** [github](#) · [docs](#)
---
### project-beta
A web app for Y. TypeScript front-to-back.
**Tags:** `typescript` `web`
**Links:** [github](#) · [live](#)
---
### project-gamma
Open source tooling. Because the world needs more of it.
**Tags:** `python` `oss`
**Links:** [github](#)
---
[← back](/boring/)

3
index.md Normal file
View file

@ -0,0 +1,3 @@
---
permalink: false
---

197
index.njk Normal file
View file

@ -0,0 +1,197 @@
---
layout: base.njk
title: ~/home
permalink: /
---
{# ── Desktop icons ───────────────────────────────── #}
<div class="desktop-icons" aria-label="Desktop icons">
<div class="desktop-icon" data-opens="win-about" role="button" tabindex="0" aria-label="Open about">
<div class="icon-art">[i]</div>
<div class="icon-label">about</div>
</div>
<div class="desktop-icon" data-opens="win-blog" role="button" tabindex="0" aria-label="Open blog">
<div class="icon-art">[/]</div>
<div class="icon-label">blog/</div>
</div>
<div class="desktop-icon" data-opens="win-projects" role="button" tabindex="0" aria-label="Open projects">
<div class="icon-art">[*]</div>
<div class="icon-label">projects</div>
</div>
<div class="desktop-icon" data-opens="win-contact" role="button" tabindex="0" aria-label="Open contact">
<div class="icon-art">[@]</div>
<div class="icon-label">contact</div>
</div>
</div>
{# ════════════════════════════════════════════════ #}
{# ABOUT WINDOW #}
{# ════════════════════════════════════════════════ #}
<div class="window hidden"
id="win-about"
data-wid="win-about"
data-title="about.sh"
style="top:50px; left:120px; width:440px;">
<div class="window-content">
<h1>$ ./about.sh</h1>
<p>Hi, I'm <strong>{{ site.author }}</strong>.</p>
<p>{{ site.bio }}</p>
<h2>Skills</h2>
<ul>
<li>Systems &amp; low-level programming</li>
<li>Web development (front + back)</li>
<li>Open source / UNIX tooling</li>
</ul>
<h2>Contact</h2>
<p>
<a href="{{ site.github }}" target="_blank" rel="noopener">github</a> ·
<a href="mailto:{{ site.email }}">email</a>
</p>
<p><a href="/boring/about/">→ plaintext version</a></p>
</div>
</div>
{# ════════════════════════════════════════════════ #}
{# BLOG WINDOW #}
{# ════════════════════════════════════════════════ #}
<div class="window hidden"
id="win-blog"
data-wid="win-blog"
data-title="blog/"
style="top:60px; left:400px; width:500px; height:460px;">
<div class="window-content" id="blog-window-content">
{# ── Listing view ─────────────────────────── #}
<div id="blog-listing">
{# Tag filters #}
{% set tagCounts = collections.posts | tagCounts %}
{% if tagCounts.length %}
<div class="blog-tags">
<button class="blog-tag active" data-tag="*">all <span class="tag-count">{{ collections.posts.length }}</span></button>
{% for tc in tagCounts %}
<button class="blog-tag" data-tag="{{ tc.tag }}">{{ tc.tag }} <span class="tag-count">{{ tc.count }}</span></button>
{% endfor %}
</div>
{% endif %}
{# Post list #}
{% if collections.posts.length %}
<ul class="post-list" id="blog-post-list">
{% for post in collections.posts %}
<li class="post-list-item"
data-tags="{{ post.data.tags | join(' ') if post.data.tags else '' }}">
<a href="#"
class="blog-open-post"
data-post-idx="{{ loop.index0 }}">{{ post.data.title }}</a>
<span class="post-date">{{ post.date | readableDate }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p style="color:var(--p-dim);">No posts yet — check back soon.</p>
{% endif %}
<p style="margin-top:14px; border-top:1px solid var(--p-dim); padding-top:10px;">
<a href="/boring/blog/">→ plaintext version</a>
</p>
</div>
{# ── Per-post views (built at compile time, hidden by default) ── #}
{% for post in collections.posts %}
<div class="blog-post-panel hidden" id="blog-post-{{ loop.index0 }}">
<button class="blog-back">← back</button>
<h2 style="margin-top:10px;">{{ post.data.title }}</h2>
<p class="post-meta">
{{ post.date | readableDate }}
{% if post.data.tags %}· {{ post.data.tags | join(', ') }}{% endif %}
</p>
<hr class="post-rule">
{{ post.templateContent | safe }}
</div>
{% endfor %}
</div>
</div>
{# ════════════════════════════════════════════════ #}
{# PROJECTS WINDOW #}
{# ════════════════════════════════════════════════ #}
<div class="window hidden"
id="win-projects"
data-wid="win-projects"
data-title="projects.sh"
style="top:140px; left:120px; width:560px; height:380px;">
<div class="window-content">
<h2>Projects</h2>
<div class="project-grid">
<div class="project-card">
<h3>project-alpha</h3>
<p>Sample project. Replace with your actual work.</p>
<div class="tags">
<span class="tag">rust</span>
<span class="tag">cli</span>
</div>
</div>
<div class="project-card">
<h3>project-beta</h3>
<p>Another interesting thing you built.</p>
<div class="tags">
<span class="tag">typescript</span>
<span class="tag">web</span>
</div>
</div>
<div class="project-card">
<h3>project-gamma</h3>
<p>Open source tooling for fun and profit.</p>
<div class="tags">
<span class="tag">python</span>
<span class="tag">oss</span>
</div>
</div>
</div>
<p style="margin-top:14px"><a href="/boring/projects/">→ plaintext version</a></p>
</div>
</div>
{# ════════════════════════════════════════════════ #}
{# CONTACT WINDOW #}
{# ════════════════════════════════════════════════ #}
<div class="window hidden"
id="win-contact"
data-wid="win-contact"
data-title="contact"
style="top:120px; left:240px; width:480px;">
<div class="window-content">
<h2>Get in touch</h2>
<h3>Mail</h3>
<p><a href="mailto:{{ site.email }}">{{ site.email }}</a></p>
<h3>GitHub</h3>
<p><a href="{{ site.github }}" target="_blank" rel="noopener">{{ site.github }}</a></p>
<h3>PGP</h3>
<p style="color:var(--p-dim); font-size:13px; margin-bottom:6px;">
Fingerprint:
</p>
<pre>{{ site.pgp_fingerprint }}</pre>
<p style="color:var(--p-dim); font-size:13px;">
Key available on
<a href="https://keys.openpgp.org" target="_blank" rel="noopener">keys.openpgp.org</a>
or on request.
</p>
<p style="margin-top:14px"><a href="/boring/contact/">→ plaintext version</a></p>
</div>
</div>
<script src="/assets/js/blog.js"></script>

2620
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

13
package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "blag",
"version": "1.0.0",
"private": true,
"description": "Personal site — phosphor terminal aesthetic",
"scripts": {
"start": "eleventy --serve",
"build": "eleventy"
},
"devDependencies": {
"@11ty/eleventy": "^2.0.1"
}
}

34
projects.md Normal file
View file

@ -0,0 +1,34 @@
---
permalink: false
---
## $ ./projects.sh
A selection of things I've built or am currently building.
---
### project-alpha
A CLI tool for doing X. Written in Rust.
**Tags:** `rust` `cli`
**Links:** [github](#) · [docs](#)
---
### project-beta
A web app for Y. TypeScript front-to-back.
**Tags:** `typescript` `web`
**Links:** [github](#) · [live](#)
---
### project-gamma
Open source tooling. Because the world needs more of it.
**Tags:** `python` `oss`
**Links:** [github](#)