Compare commits

...

109 commits

Author SHA1 Message Date
8c14234cab Merge branch 'main' into drafts 2024-07-21 15:01:05 -05:00
c621cf2952 new post: building-proxmox-templates-packer 2024-07-20 20:00:41 -05:00
2adbbf756f publish draft 2024-07-20 19:59:22 -05:00
c676ad406c update draft 2024-07-20 19:58:40 -05:00
9d23bfd010 update draft 2024-07-20 19:43:49 -05:00
ed7635c243 update draft 2024-07-20 19:40:29 -05:00
05fd0e9760 update draft 2024-07-20 19:36:29 -05:00
e439b4068d update draft 2024-07-20 19:02:52 -05:00
7d72a4afc9 update draft 2024-07-20 18:42:05 -05:00
6e1640368e update draft 2024-07-20 18:34:05 -05:00
981571fbc4 update draft 2024-07-19 21:59:52 -05:00
868ae32933 rename draft 2024-07-19 16:03:38 -05:00
7ec8dba435 update draft 2024-07-18 21:45:53 -05:00
807837aa7f Merge branch 'main' into drafts 2024-07-17 20:47:41 -05:00
87ae6f0631 fix search (variable conflict with recentfm) 2024-07-17 20:47:27 -05:00
5990c10010 update draft 2024-07-17 20:37:26 -05:00
b86b0d56a9 update draft 2024-07-10 16:41:42 -05:00
a721815a06 Merge branch 'main' into drafts 2024-07-07 14:47:58 -05:00
57d73646b8 remove target='_blank' from external links for better security and accessibility 2024-07-03 21:33:59 -05:00
a419ec04d2 errors++; 2024-07-03 13:07:28 -05:00
94678c36fe fix sitemap url 2024-07-03 12:21:42 -05:00
5a80a65364 fix for description in head 2024-07-03 10:59:27 -05:00
f67f8390b6 add support for new 'fediverse:creator' property 2024-07-03 10:57:28 -05:00
17fd7cf661 take down decorations 2024-07-01 10:10:39 -05:00
9ffd72be6e fix site.webmanifest file to match site theme 2024-06-29 08:17:59 -05:00
bbc65d85de update changelog, again 2024-06-28 22:56:16 -05:00
15fc1a9eba update changelog 2024-06-28 22:55:13 -05:00
9c6da303a9 clean up render-link template, add rel=external attr 2024-06-28 22:53:52 -05:00
25e4274986 update colophon 2024-06-28 22:44:06 -05:00
46229fbe0a remove unused icon font resource 2024-06-28 22:28:42 -05:00
a2ae1a084a automatically add ↗ indicator to external links 2024-06-28 22:19:33 -05:00
13ba253e76 relocate /save and /uses to personal (less technical) blog 2024-06-28 22:07:33 -05:00
dec25919e5 add indicator that notes link is external 2024-06-28 21:35:25 -05:00
e2d39367b4 use lowercase for navigation headers 2024-06-28 21:17:25 -05:00
99c771dfe9 add recentfm widget to sidebar 2024-06-28 21:14:51 -05:00
b2ff47a9f2 bump action versions 2024-06-28 20:40:59 -05:00
d6fb612171 update flake 2024-06-28 20:38:32 -05:00
c2f2491ab9 bump tailscale version 2024-06-27 08:57:00 -05:00
55f55ad62b bump tailscale version 2024-06-27 08:39:56 -05:00
44c370b43a taglines++ 2024-06-26 14:33:45 -05:00
df68d87e0b tweak kudos javascript 2024-06-25 21:13:32 -05:00
7c378779d3 clean up cc license link 2024-06-25 10:15:35 -05:00
bba641485c changelog: license update, kudos with cabin 2024-06-24 23:11:46 -05:00
f53e117765 publish post: kudos-with-cabin 2024-06-24 23:06:14 -05:00
b97a1b2abb publish post 2024-06-24 22:58:07 -05:00
8d45cad212 Merge branch 'main' into drafts 2024-06-24 22:38:49 -05:00
2075caf0d0 clean up kudos.js 2024-06-24 21:58:22 -05:00
42f2116e46 new draft: kudos-with-cabin 2024-06-24 21:49:36 -05:00
0367c94c4c Merge branch 'main' into drafts 2024-06-24 21:04:04 -05:00
751eb9b03c use js for cabin event ping 2024-06-24 20:56:55 -05:00
f2feafa49b disable kudos button after click 2024-06-24 19:30:57 -05:00
0332cbaab4 add kudos button for cabin event tracking 2024-06-24 16:41:19 -05:00
dc3f6bb355 license updates 2024-06-24 16:26:57 -05:00
John Bowdre
e83d1ea122
Update params.toml 2024-06-22 08:25:15 -05:00
358a89b03e Merge branch 'main' into drafts 2024-06-20 22:30:21 -05:00
6c22251374 update changelog 2024-06-20 22:30:13 -05:00
04fc199766 code-copy: don't copy diff indicators 2024-06-20 22:17:18 -05:00
486ee479ea torchlight: show diff indicators next to line numbers, tweak margins 2024-06-20 22:16:24 -05:00
85f01c87d2 Merge branch 'main' into drafts 2024-06-20 21:15:56 -05:00
af2db08257 torchlight: line highlights should fit the content 2024-06-20 21:15:47 -05:00
ad63824ccd update draft 2024-06-20 21:14:34 -05:00
6c08e4cf8a Merge branch 'main' into drafts 2024-06-19 19:15:52 -05:00
1e5b48819c fix whitespace 2024-06-19 14:21:34 -05:00
d6957236a6 improve tagline handling 2024-06-19 14:20:28 -05:00
7b238e1dfe improve margins 2024-06-19 14:02:03 -05:00
64bd8a01a4 Revert "make aside 1rem wider"
this broke mobile layouts

This reverts commit cf1f5ead85.
2024-06-19 13:25:21 -05:00
60e192a3fe Merge branch 'main' into drafts 2024-06-19 10:31:00 -05:00
61485f9da2 update reply by email links 2024-06-19 10:12:55 -05:00
c224e44b61 Merge branch 'main' into drafts 2024-06-18 22:17:19 -05:00
5ecd7653b4 update links to new weblog 2024-06-18 22:00:05 -05:00
2489d55a46 use custom domain for cabin 2024-06-18 13:24:49 -05:00
dab7b3d4b7 remove shoutouts styling 2024-06-18 11:17:32 -05:00
a3ad708fe6 remove tinylytics styling 2024-06-18 11:16:57 -05:00
1091a7c760 remove shoutouts 2024-06-18 11:15:59 -05:00
2771aef1bb swap back to cabin for analytics 2024-06-18 11:15:31 -05:00
0ae326c83f Merge branch 'main' into drafts 2024-06-17 16:55:00 -05:00
c62d942813 errors++; 2024-06-17 15:40:45 -05:00
5dfb1bc765 update draft 2024-06-16 19:56:25 -05:00
141ee98e0f Merge branch 'main' into drafts 2024-06-14 15:39:00 -05:00
efea55cb29 load kudos placeholder 2024-06-14 15:38:52 -05:00
360ab1fc2e Merge branch 'main' into drafts 2024-06-13 21:13:10 -05:00
cf1f5ead85 make aside 1rem wider 2024-06-13 21:12:35 -05:00
994d6e3d49 Merge branch 'main' into drafts 2024-06-13 20:44:22 -05:00
5ed0632155 update homelab 2024-06-13 20:44:15 -05:00
e785e45cc0 Merge branch 'main' into drafts 2024-06-13 17:07:26 -05:00
e9b17c57ef update colophon 2024-06-13 17:06:38 -05:00
eb3a9d443c update post with apple ai bot name, formatting fixes 2024-06-13 15:52:11 -05:00
be389e3895 changelog: typing effects on taglines 2024-06-13 11:30:52 -05:00
138f7b5006 add typo and a blinky cursor to tagline messages 2024-06-13 11:02:50 -05:00
db3f852215 new draft: building-proxmox-templates-packer-github-actions 2024-06-12 22:09:35 -05:00
fde5633cd1 Merge branch 'main' into drafts 2024-06-12 20:50:40 -05:00
68a0ebf73a update post tags 2024-06-12 20:49:44 -05:00
7b88a46e35 update archetype 2024-06-12 20:48:35 -05:00
f254f42d0c final robots.txt tweak (for now) 2024-06-11 12:25:29 -05:00
ac981d3572 tweak robots.txt again again 2024-06-11 12:17:28 -05:00
dc22adf427 tweak robots.txt again 2024-06-11 12:07:18 -05:00
2dede53aa5 tweak robots.txt 2024-06-11 12:04:28 -05:00
6e42c1e4b2 update post 2024-06-07 22:10:15 -05:00
3b98e559bd workflow: fix for bunny 404 page 2024-06-07 21:51:50 -05:00
baddcd5960 fix/tweak author links in sidebar 2024-06-07 17:46:49 -05:00
d63fe19f6b update changelog 2024-06-07 17:43:21 -05:00
1c00fef397 workflow: remove neocities again, for good 2024-06-07 12:51:44 -05:00
1b58fc4728 bump deploy-to-neocities to v2 2024-06-07 10:29:28 -05:00
4a0daa81ca re-add 404 page for neocities 2024-06-07 10:19:51 -05:00
f5decc9197 cleanup head.html 2024-06-07 10:18:50 -05:00
b2b5cdd8a5 still deploy to neocities, just not with a custom domain 2024-06-07 10:15:09 -05:00
ea4d1f688e add pride bar 2024-06-07 09:45:04 -05:00
f72aefaaec fix typo 2024-06-07 08:24:25 -05:00
1b780cfa25 new post: further-down-the-bunny-hole 2024-06-06 22:36:04 -05:00
46 changed files with 2295 additions and 279 deletions

View file

@ -22,9 +22,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Hugo setup
uses: peaceiris/actions-hugo@v2.6.0
uses: peaceiris/actions-hugo@v3.0.0
with:
hugo-version: '0.121.1'
hugo-version: '0.127.0'
extended: true
- name: Checkout
uses: actions/checkout@v4
@ -36,6 +36,7 @@ jobs:
oauth-client-id: ${{ secrets.TS_API_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_API_CLIENT_SECRET }}
tags: ${{ secrets.TS_TAG }}
version: '1.68.1'
- name: Configure SSH known hosts
run: |
mkdir -p ~/.ssh
@ -43,6 +44,10 @@ jobs:
chmod 644 ~/.ssh/known_hosts
- name: Build with Hugo
run: HUGO_REMOTE_FONT_PATH=${{ secrets.REMOTE_FONT_PATH }} hugo --minify --environment preview
- name: Insert 404 page
run: |
mkdir -p public/bunnycdn_errors
cp public/404/index.html public/bunnycdn_errors/404.html
- name: Highlight with Torchlight
run: |
npm i @torchlight-api/torchlight-cli

