first commit
384
README.md
Normal 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
@@ -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
@@ -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
|
||||
17
layouts/_default/baseof.html
Normal 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
@@ -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 •
|
||||
<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 }}"><< 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 >></a>
|
||||
{{- end }}
|
||||
</nav>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
113
layouts/_default/single.html
Normal 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"><< 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 >></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
@@ -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
@@ -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>
|
||||
|
||||
<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 }}
|
||||
|
||||
{{- 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 }}
|
||||
13
layouts/partials/comments.html
Normal 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 }}
|
||||
81
layouts/partials/footer.html
Normal 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">
|
||||
© {{ now.Year }}
|
||||
{{- with .Site.Params.author }} {{ . }}{{ end }}
|
||||
— CODEX OBSCURA THEME
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.site-wrapper -->
|
||||
</footer>
|
||||
85
layouts/partials/head.html
Normal 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 }} — {{ .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>
|
||||
51
layouts/partials/header.html
Normal 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>
|
||||
80
layouts/partials/sidebar.html
Normal 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;">
|
||||
“{{ . }}”
|
||||
</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;">
|
||||
— {{ . }}
|
||||
</p>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
|
||||
</aside>
|
||||
48
layouts/resume/single.html
Normal 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
@@ -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 •
|
||||
<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 }}"><< 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 >></a>
|
||||
{{- end }}
|
||||
</nav>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
30
layouts/tags/terms.html
Normal 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 •
|
||||
<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
|
After Width: | Height: | Size: 10 KiB |
1491
static/css/main.css
Normal file
BIN
static/ethereum_logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
9
static/favicon.svg
Normal 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
@@ -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: 1–2.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 ~12–35 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
|
After Width: | Height: | Size: 2.0 KiB |
1
static/liberapay.svg
Normal 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
|
After Width: | Height: | Size: 14 KiB |
BIN
static/monero_logo.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
static/profile_pic.png
Normal file
|
After Width: | Height: | Size: 877 KiB |
16
theme.toml
Normal 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
|
||||