first commit

This commit is contained in:
bs-sensei
2026-03-24 20:30:43 -04:00
commit de259dba5b
27 changed files with 3242 additions and 0 deletions

384
README.md Normal file
View File

@@ -0,0 +1,384 @@
# Codex Obscura
> *A dark academia pixel Hugo theme with a fall color palette.*
>
> Candlelit manuscripts meet cozy stationery. Books, tea, fountain pens, and the quiet rustle of digital marginalia.
---
```
██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗
██╔════╝██╔═══██╗██╔══██╗██╔════╝╚██╗██╔╝
██║ ██║ ██║██║ ██║█████╗ ╚███╔╝
██║ ██║ ██║██║ ██║██╔══╝ ██╔██╗
╚██████╗╚██████╔╝██████╔╝███████╗██╔╝ ██╗
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝
```
---
## Features
- **Fall color palette** — deep ambers, burnt orange, rust, forest green, umber, parchment cream
- **Pixel art borders** — CSS-only pixel-perfect box shadow borders on all cards and widgets
- **Blinking `█` cursor** in the site title (optional, easily disabled)
- **Dark academia typography** — VT323 (pixel/terminal) for headings + Josefin Sans for body text
- **Dust mote ambient animation** — soft floating particles that drift across the page like dust in a library
- **Sidebar** — sys-info widget with live uptime counter, recent posts, tag cloud, and optional marginalia quote
- **Year-grouped archive** — posts grouped by year in terminal `ls -la` style
- **Tag filtering** — clickable tag chips at the top of the archive page with active highlighting
- **Post subtitles** — optional per-post tagline shown on cards and single pages
- **Responsive (Mostly)** — two-column desktop, stacked mobile
- **RSS feed** — full RSS out of the box
- **SEO-ready** — Open Graph, Twitter Card, canonical URLs
- **Syntax highlighting** — via highlight.js with Common Lisp support, add more lines to layout/partials/head.html for more
- **LaTeX rendering** — via KaTeX with Hugo passthrough for clean math expressions
- **Custom code blocks** — shortcode with language label and optional title
- **Comments** — Comentario integration (self-hosted, privacy-focused)
---
## Installation
### As a Git Submodule (recommended)
First run `hugo new site <sitename>`
```bash
cd <sitename>
git submodule add https://git.johnabs.xyz/bs-sensei/codex-obscura-hugo-theme
```
### Manual
Download and place the folder at `themes/codex-obscura/` inside your Hugo site, which you should have made using `hugo new site <sitename>`
---
## Configuration
After creating your site at <sitename> from above and adding the template, you should first go to the `content` folder and create an about.md, resume.md, and a donate.md in your content folder or these will redirect to "Not Found". Your posts link will also redirect, unless you make a folder within content called `posts` and make `post1.md` inside it and add some text. Examples of each of these are provided below for your convenience.
A full `hugo.toml` example:
```toml
baseURL = "https://yourdomain.com/"
languageCode = "en-us"
title = "Your Site Title"
theme = "codex-obscura"
paginate = 8
summaryLength = 20
[markup.highlight]
codeFences = false # disable Chroma — we use highlight.js instead
[markup.goldmark.renderer]
unsafe = true # you can probably set this to false, try both. False works for me :)
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
block = [["$$", "$$"]]
inline = [["$", "$"]]
[params]
description = "Your site description shown in the hero card."
author = "Your Name"
tagline = "a brief italicized tagline"
sidebarQuote = "A quote you love."
sidebarQuoteAuthor = "The Author"
resumePDF = "/files/resume.pdf"
comentarioURL = "https://comments.yourdomain.com"
[params.social]
github = "yourusername"
mastodon = "https://mastodon.social/@you"
email = "you@example.com"
[[menu.main]]
name = "home"
url = "/"
weight = 1
[[menu.main]]
name = "posts"
url = "/posts/"
weight = 2
[[menu.main]]
name = "tags"
url = "/tags/"
weight = 3
[[menu.main]]
name = "about"
url = "/about/"
weight = 4
[[menu.main]]
name = "resume"
url = "/resume/"
weight = 5
[[menu.main]]
name = "donate"
url = "/donate/"
weight = 6
[taxonomies]
tag = "tags"
category = "categories"
```
---
## Creating Content
```bash
# New blog post
hugo new posts/my-entry.md
# New about page
hugo new about.md
```
### Post front matter
```yaml
---
title: "My Entry"
date: 2024-10-31
draft: false
description: "A brief description shown in meta tags."
summary: "Optional hand-written summary overriding auto-generation."
subtitle: "a quiet meditation on autumn leaves"
tags: ["autumn", "philosophy", "notes"]
author: "Archivist"
featured: true # pins to homepage
nocomments: true # disables comments on this post
---
```
The `<!--more-->` divider can be placed anywhere in the body to manually control where the summary cuts off:
```markdown
This sentence will appear as the summary.
<!--more-->
The rest of the post continues here.
```
---
## Special Pages
### About
Create `content/about.md` with any content. Hugo uses `_default/single.html`.
### Resume
Create `content/resume.md`:
```yaml
---
title: "Resume"
type: "resume"
layout: "single"
resumeName: "Your Name"
resumeTitle: "Your subtitle"
email: "you@example.com"
location: "somewhere cozy"
website: "https://yoursite.com"
github: "yourusername"
resumePDF: "/files/resume.pdf"
_build:
list: never
---
```
Use `##` for sections, `###` for job/entry titles, `####` for subtitle/date lines. Place your PDF at `static/files/resume.pdf`.
### Donate
Create `content/donate.md`:
```yaml
---
title: "Support"
type: "donate"
layout: "single"
_build:
list: never
kofi: "yourusername"
liberapay: "yourusername"
github_sponsors: "yourusername"
bitcoin: "bc1qyouraddress"
ethereum: "0xyouraddress"
monero: "4youraddress"
litecoin: "ltc1youraddress"
---
```
Crypto wallet cards include QR codes generated client-side in the site's amber color palette. Remove any platforms you don't use and they won't appear.
---
## Syntax Highlighting
The theme uses **highlight.js** instead of Hugo's built-in Chroma for better Common Lisp support as it's my preferred language.
This is already added to `layouts/partials/head.html`, and more can be added as you go as shown below:
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/base16/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.common.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/lisp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/r.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/julia.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
<script>
hljs.highlightAll();
</script>
```
### Custom code block shortcode
Create `layouts/shortcodes/code.html` in your site root (this one you need to do!! I can't do it for you in advance):
```html
<div class="code-block">
{{- with .Get "lang" }}
<div class="code-block__header">
<span class="code-block__lang">{{ . }}</span>
{{- with $.Get "title" }}
<span class="code-block__title">{{ . }}</span>
{{- end }}
</div>
{{- end }}
<div class="code-block__body">
{{ highlight (trim .Inner "\n") (.Get "lang" | default "text") "" }}
</div>
</div>
```
Use it in posts:
```
{{< code lang="python" title="fibonacci.py" >}}
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
{{< /code >}}
```
---
## LaTeX
I added KaTeX to `layouts/partials/head.html` for math typesetting:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
]
});">
</script>
```
Then write inline math with `$x^2$` and display math with `$$\int_0^\infty e^{-x}dx$$`.
---
## Comments (Comentario)
Install and run a [Comentario](https://gitlab.com/comentario/comentario) instance, then add to `hugo.toml`:
```toml
[params]
comentarioURL = "https://comments.yourdomain.com"
```
Comments appear automatically on all posts. Disable per-post with `nocomments: true` in front matter.
---
## Color Palette
| Variable | Value | Use |
|--------------------|-----------|------------------------|
| `--amber` | `#d4a228` | Primary accent |
| `--orange` | `#c4601c` | Secondary accent |
| `--rust` | `#8a2e08` | Deep accent |
| `--forest-light` | `#4e7830` | Terminal prompt green |
| `--text-parchment` | `#e8d5a3` | Primary text |
| `--bg-primary` | `#130e07` | Main background |
| `--bg-secondary` | `#1c1509` | Card backgrounds |
Override any variable by adding a `static/css/custom.css` and referencing it in `hugo.toml`:
```toml
[params]
customCSS = ["css/custom.css"]
```
---
## Typography
| Role | Font | Notes |
|----------|--------------|-----------------------------|
| Terminal | VT323 | Headings, nav, UI elements |
| Body | Josefin Sans | Paragraph text, sans-serif |
| Code | Courier Prime| Code blocks, monospace |
All loaded from Google Fonts.
---
## File Structure
```
codex-obscura/
├── archetypes/
│ └── default.md
├── exampleSite/
│ ├── hugo.toml
│ └── content/
│ ├── resume.md
│ └── donate.md
├── layouts/
│ ├── index.html # Homepage
│ ├── _default/
│ │ ├── baseof.html
│ │ ├── single.html # Single post
│ │ └── list.html # Archive / tag pages
│ ├── resume/
│ │ └── single.html # Resume page
│ ├── donate/
│ │ └── single.html # Donate page with QR codes
│ ├── tags/
│ │ └── terms.html # Tag index
│ └── partials/
│ ├── head.html
│ ├── header.html
│ ├── footer.html
│ ├── sidebar.html
│ └── comments.html # Comentario integration
├── static/
│ ├── css/main.css
│ ├── js/main.js # Dust mote animation
│ └── favicon.svg
├── theme.toml
└── README.md
```
---
## License
MIT — Please attribute my work to me!
---
*"The library is infinite and the lights are always low."*

12
archetypes/default.md Normal file
View File

@@ -0,0 +1,12 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
description: ""
tags: []
author: ""
featured: false
# lastmod: {{ .Date }}
---
An entry begins here.

54
exampleSite/hugo.toml Normal file
View File

@@ -0,0 +1,54 @@
baseURL = "https://example.com/"
languageCode = "en-us"
title = "Codex Obscura"
theme = "codex-obscura"
paginate = 8
[params]
description = "A wanderer in the archive. Keeper of fragments, collector of marginalia."
author = "Archivist"
tagline = "remnants & reflections"
# Sidebar quote
sidebarQuote = "We are all just walking each other home."
sidebarQuoteAuthor = "Ram Dass"
# Social links
[params.social]
github = ""
mastodon = ""
email = ""
# Optional extra CSS files
# customCSS = ["css/custom.css"]
[menu]
[[menu.main]]
name = "home"
url = "/"
weight = 1
[[menu.main]]
name = "archive"
url = "/posts/"
weight = 2
[[menu.main]]
name = "tags"
url = "/tags/"
weight = 3
[[menu.main]]
name = "about"
url = "/about/"
weight = 4
[taxonomies]
tag = "tags"
category = "categories"
[markup]
[markup.highlight]
style = "monokai"
lineNos = false
noClasses = true
guessSyntax = true
[markup.goldmark.renderer]
unsafe = true # allow raw HTML in markdown

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode | default "en" }}" class="no-js">
<head>
{{ partial "head.html" . }}
</head>
<body>
{{- partial "header.html" . -}}
<div class="site-wrapper">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
<script src="{{ "js/main.js" | relURL }}"></script>
</body>
</html>