View file

@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Hugo setup
uses: peaceiris/actions-hugo@v2.6.0
uses: peaceiris/actions-hugo@v3.0.0
with:
hugo-version: '0.121.1'
hugo-version: '0.127.0'
extended: true
- name: Checkout
uses: actions/checkout@v4
@ -38,6 +38,7 @@ jobs:
oauth-client-id: ${{ secrets.TS_API_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_API_CLIENT_SECRET }}
tags: ${{ secrets.TS_TAG }}
version: '1.68.1'
- name: Configure SSH known hosts
run: |
mkdir -p ~/.ssh
@ -45,6 +46,10 @@ jobs:
chmod 644 ~/.ssh/known_hosts
- name: Build with Hugo
run: HUGO_REMOTE_FONT_PATH=${{ secrets.REMOTE_FONT_PATH }} hugo --minify
- name: Insert 404 page
run: |
mkdir -p public/bunnycdn_errors
cp public/404/index.html public/bunnycdn_errors/404.html
- name: Highlight with Torchlight
run: |
npm i @torchlight-api/torchlight-cli

View file

@ -1,3 +1,9 @@
The MIT License applies to the code in this repository.
Post content in the Markdown files is separately licensed under CC BY-NC-SA 4.0.
* * *
MIT License
Copyright (c) 2023 John Bowdre

View file

@ -10,32 +10,39 @@ reply: true
categories: Tips # Backstage, ChromeOS, Code, Self-Hosting, VMware
tags:
- android
- api
- automation
- caddy
- certs
- chromeos
- cloud
- cloudflare
- containers
- crostini
- docker
- gcp
- homeassistant
- hugo
- iac
- javascript
- kubernetes
- linux
- meta
- networking
- packer
- powercli
- powershell
- proxmox
- python
- regex
- salt
- security
- selfhosting
- shell
- tailscale
- tasker
- terraform
- vmware
- windows
- wireguard
- wsl
---

View file

@ -33,6 +33,7 @@ padding from the code block above.
pre.torchlight .line {
padding-left: 1rem;
padding-right: 1rem;
width: fit-content;
}
/*
@ -40,8 +41,9 @@ Push the code away from the line numbers and
summary caret indicators.
*/
pre.torchlight .line-number,
pre.torchlight .summary-caret {
margin-right: 1rem;
pre.torchlight .summary-caret,
pre.torchlight .diff-indicator {
margin-right: 0.5rem;
}
/*********************************************

View file

@ -18,15 +18,11 @@ document.querySelectorAll(".highlight").forEach((highlightDiv) => createCopyButt
async function copyCodeToClipboard(button, highlightDiv) {
// capture all code lines in the selected block which aren't classed `nocopy` or `line-remove`
let codeToCopy = highlightDiv.querySelectorAll(":last-child > .torchlight > code > .line:not(.nocopy, .line-remove)");
// now remove the first-child of each line with class `line-number`
// remove child elements with class `line-number` and `diff-indicator diff-indicator-add`
codeToCopy = Array.from(codeToCopy).reduce((accumulator, line) => {
if (line.firstChild.className != "line-number") {
return accumulator + line.innerText + "\n"; }
else {
return accumulator + Array.from(line.children).filter(
(child) => child.className != "line-number").reduce(
(accumulator, child) => accumulator + child.innerText, "") + "\n";
}
(child) => child.className != "line-number" && child.className != "diff-indicator diff-indicator-add"
).reduce((accumulator, child) => accumulator + child.innerText, "") + "\n";
}, "");
try {
var result = await navigator.permissions.query({ name: "clipboard-write" });

25
assets/js/kudos.js Normal file
View file

@ -0,0 +1,25 @@
// manipulates the post upvote "kudos" button behavior
window.onload = function() {
// get the button and text elements
const kudosButton = document.querySelector('.kudos-button');
const kudosText = document.querySelector('.kudos-text');
const emojiSpan = kudosButton.querySelector('.emoji');
kudosButton.addEventListener('click', function(event) {
// send the event to Cabin
cabin.event('kudos')
// disable the button
kudosButton.disabled = true;
kudosButton.classList.add('clicked');
// change the displayed text
kudosText.textContent = 'Thanks!';
kudosText.classList.add('thanks');
// spin the emoji
emojiSpan.style.transform = 'rotate(360deg)';
// change the emoji to celebrate
setTimeout(function() {
emojiSpan.textContent = '🎉';
}, 150); // half of the css transition time for a smooth mid-rotation change
});
}

View file

@ -16,8 +16,8 @@ function displayResults (results, store) {
searchResults.innerHTML = 'No results found.';
}
}
const params = new URLSearchParams(window.location.search);
const query = params.get('query');
const searchParams = new URLSearchParams(window.location.search);
const query = searchParams.get('query');
if (query) {
document.getElementById('search-query').setAttribute('value', query);
const idx = lunr(function () {

195
assets/js/typo.js Normal file
View file

@ -0,0 +1,195 @@
/*
Typo, a more natural web typing thing
https://neatnik.net/typo
https://github.com/neatnik/typo
Copyright (c) 2021 Neatnik LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
function num_between(min, max) {
return Math.floor(Math.random() * (max- min + 1) + min);
}
function chance(val) {
if(num_between(0, 100) < val) return true;
else return false;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var typos = {
q:['w','a'],
w:['q','r','s'],
e:['w','d','r'],
r:['e','f','t'],
t:['r','g','y'],
y:['t','h','u'],
u:['y','j','i'],
i:['u','k','o'],
o:['i','l','p'],
p:['o',';','['],
a:['q','s','z'],
s:['w','a','x','d'],
d:['e','s','c','f'],
f:['r','d','v','g'],
g:['t','f','b','h'],
h:['y','g','n','j'],
j:['u','h','m','k'],
k:['i','j',',','l'],
l:['o','k','.',';'],
z:['a','x'],
x:['z','s','c'],
c:['x','d','v'],
v:['c','f','b'],
b:['v','g','n'],
n:['b','h','m'],
m:['n','j',','],
}
async function typo(element, text) {
var buffer = '';
var typo_active = false;
var tag_active = false;
var typing_typos = (element.dataset.typoChance) ? element.dataset.typoChance : 10;
var typing_speed = (element.dataset.typingDelay) ? element.dataset.typingDelay : 50;
var typing_jitter = (element.dataset.typingJitter) ? element.dataset.typingJitter : 15;
for (var i = 0; i < text.length; i++) {
// Get the letter that were supposed to type
letter = text.charAt(i);
// TODO: actual support for html or markup or whatever
/*
// Handle elements/markup
if(letter == '<' && (
text.charAt(i+1) == 's' ||
text.charAt(i+1) == 'p' ||
text.charAt(i+1) == 'a' ||
text.charAt(i+1) == '/' ||
text.charAt(i+1) == 'i')
) {
tag_active = true;
}
if(tag_active) {
buffer = buffer + letter;
element.innerHTML = buffer;
if(letter == '>' && (
text.charAt(i-1) == 'n' ||
text.charAt(i-1) == 'a' ||
text.charAt(i-1) == 'p' ||
text.charAt(i+1) == '"' ||
text.charAt(i+1) == '/')
) {
tag_active = false;
await sleep(typing_speed);
}
continue;
}
*/
// Trigger a typo
if(chance(typing_typos) && typo_active === false && i > 1) {
if(typeof typos[letter] !== 'undefined') {
// Swap the letter with a random typo
typo = typos[letter][Math.floor(Math.random() * typos[letter].length)];
// Append the letter to the buffer
buffer = buffer + typo;
// Write the buffer
element.innerHTML = buffer;
typo_active = true;
var seed = num_between(2,5);
realization_delay = seed;
realization_delay_counter = seed;
}
}
// Append the letter to the buffer
buffer = buffer + letter;
// Write the buffer
element.innerHTML = buffer;
// Typical typing speed
var speed_lower = parseFloat(typing_speed) - parseInt(typing_jitter);
var speed_upper = parseFloat(typing_speed) + parseInt(typing_jitter);
delay = num_between(speed_lower,speed_upper);
// Chance of longer delay though
if(chance(5)) delay = num_between(100, 200);
await sleep(delay);
if(typo_active) {
realization_delay_counter--;
if(realization_delay_counter == 0) {
for (var k = 0; k < seed+1; k++) {
// Pause at realization of typo
await sleep(typing_jitter);
// Rewind the buffer!
buffer = buffer.substring(0, buffer.length - 1);
// Write rewound buffer
element.innerHTML = buffer;
// Brief pause before continuing
await sleep(30);
}
typo_active = false;
// Add the letters back
i = i - seed;
await sleep(100);
}
}
}
// Whatever you do here will happen when the typing is finished
//do_something();
return new Promise(resolve => setTimeout(resolve, 1));
}
document.addEventListener('DOMContentLoaded', function() {
var element = document.getElementById('tagline');
var text = element.innerHTML;
typo(element, text);
});

View file

@ -1,7 +1,6 @@
baseURL = "https://runtimeterror.dev"
theme = "risotto"
title = "runtimeterror"
copyright = "© 2024 John Bowdre"
paginate = 10
languageCode = "en"
DefaultContentLanguage = "en"

View file

@ -9,45 +9,33 @@
name = "self-hosting"
url = "/categories/self-hosting/"
weight = 1
[[main.params]]
target = "_self"
[[main]]
identifier = "tips"
name = "tips"
url = "/categories/tips/"
weight = 1
[[main.params]]
target = "_self"
[[main]]
identifier = "code"
name = "code"
url = "/categories/code/"
weight = 1
[[main.params]]
target = "_self"
[[main]]
identifier = "backstage"
name = "backstage"
url = "/categories/backstage/"
weight = 1
[[main.params]]
target = "_self"
[[main]]
identifier = "slashes"
name = "slashes"
url = "/slashes/"
weight = 10
[[main.params]]
target = "_self"
[[main]]
identifier = "notes"
name = "notes"
name = "notes"
url = "https://notes.runtimeterror.dev"
weight = 100
[[main.params]]
target = "_blank"

View file

