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:
parent
9113eb8b0a
commit
955a330ad9
26 changed files with 4379 additions and 0 deletions
9
_data/site.json
Normal file
9
_data/site.json
Normal 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
38
_includes/base.njk
Normal 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
31
_includes/plain.njk
Normal 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
11
_includes/post-plain.njk
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
layout: plain.njk
|
||||||
|
---
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p style="font-size:0.85em; color:#555;">
|
||||||
|
{{ date | readableDate }}{% if tags %} · {{ 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
22
_includes/post.njk
Normal 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
240
_site/index.html
Normal 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 & 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
30
about.md
Normal 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
586
assets/css/phosphor.css
Normal 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
40
assets/js/blog.js
Normal 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
16
assets/js/clock.js
Normal 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
45
assets/js/images.js
Normal 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
234
assets/js/wm.js
Normal 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
18
blog/index.njk
Normal 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
23
blog/posts/_template.md
Normal 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
|
||||||
|
|
||||||
|

|
||||||
3
blog/posts/posts.11tydata.json
Normal file
3
blog/posts/posts.11tydata.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"layout": "post-plain.njk"
|
||||||
|
}
|
||||||
30
boring.md
Normal file
30
boring.md
Normal 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
35
boring/about.md
Normal 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
19
boring/blog.md
Normal 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
17
boring/blog.njk
Normal 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
25
boring/contact.md
Normal 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
40
boring/projects.md
Normal 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
3
index.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
permalink: false
|
||||||
|
---
|
||||||
197
index.njk
Normal file
197
index.njk
Normal 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 & 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
2620
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
13
package.json
Normal file
13
package.json
Normal 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
34
projects.md
Normal 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](#)
|
||||||
Loading…
Reference in a new issue