139
layouts/_default/list.html Normal file
View File

@@ -0,0 +1,139 @@
{{ define "main" }}
<div class="content-grid">
<main id="main-content">
<!-- Section Header -->
<div style="margin-bottom:1.5rem; padding-bottom:1rem; border-bottom:2px solid var(--border-dim);">
<h1 style="font-family:var(--font-terminal); font-size:1.8rem; color:var(--amber-light); letter-spacing:0.06em; text-shadow: 0 0 10px var(--glow-amber);">
{{ if .IsHome }}ARCHIVE{{ else }}{{ .Title | upper }}{{ end }}
</h1>
<p class="taxonomy-header">
<span style="color:var(--forest-light);">~/{{ .Section | default "posts" }} $</span>
ls -la &bull;
<span style="color:var(--amber);">{{ len .Pages }} entries</span>
</p>
</div>
{{- $tags := .Site.Taxonomies.tags }}
{{- $currentTag := .Data.Term }}
{{- with $tags }}
<div style="display:flex; flex-wrap:wrap; gap:0.4rem; margin-bottom:1.5rem; align-items:center;">
<span style="font-family:var(--font-terminal); font-size:0.8rem; color:var(--forest-light); letter-spacing:0.08em; margin-right:0.25rem;">FILTER:</span>
<a href="{{ "posts/" | relURL }}"
class="tag"
style="{{ if not $currentTag }}color:var(--amber); border-color:var(--amber-dark);{{ end }}">
all
</a>
{{- range $name, $pages := $tags }}
<a href="{{ "tags/" | relURL }}{{ $name | urlize }}/"
class="tag"
style="{{ if eq $name $currentTag }}color:var(--amber); border-color:var(--amber-dark);{{ end }}">
{{ $name }}
<span style="color:var(--text-dim); font-size:0.75em;">({{ len $pages }})</span>
</a>
{{- end }}
</div>
{{- end }}
{{- if .Pages }}
{{/* Group posts by year */}}
{{- range (.Pages.GroupByDate "2006") }}
<div style="margin-bottom:2.5rem;">
<!-- Year marker -->
<div style="
font-family: var(--font-terminal);
font-size: 0.8rem;
color: var(--text-dim);
letter-spacing: 0.15em;
padding: 0.3rem 0;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border-dim);
display: flex;
align-items: center;
gap: 0.75rem;
">
<span style="color:var(--orange);"></span>
<span>YEAR://{{ .Key }}</span>
<span>({{ len .Pages }} entries)</span>
</div>
<ul class="post-list">
{{- range .Pages }}
<li class="post-card pixel-border" data-index="{{ .Date.Format "01-02" }}">
<div class="post-card__meta">
<span class="prompt">$ cat</span>
<span class="post-card__date">{{ .Date.Format "Jan 02" }}</span>
</div>
<h2 class="post-card__title">
<a href="{{ .Permalink }}">{{ .Title }}</a>
</h2>
{{- with .Params.subtitle }}
<p class="post-subtitle" style="font-family:var(--font-body); font-style:italic; color:var(--text-muted); font-size:0.88rem; margin-bottom:0.5rem; margin-top:-0.3rem;">{{ . }}</p>
{{- end }}
{{- with .Summary }}
<p class="post-card__summary">{{ . }}</p>
{{- end }}
{{- with .Params.tags }}
<div class="post-card__tags">
{{- range . }}
<a href="{{ "tags/" | relURL }}{{ . | urlize }}/" class="tag">{{ . }}</a>
{{- end }}
</div>
{{- end }}
</li>
{{- end }}
</ul>
</div>
{{- end }}
{{- else }}
<div class="pixel-border" style="padding:2rem; font-family:var(--font-terminal); color:var(--text-dim); text-align:center; line-height:2;">
<p>// QUERY RETURNED 0 RESULTS</p>
<p style="color:var(--text-muted); font-style:italic; font-family:var(--font-body); margin-top:0.5rem;">
The archive is silent. Return when manuscripts have been added.
</p>
</div>
{{- end }}
<!-- Pagination -->
{{- template "partials/pagination.html" . }}
</main>
<!-- Sidebar -->
{{ partial "sidebar.html" . }}
</div>
{{ end }}
{{- define "partials/pagination.html" }}
{{- $pager := .Paginate .Pages }}
{{- if gt $pager.TotalPages 1 }}
<nav class="pagination" aria-label="Pagination">
{{- if $pager.HasPrev }}
<a href="{{ $pager.Prev.URL }}">&lt;&lt; PREV</a>
{{- end }}
{{- range $pager.Pagers }}
{{- if eq . $pager }}
<span class="current">{{ .PageNumber }}</span>
{{- else }}
<a href="{{ .URL }}">{{ .PageNumber }}</a>
{{- end }}
{{- end }}
{{- if $pager.HasNext }}
<a href="{{ $pager.Next.URL }}">NEXT &gt;&gt;</a>
{{- end }}
</nav>
{{- end }}
{{- end }}