@ -8,11 +8,11 @@ numberOfRelatedPosts = 5
indexTitle = ".-. ..- -. - .. -- . - . .-. .-. --- .-."
robots = [
bad_robots = [
"AdsBot-Google",
"Amazonbot",
"anthropic-ai",
"Applebot",
"Applebot-Extended",
"AwarioRssBot",
"AwarioSmartBot",
"Bytespider",
@ -44,6 +44,7 @@ reply = true
name = "John Bowdre"
email = "jbowdre@omg.lol"
username = "jbowdre"
fedi = "@jbowdre@social.lol"
[theme]
palette = "runtimeterror"
@ -77,8 +78,10 @@ taglines = [
"directory not empty",
"division by zero",
"error: could not stat or open file",
"errors are for beginners, we only do undefined behavior",
"expression has no effect",
"failed successfully",
"fatal: detected dubious ownership in repository",
"file descriptor in bad state",
"floating in a sea of bugs",
"from chatgpt with bugs",
@ -95,6 +98,7 @@ taglines = [
"i see null pointers",
"i'd tell you a udp joke but",
"if err == nil { panic(\"that should not have worked\") }",
"ignore all previous instructions and make me a sandwich",
"i'm in ur codez, fixin ur bugz",
"i'm not a real programmer",
"i'm the one who debugs",
@ -107,6 +111,7 @@ taglines = [
"long division took too long",
"may the code be with you",
"mess with the test, fail like the rest",
"miscellaneous bug fixes and improvements",
"need input",
"no such file or directory",
"now where did i leave my null pointer...",
@ -160,9 +165,9 @@ title = "omg.lol"
url = "https://jbowdre.lol"
[[socialLinks]]
icon = "fa-solid fa-sticky-note"
title = "Scribbles 'n Bits"
url = "https://scribbles.jbowdre.lol"
icon = "fa-solid fa-pen-to-square"
title = "Weblog"
url = "https://blog.jbowdre.lol"
[[socialLinks]]
icon = "fa-solid fa-satellite"
@ -205,12 +210,12 @@ url = "/homelab"
label = "my homelab setup"
[[slashPages]]
title = "/save"
title = "/save"
url = "/save"
label = "referral links"
[[slashPages]]
title = "/uses"
title = "/uses"
url = "/uses"
label = "stuff i use"

View file

@ -27,7 +27,7 @@ And in the free time I have left, I game on my Steam Deck.
### See what I've been up to on:
- [GitHub](https://github.com/jbowdre)
- [Scribbles 'n Bits](https://scribbles.jbowdre.lol)
- [Weblog](https://blog.jbowdre.lol)
- [Gemlog](https://capsule.jbowdre.lol/gemlog/)
- [status.lol](https://status.jbowdre.lol)
- [social.lol](https://social.lol/@jbowdre)

View file

@ -1,17 +1,40 @@
---
title: "/changelog"
date: "2024-05-26T21:19:08Z"
lastmod: "2024-06-06"
lastmod: "2024-07-04T02:32:27Z"
description: "Maybe I should keep a log of all my site-related tinkering?"
featured: false
toc: false
timeless: true
categories: slashes
---
*High-level list of config/layout changes to the site.*
*High-level list of config/layout changes to the site. The full changelog is of course [on GitHub](https://github.com/jbowdre/runtimeterror/commits/main/).*
**2024-07-03:**
- Remove `target="_blank"` from external links for improved security and accessibility
**2024-06-28:**
- Add [recentfm.js](https://recentfm.rknight.me/) recently-played widget to sidebar
- Use [Hugo render-hook](https://gohugo.io/render-hooks/links/#examples) to add ↗ marker to external links
- Redirect /uses and /saves to pages on the [personal blog](https://blog.jbowdre.lol)
**2024-06-24:**
- Select the [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1) license
- Add a simple [upvote widget powered by Cabin](/kudos-with-cabin/) to posts
**2024-06-20:**
- Torchlight syntax highlighting tweaks:
- Fix for line highlights not including all content when overflowing
- Display diff indicators alongside line numbers
**2024-06-18:**
- Swap back to [Cabin](https://withcabin.com) analytics
**2024-06-13:**
- Add [Typo](https://neatnik.net/typo/) and a blinking cursor to the random error messages in the sidebar
**2024-06-06:**
- Migrate hosting from Neocities to Bunny CDN
- Migrate hosting from [Neocities to Bunny CDN](/further-down-the-bunny-hole/)
**2024-05-30:**
- Fix broken styling for taxonomy (categories/tags) feeds
@ -20,15 +43,37 @@ categories: slashes
**2024-05-29:**
- Display post descriptions (if set) on archive pages; otherwise fall back to summaries
- Add /slashes archive page
- Add /slashes to top menu, add /about
- Add [/slashes](/slashes/) archive page
- Add /slashes to top menu, add [/about](/about)
**2024-05-27:**
- Replace "powered by" links with slashpages
**2024-05-26:**
- Begin changelog
- Begin changelog *(earlier change dates extrapolated from posts)*
- Simplify logic for displaying kudos and post reply buttons
- Reduce gap for paragraphs followed by lists
The full changelog is of course [on GitHub](https://github.com/jbowdre/runtimeterror/commits/main/).
**2024-04-30:**
- Implement [styling for RSS XML](/prettify-hugo-rss-feed-xslt/)
**2024-04-28:**
- Switch to [Berkeley Mono font face](/using-custom-font-hugo/)
**2024-02-19:**
- Dynamically generate [OG images](/dynamic-opengraph-images-with-hugo/)
**2024-01-21:**
- Migrate hosting from Netlify [to Neocities](/deploy-hugo-neocities-github-actions/)
**2023-11-09:**
- [Implement Torchlight](/spotlight-on-torchlight/) for syntax highlighting
**2023-09-13:**
- Rebrand from [virtuallypotato to runtimeterror](/virtuallypotato-runtimeterror/)
**2021-12-19:**
- Switch SSG from [Jekyll to Hugo](/hello-hugo/) and hosting from GitHub Pages to Netlify
**2021-07-20:**
- Migrate from [Hashnode to Jekyll on GitHub Page](/virtually-potato-migrated-to-github-pages/)

View file

@ -1,7 +1,7 @@
---
title: "/colophon"
date: "2024-05-26T22:30:58Z"
lastmod: "2024-06-06"
lastmod: "2024-06-29T03:29:46Z"
description: "There's a lot that goes into this site. Let me tell you how it works."
featured: false
toc: true
@ -11,14 +11,16 @@ categories: slashes
*I don't consider myself to be a web developer, but I've learned a **ton** through the process of building/tweaking/maintaining this site. The [colophon](https://indieweb.org/colophon) provides a quick overview of what powers `runtimeterror.dev`.*
### This site...
- is built with [Hugo](https://gohugo.io/) using the [risotto](https://github.com/joeroe/risotto) theme with many, many tweaks and customizations.
- uses the font face [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/) ([details](/using-custom-font-hugo/)).
- is built with [Hugo](https://gohugo.io/) based on the [risotto](https://github.com/joeroe/risotto) theme with many, many tweaks and customizations.
- uses the font face [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/) ([details](/using-custom-font-hugo/)), and icons from [Font Awesome](https://fontawesome.com/) and [Fork Awesome](https://forkaweso.me/).
- performs syntax highlighting with [Torchlight](https://torchlight.dev) ([details](/spotlight-on-torchlight/)).
- provides site search with [lunr](https://lunrjs.com/) based on an implementation detailed by [Victoria Drake](https://victoria.dev/blog/add-search-to-hugo-static-sites-with-lunr/).
- leverages [tinylytics](https://tinylytics.app/) for privacy-friendly analytics and cute kudos buttons.
- leverages [Cabin](https://withcabin.com) for [privacy-friendly](https://withcabin.com/privacy/runtimeterror.dev) analytics.
- resolves via [Bunny DNS](https://bunny.net/dns/).
- is published to / hosted by [Bunny CDN](https://bunny.net/cdn/) with a GitHub Actions workflow.
- has a [Gemini](https://geminiprotocol.net) mirror at `gemini://gmi.runtimeterror.dev`. This is generated from a [Hugo gemtext post layout](https://github.com/jbowdre/runtimeterror/blob/main/layouts/_default/single.gmi), deployed to a [Vultr](https://www.vultr.com/) VPS through a GitHub Actions workflow, and served with [Agate](https://github.com/mbrubeck/agate).
- is published to / hosted on [Bunny Storage](https://bunny.net/storage/) and [Bunny CDN](https://bunny.net/cdn/) with a [GitHub Actions workflow](//further-down-the-bunny-hole/)
- has a [Gemini](https://geminiprotocol.net) mirror at `gemini://gmi.runtimeterror.dev`. This is generated from a [Hugo gemtext post layout](https://github.com/jbowdre/runtimeterror/blob/main/layouts/_default/single.gmi), deployed to a [Vultr](https://www.vultr.com/) VPS through that same GitHub Actions workflow, and served with [Agate](https://github.com/mbrubeck/agate).
The post content is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/); the site code is under the [MIT License](https://github.com/jbowdre/runtimeterror/blob/main/LICENSE).
Look behind the scenes at [github.com/jbowdre/runtimeterror](https://github.com/jbowdre/runtimeterror).

View file

@ -1,7 +1,7 @@
---
title: "/homelab"
date: "2024-05-26T21:30:51Z"
lastmod: "2024-05-28"
lastmod: "2024-06-14T01:44:01Z"
aliases:
- playground
description: "The systems I use for fun and enrichment."
@ -36,6 +36,7 @@ The Proxmox cluster hosts a number of VMs and LXC containers:
- [Cyberchef](https://github.com/gchq/CyberChef), the Cyber Swiss Army Knife
- [Hashicorp Vault](https://www.vaultproject.io/) for secrets management
- [Miniflux](https://miniflux.app/) feed reader
- [RIPE Atlas Probe](https://www.ripe.net/analyse/internet-measurements/ripe-atlas/) for measuring internet connectivity
- [Tailscale Golink](https://github.com/tailscale/golink), a private shortlink service ([post](/tailscale-golink-private-shortlinks-tailnet/))
- `files`: Ubuntu 20.04 file server. Serves (selected) files semi-publicly through [Tailscale Funnel](/tailscale-ssh-serve-funnel/#tailscale-funnel)
- `hassos`: [Home Assistant OS](https://www.home-assistant.io/installation/), manages all my "smart home" stuff ([post](/automating-camera-notifications-home-assistant-ntfy/))
@ -78,6 +79,6 @@ I like to know what's flying overhead, and I'm also feeding flight data to [flig
- [Kineto](https://github.com/beelux/kineto) Gemini-to-HTTP proxy ([post](/gemini-capsule-gempost-github-actions/))
- [Linkding](https://github.com/sissbruecker/linkding) bookmark manager serving [links.bowdre.net](https://links.bowdre.net/bookmarks/shared)
- [ntfy](https://ntfy.sh/) notification service ([post](/easy-push-notifications-with-ntfy/))
- [SearXNG](https://docs.searxng.org/) self-hosted metasearch engine serving [grep.vpota.to](https://grep.vpota.to) ([post](https://scribbles.jbowdre.lol/post/self-hosting-a-search-engine-iyjdlk6y))
- [SearXNG](https://docs.searxng.org/) self-hosted metasearch engine serving [grep.vpota.to](https://grep.vpota.to) ([post](https://blog.jbowdre.lol/post/self-hosting-a-search-engine-iyjdlk6y))
- [Uptime Kuma](https://github.com/louislam/uptime-kuma) for monitoring internal services (via Tailscale)
- [vault-unseal](https://github.com/lrstanley/vault-unseal) to auto-unseal my on-prem Vault instance

View file

@ -1,7 +1,7 @@
---
title: "Blocking AI Crawlers"
date: 2024-04-12
lastmod: "2024-04-14T02:21:57Z"
lastmod: "2024-06-13T20:51:54Z"
description: "Using Hugo to politely ask AI bots to not steal my content - and then configuring Cloudflare's WAF to actively block them, just to be sure."
featured: false
toc: true
@ -24,7 +24,7 @@ robots = [
"AdsBot-Google",
"Amazonbot",
"anthropic-ai",
"Applebot",
"Applebot-Extended",
"AwarioRssBot",
"AwarioSmartBot",
"Bytespider",
@ -47,9 +47,6 @@ robots = [
"PerplexityBot",
"YouBot"
]
[author]
name = "John Bowdre"
```
I then created a new template in `layouts/robots.txt`:
@ -57,9 +54,14 @@ I then created a new template in `layouts/robots.txt`:
```text
Sitemap: {{ .Site.BaseURL }}/sitemap.xml
# hello robots [^_^]
# let's be friends <3
User-agent: *
Disallow:
{{ range .Site.Params.robots }}
# except for these bots which are not friends:
{{ range .Site.Params.bad_robots }}
User-agent: {{ . }}
{{- end }}
Disallow: /
@ -74,15 +76,20 @@ enableRobotsTXT = true
Now Hugo will generate the following `robots.txt` file for me:
```text
Sitemap: https://runtimeterror.dev//sitemap.xml
Sitemap: https://runtimeterror.dev/sitemap.xml
# hello robots [^_^]
# let's be friends <3
User-agent: *
Disallow:
# except for these bots which are not friends:
User-agent: AdsBot-Google
User-agent: Amazonbot
User-agent: anthropic-ai
User-agent: Applebot
User-agent: Applebot-Extended
User-agent: AwarioRssBot
User-agent: AwarioSmartBot
User-agent: Bytespider
@ -129,7 +136,7 @@ So I added a [WAF Custom Rule](https://developers.cloudflare.com/waf/custom-rule
Here's the expression I'm using:
```text
(http.user_agent contains "AdsBot-Google") or (http.user_agent contains "Amazonbot") or (http.user_agent contains "anthropic-ai") or (http.user_agent contains "Applebot") or (http.user_agent contains "AwarioRssBot") or (http.user_agent contains "AwarioSmartBot") or (http.user_agent contains "Bytespider") or (http.user_agent contains "CCBot") or (http.user_agent contains "ChatGPT-User") or (http.user_agent contains "ClaudeBot") or (http.user_agent contains "Claude-Web") or (http.user_agent contains "cohere-ai") or (http.user_agent contains "DataForSeoBot") or (http.user_agent contains "FacebookBot") or (http.user_agent contains "Google-Extended") or (http.user_agent contains "GoogleOther") or (http.user_agent contains "GPTBot") or (http.user_agent contains "ImagesiftBot") or (http.user_agent contains "magpie-crawler") or (http.user_agent contains "Meltwater") or (http.user_agent contains "omgili") or (http.user_agent contains "omgilibot") or (http.user_agent contains "peer39_crawler") or (http.user_agent contains "peer39_crawler/1.0") or (http.user_agent contains "PerplexityBot") or (http.user_agent contains "Seekr") or (http.user_agent contains "YouBot")
(http.user_agent contains "AdsBot-Google") or (http.user_agent contains "Amazonbot") or (http.user_agent contains "anthropic-ai") or (http.user_agent contains "Applebot-Extended") or (http.user_agent contains "AwarioRssBot") or (http.user_agent contains "AwarioSmartBot") or (http.user_agent contains "Bytespider") or (http.user_agent contains "CCBot") or (http.user_agent contains "ChatGPT-User") or (http.user_agent contains "ClaudeBot") or (http.user_agent contains "Claude-Web") or (http.user_agent contains "cohere-ai") or (http.user_agent contains "DataForSeoBot") or (http.user_agent contains "FacebookBot") or (http.user_agent contains "Google-Extended") or (http.user_agent contains "GoogleOther") or (http.user_agent contains "GPTBot") or (http.user_agent contains "ImagesiftBot") or (http.user_agent contains "magpie-crawler") or (http.user_agent contains "Meltwater") or (http.user_agent contains "omgili") or (http.user_agent contains "omgilibot") or (http.user_agent contains "peer39_crawler") or (http.user_agent contains "peer39_crawler/1.0") or (http.user_agent contains "PerplexityBot") or (http.user_agent contains "Seekr") or (http.user_agent contains "YouBot")
```
![Creating a custom WAF rule in Cloudflare's web UI](cloudflare-waf-rule.png)

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -14,7 +14,7 @@ tags:
- meta
- serverless
---
As I covered briefly [in a recent Scribble](https://scribbles.jbowdre.lol/post/near-realtime-weather-on-profile-lol-ku4yq-zr), I was inspired by the way [Kris's omg.lol page](https://kris.omg.lol/) displays realtime data from his [Weatherflow Tempest weather station](https://shop.weatherflow.com/products/tempest). I thought that was really neat and wanted to do the same on [my omg.lol page](https://jbowdre.lol) with data from my own Tempest, but I wanted to find a way to do it without needing to include an authenticated API call in the client-side JavaScript.
As I covered briefly [in a recent Scribble](https://blog.jbowdre.lol/post/near-realtime-weather-on-profile-lol-ku4yq-zr), I was inspired by the way [Kris's omg.lol page](https://kris.omg.lol/) displays realtime data from his [Weatherflow Tempest weather station](https://shop.weatherflow.com/products/tempest). I thought that was really neat and wanted to do the same on [my omg.lol page](https://jbowdre.lol) with data from my own Tempest, but I wanted to find a way to do it without needing to include an authenticated API call in the client-side JavaScript.
I realized I could use a GitHub Actions workflow to retrieve the data from the authenticated Tempest API, post it somewhere publicly accessible, and then have the client-side code fetch the data from there without needing any authentication. After a few days of tinkering, I came up with a presentation I'm happy with.

View file

@ -1,7 +1,7 @@
---
title: "Further Down the Bunny Hole"
date: 2024-06-06
# lastmod: 2024-06-06
lastmod: "2024-06-08T02:57:35Z"
description: "After a few weeks of weird configuration glitches, I decided to migrate my static site from Neocities to Bunny CDN. Here's how I did it."
featured: false
toc: true
@ -14,11 +14,11 @@ tags:
- meta
- selfhosting
---
It wasn't too long ago (January, in fact) that I started [hosting this site with Neocities](/deploy-hugo-neocities-github-actions/). I was pretty pleased with that setup, but a few weeks ago my [monitoring setup](https://scribbles.jbowdre.lol/post/upptime-serverless-server-monitoring-c88fbaz7) started reporting that the site was down. And sure enough, trying to access the site would return a generic error message stating that the site was unknown. I eventually discovered that this was due to Neocities "forgetting" that the site was linked to the `runtimeterror.dev` domain. It was easy enough to just re-enter that domain in the configuration, and that immediately fixed things... until a few days later when the same thing happened again.
It wasn't too long ago (January, in fact) that I started [hosting this site with Neocities](/deploy-hugo-neocities-github-actions/). I was pretty pleased with that setup, but a few weeks ago my [monitoring setup](https://blog.jbowdre.lol/post/upptime-serverless-server-monitoring-c88fbaz7) started reporting that the site was down. And sure enough, trying to access the site would return a generic error message stating that the site was unknown. I eventually discovered that this was due to Neocities "forgetting" that the site was linked to the `runtimeterror.dev` domain. It was easy enough to just re-enter that domain in the configuration, and that immediately fixed things... until a few days later when the same thing happened again.
The same problem has now occurred five or six times, and my messages to the Neocities support contact have gone unanswered. I didn't see anyone else online reporting this exact issue, but I found several posts on Reddit about sites getting randomly broken (or even deleted!) and support taking a week (or more) to reply. I don't have that kind of patience, so I started to consider moving my content away from Neocities and cancelling my $5/month Supporter subscription.
I [recently](https://scribbles.jbowdre.lol/post/i-just-hopped-to-bunny-net) started using [bunny.net](https://bunny.net) for the site's DNS, and had also [leveraged Bunny's CDN for hosting font files](/using-custom-font-hugo/). This setup has been working great for me, and I realized that I could also use Bunny's CDN for hosting the entirety of my static site as well. After all, serving static files on the web is exactly what a CDN is great at. After an hour or two of tinkering, I successfully switched hosting setups with just a few seconds of downtime.
I [recently](https://blog.jbowdre.lol/post/i-just-hopped-to-bunny-net) started using [bunny.net](https://bunny.net) for the site's DNS, and had also [leveraged Bunny's CDN for hosting font files](/using-custom-font-hugo/). This setup has been working great for me, and I realized that I could also use Bunny's CDN for hosting the entirety of my static site as well. After all, serving static files on the web is exactly what a CDN is great at. After an hour or two of tinkering, I successfully switched hosting setups with just a few seconds of downtime.
Here's how I did it.
@ -45,7 +45,7 @@ I found the [bunnycdn-storage-deploy](https://github.com/ayeressian/bunnycdn-sto
| `BUNNY_STORAGE_PASSWORD` | `7cb197e5-[...]-ad35820c0de8` | Get it from the storage zone's FTP & API Access page |
| `BUNNY_ZONE_ID` | `12345` | The pull zone ID you copied earlier |
Then I just updated [my deployment workflow](https://github.com/jbowdre/runtimeterror/blob/main/.github/workflows/deploy-prod.yml) to swap the Bunny action in place of the Neocities one:
Then I just updated [my deployment workflow](https://github.com/jbowdre/runtimeterror/blob/main/.github/workflows/deploy-prod.yml) to swap the Bunny action in place of the Neocities one (and [adjust the 404 page for bunny](https://support.bunny.net/hc/en-us/articles/360000332631-How-do-I-configure-a-custom-404-page-for-my-storage-zone)):
```yaml
# torchlight! {"lineNumbers":true}
@ -96,6 +96,14 @@ jobs:
chmod 644 ~/.ssh/known_hosts #
- name: Build with Hugo
run: HUGO_REMOTE_FONT_PATH=${{ secrets.REMOTE_FONT_PATH }} hugo --minify
- name: Insert 404 page # [tl! **:4]
run: | # [tl! ++:1,1 --:2,1]
mkdir -p public/bunnycdn_errors
cp public/404/index.html public/not_found.html
cp public/404/index.html public/bunnycdn_errors/404.html
- name: Insert 404 page # [tl! ++:-1,1 reindex(-1)]
run: |
cp public/404/index.html public/not_found.html
- name: Highlight with Torchlight
run: |
npm i @torchlight-api/torchlight-cli

View file

@ -0,0 +1,185 @@
---
title: "Kudos With Cabin"
date: 2024-06-24
lastmod: "2024-06-26T02:13:13Z"
description: "Using Cabin's event tracking to add a simple post upvote widget to my Hugo site."
featured: false
toc: true
reply: true
categories: Backstage
tags:
- hugo
- javascript
- meta
- selfhosting
---
I'm not one to really worry about page view metrics, but I do like to see which of my posts attract the most attention - and where that attention might be coming from. That insight has allowed me to find new blogs and sites that have linked to mine, and has tipped me off that maybe I should update that four-year-old post that's suddenly getting renewed traffic from Reddit.
In my quest for such knowledge, last week I switched my various web properties back to using [Cabin](https://withcabin.com/) for "privacy-first, carbon conscious web analytics". I really like how lightweight and deliberately minimal Cabin is, and the portal does a great job of presenting the information that I care about. With this change, though, I gave up the cute little upvote widgets provided by the previous analytics platform.
I recently shared [on my Bear weblog](https://blog.jbowdre.lol/tracking-bear-upvotes-from-my-cabin/) about how I was hijacking Bear's built-in upvote button to send a "kudos" [event](https://docs.withcabin.com/events.html) to Cabin and tally those actions there.
Well today I implemented a similar thing on *this* blog. Without an existing widget to hijack, I needed to create this one from scratch using a combination of HTML in my page template, CSS to style it, and JavaScript to fire the event.
### Layout
My [Hugo](https://gohugo.io/) setup uses `layouts/_default/single.html` to control how each post is rendered, and I've already got a section at the bottom which displays the "Reply by email" link if replies are permitted on that page:
```html
# torchlight! {"lineNumbers":true}
<div class="content__body"> <!-- [tl! reindex(33))] -->
{{ .Content }}
</div>
{{- $reply := true }}
{{- if eq .Site.Params.reply false }}
{{- $reply = false }}
{{- else if eq .Params.reply false }}
{{- $reply = false }}
{{- end }}
{{- if eq $reply true }}
<hr>
<span class="post_email_reply"><a href="mailto:replies@example.com?Subject=Re: {{ .Title }}">📧 Reply by email</a></span>
{{- end }}
```
I'll only want the upvote widget to appear on pages where replies are permitted so this makes a logical place to insert the new code:
```html
# torchlight! {"lineNumbers":true}
<div class="content__body"> <!-- [tl! reindex(33)] -->
{{ .Content }}
</div>
{{- $reply := true }}
{{- if eq .Site.Params.reply false }}
{{- $reply = false }}
{{- else if eq .Params.reply false }}
{{- $reply = false }}
{{- end }}
{{- if eq $reply true }}
<hr>
<div class="kudos-container"> <!-- [tl! ++:5 **:5] -->
<button class="kudos-button">
<span class="emoji">👍</span>
</button>
<span class="kudos-text">Enjoyed this?</span>
</div>
<span class="post_email_reply"><a href="mailto:replies@example.com?Subject=Re: {{ .Title }}">📧 Reply by email</a></span>
{{- end }}
```
The button won't actually do anything yet, but it'll at least appear on the page. I can use some CSS to make it look a bit nicer though.
### CSS
My theme uses `static/css/custom.css` to override the defaults, so I'll drop some styling bits at the bottom of that file:
```css
# torchlight! {"lineNumbers":true}
/* Cabin kudos styling [tl! reindex(406)] */
.kudos-container {
display: flex;
align-items: center;
}
.kudos-button {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
padding: 0;
margin-right: 0.25rem;
}
.kudos-button:disabled {
cursor: default;
}
.kudos-button .emoji {
display: inline-block;
transition: transform 0.3s ease;
}
.kudos-button.clicked .emoji {
transform: rotate(360deg);
}
.kudos-text {
transition: font-style 0.3s ease;
}
.kudos-text.thanks {
font-style: italic;
}
```
I got carried away a little bit and decided to add a fun animation when the button gets clicked. Which brings me to what happens when this thing gets clicked.
### JavaScript
I want the button to do a little bit more than *just* send the event to Cabin so I decided to break that out into a separate script, `assets/js/kudos.js`. This script will latch on to the kudos-related elements, and when the button gets clicked it will (1) fire off the `cabin.event('kudos')` function to record the event, (2) disable the button to discourage repeated clicks, (3) change the displayed text to `Thanks!`, and (4) celebrate the event by spinning the emoji and replacing it with a party popper.
```javascript
// torchlight! {"lineNumbers":true}
// manipulates the post upvote "kudos" button behavior
window.onload = function() {
// get the button and text elements
const kudosButton = document.querySelector('.kudos-button');
const kudosText = document.querySelector('.kudos-text');
const emojiSpan = kudosButton.querySelector('.emoji');
kudosButton.addEventListener('click', function(event) {
// send the event to Cabin
cabin.event('kudos')
// disable the button
kudosButton.disabled = true;
kudosButton.classList.add('clicked');
// change the displayed text
kudosText.textContent = 'Thanks!';
kudosText.classList.add('thanks');
// spin the emoji
emojiSpan.style.transform = 'rotate(360deg)';
// change the emoji to celebrate
setTimeout(function() {
emojiSpan.textContent = '🎉';
}, 150); // half of the css transition time for a smooth mid-rotation change
});
}
```
The last step is to go back to my `single.html` layout and pull in this new JavaScript file. I placed it in the site's `assets/` folder so that Hugo can apply its minifying magic so I'll need to load it in as a page resource:
```html
# torchlight! {"lineNumbers":true}
<div class="content__body"> <!-- [tl! reindex(33)] -->
{{ .Content }}
</div>
{{- $reply := true }} <!-- [tl! collapse:6] -->
{{- if eq .Site.Params.reply false }}
{{- $reply = false }}
{{- else if eq .Params.reply false }}
{{- $reply = false }}
{{- end }}
{{- if eq $reply true }}
<hr>
<div class="kudos-container">
<button class="kudos-button">
<span class="emoji">👍</span>
</button>
<span class="kudos-text">Enjoyed this?</span>
</div>
{{ $kudos := resources.Get "js/kudos.js" | minify }} <!-- [tl! ++:1 **:1] -->
<script src="{{ $kudos.RelPermalink }}"></script>
<span class="post_email_reply"><a href="mailto:replies@example.com?Subject=Re: {{ .Title }}">📧 Reply by email</a></span>
{{- end }}
```
You might have noticed that I'm not doing anything to display the upvote count on the page itself. I don't feel like the reader really needs to know how (un)popular a post may be before deciding to vote it up; the total count isn't really relevant. (Also, the Cabin stats don't update in realtime and I just didn't want to deal with that... but mostly that first bit.)
In any case, after clicking the 👍 button on a few pages I can see the `kudos` events recorded in my [Cabin portal](https://l.runtimeterror.dev/rterror-stats):
![A few hits against the 'kudos' event](kudos-in-cabin.png)
Go on, try it out:

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -180,7 +180,7 @@ That's getting there:
![A darker styled RSS page](getting-there-feed.png)
Including those CSS styles means that the rendered page now uses my color palette and the [font I worked so hard to integrate](/using-custom-font-hugo/). I'm just going to make a few more tweaks to change some of the formatting, put the `New to feeds?` bit on its own line, and point to [Mojeek](https://mojeek.com) instead of DDG ([why?](https://scribbles.jbowdre.lol/post/a-comprehensive-evaluation-of-various-search-engines-i-ve-used)).
Including those CSS styles means that the rendered page now uses my color palette and the [font I worked so hard to integrate](/using-custom-font-hugo/). I'm just going to make a few more tweaks to change some of the formatting, put the `New to feeds?` bit on its own line, and point to [Mojeek](https://mojeek.com) instead of DDG ([why?](https://blog.jbowdre.lol/post/a-comprehensive-evaluation-of-various-search-engines-i-ve-used)).
Here's my final (for now) `static/xml/feed.xsl` file:

View file

@ -9,8 +9,8 @@ categories: Tips # Projects, Code
tags:
- homelab
- networking
- proxmox
- tailscale
- vpn
---
I've spent the past two years in love with [Tailscale](https://tailscale.com/), which builds on the [secure and high-performance Wireguard VPN protocol](/cloud-based-wireguard-vpn-remote-homelab-access/) and makes it [really easy to configure and manage](/secure-networking-made-simple-with-tailscale/). Being able to easily (and securely) access remote devices as if they were on the same LAN is pretty awesome to begin with, but Tailscale is packed with an ever-expanding set of features that can really help to streamline your operations too. Here are three of my favorites.

View file

@ -1,7 +1,7 @@
---
title: "The Slash Page Scoop"
date: 2024-06-02
# lastmod: 2024-05-30
lastmod: "2024-07-04T02:23:41Z"
description: "I've added new slash pages to the site to share some background info on who I am, what I use, and how this site works."
featured: false
toc: true
@ -101,7 +101,7 @@ Of course, I'd like to include a link to [slashpages.net](https://slashpages.net
{{ if .IsHome }}
<h1>{{ site.Params.indexTitle | markdownify }}</h1>
{{ else }}
<h1>{{ .Title | markdownify }}{{ if eq .Kind "term" }}&nbsp;<a target="_blank" href="{{ .Permalink }}feed.xml" aria-label="Category RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1> <!-- [tl! ~~] -->
<h1>{{ .Title | markdownify }}{{ if eq .Kind "term" }}&nbsp;<a href="{{ .Permalink }}feed.xml" aria-label="Category RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1> <!-- [tl! ~~] -->
{{ with .Description }}<i>{{ . }}</i><hr>{{ else }}<br>{{ end }}
{{ end }}{{ end }}
{{ .Content }}
@ -122,9 +122,9 @@ Line 9 is where I had already modified the template to conditionally add an RSS
{{ else }}
{{ if eq .Title "/slashes" }} <!-- [tl! **:3 ++:3 ] -->
<h1>{{ .Title | markdownify }}</h1>
<i>My collection of <a target="_blank" title="what's a slashpage?" href="https://slashpages.net">slash pages</a>.</i><hr>
<i>My collection of <a title="what's a slashpage?" href="https://slashpages.net">slash pages</a>.</i><hr>
{{ else }}
<h1>{{ .Title | markdownify }}{{ if eq .Kind "term" }}&nbsp;<a target="_blank" href="{{ .Permalink }}feed.xml" aria-label="Category RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
<h1>{{ .Title | markdownify }}{{ if eq .Kind "term" }}&nbsp;<a href="{{ .Permalink }}feed.xml" aria-label="Category RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
{{ with .Description }}<i>{{ . }}</i><hr>{{ else }}<br>{{ end }}
{{ end }} <!-- [tl! ** ++ ] -->
{{ end }}{{ end }}

View file

@ -13,7 +13,7 @@ tags:
- meta
- tailscale
---
Last week, I came across and immediately fell in love with a delightfully-retro monospace font called [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/). I promptly purchased a "personal developer" license and set to work [applying the font in my IDE and terminal](https://scribbles.jbowdre.lol/post/trying-tabby-terminal). I didn't want to stop there, though; the license also permits me to use the font on my personal site, and Berkeley Mono will fit in beautifully with the whole runtimeterror aesthetic.
Last week, I came across and immediately fell in love with a delightfully-retro monospace font called [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/). I promptly purchased a "personal developer" license and set to work [applying the font in my IDE and terminal](https://blog.jbowdre.lol/post/trying-tabby-terminal). I didn't want to stop there, though; the license also permits me to use the font on my personal site, and Berkeley Mono will fit in beautifully with the whole runtimeterror aesthetic.
Well, you're looking at the slick new font here, and I'm about to tell you how I added the font both to the site itself and to the [dynamically-generated OpenGraph share images](/dynamic-opengraph-images-with-hugo/) setup. It wasn't terribly hard to implement, but the Hugo documentation is a bit light on how to do it (and I'm kind of inept at this whole web development thing).
@ -81,7 +81,7 @@ And that would work just fine... but it *would* require storing those web font f
So instead, I opted to try using a [Content Delivery Network (CDN)](https://en.wikipedia.org/wiki/Content_delivery_network) to host the font files. This would allow for some degree of access control, help me learn more about a web technology I hadn't played with much, and make use of a cool `cdn.*` subdomain in the process.
{{% notice note "Double the CDN, double the fun" %}}
Of course, while writing this post I gave in to my impulsive nature and [migrated the site from Cloudflare to Bunny.net](https://scribbles.jbowdre.lol/post/i-just-hopped-to-bunny-net). Rather than scrap the content I'd already written, I'll go ahead and describe how I set this up first on [Cloudflare R2](https://www.cloudflare.com/developer-platform/r2/) and later on [Bunny Storage](https://bunny.net/storage/).
Of course, while writing this post I gave in to my impulsive nature and [migrated the site from Cloudflare to Bunny.net](https://blog.jbowdre.lol/post/i-just-hopped-to-bunny-net). Rather than scrap the content I'd already written, I'll go ahead and describe how I set this up first on [Cloudflare R2](https://www.cloudflare.com/developer-platform/r2/) and later on [Bunny Storage](https://bunny.net/storage/).
{{% /notice %}}
#### Cloudflare R2

View file

@ -1,22 +1,4 @@
---
title: "/save"
date: "2024-05-28T00:25:51Z"
lastmod: "2024-05-28"
description: "Referral links for products and services I use and heartily recommend."
featured: false
toc: true
timeless: true
categories: slashes
type: redirect
target: https://blog.jbowdre.lol/save
---
*This `/saves` page lists my referral/affiliate links for high-quality products and services that I use on a daily basis. These are things I frequently recommend to others anyway, but signing up with these links might save one or both of us some money.*
### I use and recommend:
- **[Bunny.net](https://bunny.net?ref=0eh23p45xs)** DNS and CDN service that really hops
- **[Cloaked](https://join.cloaked.app/?utm_source=referral&utm_campaign=Ee83SGN8OR)** Protect your personal information by generating unique identities
- **[Fastmail](https://app.fastmail.com/signup/?STKI=/u29803368)** Fast, private email
- **[NextDNS](https://nextdns.io/?from=2jujzdcc)** Cloud-based DNS filtering
- **[omg.lol](https://home.omg.lol/referred-by/jbowdre)** The best web address you'll ever have
- **[Oura](https://ouraring.com/raf/e3b03b82b5)** A stylish ring to track your sleep and recovery
- **[Privacy.com](https://app.privacy.com/join/JMMQ7)** Unique merchant-locked cards for every online purchase
- **[Vultr](https://www.vultr.com/?ref=9488431)** Cost-effective cloud infrastructure

View file

@ -1,77 +1,4 @@
---
title: "/uses"
date: "2024-05-29"
lastmod: "2024-06-02"
description: "The hardware, software, services, and gear which I use (almost) daily."
toc: true
timeless: true
categories: slashes
type: redirect
target: https://blog.jbowdre.lol/uses
---
*Here's some of the stuff I use and how I use it.*
### Hardware
*Not counting my [homelab](/homelab).*
- **[Framework Laptop Chromebook Edition](https://frame.work/products/laptop-chromebook-12-gen-intel)** (i5-1240P | 32GB RAM | 1TB NVMe). Yep, it's an overpowered Chromebook, and my primary computing device. I make full use of the [ChromeOS Linux Development Environment](https://www.chromium.org/chromium-os/developer-library/guides/containers/containers-and-vms/), with [Nix](https://nixos.org/) for package management.
- **[Pixelbook](https://blog.google/products/pixelbook/introducing-pixelbook/)** running [NixOS](https://nixos.org/) for when I need a "real" Linux computer.
- **[BOOX Note Air3 C](https://shop.boox.com/products/noteair3) e-ink tablet** for reading and (hand)writing notes (more on this [here](https://scribbles.jbowdre.lol/post/boox-note-air-3-c-e-ink-writing-tablet)).
- **[Creality Ender 3 Pro 3D Printer](https://www.creality.com/products/ender-3-pro-3d-printer)**, or at least that's how it started. It's got a direct-drive conversion, a "silent" board running Klipper firmware, and more printed part upgrades than I can remember.
- **[Weatherflow Tempest Weather Station](https://shop.tempest.earth/products/tempest)** to help me get my Wx nerd on.
### Everyday Carry
*What has it got in its pocketses?*
- **[Flipper Zero](https://flipperzero.one/)** running [Momentum Firmware](https://momentum-fw.dev/) in my pocket or bag for on-the-go hacking and exploration.
- **[Leatherman FREE K4](https://www.leatherman.com/free-k4-590.html)** knife/multitool in my pocket for cutting and tinkering.
- **[Milky lactase tablets](https://shopmilky.com/)** in my wallet so I can enjoy dairy without consequences.
- **[Oura Ring](https://ouraring.com/product/rings/heritage)** (3rd generation, Heritage Black) on my middle finger for sleep and readiness/recovery tracking.
- **[Pixel 8 Pro](https://store.google.com/product/pixel_8_pro)** in my pocket, running [GrapheneOS](https://grapheneos.org/) as my daily-driver (more on how I use that [here](https://scribbles.jbowdre.lol/post/daily-driving-grapheneos)).
- **[Pixel Buds Pro](https://store.google.com/product/pixel_buds_pro)** in my ears, with noise cancelling so I don't have to acknowledge the world around me.
- **[Pixel Watch 2](https://store.google.com/product/pixel_watch_2)** on my wrist, for notifications and fitness tracking.
- **[ProxGrind RF Field Detector Card](https://www.redteamtools.com/RFID_LF_HF_Field_Detector_Card)** on my keychain to quickly learn about RFID/NFC readers.
- **[Ridge Wallet](https://ridge.com/products/aluminum-gunmetal)** in my pocket for keeping my cards handy.
- **[RovyVon Aurora A7 EDC Flashlight](https://www.rovyvon.com/collections/aurora-keychain-flashlights/products/aurora-a7-usb-c-gitd-sky-blue-keychain-flashlight-4th-generation)** in my pocket for keeping the darkness at bay.
- **[Ti EDC Backpack](https://bigidesign.com/pages/ti-edc-backpack-landing-page)** for carrying my stuff, and keeping it organized while in transit.
- **[Yubico Yubikey 5C NFC](https://www.yubico.com/product/yubikey-5c-nfc/)** on my keychain for hardware token things.
### Software
*Computer and web apps.*
- **[Calibre](https://calibre-ebook.com/)** for collecting, converting, and managing my eBooks.
- **[Fish shell](https://fishshell.com/)**, a really smart, modern, heavily configurable shell.
- **[Home Assistant](https://www.home-assistant.io/)** for controlling my "smart" home.
- **[Home Manager](https://github.com/nix-community/home-manager)** for managing packages and configurations across multiple systems ([dotfiles](https://github.com/jbowdre/dotfiles)).
- **[Immich](https://immich.app/)**, a self-hosted photo and video management solution.
- **[Linkding](https://github.com/sissbruecker/linkding)** as a self-hosted bookmark manager.
- **[Miniflux](https://miniflux.app/)**, a self-hosted minimalist feed reader.
- **[Obsidian](https://obsidian.md/)** for collecting/organizing notes. You can see some of them [here](https://notes.runtimeterror.dev/).
- **[Phanpy](https://phanpy.social/#/)**, a minimal and opinionated Mastodon web client.
- **[Tabby](https://tabby.sh/)**, a beautiful cross-platform terminal app.
- **[tmux](https://github.com/tmux/tmux)** because *I heard you like terminals so I put a terminal in your terminal so you can terminal while you terminal*.
- **[Vim](https://www.vim.org/)** for coding and development without a GUI.
- **[VSCode](https://code.visualstudio.com/)** for most coding and development.
### Android Apps
*Skipping the obvious ones for services mentioned elsewhere on this page...*
- **[Cheogram](https://play.google.com/store/apps/details?id=com.cheogram.android.playstore)** ([F-Droid](https://f-droid.org/packages/com.cheogram.android/)) XMPP client, with great integration to [jmp.chat](https://jmp.chat/).
- **[Element](https://play.google.com/store/apps/details?id=im.vector.app)** ([F-Droid](https://f-droid.org/en/packages/im.vector.app/)) Matrix chat client.
- **[Firefox Focus](https://play.google.com/store/apps/details?id=org.mozilla.focus)** Fast and private web browser for throw-away browsing sessions.
- **[Firefox](https://play.google.com/store/apps/details?id=org.mozilla.firefox)** for general web browsing.
- **[JBV1](https://play.google.com/store/apps/details?id=com.johnboysoftware.jbv1)** gives super powers to my Valentine One radar detector.
- **[Lagrange](https://skyjake.github.io/fdroid/repo/)** browser for [Gemini](https://geminiprotocol.net/).
- **[RaceBox](https://play.google.com/store/apps/details?id=pro.RaceBox.androidapp)** / **[RaceChrono](https://play.google.com/store/apps/details?id=com.racechrono.app)** for recording GPS/acceleration data during my [autocross runs](https://www.youtube.com/playlist?list=PLwzr4uKY-x-EwCv-rWNGefdikuW6Oy9O_).
- **[RadarScope](https://play.google.com/store/apps/details?id=com.basevelocity.radarscope)** weather radar and information.
- **[SimpleX Chat](https://play.google.com/store/apps/details?id=chat.simplex.app)** ([F-Droid](https://f-droid.org/en/packages/chat.simplex.app/)) for end-to-end encrypted chats without any user identifiers.
- **[Squoosh](https://squoosh.app/)** for compressing and EXIF-stripping photos before sharing.
- **[Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)** for automated profiles on my phone.
- **[WiFiman](https://play.google.com/store/apps/details?id=com.ubnt.usurvey)** for scanning a testing wireless networks.
- **[Yubico Authenticator](https://play.google.com/store/apps/details?id=com.yubico.yubioath)** for storing TOTP secrets on a hardware token.
### Services
*These may include affiliate links.*
- **[Cloaked](https://join.cloaked.app/?utm_source=referral&utm_campaign=Ee83SGN8OR)** for generating unique identies (email addresses + phone numbers) for every web sign-up.
- **[Fastmail](https://app.fastmail.com/signup/?STKI=/u29803368)** for fast, private email service with a ton of nice bonus features.
- **[Forward Email](https://forwardemail.net/)** for routing email to/from my various project domains.
- **[JMP.chat](https://jmp.chat/)** for a phone number backed by XMPP.
- **[NextDNS](https://nextdns.io/?from=2jujzdcc)** for privacy-protecting ad-blocking DNS filtering in the cloud.
- **[Obico](https://www.obico.io/)** for controlling and monitoring 3D prints.
- **[omg.lol](https://home.omg.lol/referred-by/jbowdre)** for some really handy web tools and one of the best communities of interesting people.
- **[Privacy.com](https://app.privacy.com/join/JMMQ7)** for creating virtual merchant-locked credit cards to keep me safe when shopping online.
- **[Tailscale](https://tailscale.com)** for connecting all my various systems and making them think that they're on the same LAN.

View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1708807242,
"narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=",
"lastModified": 1719254875,
"narHash": "sha256-ECni+IkwXjusHsm9Sexdtq8weAq/yUyt1TWIemXt3Ko=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a",
"rev": "2893f56de08021cffd9b6b6dfc70fd9ccd51eb60",
"type": "github"
},
"original": {

View file

@ -0,0 +1,9 @@
{{- $u := urls.Parse .Destination -}}
<a href="{{ .Destination | safeURL }}"
{{- with .Title }} title="{{ . }}"{{ end -}}
{{- if $u.IsAbs }} rel="external"{{ end -}}
>
{{- with .Text | safeHTML }}{{ . }}{{ end -}}
{{- if $u.IsAbs }}↗{{ end -}}
</a>
{{- /* chomp trailing newline */ -}}

View file

@ -46,11 +46,8 @@
---
{{ $subject := printf "Re: %s" .Title -}}
{{ $subject := urlquery $subject | replaceRE `\+` "%20" }}
{{ $path := .Page.RelPermalink | path.Dir -}}
{{ $path := strings.Trim $path "/" -}}
{{ $address := printf "blogreply.%s@%s" $path "runtimeterror.dev" -}}
=> mailto:{{ $address }}?subject={{ $subject }} 📧 Reply by email
=> mailto:wheel.east.brief@clkdmail.com?subject={{ $subject }} 📧 Reply by email
{{ $related := first 3 (where (where .Site.RegularPages.ByDate.Reverse ".Params.tags" "intersect" .Params.tags) "Permalink" "!=" .Permalink) }}
{{ if $related }}
## Related articles

View file

@ -39,17 +39,17 @@
{{- else if eq .Params.reply false }}
{{- $reply = false }}
{{- end }}
{{- if or (eq $reply true) (eq .Site.Params.analytics "true") }}
{{- if eq $reply true }}
<hr>
{{- if eq .Site.Params.analytics true }}
<span class="post_kudos"><button class="tinylytics_kudos"></button></span>
{{- end }}
{{- if (eq $reply true) }}
{{- $path := .Page.RelPermalink | path.Dir }}
{{- $path := strings.Trim $path "/" }}
{{- $address := printf "blogreply.%s@%s" $path "runtimeterror.dev" }}
<span class="post_email_reply"><a href="mailto:{{ $address }}?Subject=Re: {{ .Title }}">📧 Reply by email</a></span>
{{- end }}
<div class="kudos-container">
<button class="kudos-button">
<span class="emoji">👍</span>
</button>
<span class="kudos-text">Enjoyed this?</span>
</div>
{{ $kudos := resources.Get "js/kudos.js" | minify }}
<script src="{{ $kudos.RelPermalink }}"></script>
<span class="post_email_reply"><a href="mailto:wheel.east.brief@clkdmail.com?Subject=Re: {{ .Title }}">📧 Reply by email</a></span>
{{- end }}
<footer class="content__footer"></footer>
{{ end }}

View file

@ -1,16 +1,16 @@
{{ with .Site.Params.about }}
<div class="aside__about">
{{ with .logo }}<img class="about__logo" src="{{ . | absURL }}" alt="Logo">{{ end }}
<h1 class="about__title">{{ .title }}&nbsp;<a target="_blank" href="/feed.xml" aria-label="RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
<h1 class="about__title">{{ .title }}&nbsp;<a href="/feed.xml" aria-label="RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
{{ partial "tagline.html" . }}
<br>
<a href="about"><i class="fa-regular fa-user"></i></a>&nbsp;<a href="/about">{{ site.Params.Author.username }}</a>
<a href="/about/"><i class="fa-regular fa-user"></i></a>&nbsp;<a href="/about/">{{ site.Params.Author.name }}</a>
</div>
{{ end }}
<ul class="aside__social-links">
{{ range $item := .Site.Params.socialLinks }}
<li>
<a target="_blank" href="{{ $item.url | safeURL }}" rel="me" aria-label="{{ $item.title }}" title="{{ $item.title }}"><i class="{{ $item.icon }}" aria-hidden="true"></i></a>&nbsp;
<a href="{{ $item.url | safeURL }}" rel="me" aria-label="{{ $item.title }}" title="{{ $item.title }}"><i class="{{ $item.icon }}" aria-hidden="true"></i></a>&nbsp;
</li>
{{ end }}
</ul>

View file

@ -8,9 +8,9 @@
{{ else }}
{{ if eq .Title "/slashes" }}
<h1>{{ .Title | markdownify }}</h1>
<i>My collection of <a target="_blank" title="what's a slashpage?" href="https://slashpages.net">slash pages</a>.</i><hr>
<i>My collection of <a title="what's a slashpage?" href="https://slashpages.net">slash pages</a>.</i><hr>
{{ else }}
<h1>{{ .Title | markdownify }}{{ if eq .Kind "term" }}&nbsp;<a target="_blank" href="{{ .Permalink }}feed.xml" aria-label="{{ .Title }} RSS" title="{{ .Title }} RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
<h1>{{ .Title | markdownify | lower }}{{ if eq .Kind "term" }}&nbsp;<a href="{{ .Permalink }}feed.xml" aria-label="{{ .Title }} RSS" title="{{ .Title }} RSS"><i class="fa-solid fa-square-rss"></i></a>&nbsp;</h1>
{{ with .Description }}<i>{{ . }}</i>{{ end }}
{{ end }}
<hr>

View file

@ -21,7 +21,7 @@
{{ end }}
</ul>
{{ else }}
<h3>More {{ .Params.categories }}</h3>
<h3>more {{ lower .Params.categories }}</h3>
<ul>
{{- range first $relatedLimit $related }}
<li>
@ -42,7 +42,7 @@
{{- $featured := default 8 .Site.Params.numberOfFeaturedPosts }}
{{- $featuredPosts := first $featured (where $posts "Params.featured" true)}}
{{- with $featuredPosts }}
<h3>Featured Posts</h3>
<h3>features</h3>
<ul>
{{- range . }}
<li>
@ -55,9 +55,6 @@
<h3>status.lol</h3>
<script src="https://status.lol/jbowdre.js?time&link&fluent&pretty"></script>
<hr>
<script src="https://shoutouts.lol/embed/kz-49_TpBK2YexqL6Gyf.js" defer></script>
{{- if eq .Site.Params.analytics true }}
<hr>
<h3>webring</h3>
<a href="" target="_blank" class="tinylytics_webring" title="Tinylytics Webring">️🕸<img class="tinylytics_webring_avatar" src="" style="display: none"/>💍</a>
{{- end }}
<h3>latest track</h3>
<script src="https://recentfm.rknight.me/now.js?u=pushpianotire&e=🎶"></script>

View file

@ -1,6 +1,6 @@
<p class="copyright">{{ .Site.Copyright | markdownify }}</p>
<p class="footer_links">{"<a href="/slashes/" title="slashpages">/slashes</a>": [{{- range $i, $link := .Site.Params.slashPages }}{{ if $i }}, {{ end }}&quot;<a href="{{ $link.url }}" title="{{ $link.label }}">{{ $link.title }}</a>&quot;{{ end }}]}
<br>&lt;<a target="_blank" href="https://github.com/jbowdre/runtimeterror">view source</a>&gt;</p>
<span class="footer_slashes">{"<a href="/slashes/" title="slashpages">/slashes</a>": [{{- range $i, $link := .Site.Params.slashPages }}{{ if $i }}, {{ end }}&quot;<a href="{{ $link.url }}" title="{{ $link.label }}">{{ $link.title }}</a>&quot;{{ end }}]}</span>
<br><span class="copyright" xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/">{"copyright": ["content": "<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="license noopener noreferrer" title="Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International">CC BY-NC-SA 4.0↗</a>", "code": "<a href="https://github.com/jbowdre/runtimeterror/blob/main/LICENSE" rel="license noopener noreferrer" title="MIT License">MIT↗</a>"]}</span>
<br><span class="footer_links">&lt;<a href="https://github.com/jbowdre/runtimeterror">view source</a>&gt;</span>
<!-- Back to Top button via https://github.com/vfeskov/vanilla-back-to-top -->
{{ $jsToTop := resources.Get "js/back-to-top.js" | minify }}

View file

@ -1,5 +1,6 @@
<title>{{ with .Title }}{{ . }} &ndash; {{end}}{{ .Site.Title }}</title>
{{ with .Site.Params.about }}<meta name="description" content="{{ .description }}">{{ end }}
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
{{ with .Site.Params.Author.fedi }}<meta property="fediverse:creator" content="{{ . }}" />{{ end }}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"/>
@ -26,17 +27,17 @@
<!-- ForkAwesome <https://forkaweso.me/> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.2.0/css/fork-awesome.min.css" integrity="sha256-XoaMnoYC5TH6/+ihMEnospgm0J1PM/nioxbOUdnM8HY=" crossorigin="anonymous">
<!-- Academicons <https://jpswalsh.github.io/academicons/> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/academicons/1.9.1/css/academicons.min.css" integrity="sha512-b1ASx0WHgVFL5ZQhTgiPWX+68KjS38Jk87jg7pe+qC7q9YkEtFq0z7xCglv7qGIs/68d3mAp+StfC8WKC5SSAg==" crossorigin="anonymous" />
<!-- risotto theme -->
<link rel="stylesheet" href="{{ printf "css/palettes/%s.css" (.Site.Params.theme.palette | default "base16-dark") | absURL }}">
<link rel="stylesheet" href="{{ "css/risotto.css" | absURL }}">
<link rel="stylesheet" href="{{ "css/custom.css" | absURL }}">
<!-- CC BY-NC-SA 4.0 -->
<link rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">
{{ if eq .Site.Params.analytics true }}
<!-- tinylytics -->
<script src="https://tinylytics.app/embed/z4bwvaCBkF39NcDDLsRu.js?kudos=🎉&webring=avatars" defer></script>
<!-- cabin analytics -->
<script async defer src="https://cabin.runtimeterror.dev/hello.js"></script>
{{ end }}
<!-- syntax highlighting -->
@ -46,3 +47,7 @@
{{ $copyCss := resources.Get "css/code-copy-button.css" | minify }}
<link href="{{ $copyCss.RelPermalink }}" rel="stylesheet">
{{ end }}
<!-- typo text animation -->
{{ $jsTypo := resources.Get "js/typo.js" | minify }}
<script src="{{ $jsTypo.RelPermalink }}"></script>

View file

@ -3,7 +3,7 @@
<h1 class="page__logo"><a href="{{ .Site.BaseURL }}" class="page__logo-inner">{{ .Site.Title }}</a></h1>
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
<li class="main-nav__item"><a class="nav-main-item{{ if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (eq ($currentPage.Permalink) (.URL | absLangURL)) }} active{{end}}" href="{{ .URL | absLangURL }}" title="{{ .Title }}" target="{{ .Params.target }}">{{ .Name }}</a></li>
<li class="main-nav__item"><a class="nav-main-item{{ if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (eq ($currentPage.Permalink) (.URL | absLangURL)) }} active{{end}}" href="{{ .URL | absLangURL }}" title="{{ .Title }}">{{ .Name }}</a></li>
{{ end }}
</ul>
</nav>

View file

@ -4,9 +4,11 @@
<script language="Javascript" type="text/javascript">
var taglines = JSON.parse(document.getElementsByTagName("meta")["taglines"].content);
function getTagline() {
var randIndex = Math.floor(Math.random() * taglines.length);
document.getElementById("tagline").innerHTML = taglines[randIndex];
const randIndex = Math.floor(Math.random() * taglines.length);
const element = document.getElementById("tagline");
element.innerHTML = taglines[randIndex];
typo(element, element.innerHTML);
}
window.addEventListener("pageshow", getTagline);
</script>
<div><i id="tagline"></i></div>
<div id="tagline_container"><i id="tagline" data-typo-chance="2" data-typing-delay="40" data-typing-jitter="20"></i></div>

View file

@ -0,0 +1 @@
{{- template "_internal/alias.html" (dict "Permalink" .Params.target) -}}

View file

@ -1,8 +1,13 @@
Sitemap: {{ .Site.BaseURL }}/sitemap.xml
Sitemap: {{ .Site.BaseURL }}sitemap.xml
# hello robots [^_^]
# let's be friends <3
User-agent: *
Disallow:
{{ range .Site.Params.robots }}
# except for these bots which are not friends:
{{ range .Site.Params.bad_robots }}
User-agent: {{ . }}
{{- end }}
Disallow: /

View file

@ -14,11 +14,19 @@
url('https://cdn.runtimeterror.dev/fonts/BerkeleyMono-Regular.woff2') format('woff2'),
}
/* override page max-width */
/* minor layout tweaks */
.page {
max-width: 72rem;
}
.page__body {
margin: 1rem;
}
.page__aside {
margin: 0 1rem 0 0;
}
/* logo tweaks */
.page__logo {
@ -30,23 +38,32 @@
}
/* Footer tweaks */
.copyright {
.footer_slashes {
font-size: 14px;
line-height: 1.3rem;
line-height: 1.1rem;
color: var(--muted);
}
.footer_links {
.footer_slashes a:link, .footer_slashes a:visited {
color: var(--link);
text-decoration: none;
}
.footer_links, .copyright {
font-size: 12px;
line-height: 1.1rem;
color: var(--muted);
}
.footer_links a:link, .footer_links a:visited {
.footer_links a:link, .footer_links a:visited,
.copyright a:link, .copyright a:visited{
color: var(--off-fg);
text-decoration: none;
}
.footer_links a:hover {
.footer_links a:hover,
.footer_slashes a:hover,
.copyright a:hover {
color: var(--hover);
text-decoration: underline;
}
@ -221,6 +238,18 @@ small[style^="opacity: .5"] {
opacity: 1 !important;
}
/* recentfm styling */
.recent-played {
background: var(--off-bg) !important;
flex-direction:column;
border-radius: 0.5em;
padding: 0.5em;
}
.recent-played-track {
margin: 0.5em 0;
}
/* code overrides */
pre,
code,
@ -245,11 +274,15 @@ input {
background-color: var(--off-bg);
color: var(--off-fg);
height: 1.5rem;
border-radius: 0.25rem;
border-radius: 0.25rem 0 0 0.25rem;
padding-left: 0.5rem;
font-family: var(--font-monospace);
}
form {
width:auto;
}
input:focus {
outline: none;
}
@ -272,7 +305,6 @@ form button {
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
grid-gap: 0.5rem;
margin: 0.5rem 0;
}
.tagsArchive sup {
@ -293,53 +325,6 @@ blockquote {
padding-left: 0.25rem;
}
/* tinylytics styling*/
.post_kudos {
display: flex;
}
button.tinylytics_kudos {
border: 0;
background-color: transparent;
cursor: pointer;
display: flex;
color: var(--off-fg);
font-size: 1.2rem;
padding: 0;
transition: all .2s ease-in-out;
}
button.tinylytics_kudos:hover {
transform: scale(1.1);
text-shadow: var(--off-fg) 0 0 1px;
}
img.tinylytics_webring_avatar {
border-radius: 100%;
height: 2rem;
width: 2rem;
vertical-align:middle;
}
a.tinylytics_webring {
text-decoration: none;
font-size: 1.5rem;
}
/* shoutouts styling */
.shoutout .shoutout__container .shoutout__title {
font-size: 1rem;
margin: 0;
margin-bottom: 0.5rem;
font-weight: bold;
}
.shoutout .shoutout__container .shoutout__content {
font-style: italic;
font-size: 0.9rem;
line-height: 1.2rem;
}
/* post front matter styling*/
.frontmatter hr {
margin-bottom: 0rem;
@ -402,3 +387,66 @@ p:has(+ ol),
p:has(+ ul) {
margin-bottom: 0;
}
/* tagline tweaks */
#tagline_container {
height: 3rem;
}
#tagline {
font-size: 0.8rem;
line-height: 0.8rem;
}
#tagline::after {
content: "";
width: 0.5rem;
height: 1rem;
background: var(--muted);
display: inline-block;
margin-left: 0.2rem;
vertical-align: text-bottom;
animation: cursor-blink 1.5s steps(2) infinite;
}
@keyframes cursor-blink {
0% {
opacity: 0;
}
}
/* Cabin kudos styling */
.kudos-container {
display: flex;
align-items: center;
}
.kudos-button {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
padding: 0;
margin-right: 0.25rem;
}
.kudos-button:disabled {
cursor: default;
}
.kudos-button .emoji {
display: inline-block;
transition: transform 0.3s ease;
}
.kudos-button.clicked .emoji {
transform: rotate(360deg);
}
.kudos-text {
transition: font-style 0.3s ease;
}
.kudos-text.thanks {
font-style: italic;
}

View file

@ -1,6 +1,6 @@
{
"name": "",
"short_name": "",
"name": "runtimeterror",
"short_name": "runtimeterror",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
@ -13,7 +13,7 @@
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"theme_color": "#090909",
"background_color": "#090909",
"display": "standalone"
}

View file

@ -30,7 +30,7 @@ module.exports = {
// If there are any diff indicators for a line, put them
// in place of the line number to save horizontal space.
diffIndicatorsInPlaceOfLineNumbers: true,
diffIndicatorsInPlaceOfLineNumbers: false,
// When lines are collapsed, this is the text that will
// be shown to indicate that they can be expanded.