diff --git a/assets/js/typo.js b/assets/js/typo.js new file mode 100644 index 0000000..b1416e6 --- /dev/null +++ b/assets/js/typo.js @@ -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 we’re 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); +}); \ No newline at end of file diff --git a/content/changelog.md b/content/changelog.md index 3dcad73..dcc6567 100644 --- a/content/changelog.md +++ b/content/changelog.md @@ -1,7 +1,7 @@ --- title: "/changelog" date: "2024-05-26T21:19:08Z" -lastmod: "2024-06-07T22:41:44Z" +lastmod: "2024-06-13T16:20:54Z" description: "Maybe I should keep a log of all my site-related tinkering?" featured: false toc: false @@ -10,6 +10,9 @@ categories: slashes --- *High-level list of config/layout changes to the site.* +**2024-06-13:** +- Add [Typo](https://neatnik.net/typo/) and a blinking cursor to the random taglines in the sidebar + **2024-06-06:** - Migrate hosting from [Neocities to Bunny CDN](/further-down-the-bunny-hole/) diff --git a/content/colophon.md b/content/colophon.md index 3398b44..52f78e1 100644 --- a/content/colophon.md +++ b/content/colophon.md @@ -1,7 +1,7 @@ --- title: "/colophon" date: "2024-05-26T22:30:58Z" -lastmod: "2024-06-06" +lastmod: "2024-06-13T22:06:26Z" description: "There's a lot that goes into this site. Let me tell you how it works." featured: false toc: true @@ -17,7 +17,7 @@ categories: slashes - 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. - resolves via [Bunny DNS](https://bunny.net/dns/). -- is published to / hosted by [Bunny CDN](https://bunny.net/cdn/) with a GitHub Actions workflow. +- 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 a GitHub Actions workflow, and served with [Agate](https://github.com/mbrubeck/agate). diff --git a/content/posts/blocking-ai-crawlers/index.md b/content/posts/blocking-ai-crawlers/index.md index 1e9beb9..99d687a 100644 --- a/content/posts/blocking-ai-crawlers/index.md +++ b/content/posts/blocking-ai-crawlers/index.md @@ -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) diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 5ee02a5..00325a4 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -49,3 +49,7 @@ {{ $copyCss := resources.Get "css/code-copy-button.css" | minify }} {{ end }} + + +{{ $jsTypo := resources.Get "js/typo.js" | minify }} + \ No newline at end of file diff --git a/layouts/partials/tagline.html b/layouts/partials/tagline.html index 402662a..7a7c7fc 100644 --- a/layouts/partials/tagline.html +++ b/layouts/partials/tagline.html @@ -4,9 +4,11 @@ -