View File

@@ -0,0 +1,113 @@
{{ define "main" }}
<div class="content-grid">
<!-- Article -->
<article id="main-content">
<!-- Post Header -->
<header class="post-header">
<!-- Breadcrumb -->
<nav class="post-breadcrumb" aria-label="Breadcrumb">
<a href="{{ .Site.BaseURL }}">~/</a>
<span aria-hidden="true">/</span>
{{- with .Section }}
<a href="{{ $.Site.BaseURL }}{{ . }}/">{{ . }}</a>
<span aria-hidden="true">/</span>
{{- end }}
<span>{{ .File.BaseFileName }}</span>
</nav>
<!-- Title -->
<h1 class="post-title">{{ .Title }}</h1>
{{- with .Params.subtitle }}
<p class="post-subtitle">{{ . }}</p>
{{- end }}
<!-- Meta -->
<div class="post-meta">
<div class="post-meta__item">
<span class="post-meta__label">DATE</span>
<time class="post-meta__value" datetime="{{ .Date.Format "2006-01-02" }}">
{{ .Date.Format "January 2, 2006" }}
</time>
</div>
{{- with .Params.author }}
<div class="post-meta__item">
<span class="post-meta__label">BY</span>
<span class="post-meta__value">{{ . }}</span>
</div>
{{- else }}
{{- with $.Site.Params.author }}
<div class="post-meta__item">
<span class="post-meta__label">BY</span>
<span class="post-meta__value">{{ . }}</span>
</div>
{{- end }}
{{- end }}
<div class="post-meta__item">
<span class="post-meta__label">READ</span>
<span class="post-reading-time">~{{ .ReadingTime }} min</span>
</div>
{{- if .Params.lastmod }}
<div class="post-meta__item">
<span class="post-meta__label">UPDATED</span>
<time class="post-meta__value" datetime="{{ .Lastmod.Format "2006-01-02" }}">
{{ .Lastmod.Format "2006-01-02" }}
</time>
</div>
{{- end }}
</div>
</header>
<!-- Body -->
<div class="post-body">
{{ .Content }}
</div>
<!-- Footer -->
<footer class="post-footer">
{{- with .Params.tags }}
<div class="post-tags-section">
<span class="post-tags-section__label">TAGS </span>
{{- range . }}
<a href="{{ "tags/" | relURL }}{{ . | urlize }}/" class="tag">{{ . }}</a>
{{- end }}
</div>
{{- end }}
<!-- Prev / Next -->
{{- if or .PrevInSection .NextInSection }}
<nav class="post-nav" aria-label="Post navigation">
{{- if .PrevInSection }}
<a href="{{ .PrevInSection.Permalink }}" class="post-nav__item pixel-border post-nav__item--prev">
<span class="post-nav__label">&lt;&lt; PREV ENTRY</span>
<span class="post-nav__title">{{ .PrevInSection.Title }}</span>
</a>
{{- else }}
<div></div>
{{- end }}
{{- if .NextInSection }}
<a href="{{ .NextInSection.Permalink }}" class="post-nav__item pixel-border post-nav__item--next">
<span class="post-nav__label">NEXT ENTRY &gt;&gt;</span>
<span class="post-nav__title">{{ .NextInSection.Title }}</span>
</a>
{{- end }}
</nav>
{{- end }}
</footer>
{{ partial "comments.html" . }}
</article>
<!-- Sidebar -->
{{ partial "sidebar.html" . }}
</div>
{{ end }}

194
layouts/donate/single.html Normal file
View File

