implement static cache
All checks were successful
ci/woodpecker/push/build-and-deploy Pipeline was successful

This commit is contained in:
sorrel 2024-01-17 21:01:09 -05:00
parent 2847291391
commit d78aa17404
6 changed files with 155 additions and 166 deletions

View file

@ -1,6 +1,15 @@
2024/01/15
- cache static logic
- START HERE:
- test caching everywhere
- add content in atom
2024/01/14
- fix stylesheets
-
- update all url references
- START HERE:
- add content in atom
- cache static logic
2024/01/13
- update atom-table-writing

1
.gitignore vendored
View file

@ -3,5 +3,6 @@
sorrel/**
deploy-dir/**
tool/**
static/**
publish-test/**
todo.txt

View file

@ -31,98 +31,180 @@
'()
(list content)))
;; bound path builders
;; extension is always #".txt" because source files are not well-formed racket,
;; but must be read and evaluated in this namespace
(define (make-path-no-param root-folder extension)
(lambda (t)
(path-add-extension
(if (and (non-empty-string? t) (not (eq? t "root")))
(build-path root-folder t "index")
(build-path root-folder "index"))
extension)))
(define (make-path-id-param root-folder extension)
(lambda (t id)
(path-add-extension
(build-path root-folder
(if (eq? t "root")
(if (eq? id "")
;; if "root/" something weird has happened
"index"
;; allows for loading "/about" and other root resources
(~a id))
(if
;; if url has trailing backslash, id will be an empty string
;; and should return index for that type
(or (not (string? id))
(non-empty-string? id))
(build-path t (~a id))
(build-path t "index"))))
extension)))
(define (make-source-path-no-param t)
((make-path-no-param "source" #".txt") t))
(define (make-source-path-id-param t id)
((make-path-id-param "source" #".txt") t id))
(define (make-static-path-no-param api-type t)
(let ([root-folder
(cond
[(eq? api-type 'httpx) "static/fragment"]
[(eq? api-type 'page) "static/page"]
[else (error "api-type ~a not recognized" api-type)])])
((make-path-no-param root-folder #".html") t)))
(define (make-static-path-id-param api-type t id)
(let ([root-folder
(cond
[(eq? api-type 'httpx) "static/fragment"]
[(eq? api-type 'page) "static/page"]
[else (error "api-type ~a not recognized" api-type)])])
((make-path-id-param root-folder #".html") t id)))
(define (static-file-res static-path)
(string->bytes/utf-8 (file->string static-path)))
;; wrapper for resource-processor
(define (wrapper-for-resource-processor static-path res-processor)
(if (not (file-exists? static-path))
(html-response (static-file-res static-path))
(res-processor)))
;; write static-file
;; return data that was written
(define (write-static-file static-path)
(lambda (data)
(let ([out-port (open-output-file static-path #:exists 'error)])
(if (port-try-file-lock? out-port 'exclusive)
(begin
(display data out-port)
(port-file-unlock out-port)
(close-output-port out-port)
data)
(error "couldn't obtain file lock on ~a" out-port)))))
(define (404-handler api-type)
(cond
[(eq? api-type 'httpx) (404-hx)]
[(eq? api-type 'page) (404-page)]
[else (error "api-type ~a not recognized" api-type)]))
;; takes a resource-type (corresponds to the REST resource and the system path or
;; an index resource in the source path)
;; and a resource-processor (-> file-handle . http-response)
;; and returns a valid html-response irrespective of hx/ type fragment of html or full page
(define respond-resource-with-processor
(lambda (type resource-processor)
;; bound path builders
;; extension is always #".txt" because source files are not well-formed racket,
;; but must be read and evaluated in this namespace
(define (make-res-path-no-param t)
(path-add-extension
(if (and (non-empty-string? t) (not (eq? t "root")))
(build-path "source" t "index")
(build-path "source" "index" ))
#".txt"))
(define (make-res-path-id-param t id)
(path-add-extension
(build-path "source" (if (eq? t "root")
(if (eq? id "")
;; if "root/" something weird has happened
"index"
;; allows for loading "/this" and other root resources
(~a id))
(if
;; if url has trailing backslash, id will be an empty string
;; and should return index for that type
(or (not (string? id))
(non-empty-string? id))
(build-path t (~a id))
(build-path t "index"))))
#".txt"))
(lambda (api-type res-type resource-processor)
;; this lambda will be called by dispatch-rules
;; (req) is the request object
(case-lambda
[(req) (let ([res-path (make-res-path-no-param type)])
[(req)
(let ([static-path (make-static-path-no-param api-type res-type)])
(if (file-exists? static-path)
(html-response (static-file-res static-path))
(let ([res-path (make-source-path-no-param res-type)])
(if (file-exists? res-path)
(html-response (resource-processor res-path))
(html-response (resource-processor "source/not-found.txt"))))]
(html-response
(resource-processor res-path (write-static-file static-path)))
(404-handler api-type)))))]
;; (req id) is (request object . url parameter)
[(req id) (let ([res-path (make-res-path-id-param type id)])
(if (file-exists? res-path)
(html-response (resource-processor res-path))
(html-response (resource-processor "source/not-found.txt"))))])))
[(req id)
(let ([static-path (make-static-path-id-param api-type res-type id)])
(if (file-exists? static-path)
(html-response (static-file-res static-path))
(let ([res-path (make-source-path-id-param res-type id)])
(if (file-exists? res-path)
(html-response (resource-processor res-path (write-static-file static-path)))
(404-handler api-type)))))])))
;;; httpx
;; sends only the requested resource
(define (xexpr-file->xml file)
(define (xexpr-file->res file static-write)
(string->bytes/utf-8
(xexpr->string (read (open-input-file file)))))
(static-write (xexpr-file->xml file))))
(define (xexpr-file->xml file)
(xexpr->string (read (open-input-file file))))
(define (404-hx request)
(html-response (xexpr-file->xml "source/not-found.txt")))
(define (404-hx)
(let ([static-path "static/fragment/not-found.html"])
(html-response
(if (file-exists? static-path)
(static-file-res static-path)
(xexpr-file->res "source/not-found.txt" (write-static-file static-path))))))
(define-values (httpx-app reverse-httpx-uri)
(dispatch-rules
[("hx" "home") (respond-resource-with-processor "root" xexpr-file->xml)]
[("hx" "settled") (respond-resource-with-processor "settled" xexpr-file->xml)]
[("hx" "settled" (integer-arg)) (respond-resource-with-processor "settled" xexpr-file->xml)]
[("hx" "unsettled") (respond-resource-with-processor "unsettled" xexpr-file->xml)]
[("hx" "unsettled" (integer-arg)) (respond-resource-with-processor "unsettled" xexpr-file->xml)]
[("hx" "tagged") (respond-resource-with-processor "tagged" xexpr-file->xml)]
[("hx" "tagged" (string-arg)) (respond-resource-with-processor "tagged" xexpr-file->xml)]
[("hx" (string-arg)) (respond-resource-with-processor "root" xexpr-file->xml)]
[else 404-hx]))
[("hx" "home") (respond-resource-with-processor 'httpx "root" xexpr-file->res)]
[("hx" "settled") (respond-resource-with-processor 'httpx "settled" xexpr-file->res)]
[("hx" "settled" (integer-arg)) (respond-resource-with-processor 'httpx "settled" xexpr-file->res)]
[("hx" "unsettled") (respond-resource-with-processor 'httpx "unsettled" xexpr-file->res)]
[("hx" "unsettled" (integer-arg)) (respond-resource-with-processor 'httpx "unsettled" xexpr-file->res)]
[("hx" "tagged") (respond-resource-with-processor 'httpx "tagged" xexpr-file->res)]
[("hx" "tagged" (string-arg)) (respond-resource-with-processor 'httpx "tagged" xexpr-file->res)]
[("hx" (string-arg)) (respond-resource-with-processor 'httpx "root" xexpr-file->res)]
[else (lambda (req) (404-hx))]))
;;; page-app
;; constructs entire page for each response
(define (make-page resource)
(define (make-page resource static-write)
(string->bytes/utf-8
(xexpr->string
(static-write
(fragment->page resource))))
(define (fragment->page resource)
(xexpr->string
`(html ((lang "en"))
,(read (open-input-file "source/head.txt"))
(body
,(read (open-input-file "source/header.txt"))
(main ,(read (open-input-file resource))))))))
(main ,(read (open-input-file resource)))))))
(define (404-page)
(let ([static-path "static/page/not-found.html"])
(html-response
(if (file-exists? static-path)
(static-file-res static-path)
(make-page "source/not-found.txt" (write-static-file static-path))))))
(define-values (page-app reverse-page-uri)
(dispatch-rules
[("") (respond-resource-with-processor "" make-page)]
[("home") (respond-resource-with-processor "" make-page)]
[("settled") (respond-resource-with-processor "settled" make-page)]
[("settled" (integer-arg)) (respond-resource-with-processor "settled" make-page)]
[("unsettled") (respond-resource-with-processor "unsettled" make-page)]
[("unsettled" (integer-arg)) (respond-resource-with-processor "unsettled" make-page)]
[("tagged") (respond-resource-with-processor "tagged" make-page)]
[("tagged" (string-arg)) (respond-resource-with-processor "tagged" make-page)]
[((string-arg)) (respond-resource-with-processor "root" make-page)]
[else not-found]))
[("") (respond-resource-with-processor 'page "" make-page)]
[("home") (respond-resource-with-processor 'page "" make-page)]
[("settled") (respond-resource-with-processor 'page "settled" make-page)]
[("settled" (integer-arg)) (respond-resource-with-processor 'page "settled" make-page)]
[("unsettled") (respond-resource-with-processor 'page "unsettled" make-page)]
[("unsettled" (integer-arg)) (respond-resource-with-processor 'page "unsettled" make-page)]
[("tagged") (respond-resource-with-processor 'page "tagged" make-page)]
[("tagged" (string-arg)) (respond-resource-with-processor 'page "tagged" make-page)]
[((string-arg)) (respond-resource-with-processor 'page "root" make-page)]
[else (lambda (req) (404-page))]))
@ -152,8 +234,7 @@
;;; 404
(define (not-found request)
(html-response (make-page "source/not-found.txt")))
(lambda (req) ((404-page))))
;;; server
(define stop

View file

@ -1 +0,0 @@
<section><h1>404</h1><p>hey, i couldn't find that. could ya try something else maybe?</p></section>

View file

@ -1,53 +0,0 @@
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta name="ROBOTS" content="NOINDEX, NOFOLLOW"/><title>learning and making things</title><meta name="author" content="sorrel"/><meta name="description" content="yr place for sorrel on the world wide web"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="ascii-art-description" content="it's a crop of the work 'Oxalis acetosella', Otto Wilhelm Thomé
(1885) run through the ascii-art generation tool on https://www.asciiart.eu/"/><link rel="stylesheet" href="/static/style/styles.css"/><style>
p.ascii {
font-family: 'Courier New', Courier, monospace;
font-size: 8px;
white-space: pre;
margin: 0;
}
p.ascii.but-normal-size {
font-size: medium;
}</style><script src="/static/script/htmx.min.js" defer=""></script></head><body><header><div class="banner" hx-get="/hx/home" hx-target="main" hx-swap="innerHTML"><p class="ascii">▒▓░ <a href="/">home</a> ░░▒ ▒▒▓█▒ ░▒
░▒▒░▓▓ ▒▒▒▒▒▒▓█ ▒▓
▒▒▓▓░▒▓ ▓█▓▓ ░▓
▓▒▒ ▒▒ ░
▒ ▒▓▒▓▒ ▒░ ▒░
░░░░░░▒▒▒▓▒▓░ ░▒ ▒
▓░▒▒▒░▒▒▒▒▒▓▓▓▓▓ ░░ ░
▒▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓█ ░░ ░▒
▓▒▒▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓ ░▒░░░░░░▒░ ▓▓ ▒
▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓█▒▒░░▒░▒░░░░░▒░▒ ▒ ▒▒
░▒▒▒▒▒▓▒▓ ▒▓▓▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒░░░░░░░░░░░▒ ▓▒ ░
▒░░▒▓▓▒▒▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒▒░░▒░▒░░░░░░▒▓ █ ░ ▓▓
░ ░░▓▒▒ ░ ▓░░░▒▓▒▓▒▓▓▓▓▓▓▓▓▒ ▓▓▒▓▓▓█▓▒▒▒▒░▒░░░░▒░▒░░░░▒░░▒░ ▓ ▓ ▓▒░░░░░▒
░ ░▒▓▓ ▓ ▒ ▓░░▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓▓▒ ▓▓▓▓█▓▒▒░▒▒▒▒░▒▒▒░░░░░░░░░░▒▒ ▓ ▒▒▒▒░▒░░▒░▓
▒▒▒▓▓▒ ▒░░ ▒▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓█▒▓▒▒▒▒▒▒░▒▒▒░░▒░▒░░░░█▒ ▒▒▓▒ ▒▒▒▒▒▒░░░▒░░
▒▒▓▓░ ░ ▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▒ ▓▓▒▒▒▓▒▒▒▒▒░▒░░░░░░░ ▓▓▒░ ░█▓ ▒▒▒▒▒▒░░░░░░
░▓ ▒░▒ ▓▒▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓█▓████▓▒▒▒▒▓▓▓▓▓▓▓▒▒▒▒▒▒▓░ ▓▒ ▓▒▒ █▓▓▓▒▒▒▒░░░░░
▒░ ▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓█▓█▓▒▒▒▒▓▒▒▒▒ ▓░ ▒▒ ▒▓▓▓▓▓▒▒▒░░░▒
▓ ▓ ▓▓▓▓▓▒▓▒▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓ ░▓▓█▓▓▓▓▓▓▓▓▓▒▒▒▓ ░░ ▒█ ░▒▒▒▓▓▓▓▓▒▒▒▒▒
░ ▒▓▓▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓ ▒██▒██▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ▓░ ▒▓▓▓▓▒▓▓▒▒▒▒░
▒▒▒▒░▒▓ ▒ ▒▓▓▒▒▒▒▒▒▒░▒░░░▒▒▒▒▓ ▒███▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░▒▒▒░▒▓▓▓▓▒▓▒▒▒
▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒░▒░░░░░░░░░░░░ ▒██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░░░░▒░░▒▒▒▒▒▒▒▒▒▒▓
▒▒▒▒▒▒▒▒▓▒▓▒░░▒░░░▒░ ▒▒▒▒░░▒░░░░░░░░░▒ ▒▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒ ▓▒▒▒░▒░░▒▒▒▓▓▓▒▒▒▒▓
▒▒▒▒▒▒▓▒▓▓▒▒▒▒▒▒▒▒▒▒▒░▓ ▒░░░░░▒░░░░░░░▒▒ ▒▒█▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░ ▒▒▒▒▒▒░░▒▒▒▒▓▓▒▓▒ ▒
▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░░░░░░░░ ▒▓█▓█▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒░ ▒░░░▒▒▒▒▒▒▒▓▓▒▒▓ ▒░▒
▒▒▓▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░▒▒░░░ ▒██▓▓▓▓▓█▓▒▓▓▓▓▓▓▒▒▒▒▒▓▒ ░░▒ ▓▒░▒▒▒▒▒▒▒▓▒▒▒ ▒ ▒▒▒
▒▒▒▓▓█▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▓▒░░░░░░░░░▒ ▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒ ▒▒ ▒▒▒▒▒▒▒▓▓▓▒▒░░░░▓▓██
▒▓▓▓█▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▓░░░░░▒░▒ ▓▓█▓▓▓▓▓▓▓██▓▓▓▓▓▓▒▒▒▓ ░▒ ▓▓▒▒▓▒▓▒▒░ ▓▓▓▓▓▓█▓
▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ ░▓▓▒ ░░ ▓▓█▓▓▓▓▓▓▓▓██▒▓▒▒▒▒▓▓ ░▒ ▒▓▓ ░██▓▓▓▓▓▓▓▓▓▓
▓▓▓█▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓ ▒ ░░ ▓▓█▓▓▓▓▓▓▓▓▓▓▒▒ ▒ ░▓ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▒▓▒▒▒▒▓▒▒▒▒▓ ▒▒░ ▒▓▓▓▓▓▓▓▓▓█▓ ▓▒ ░ ░ ░▒ ░░ ▒ █▓▓▓▓▓▓▓▓▓▓▓▓▓
███▒▒▒▒▒▓░ ▒▓░░ ▒▓▓▓▓▓▓▓▓ ▓▒░ ▒░ ░▒ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
█▒▓▓ ▒▒▓▓▓▓▓▓▓▒ ▒░▓ ▒▒▓▓▓▓▓▒ ▒▒░ ▒ ▒▒ ▒ ░▓▓▓▓▓▓▓▓▓▓
░▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓ ▓░ ░▓▒ ░▒░▒ ▓▒ ▓ ▒ ▓▓▓▓░
░▓▒▓▓▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓▒▓ ░░▓ ▒▒░ ▒ ▓░ ▒
▓▒░ ▓▒▒▒█▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▒░ ▓▒▓ ▒▒ ▒ ░
░▒▓ ▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▒░▓ ░▒░▒ ░░
▒▓ ░▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒▒▒▒▓▒▓▓▓▒▒ ▓░ ░▒ ░▓ </p></div><nav><a href="/about" hx-get="/hx/about" hx-target="main" hx-swap="innerHTML">about sorrel (the bitch who made this)</a><a href="/unsettled" hx-get="/hx/unsettled" hx-target="main" hx-swap="innerHTML">unsettled thoughts (something like a blog)</a><a href="/settled" hx-get="/hx/settled" hx-target="main" hx-swap="innerHTML">"settled" thoughts (projects built/in progress)</a><a href="/feed.atom">þͤ olde rss</a></nav></header><main aria-live="polite"><article><noscript><span class="noscript"><p>hey! yr not letting yr browser execute javascript served by my page.</p><p>that's cool!</p><p>browser (by google?) as arbitrary code execution platform is one of the
weird, regrettable (at least as it is currently implemented)
consequences of our political economy.</p><p>anyway, feel free to browse! yr experience won't be much different,
you'll just get bigger html blob.</p><p>the only js i deliver is <a href="https://htmx.org">this little REST tool called htmx</a> if you want to see what that's about.</p><p>o! and if you want to hear/read <a href="/tagged/javascript">what i have to say about javascript</a> you could do that maybe</p><p>/noscript</p></span></noscript><p>hey! i'm sorrel.</p><p>(called like the plant up there)</p><p>this is my new-fangled website computer page on the world wide web. i had
a nice time building this little thing <span class="hx-target"><a href="/this" hx-get="/hx/this" hx-target="closest span" hx-swap="innerHTML">(how i build this little page.)</a></span></p><p> i hope you have a nice time looking at things here.</p><p class="ascii but-normal-size">︿︿
</p></article></main></body></html>

View file

@ -1,48 +0,0 @@
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta name="ROBOTS" content="NOINDEX, NOFOLLOW"/><title>learning and making things</title><meta name="author" content="sorrel"/><meta name="description" content="yr place for sorrel on the world wide web"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="ascii-art-description" content="it's a crop of the work 'Oxalis acetosella', Otto Wilhelm Thomé
(1885) run through the ascii-art generation tool on https://www.asciiart.eu/"/><link rel="stylesheet" href="/static/style/styles.css"/><style>
p.ascii {
font-family: 'Courier New', Courier, monospace;
font-size: 8px;
white-space: pre;
margin: 0;
}
p.ascii.but-normal-size {
font-size: medium;
}</style><script src="/static/script/htmx.min.js" defer=""></script></head><body><header><div class="banner" hx-get="/hx/home" hx-target="main" hx-swap="innerHTML"><p class="ascii">▒▓░ <a href="/">home</a> ░░▒ ▒▒▓█▒ ░▒
░▒▒░▓▓ ▒▒▒▒▒▒▓█ ▒▓
▒▒▓▓░▒▓ ▓█▓▓ ░▓
▓▒▒ ▒▒ ░
▒ ▒▓▒▓▒ ▒░ ▒░
░░░░░░▒▒▒▓▒▓░ ░▒ ▒
▓░▒▒▒░▒▒▒▒▒▓▓▓▓▓ ░░ ░
▒▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓█ ░░ ░▒
▓▒▒▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓ ░▒░░░░░░▒░ ▓▓ ▒
▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓█▒▒░░▒░▒░░░░░▒░▒ ▒ ▒▒
░▒▒▒▒▒▓▒▓ ▒▓▓▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒░░░░░░░░░░░▒ ▓▒ ░
▒░░▒▓▓▒▒▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒▒░░▒░▒░░░░░░▒▓ █ ░ ▓▓
░ ░░▓▒▒ ░ ▓░░░▒▓▒▓▒▓▓▓▓▓▓▓▓▒ ▓▓▒▓▓▓█▓▒▒▒▒░▒░░░░▒░▒░░░░▒░░▒░ ▓ ▓ ▓▒░░░░░▒
░ ░▒▓▓ ▓ ▒ ▓░░▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓▓▒ ▓▓▓▓█▓▒▒░▒▒▒▒░▒▒▒░░░░░░░░░░▒▒ ▓ ▒▒▒▒░▒░░▒░▓
▒▒▒▓▓▒ ▒░░ ▒▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓█▒▓▒▒▒▒▒▒░▒▒▒░░▒░▒░░░░█▒ ▒▒▓▒ ▒▒▒▒▒▒░░░▒░░
▒▒▓▓░ ░ ▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▒ ▓▓▒▒▒▓▒▒▒▒▒░▒░░░░░░░ ▓▓▒░ ░█▓ ▒▒▒▒▒▒░░░░░░
░▓ ▒░▒ ▓▒▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓█▓████▓▒▒▒▒▓▓▓▓▓▓▓▒▒▒▒▒▒▓░ ▓▒ ▓▒▒ █▓▓▓▒▒▒▒░░░░░
▒░ ▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓█▓█▓▒▒▒▒▓▒▒▒▒ ▓░ ▒▒ ▒▓▓▓▓▓▒▒▒░░░▒
▓ ▓ ▓▓▓▓▓▒▓▒▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓ ░▓▓█▓▓▓▓▓▓▓▓▓▒▒▒▓ ░░ ▒█ ░▒▒▒▓▓▓▓▓▒▒▒▒▒
░ ▒▓▓▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓ ▒██▒██▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ▓░ ▒▓▓▓▓▒▓▓▒▒▒▒░
▒▒▒▒░▒▓ ▒ ▒▓▓▒▒▒▒▒▒▒░▒░░░▒▒▒▒▓ ▒███▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░▒▒▒░▒▓▓▓▓▒▓▒▒▒
▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒░▒░░░░░░░░░░░░ ▒██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░░░░▒░░▒▒▒▒▒▒▒▒▒▒▓
▒▒▒▒▒▒▒▒▓▒▓▒░░▒░░░▒░ ▒▒▒▒░░▒░░░░░░░░░▒ ▒▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒ ▓▒▒▒░▒░░▒▒▒▓▓▓▒▒▒▒▓
▒▒▒▒▒▒▓▒▓▓▒▒▒▒▒▒▒▒▒▒▒░▓ ▒░░░░░▒░░░░░░░▒▒ ▒▒█▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░ ▒▒▒▒▒▒░░▒▒▒▒▓▓▒▓▒ ▒
▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░░░░░░░░ ▒▓█▓█▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒░ ▒░░░▒▒▒▒▒▒▒▓▓▒▒▓ ▒░▒
▒▒▓▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░▒▒░░░ ▒██▓▓▓▓▓█▓▒▓▓▓▓▓▓▒▒▒▒▒▓▒ ░░▒ ▓▒░▒▒▒▒▒▒▒▓▒▒▒ ▒ ▒▒▒
▒▒▒▓▓█▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▓▒░░░░░░░░░▒ ▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒ ▒▒ ▒▒▒▒▒▒▒▓▓▓▒▒░░░░▓▓██
▒▓▓▓█▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▓░░░░░▒░▒ ▓▓█▓▓▓▓▓▓▓██▓▓▓▓▓▓▒▒▒▓ ░▒ ▓▓▒▒▓▒▓▒▒░ ▓▓▓▓▓▓█▓
▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ ░▓▓▒ ░░ ▓▓█▓▓▓▓▓▓▓▓██▒▓▒▒▒▒▓▓ ░▒ ▒▓▓ ░██▓▓▓▓▓▓▓▓▓▓
▓▓▓█▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓ ▒ ░░ ▓▓█▓▓▓▓▓▓▓▓▓▓▒▒ ▒ ░▓ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▒▓▒▒▒▒▓▒▒▒▒▓ ▒▒░ ▒▓▓▓▓▓▓▓▓▓█▓ ▓▒ ░ ░ ░▒ ░░ ▒ █▓▓▓▓▓▓▓▓▓▓▓▓▓
███▒▒▒▒▒▓░ ▒▓░░ ▒▓▓▓▓▓▓▓▓ ▓▒░ ▒░ ░▒ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
█▒▓▓ ▒▒▓▓▓▓▓▓▓▒ ▒░▓ ▒▒▓▓▓▓▓▒ ▒▒░ ▒ ▒▒ ▒ ░▓▓▓▓▓▓▓▓▓▓
░▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓ ▓░ ░▓▒ ░▒░▒ ▓▒ ▓ ▒ ▓▓▓▓░
░▓▒▓▓▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓▒▓ ░░▓ ▒▒░ ▒ ▓░ ▒
▓▒░ ▓▒▒▒█▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▒░ ▓▒▓ ▒▒ ▒ ░
░▒▓ ▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▒░▓ ░▒░▒ ░░
▒▓ ░▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒▒▒▒▓▒▓▓▓▒▒ ▓░ ░▒ ░▓ </p></div><nav><a href="/about" hx-get="/hx/about" hx-target="main" hx-swap="innerHTML">about sorrel (the bitch who made this)</a><a href="/unsettled" hx-get="/hx/unsettled" hx-target="main" hx-swap="innerHTML">unsettled thoughts (something like a blog)</a><a href="/settled" hx-get="/hx/settled" hx-target="main" hx-swap="innerHTML">"settled" thoughts (projects built/in progress)</a><a href="/feed.atom">þͤ olde rss</a></nav></header><main aria-live="polite"><section><h1>404</h1><p>hey, i couldn't find that. could ya try something else maybe?</p></section></main></body></html>