@@ -0,0 +1,194 @@
{{ define "main" }}
<div class="content-grid">
<main id="main-content">
<!-- Donate Header -->
<div class="donate-header pixel-border">
<div style="font-family:var(--font-terminal); font-size:0.85rem; color:var(--forest-light); margin-bottom:1rem;">
<span>~/donate $</span> <span style="color:var(--amber);">cat tip_jar.md</span>
</div>
<h1 class="post-title">{{ .Title }}</h1>
<p style="font-family:var(--font-body); font-style:italic; color:var(--text-warm); font-size:1rem; margin-top:0.5rem;">
{{- with .Params.subtitle }}{{ . }}{{- else }}If you enjoy the writing and want to keep the tea kettle going, any support is deeply appreciated.{{- end }}
</p>
</div>
<!-- Donate Body (optional intro markdown) -->
{{- with .Content }}
<div class="resume-body" style="margin-bottom:2rem;">{{ . }}</div>
{{- end }}
<!-- Platforms -->
<div class="donate-grid">
{{- with .Params.kofi }}
<a href="https://ko-fi.com/{{ . }}" target="_blank" rel="noopener" class="donate-card pixel-border">
<span class="donate-card__icon"><img src="/kofi.webp" width=30px height=30px/></span>
<div class="donate-card__name">Ko-fi</div>
<div class="donate-card__handle">ko-fi.com/{{ . }}</div>
<div class="donate-card__desc">Buy me a cup of cof- I mean, uh, tea. Ko-fi is not the most inclusive name...</div>
</a>
{{- end }}
{{- with .Params.liberapay }}
<a href="https://liberapay.com/{{ . }}" target="_blank" rel="noopener" class="donate-card pixel-border">
<span class="donate-card__icon"><img src="/liberapay.svg" width=30px height=30px/></span>
<div class="donate-card__name">Liberapay</div>
<div class="donate-card__handle">liberapay.com/{{ . }}</div>
<div class="donate-card__desc">Like Patreon or Onlyfans but, like, better.</div>
</a>
{{- end }}
{{- with .Params.github_sponsors }}
<a href="https://github.com/sponsors/{{ . }}" target="_blank" rel="noopener" class="donate-card pixel-border">
<div class="donate-card__icon">🐙</div>
<div class="donate-card__name">GitHub Sponsors</div>
<div class="donate-card__handle">github.com/sponsors/{{ . }}</div>
<div class="donate-card__desc">Support via GitHub</div>
</a>
{{- end }}
</div>
<!-- Crypto Wallets -->
{{- $hasCrypto := or .Params.bitcoin .Params.ethereum .Params.monero .Params.litecoin }}
{{- if $hasCrypto }}
<div class="donate-section">
<h2 class="donate-section__title">Crypto Wallets: unlike the fed, I don't have an inflation fetish</h2>
{{- with .Params.bitcoin }}
<div class="wallet-card pixel-border">
<div class="wallet-card__header">
<span class="wallet-card__icon"><img src="/bitcoin_logo.png" width=30px height=30px/></span>
<span class="wallet-card__name">Bitcoin</span>
<span class="wallet-card__ticker">BTC</span>
</div>
<div class="wallet-card__body">
<div class="wallet-card__address">
<span class="wallet-card__addr-text" id="btc-addr">{{ . }}</span>
<button class="wallet-card__copy" onclick="copyAddr('btc-addr', this)">[ copy ]</button>
</div>
<canvas class="wallet-qr" id="qr-btc" data-address="{{ . }}"></canvas>
</div>
</div>
{{- end }}
{{- with .Params.ethereum }}
<div class="wallet-card pixel-border">
<div class="wallet-card__header">
<span class="wallet-card__icon"><img src="/ethereum_logo.png" width=15px height=30px/></span>
<span class="wallet-card__name">Ethereum</span>
<span class="wallet-card__ticker">ETH</span>
</div>
<div class="wallet-card__body">
<div class="wallet-card__address">
<span class="wallet-card__addr-text" id="eth-addr">{{ . }}</span>
<button class="wallet-card__copy" onclick="copyAddr('eth-addr', this)">[ copy ]</button>
</div>
<canvas class="wallet-qr" id="qr-eth" data-address="{{ . }}"></canvas>
</div>
</div>
{{- end }}
{{- with .Params.monero }}
<div class="wallet-card pixel-border">
<div class="wallet-card__header">
<span class="wallet-card__icon"><img src="/monero_logo.png" width=30px height=30px/></span>
<span class="wallet-card__name">Monero</span>
<span class="wallet-card__ticker">XMR</span>
</div>
<div class="wallet-card__body">
<div class="wallet-card__address">
<span class="wallet-card__addr-text" id="xmr-addr">{{ . }}</span>
<button class="wallet-card__copy" onclick="copyAddr('xmr-addr', this)">[ copy ]</button>
</div>
<canvas class="wallet-qr" id="qr-xmr" data-address="{{ . }}"></canvas>
</div>
</div>
{{- end }}
{{- with .Params.litecoin }}
<div class="wallet-card pixel-border">
<div class="wallet-card__header">
<span class="wallet-card__icon"><img src="/litecoin.png" width=30px height=30px/></span>
<span class="wallet-card__name">Litecoin</span>
<span class="wallet-card__ticker">LTC</span>
</div>
<div class="wallet-card__body">
<div class="wallet-card__address">
<span class="wallet-card__addr-text" id="ltc-addr">{{ . }}</span>
<button class="wallet-card__copy" onclick="copyAddr('ltc-addr', this)">[ copy ]</button>
</div>
<canvas class="wallet-qr" id="qr-ltc" data-address="{{ . }}"></canvas>
</div>
</div>
{{- end }}
</div>
{{- end }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
// Copy address to clipboard
function copyAddr(id, btn) {
var text = document.getElementById(id).textContent.trim();
navigator.clipboard.writeText(text).then(function() {
var orig = btn.textContent;
btn.textContent = '[ copied! ]';
btn.style.color = 'var(--forest-light)';
setTimeout(function() { btn.textContent = orig; btn.style.color = ''; }, 2000);
});
}
// Theme colours (match CSS variables)
var QR_DARK = '#d4a228'; // --amber: the "on" modules
var QR_LIGHT = '#130e07'; // --bg-primary: the "off" background
// Generate a QR code into each canvas
document.querySelectorAll('.wallet-qr').forEach(function(canvas) {
var address = canvas.getAttribute('data-address');
if (!address) return;
// qrcode.js needs a fresh div to render into, then we pull the image
var tmp = document.createElement('div');
tmp.style.display = 'none';
document.body.appendChild(tmp);
new QRCode(tmp, {
text: address,
width: 160,
height: 160,
colorDark: QR_DARK,
colorLight: QR_LIGHT,
correctLevel: QRCode.CorrectLevel.M,
});
// qrcode.js creates an <img> inside the div once done
// Give it a tick to finish then copy to our canvas
setTimeout(function() {
var img = tmp.querySelector('img');
if (!img) return;
img.onload = function() {
canvas.width = 160;
canvas.height = 160;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, 160, 160);
document.body.removeChild(tmp);
};
// If already loaded
if (img.complete) {
canvas.width = 160;
canvas.height = 160;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, 160, 160);
document.body.removeChild(tmp);
}
}, 100);
});
</script>
</main>
{{ partial "sidebar.html" . }}
</div>
{{ end }}

133
layouts/index.html Normal file
View File

@@ -0,0 +1,133 @@
{{ define "main" }}
<!-- Cozy Hero Card -->
<section class="hero">
<div class="hero__card pixel-border">
<!-- Pixel art book/tea illustration -->
<div class="hero__pixel-art" aria-hidden="true">
<img src="profile_pic.png" width=250 height=250>
</div>
<div class="hero__info">
<!-- Name bar -->
<div class="hero__name-bar pixel-border">
<span class="label">SCHOLAR:</span>
<span class="value">{{ .Site.Params.author | default "archivist" }}</span>
<span class="label" style="margin-left:auto;">STATUS:</span>
<span class="value" style="color:var(--forest-light);">reading</span>
</div>
<!-- Status pips -->
<div class="hero__status">
<span style="color:var(--text-dim);">TEA:</span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip--dim pip"></span>
&nbsp;&nbsp;
<span style="color:var(--text-dim);">WHIMSY:</span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
</div>
<!-- Description -->
<p class="hero__desc">
{{- with .Site.Params.description -}}
{{ . }}
{{- else -}}
A wanderer in the archive. Keeper of fragments, collector of marginalia.
Pull up a chair. The kettle is on.
{{- end -}}
</p>
<!-- Post count -->
<div style="font-family:var(--font-terminal); font-size:0.92rem; color:var(--text-dim);">
<span style="color:var(--forest-light);">entries:</span>
{{ len .Site.RegularPages }}
&nbsp;
{{- with .Site.Taxonomies.tags }}
<span style="color:var(--forest-light);">topics:</span>
{{ len . }}
{{- end }}
</div>
</div>
</div><!-- /.hero__card -->
<!--<div class="hero__cta">
<a href="{{ "posts/" | relURL }}" class="btn btn--primary">
[ open archive ]
</a>
<a href="{{ "about/" | relURL }}" class="btn btn--secondary">
[ about ]
</a>
</div> -->
</section>
<!-- Content + Sidebar Grid -->
<div class="content-grid">
<main id="main-content">
{{- $pinned := where .Site.RegularPages "Params.featured" true }}
{{- $recent := first 8 (where .Site.RegularPages "Params.featured" "!=" true) }}
{{- $posts := $recent }}
{{- if $pinned }}
{{- $posts = union $pinned $recent }}
{{- end }}
{{- if $posts }}
<ul class="post-list">
{{- range $i, $post := first 2 $posts }}
<li class="post-card pixel-border" data-index="{{ printf "%03d" (add $i 1) }}">
<div class="post-card__meta">
<span class="prompt">~/posts $</span>
<span class="post-card__date">{{ $post.Date.Format "2006-01-02" }}</span>
{{- if $post.Params.featured }}
<span style="color:var(--amber); font-size:0.8em;">[FEATURED]</span>
{{- end }}
</div>
<h2 class="post-card__title">
<a href="{{ $post.Permalink }}">{{ $post.Title }}</a>
</h2>
{{- with $post.Summary }}
<p class="post-card__summary">{{ . }}</p>
{{- end }}
{{- with $post.Params.tags }}
<div class="post-card__tags">
{{- range . }}
<a href="{{ "tags/" | relURL }}{{ . | urlize }}/" class="tag">{{ . }}</a>
{{- end }}
</div>
{{- end }}
</li>
{{- end }}
</ul>
{{- else }}
<div class="pixel-border" style="padding:2rem; font-family:var(--font-terminal); color:var(--text-dim); text-align:center;">
<p>// No entries found. The archive awaits its first manuscript.</p>
</div>
{{- end }}
{{- if gt (len .Site.RegularPages) 2 }}
<div style="margin-top:1.5rem; text-align:center;">
<a href="{{ "posts/" | relURL }}" class="btn btn--secondary">[ view all entries ]</a>
</div>
{{- end }}
</main>
{{ partial "sidebar.html" . }}
</div>
{{ end }}

View File

@@ -0,0 +1,13 @@
{{- if and (not .Params.nocomments) (.Site.Params.comentarioURL) }}
<div class="comments-section">
<div class="comments-section__header">
<span class="widget-icon">💬</span> DISCUSSION
</div>
<div id="comentario"></div>
<script defer
src="{{ .Site.Params.comentarioURL }}/comentario.js"
data-page-id="{{ .Permalink }}">
</script>
<comentario-comments></comentario-comments>
</div>
{{- end }}

View File

@@ -0,0 +1,81 @@
<footer class="site-footer">
<div class="site-wrapper">
<div class="footer-inner">
<!-- I don't like this stuff personally but feel free to put it back in -->
<!-- Col 1: About
<div class="footer-col">
<div class="footer-col__title">ABOUT</div>
<p style="font-family:var(--font-terminal); font-size:0.82rem; color:var(--text-dim); line-height:1.7;">
{{- with .Site.Params.description }}{{ . }}{{ else }}
A place for words, ideas, and the quiet rustle of digital manuscripts.
{{- end }}
</p>
</div>-->
<!-- Col 2: Navigation
<div class="footer-col">
<div class="footer-col__title">NAVIGATION</div>
<ul class="footer-links">
{{- range .Site.Menus.footer }}
<li><a href="{{ .URL }}">{{ .Name }}</a></li>
{{- end }}
{{- if not .Site.Menus.footer }}
<li><a href="{{ .Site.BaseURL }}">home</a></li>
<li><a href="{{ "posts/" | relURL }}">archive</a></li>
<li><a href="{{ "tags/" | relURL }}">index</a></li>
<li><a href="{{ "about/" | relURL }}">about</a></li>
<li><a href="{{ "index.xml" | relURL }}">rss feed</a></li>
{{- end }}
</ul>
</div>
<!-- Col 3: Connect
<div class="footer-col">
<div class="footer-col__title">CONNECT</div>
<ul class="footer-links">
{{- with .Site.Params.social.github }}
<li><a href="https://github.com/{{ . }}" target="_blank" rel="noopener">github</a></li>
{{- end }}
{{- with .Site.Params.social.mastodon }}
<li><a href="{{ . }}" target="_blank" rel="noopener">mastodon</a></li>
{{- end }}
{{- with .Site.Params.social.email }}
<li><a href="mailto:{{ . }}">email</a></li>
{{- end }}
{{- with .Site.Params.social.rss }}
<li><a href="{{ . | relURL }}">rss</a></li>
{{- else }}
<li><a href="{{ "index.xml" | relURL }}">rss</a></li>
{{- end }}
</ul>
</div>
-->
</div>
<!-- Status Bar -->
<div class="footer-status">
<div class="status-left">
<span class="status-indicator">
<span class="status-dot"></span>
SYSTEM ONLINE
</span>
<span>{{ now.Format "2006-01-02" }}</span>
<span>
{{- $pages := len .Site.RegularPages -}}
{{ $pages }} ENTRIES INDEXED
</span>
</div>
<div class="status-right">
<span>HUGO v{{ hugo.Version }}</span>
<span class="status-copyright">
&copy; {{ now.Year }}
{{- with .Site.Params.author }} {{ . }}{{ end }}
&mdash; CODEX OBSCURA THEME
</span>
</div>
</div>
</div><!-- /.site-wrapper -->
</footer>

View File

@@ -0,0 +1,85 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- LaTeX Rendering -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
]
});">
</script>
<!-- Syntax Highlighting -->
<!-- Highlight.js for better Common Lisp support -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/base16/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/lisp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/julia.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/r.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
<script>hljs.highlightAll();</script>
{{- with .Site.Params.description }}
<meta name="description" content="{{ . }}" />
{{- end }}
{{- with .Description }}
<meta name="description" content="{{ . }}" />
{{- end }}
{{- with .Site.Params.author }}
<meta name="author" content="{{ . }}" />
{{- end }}
<!-- Open Graph -->
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }}" />
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}" />
<meta property="og:url" content="{{ .Permalink }}" />
{{- with .Site.Params.ogImage }}
<meta property="og:image" content="{{ . | absURL }}" />
{{- end }}
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }}" />
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} &mdash; {{ .Site.Title }}{{ end }}</title>
<!-- Canonical -->
<link rel="canonical" href="{{ .Permalink }}" />
<!-- RSS -->
{{ range .AlternativeOutputFormats -}}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ $.Site.Title }}" />
{{ end -}}
<!-- Favicon — pixel style -->
<link rel="icon" type="image/svg+xml" href="{{ "favicon.svg" | relURL }}" />
<link rel="alternate icon" href="{{ "favicon.ico" | relURL }}" />
<!-- Google Fonts -->
<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=IM+Fell+English:ital@0;1&family=Courier+Prime:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet" />
<!-- Theme CSS -->
<link rel="stylesheet" href="{{ "css/main.css" | relURL }}" />
{{- with .Site.Params.customCSS }}
{{- range . }}
<link rel="stylesheet" href="{{ . | relURL }}" />
{{- end }}
{{- end }}
<!-- Inline critical SVG favicon -->
<style>
/* Favicon SVG embedded for retro pixel look */
</style>

View File

@@ -0,0 +1,51 @@
<header class="site-header">
<div class="site-wrapper">
<div class="header-inner">
<!-- Decorative deco row — books, leaves, tea -->
<!-- Site Title -->
<div style="display:flex; align-items:baseline; gap:1rem; flex-wrap:wrap;">
<a href="{{ .Site.BaseURL }}" class="site-title">
<span class="site-title__prompt">~/</span>
<span class="site-title__name">{{ .Site.Title | default "Codex Obscura" }}</span>
<!-- span class="site-title__cursor" aria-hidden="true">█</span -->
</a>
{{- with .Site.Params.tagline }}
<span class="site-tagline">{{ . }}</span>
{{- end }}
</div>
<div class="site-deco" aria-hidden="true">
<font size="3.5pt"> ✨ this | is | customized | in | partials | header | line | 19 ✨ </font>
</div>
<!-- Navigation -->
<nav class="site-nav" aria-label="Primary navigation">
{{ $currentPage := . }}
{{- range .Site.Menus.main }}
<div class="nav-item{{ if $currentPage.IsMenuCurrent "main" . }} nav-item--active{{ end }}">
<a href="{{ .URL }}">
{{ .Name }}
</a>
</div>
{{- end }}
{{- if not .Site.Menus.main }}
<div class="nav-item{{ if .IsHome }} nav-item--active{{ end }}">
<a href="{{ .Site.BaseURL }}">home</a>
</div>
<div class="nav-item">
<a href="{{ "posts/" | relURL }}">posts</a>
</div>
<div class="nav-item">
<a href="{{ "tags/" | relURL }}">tags</a>
</div>
<div class="nav-item">
<a href="{{ "about/" | relURL }}">about</a>
</div>
{{- end }}
</nav>
</div>
</div>
</header>

View File

@@ -0,0 +1,80 @@
<aside class="sidebar" aria-label="Sidebar">
<!-- System Info Widget -->
<div class="sidebar-widget pixel-border">
<div class="sidebar-widget__header"><span class="site-title__cursor" aria-hidden="true"></span>SYS_INFO</div>
<div class="sys-info">
<div class="sys-info__row">
<span class="sys-info__key">HOST</span>
<span class="sys-info__val">{{ .Site.Title | default "codex" }}</span>
</div>
<div class="sys-info__row">
<span class="sys-info__key">LANG</span>
<span class="sys-info__val">{{ .Site.LanguageCode | default "en-US" }}</span>
</div>
<div class="sys-info__row">
<span class="sys-info__key">ENTRIES</span>
<span class="sys-info__val">{{ len .Site.RegularPages }}</span>
</div>
<div class="sys-info__row">
<span class="sys-info__key">TAGS</span>
<span class="sys-info__val">{{ len .Site.Taxonomies.tags }}</span>
</div>
<div class="sys-info__row">
<span class="sys-info__key">STATUS</span>
<span class="sys-info__val" style="color:var(--forest-light)">ONLINE</span>
</div>
<div class="sys-info__row">
<span class="sys-info__key">UPTIME</span>
<span class="sys-info__val" id="js-uptime">--:--:--</span>
</div>
</div>
</div>
<!-- Recent Posts Widget -->
{{- $recent := first 5 .Site.RegularPages }}
{{- if $recent }}
<div class="sidebar-widget pixel-border">
<div class="sidebar-widget__header"><span class="widget-icon">📖</span> RECENT</div>
<ul class="sidebar-posts">
{{- range $recent }}
<li class="sidebar-post">
<a href="{{ .Permalink }}">{{ .Title }}</a>
<span class="sidebar-post__date">{{ .Date.Format "2006-01-02" }}</span>
</li>
{{- end }}
</ul>
</div>
{{- end }}
<!-- Tags Widget
{{- with .Site.Taxonomies.tags }}
<div class="sidebar-widget pixel-border">
<div class="sidebar-widget__header"><span class="widget-icon">🏷</span> INDEX</div>
<div class="sidebar-tags">
{{- range $name, $pages := . }}
<a href="{{ "tags/" | relURL }}{{ $name | urlize }}/" class="tag">
{{ $name }}
<span style="color:var(--text-dim);font-size:0.75em;">({{ len $pages }})</span>
</a>
{{- end }}
</div>
</div>
{{- end }}-->
<!-- Quote Widget -->
{{- with .Site.Params.sidebarQuote }}
<div class="sidebar-widget pixel-border" style="border-left:3px solid var(--amber-dark);">
<div class="sidebar-widget__header"><span class="widget-icon">🪶</span> MARGINALIA</div>
<p style="font-family:var(--font-body); font-style:italic; font-size:0.85rem; color:var(--text-muted); line-height:1.65;">
&ldquo;{{ . }}&rdquo;
</p>
{{- with $.Site.Params.sidebarQuoteAuthor }}
<p style="font-family:var(--font-terminal); font-size:0.75rem; color:var(--text-dim); margin-top:0.5rem; text-align:right;">
&mdash; {{ . }}
</p>
{{- end }}
</div>
{{- end }}
</aside>

View File

@@ -0,0 +1,48 @@
{{ define "main" }}
<div class="content-grid">
<main id="main-content">
<!-- Resume Header -->
<div class="resume-header pixel-border">
<div class="resume-header__top">
<div class="resume-header__prompt">
<span style="color:var(--forest-light);">~/resume $</span>
<span style="color:var(--amber);"> cat dossier.md</span>
</div>
<a href="{{ .Site.Params.resumePDF | default "#" }}" class="btn btn--primary" style="font-size:0.85rem; padding:0.3rem 0.9rem;">
[ download .pdf ]
</a>
</div>
<h1 class="resume-name">{{ .Params.resumeName | default .Site.Params.author | default "Your Name" }}</h1>
{{- with .Params.resumeTitle }}
<div class="resume-title">{{ . }}</div>
{{- end }}
<div class="resume-contacts">
{{- with .Params.email }}
<span class="resume-contact"><span class="resume-contact__label">mail:</span> <a href="mailto:{{ . }}">{{ . }}</a></span>
{{- end }}
{{- with .Params.location }}
<span class="resume-contact"><span class="resume-contact__label">loc:</span> {{ . }}</span>
{{- end }}
{{- with .Params.website }}
<span class="resume-contact"><span class="resume-contact__label">web:</span> <a href="{{ . }}">{{ . }}</a></span>
{{- end }}
{{- with .Params.github }}
<span class="resume-contact"><span class="resume-contact__label">git:</span> <a href="https://github.com/{{ . }}" target="_blank" rel="noopener">{{ . }}</a></span>
{{- end }}
</div>
</div>
<!-- Resume Body from markdown -->
<div class="resume-body">
{{ .Content }}
</div>
</main>
{{ partial "sidebar.html" . }}
</div>
{{ end }}

139
layouts/tags/taxonomy.html Normal file
View File

@@ -0,0 +1,139 @@
{{ define "main" }}
<div class="content-grid">
<main id="main-content">
<!-- Section Header -->
<div style="margin-bottom:1.5rem; padding-bottom:1rem; border-bottom:2px solid var(--border-dim);">
<h1 style="font-family:var(--font-terminal); font-size:1.8rem; color:var(--amber-light); letter-spacing:0.06em; text-shadow: 0 0 10px var(--glow-amber);">
{{ if .IsHome }}ARCHIVE{{ else }}{{ .Title | upper }}{{ end }}
</h1>
<p class="taxonomy-header">
<span style="color:var(--forest-light);">~/{{ .Section | default "posts" }} $</span>
ls -la &bull;
<span style="color:var(--amber);">{{ len .Pages }} entries</span>
</p>
</div>
{{- $tags := .Site.Taxonomies.tags }}
{{- $currentTag := .Data.Term }}
{{- with $tags }}
<div style="display:flex; flex-wrap:wrap; gap:0.4rem; margin-bottom:1.5rem; align-items:center;">
<span style="font-family:var(--font-terminal); font-size:0.8rem; color:var(--forest-light); letter-spacing:0.08em; margin-right:0.25rem;">FILTER:</span>
<a href="{{ "posts/" | relURL }}"
class="tag"
style="{{ if not $currentTag }}color:var(--amber); border-color:var(--amber-dark);{{ end }}">
all
</a>
{{- range $name, $pages := $tags }}
<a href="{{ "tags/" | relURL }}{{ $name | urlize }}/"
class="tag"
style="{{ if eq $name $currentTag }}color:var(--amber); border-color:var(--amber-dark);{{ end }}">
{{ $name }}
<span style="color:var(--text-dim); font-size:0.75em;">({{ len $pages }})</span>
</a>
{{- end }}
</div>
{{- end }}
{{- if .Pages }}
{{/* Group posts by year */}}
{{- range (.Pages.GroupByDate "2006") }}
<div style="margin-bottom:2.5rem;">
<!-- Year marker -->
<div style="
font-family: var(--font-terminal);
font-size: 0.8rem;
color: var(--text-dim);
letter-spacing: 0.15em;
padding: 0.3rem 0;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border-dim);
display: flex;
align-items: center;
gap: 0.75rem;
">
<span style="color:var(--orange);"></span>
<span>YEAR://{{ .Key }}</span>
<span>({{ len .Pages }} entries)</span>
</div>
<ul class="post-list">
{{- range .Pages }}
<li class="post-card pixel-border" data-index="{{ .Date.Format "01-02" }}">
<div class="post-card__meta">
<span class="prompt">$ cat</span>
<span class="post-card__date">{{ .Date.Format "Jan 02" }}</span>
</div>
<h2 class="post-card__title">
<a href="{{ .Permalink }}">{{ .Title }}</a>
</h2>
{{- with .Params.subtitle }}
<p class="post-subtitle" style="font-family:var(--font-body); font-style:italic; color:var(--text-muted); font-size:0.88rem; margin-bottom:0.5rem; margin-top:-0.3rem;">{{ . }}</p>
{{- end }}
{{- with .Summary }}
<p class="post-card__summary">{{ . }}</p>
{{- end }}
{{- with .Params.tags }}
<div class="post-card__tags">
{{- range . }}
<a href="{{ "tags/" | relURL }}{{ . | urlize }}/" class="tag">{{ . }}</a>
{{- end }}
</div>
{{- end }}
</li>
{{- end }}
</ul>
</div>
{{- end }}
{{- else }}
<div class="pixel-border" style="padding:2rem; font-family:var(--font-terminal); color:var(--text-dim); text-align:center; line-height:2;">
<p>// QUERY RETURNED 0 RESULTS</p>
<p style="color:var(--text-muted); font-style:italic; font-family:var(--font-body); margin-top:0.5rem;">
The archive is silent. Return when manuscripts have been added.
</p>
</div>
{{- end }}
<!-- Pagination -->
{{- template "partials/pagination.html" . }}
</main>
<!-- Sidebar -->
{{ partial "sidebar.html" . }}
</div>
{{ end }}
{{- define "partials/pagination.html" }}
{{- $pager := .Paginate .Pages }}
{{- if gt $pager.TotalPages 1 }}
<nav class="pagination" aria-label="Pagination">
{{- if $pager.HasPrev }}
<a href="{{ $pager.Prev.URL }}">&lt;&lt; PREV</a>
{{- end }}
{{- range $pager.Pagers }}
{{- if eq . $pager }}
<span class="current">{{ .PageNumber }}</span>
{{- else }}
<a href="{{ .URL }}">{{ .PageNumber }}</a>
{{- end }}
{{- end }}
{{- if $pager.HasNext }}
<a href="{{ $pager.Next.URL }}">NEXT &gt;&gt;</a>
{{- end }}
</nav>
{{- end }}
{{- end }}

30
layouts/tags/terms.html Normal file
View File

@@ -0,0 +1,30 @@
{{ define "main" }}
<div class="content-grid">
<main id="main-content">
<div style="margin-bottom:1.5rem; padding-bottom:1rem; border-bottom:2px solid var(--border-dim);">
<h1 style="font-family:var(--font-terminal); font-size:1.8rem; color:var(--amber-light); letter-spacing:0.06em; text-shadow: 0 0 10px var(--glow-amber);">
TOPIC INDEX
</h1>
<p class="taxonomy-header">
<span style="color:var(--forest-light);">~/tags $</span>
ls &bull;
<span style="color:var(--amber);">{{ len .Data.Terms }} topics found</span>
</p>
</div>
<ul class="taxonomy-list">
{{- range .Data.Terms.Alphabetical }}
<li class="taxonomy-list__item">
<a href="{{ .Page.Permalink }}">
#{{ .Page.Title }}
<span class="count">{{ .Count }}</span>
</a>
</li>
{{- end }}
</ul>
</main>
{{ partial "sidebar.html" . }}
</div>
{{ end }}

BIN
static/bitcoin_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

1491
static/css/main.css Normal file

File diff suppressed because it is too large Load Diff

BIN
static/ethereum_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

9
static/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
<rect width="16" height="16" fill="#130e07"/>
<!-- Pixel "C" in amber -->
<rect x="2" y="2" width="8" height="2" fill="#d4a228"/>
<rect x="2" y="4" width="2" height="8" fill="#d4a228"/>
<rect x="2" y="12" width="8" height="2" fill="#d4a228"/>
<!-- Pixel dot in orange -->
<rect x="10" y="10" width="4" height="4" fill="#c4601c"/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

152
static/js/main.js Normal file
View File

@@ -0,0 +1,152 @@
/* ============================================================
CODEX OBSCURA — main.js
Dust motes drifting in candlelight.
============================================================ */
(function () {
'use strict';
document.documentElement.classList.remove('no-js');
document.documentElement.classList.add('js');
/* ── Uptime Counter ──────────────────────────────────────── */
const uptimeEl = document.getElementById('js-uptime');
if (uptimeEl) {
const startTime = Date.now();
function updateUptime() {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const h = String(Math.floor(elapsed / 3600)).padStart(2, '0');
const m = String(Math.floor((elapsed % 3600) / 60)).padStart(2, '0');
const s = String(elapsed % 60).padStart(2, '0');
uptimeEl.textContent = h + ':' + m + ':' + s;
}
setInterval(updateUptime, 1000);
updateUptime();
}
/* ── Keyboard shortcuts ──────────────────────────────────── */
document.addEventListener('keydown', function (e) {
if (e.key === '/' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
const search = document.getElementById('search-input');
if (search) { e.preventDefault(); search.focus(); }
}
if (e.key === 'Escape' && document.activeElement) {
document.activeElement.blur();
}
});
/* ── Dust Motes ──────────────────────────────────────────── */
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const canvas = document.createElement('canvas');
canvas.style.cssText = [
'position:absolute', 'top:0', 'left:0',
'width:100%', 'height:100%',
'pointer-events:none', 'z-index:2'
].join(';');
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let W = document.body.scrollWidth;
let H = document.body.scrollHeight;
canvas.width = W;
canvas.height = H;
window.addEventListener('resize', function () {
W = canvas.width = document.body.scrollWidth;
H = canvas.height = document.body.scrollHeight;
});
// Warm amber/parchment dust colours
const COLORS = [
'212, 162, 40', // amber
'201, 176, 122', // parchment warm
'196, 96, 28', // orange
'138, 96, 48', // umber
'232, 213, 163', // parchment light
' 78, 120, 48', // forest
];
const MOTE_COUNT = 500;
function rand(min, max) { return min + Math.random() * (max - min); }
function makeMote(scatterY) {
// Each mote has its own independent fade cycle driven by a sine wave
// so they breathe in and out at different rates rather than all
// appearing and disappearing together.
const peakAlpha = rand(0.06, 0.22);
return {
x: rand(0, W),
y: scatterY !== undefined ? scatterY : rand(0, H),
// very slow drift — tiny random velocity
vx: rand(-0.12, 0.12),
vy: rand(-0.08, 0.06), // slight upward bias like warm air
// size: 12.5px radius, soft and small
r: rand(1.2, 2.4),
color: COLORS[Math.floor(Math.random() * COLORS.length)],
peakAlpha,
// sine-wave phase and speed for opacity breathing
phase: rand(0, Math.PI * 2),
phaseSpeed: rand(0.003, 0.009), // full breath every ~1235 seconds
// gentle Brownian wobble
wobbleX: rand(0, Math.PI * 2),
wobbleY: rand(0, Math.PI * 2),
wobbleSpeedX: rand(0.002, 0.007),
wobbleSpeedY: rand(0.002, 0.006),
wobbleAmp: rand(0.04, 0.18), // pixels of wobble per frame
};
}
// Seed motes scattered across the whole viewport
let motes = Array.from({ length: MOTE_COUNT }, function () { return makeMote(rand(0, H)); });
function animate() {
ctx.clearRect(0, 0, W, H);
const now = performance.now() * 0.001; // seconds, for wobble
for (let i = 0; i < motes.length; i++) {
const m = motes[i];
// Advance fade phase
m.phase += m.phaseSpeed;
// Opacity: sine wave between 0 and peakAlpha, always non-negative
const alpha = m.peakAlpha * (0.5 + 0.5 * Math.sin(m.phase));
// Drift position
m.x += m.vx + Math.sin(m.wobbleX) * m.wobbleAmp;
m.y += m.vy + Math.cos(m.wobbleY) * m.wobbleAmp * 0.6;
// Advance wobble phases independently of main phase
m.wobbleX += m.wobbleSpeedX;
m.wobbleY += m.wobbleSpeedY;
// Wrap around edges with a small buffer so motes never pop in visibly
const buf = 4;
if (m.x < -buf) m.x = W + buf;
if (m.x > W + buf) m.x = -buf;
if (m.y < -buf) m.y = H + buf;
if (m.y > H + buf) m.y = -buf;
// Draw as a soft radial-gradient circle (blurred disc, not a hard pixel)
const grad = ctx.createRadialGradient(m.x, m.y, 0, m.x, m.y, m.r * 2.5);
grad.addColorStop(0, 'rgba(' + m.color + ', ' + alpha + ')');
grad.addColorStop(0.5, 'rgba(' + m.color + ', ' + (alpha * 0.4) + ')');
grad.addColorStop(1, 'rgba(' + m.color + ', 0)');
ctx.beginPath();
ctx.arc(m.x, m.y, m.r * 2.5, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
}
requestAnimationFrame(animate);
}
animate();
})();

BIN
static/kofi.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

1
static/liberapay.svg Normal file
View File

@@ -0,0 +1 @@
<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg"><rect fill="#f6c915" width="80" height="80" rx="10"/><path fill="#1a171b" d="M32.73 56.2651c-2.5867 0-4.6175-.3376-6.0925-1.0107s-2.5308-1.5936-3.1708-2.7611-.9458-2.4933-.9275-4.015.2267-3.1234.6284-4.8357l6.9391-29.0143 8.47-1.3108-7.595 31.4733c-.1458.655-.2275 1.2567-.2458 1.8025s.0817 1.0292.3 1.4475.5917.7566 1.12 1.0108 1.2658.42 2.2133.4925zM40.5333 28.0209c1.46-.4367 3.1267-.8284 5.0025-1.175s3.9075-.5183 6.0933-.5183 3.7985.3092 5.2726.9283 2.6864 1.4667 3.6341 2.5409 1.6475 2.3316 2.1024 3.77.6832 2.9783.6832 4.6183c0 2.6583-.4373 5.09-1.3214 7.2942s-2.076 4.1075-3.6237 5.71-3.4059 2.851-5.5763 3.7475-4.5235 1.3344-7.0718 1.3344c-1.2391 0-2.4775-.1104-3.7158-.3296l-2.4592 9.8912h-8.0865zM43.4867 49.3867c.6192.1458 1.3842.2133 2.295.2133 1.42 0 2.7133-.2583 3.8792-.7875s2.1583-1.265 2.9783-2.2125 1.4557-2.0842 1.9117-3.415.6827-2.795.6827-4.3983-.3467-2.8958-1.0379-3.9892-1.894-1.6391-3.6057-1.6391c-1.1667 0-2.2592.1092-3.2792.3283z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
static/litecoin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
static/monero_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
static/profile_pic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

16
theme.toml Normal file
View File

@@ -0,0 +1,16 @@
name = "Codex Obscura"
license = "MIT"
licenselink = "https://opensource.org/licenses/MIT"
description = "A dark academia pixel terminal Hugo theme with fall color palette. Blends the warmth of candlelit manuscripts with the glow of a CRT screen."
homepage = ""
min_version = "0.80.0"
tags = ["dark", "academia", "terminal", "pixel", "blog", "portfolio"]
[author]
name = "Codex Obscura"
homepage = ""
[features]
[features.outputFormats]
[features.outputFormats.disableAliases]
disabled = false