commit c1ee729671ed3850c7232968459b933f62417aac
Author: Sorrel <52061229+sorrelbri@users.noreply.github.com>
Date: Mon Oct 30 16:42:28 2023 -0400
init app
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca89f27
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+**/**.**~
+**/.DS_Store
+
diff --git a/README b/README
new file mode 100644
index 0000000..25812f1
--- /dev/null
+++ b/README
@@ -0,0 +1,112 @@
+
+ --------------
+ | λ.sorrel.dev |\
+ \--------------\ \
+ \______________\|
+
+it's my personal home page.
+it's for putting stuff on the web.
+
+it's written in racket and serves (mostly) static html pages. there's some little
+javascript in there - https://htmx.org/
+
+why's there some javascript? because javascript is still kinda important to me??
+mostly cause i think this htmx project is neat and allows me to do some cool things
+without sending several megabytes. and the pages still work without allowing scripts.
+
+so
+ ________________
+|----------------|
+| HOW IT'S BUILT |
+|----------------|
+ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
+it's a racket web server that uses xml library to generate some valid html.
+there's a dirty little dsl in there so that i can type some plain texty stuff and
+have my plain texty stuff get turned into sexprs and then into xexprs and then written
+as new-fangled html. typical lispy stuff.
+```
+(require web-server ; like so much of this module
+ xml)
+```
+that's all compiled and hosted in my friend maren's bedroom. it's all very cute.
+
+ _________________
+|-----------------|
+| WHAT'S ON THERE |
+|-----------------|
+ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
+so, yea, it's all running over there and it's serving up some html (with a little dash
+of javascript.) so what's on it?
+
+right now, the plan's for
+
+ ~UNSETTLED THOUGHTS~
+ it's like a blog. just thinking out loud. or talking about projects that aren't
+ ready to get their "tires kicked" yet
+
+ ~SETTLED THOUGHTS~
+ it's like a portfolio. links to stuff. deployed stuff. pictures of stuff. a
+ home for everything that's done enough to need a home.
+
+ ~THOUGHTS ABOUT WHO EVEN AM I~
+ yr typical about page, y'know with some biographical information, some
+ musings about life's meaning. some links to things i like or do as a
+ person with other people in the world.
+
+ ~some kinda tag system for things
+ ~some kinda apology for even serving you some javascript
+
+the structure of things is such that: there's no templating per se. if the request
+has the path prefix "/hx/" you just get little snippets of html. the htmx is there to
+stitch that stuff together. make sure yr browser insesrts the response where it
+belongs in the page and i wipe my hands of the the thing. otherwise you get a page
+stitched together from all the bits and bobs.
+
+so don't be surprised if yr response is just an ~unsettled thought~ without any header
+or footer or any of that fanciness.
+
+ _____________
+|-------------|
+| HOW TO POST |
+|-------------|
+ ¯¯¯¯¯¯¯¯¯¯¯¯¯
+remember up at the top of the README i said there was a DSL for this thing? i meant a
+domain specific language. the domain is hella specific. like just my particular web
+page specific. there's little grammars for each little part of it. i'll talk about
+those in a moment.
+
+for right now i'm going to talk about the posting. so you write up a little bit of
+plain text according to the grammar for the type of post yr going to do. how do you get
+the server to generate some stuff for you? that's where the second part of the process
+comes in. there's a little racket utility for that. if it were an ~unsettled thought~
+for example it might look something like:
+```
+$ mkpost -s source/source-file.txt -t public_html/blog/target-file.html
+```
+there's a couple things this'll do. the most obvious is that it takes yr plain text
+and it generates some html. you expected that. the other thing it does is it writes
+the name of that file to a little lookup table with all it's tags. [i haven't
+implemented this part yet]
+
+so that's simple enough. now for the
+
+ __________________________
+|--------------------------|
+| DOMAIN SPECIFIC LANGUAGE |
+|--------------------------|
+ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
+the main pages are just handwritten xexprs. not worth spending time on something you'll
+only write once.
+
+but for the posts:
+
+post = post type, tags
+
+post type = ( "blog" | "project" ) "\n" ;
+
+tags = { tag identifier, white space } "\n" ;
+tag identifier = "#", { alphabetic character}
+
+
+
+
diff --git a/sorrel.dev.rkt b/sorrel.dev.rkt
new file mode 100644
index 0000000..4b5ccd3
--- /dev/null
+++ b/sorrel.dev.rkt
@@ -0,0 +1,113 @@
+#lang racket
+
+(require net/url
+ xml
+ web-server/web-server
+ web-server/servlet-dispatch
+ web-server/dispatch
+ (prefix-in files: web-server/dispatchers/dispatch-files)
+ (prefix-in filter: web-server/dispatchers/dispatch-filter)
+ (prefix-in sequencer: web-server/dispatchers/dispatch-sequencer)
+ web-server/dispatchers/filesystem-map
+ web-server/http)
+
+(define (html-response content)
+ (response/full
+ 200
+ #"OK"
+ (current-seconds)
+ TEXT/HTML-MIME-TYPE
+ '()
+ (list content)))
+
+
+
+(define (xexpr-file->xml file)
+ (string->bytes/utf-8
+ (xexpr->string (read (open-input-file file)))))
+
+;;; httpx
+;; sends only the requested resource
+(define (about request)
+ (html-response (xexpr-file->xml "source/about.txt")))
+
+(define (home request)
+ (html-response (xexpr-file->xml "source/index.txt")))
+
+(define (404- request)
+ (html-response (xexpr-file->xml "source/not-found.txt")))
+
+(define (structures request structure-id)
+ (html-response (xexpr-file->xml "source/not-found.txt")))
+
+(define (index-structures request)
+ (html-response (xexpr-file->xml "source/not-found.txt")))
+
+(define (unstructures request structure-id)
+ (html-response (xexpr-file->xml "source/not-found.txt")))
+
+(define (index-unstructures request)
+ (html-response (xexpr-file->xml "source/not-found.txt")))
+
+(define-values (httpx-app reverse-httpx-uri)
+ (dispatch-rules
+ [("hx" "about") about]
+ [("hx" "home") home]
+ [("hx" "structures" (integer-arg)) structures]
+ [("hx" "structures") index-structures]
+ [("hx" "unstructures" (integer-arg)) unstructures]
+ [("hx" "unstructures") index-unstructures]
+ [else 404-]))
+
+;;; page-app
+;; constructs entire page for each response
+(define (make-page resource)
+ (string->bytes/utf-8
+ (xexpr->string
+ `(html ((lang "en"))
+ ,(read (open-input-file "source/head.txt"))
+ (body
+ ,(read (open-input-file "source/header.txt"))
+ ,(read (open-input-file resource)))))))
+
+(define (homepage request)
+ (html-response (make-page "source/index.txt")))
+(define (about-page request)
+ (html-response (make-page "source/about.txt")))
+
+(define-values (page-app reverse-page-uri)
+ (dispatch-rules
+ [("") homepage]
+ [("about") about-page]))
+
+
+;;; from /static
+(define url->path/static (make-url->path "static"))
+
+(define static-dispatcher
+ (files:make #:url->path (lambda (u)
+ (url->path/static
+ (struct-copy url u [path (cdr (url-path u))])))))
+
+
+;;; 404
+(define (not-found request)
+ (html-response (make-page "source/not-found.txt")))
+
+
+;;; server
+(define stop
+ (serve
+ #:dispatch (sequencer:make
+ (filter:make #rx"^/static/" static-dispatcher)
+ (dispatch/servlet #:regexp #rx"^/hx/" httpx-app)
+ (dispatch/servlet page-app)
+ (dispatch/servlet not-found)
+ )
+ #:listen-ip "127.0.0.1"
+ #:port 8000))
+
+(with-handlers ([exn:break? (lambda (e)
+ (stop))])
+ (sync/enable-break never-evt))
+
diff --git a/source/about.txt b/source/about.txt
new file mode 100644
index 0000000..0561359
--- /dev/null
+++ b/source/about.txt
@@ -0,0 +1,88 @@
+(main
+ (p "what do you want to know?")
+ (h1 "about sorrel")
+ (p "i'm a gay lil nerd who's learning everyday about community, and ecology,
+ and computer; and trying hard to be good for the world with my gay lil
+ friends.")
+ (section
+ (h2 "contact")
+ (p "- e - m- "
+ (span " a - i ")
+ (span " -l [ at ")
+ (span "] s - o- ")
+ (span "r - r- e - l [ d o")
+ (span "t ] d -e")
+ (span "-v is a good place to do the email"))
+ (p
+ "i have avoided 'online' for a long time, but i'm getting hip to it. i'll
+ add some more socials here for to socially network in the future,
+ promise."))
+ (section
+ (h2 "a bio or smth")
+ (p
+ "i am a little bug that was named after "
+ (a ((href "https://plants.usda.gov/home/plantProfile?symbol=OXOR"))
+ "a plant called sorrel")
+ " and i live in the mountains of southern appalachia on "
+ (span ((lang "chr")) "ᏣᎳᎩ")
+ " (tsalagi/cherokee) land. i live with some cats and a whole mess of plants
+ (most of them food) and a cordswain and i have sweet neighbors.")
+ (p
+ "i have never identified with the term 'hacker' but i have learned, in the
+ past few years, that i do like to orchestrate computer machines and i do
+ that with some cute friends at "
+ (a ((href "https://bunk.computer"))
+ "bunk computer club"))
+ (p "some random things i believe/ i like/ i am/etc (in no particular order")
+;; about me bullets
+ (ul
+ ;; language bullet
+ (li (p "i like linguistics a whole lot."
+ (a ((href "/tagged/conlang")
+ (hx-get "/hx/tagged/conlang")
+ (hx-target "nearest a")
+ ; swap for a tag? just a lil list inside a list?
+ (hx-swap "innerHTML"))
+ "i make languages (the human kind) for fun.")
+ "there's a whole bunch of people who do this! "
+ (a ((href "https://conlang.org/"))
+ "language creation society")
+ " i'm even working on a "
+ (a ((href "/tagged/latl")
+ (hx-get "/hx/tagged/latl")
+ (hx-target "nearest a")
+ (hx-swap "innerHTML"))
+ "language (computer) to make languages (human).")))
+ ;; mmt bullet
+ (li (p "mmt, but make it anarchist "
+ (small "and get rid of all that \"sovereignty\" stuff, ew")))
+ (li (p "the autistic-contiguous position: object-relations theory or gay
+ sex act?"
+ (small "a " (em "niche") " joke about having"
+ (a ((href "/tagged/brain")
+ (hx-get "/hx/tagged/brain")
+ (hx-target "nearest a")
+ (hx-swap "innerHTML"))
+ "a ~type~ of experience"))))
+ (li (p "being trans and gay"))
+ (li (p "א ⃝"))))
+ (section
+ (h2 "a very earnest disclaimer")
+ (p
+ "i like to talk about computation with ppl! but it is important for me that
+ you know: i am new to all this! i did not think software was ^for me^ and
+ then i learned some webdev and now i am down the rabbit hole.")
+ (p
+ "i'm not very good at many things. i don't even have a math education or.
+ have you ever met someone who *is* good at all this stuff?")
+ (p
+ "please be patient! with me and with everyone else you talk about things
+ you care about with.")
+ (p "let's hold our strong opinions loosely!")
+ (p "and first assume good faith from others!")
+ (br)
+ (br)
+ (p ((class "ascii but-normal-size"))
+ " ⃔‥̺⃝⃕")
+ (br)))
+
diff --git a/source/head.txt b/source/head.txt
new file mode 100644
index 0000000..af1b69d
--- /dev/null
+++ b/source/head.txt
@@ -0,0 +1,27 @@
+(head
+ (meta ((http-equiv "Content-Type")
+ (content "text/html; charset=utf-8")))
+ (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/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;
+}")
+ (script ((src "/static/htmx.min.js")
+ (defer "")
+ )))
diff --git a/source/header.txt b/source/header.txt
new file mode 100644
index 0000000..7abf1e7
--- /dev/null
+++ b/source/header.txt
@@ -0,0 +1,67 @@
+(header
+ (div
+ ((class "banner"))
+ (a
+ ((href "/")
+ (hx-get "/hx/home")
+ (hx-target "main")
+ (hx-swap "outerHTML"))
+ (p
+ ((class "ascii"))
+"▒▓░ home ░░▒ ▒▒▓█▒ ░▒
+░▒▒░▓▓ ▒▒▒▒▒▒▓█ ▒▓
+▒▒▓▓░▒▓ ▓█▓▓ ░▓
+ ▓▒▒ ▒▒ ░
+ ▒ ▒▓▒▓▒ ▒░ ▒░
+ ░░░░░░▒▒▒▓▒▓░ ░▒ ▒
+ ▓░▒▒▒░▒▒▒▒▒▓▓▓▓▓ ░░ ░
+ ▒▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓█ ░░ ░▒
+ ▓▒▒▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓ ░▒░░░░░░▒░ ▓▓ ▒
+ ▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓█▒▒░░▒░▒░░░░░▒░▒ ▒ ▒▒
+ ░▒▒▒▒▒▓▒▓ ▒▓▓▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒░░░░░░░░░░░▒ ▓▒ ░
+ ▒░░▒▓▓▒▒▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓█▓░▒▒▒▒▒░░▒░▒░░░░░░▒▓ █ ░ ▓▓
+░ ░░▓▒▒ ░ ▓░░░▒▓▒▓▒▓▓▓▓▓▓▓▓▒ ▓▓▒▓▓▓█▓▒▒▒▒░▒░░░░▒░▒░░░░▒░░▒░ ▓ ▓ ▓▒░░░░░▒
+░ ░▒▓▓ ▓ ▒ ▓░░▒▒▒▓▒▓▓▓▒▓▓▓▓▓▓▓▒ ▓▓▓▓█▓▒▒░▒▒▒▒░▒▒▒░░░░░░░░░░▒▒ ▓ ▒▒▒▒░▒░░▒░▓
+▒▒▒▓▓▒ ▒░░ ▒▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓█▒▓▒▒▒▒▒▒░▒▒▒░░▒░▒░░░░█▒ ▒▒▓▒ ▒▒▒▒▒▒░░░▒░░
+▒▒▓▓░ ░ ▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▒ ▓▓▒▒▒▓▒▒▒▒▒░▒░░░░░░░ ▓▓▒░ ░█▓ ▒▒▒▒▒▒░░░░░░
+░▓ ▒░▒ ▓▒▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓█▓████▓▒▒▒▒▓▓▓▓▓▓▓▒▒▒▒▒▒▓░ ▓▒ ▓▒▒ █▓▓▓▒▒▒▒░░░░░
+ ▒░ ▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓█▓█▓▒▒▒▒▓▒▒▒▒ ▓░ ▒▒ ▒▓▓▓▓▓▒▒▒░░░▒
+ ▓ ▓ ▓▓▓▓▓▒▓▒▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓ ░▓▓█▓▓▓▓▓▓▓▓▓▒▒▒▓ ░░ ▒█ ░▒▒▒▓▓▓▓▓▒▒▒▒▒
+ ░ ▒▓▓▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓ ▒██▒██▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ▓░ ▒▓▓▓▓▒▓▓▒▒▒▒░
+▒▒▒▒░▒▓ ▒ ▒▓▓▒▒▒▒▒▒▒░▒░░░▒▒▒▒▓ ▒███▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░▒▒▒░▒▓▓▓▓▒▓▒▒▒
+▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒░▒░░░░░░░░░░░░ ▒██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▒ ░░░░▒░░▒▒▒▒▒▒▒▒▒▒▓
+▒▒▒▒▒▒▒▒▓▒▓▒░░▒░░░▒░ ▒▒▒▒░░▒░░░░░░░░░▒ ▒▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒ ▓▒▒▒░▒░░▒▒▒▓▓▓▒▒▒▒▓
+▒▒▒▒▒▒▓▒▓▓▒▒▒▒▒▒▒▒▒▒▒░▓ ▒░░░░░▒░░░░░░░▒▒ ▒▒█▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░ ▒▒▒▒▒▒░░▒▒▒▒▓▓▒▓▒ ▒
+▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░░░░░░░░ ▒▓█▓█▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒░ ▒░░░▒▒▒▒▒▒▒▓▓▒▒▓ ▒░▒
+▒▒▓▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ░░░░░░░░▒▒░░░ ▒██▓▓▓▓▓█▓▒▓▓▓▓▓▓▒▒▒▒▒▓▒ ░░▒ ▓▒░▒▒▒▒▒▒▒▓▒▒▒ ▒ ▒▒▒
+▒▒▒▓▓█▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▓▒░░░░░░░░░▒ ▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒ ▒▒ ▒▒▒▒▒▒▒▓▓▓▒▒░░░░▓▓██
+▒▓▓▓█▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▓░░░░░▒░▒ ▓▓█▓▓▓▓▓▓▓██▓▓▓▓▓▓▒▒▒▓ ░▒ ▓▓▒▒▓▒▓▒▒░ ▓▓▓▓▓▓█▓
+▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ ░▓▓▒ ░░ ▓▓█▓▓▓▓▓▓▓▓██▒▓▒▒▒▒▓▓ ░▒ ▒▓▓ ░██▓▓▓▓▓▓▓▓▓▓
+▓▓▓█▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓ ▒ ░░ ▓▓█▓▓▓▓▓▓▓▓▓▓▒▒ ▒ ░▓ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
+▓▓▓▒▓▒▒▒▒▓▒▒▒▒▓ ▒▒░ ▒▓▓▓▓▓▓▓▓▓█▓ ▓▒ ░ ░ ░▒ ░░ ▒ █▓▓▓▓▓▓▓▓▓▓▓▓▓
+███▒▒▒▒▒▓░ ▒▓░░ ▒▓▓▓▓▓▓▓▓ ▓▒░ ▒░ ░▒ ░ ▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓
+ █▒▓▓ ▒▒▓▓▓▓▓▓▓▒ ▒░▓ ▒▒▓▓▓▓▓▒ ▒▒░ ▒ ▒▒ ▒ ░▓▓▓▓▓▓▓▓▓▓
+░▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓ ▓░ ░▓▒ ░▒░▒ ▓▒ ▓ ▒ ▓▓▓▓░
+░▓▒▓▓▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▓▒▓ ░░▓ ▒▒░ ▒ ▓░ ▒
+ ▓▒░ ▓▒▒▒█▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒ ▒░ ▓▒▓ ▒▒ ▒ ░
+ ░▒▓ ▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒ ▒░▓ ░▒░▒ ░░
+ ▒▓ ░▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒▒▒▒▓▒▓▓▓▒▒ ▓░ ░▒ ░▓ ")))
+ (nav
+ (a
+ ((href "/about")
+ (hx-get "/hx/about")
+ (hx-target "main")
+ (hx-swap "innerHTML"))
+ "about sorrel (the bitch who made this)")
+ (a
+ ((href "/unsettled")
+ (hx-get "/hx/unsettled")
+ (hx-target "main")
+ (hx-swap "innerHTML"))
+ "unsettled thoughts (something like a blog)")
+ (a
+ ((href "/settled")
+ (hx-get "/hx/settled")
+ (hx-target "main")
+ (hx-swap "innerHTML"))
+ "\"settled\" thoughts (projects built/in progress)")))
diff --git a/source/index.txt b/source/index.txt
new file mode 100644
index 0000000..793cee3
--- /dev/null
+++ b/source/index.txt
@@ -0,0 +1,30 @@
+(main
+ (noscript
+ (p "hey! yr not letting yr browser execute javascript served by my page.")
+ (p "that's cool!")
+ (p "(google?) browser as arbitrary code execution platform is one of the
+ weird, regrettable consequences of our political economy.")
+ (p "anyway, feel free to browse! yr experience won't be much different,
+ you'll just get bigger html blob.")
+ (p "the only js i deliver is "
+ (a ((href "https://htmx.org"))
+ "this little REST tool called htmx")
+ " if you want to see what that's about.")
+ (p "o! and if you want to hear/read "
+ (a ((href "/tagged/javascript"))
+ "what i have to say about javascript")
+ " you could do that maybe")
+ (p "/noscript"))
+ (p "hey! i'm sorrel.")
+ (p "(called like the plant up there)")
+ (p "this is my new-fangled website computer page on the world wide web. i had
+ a nice time building this little thing "
+ (a ((href "/this")
+ (hx-get "/hx/this")
+ (hx-target "closest a")
+ (hx-swap "inner HTML"))
+ "(how i build this little page.)"))
+ (p "i hope you have a nice time looking at things here.")
+ (p ((class "ascii but-normal-size"))
+"︿︿
+ 〰"))
diff --git a/source/not-found.txt b/source/not-found.txt
new file mode 100644
index 0000000..78d20c5
--- /dev/null
+++ b/source/not-found.txt
@@ -0,0 +1,3 @@
+(main
+ (h1 "404")
+ (p "hey, i couldn't find that. could ya try something else maybe?"))
diff --git a/static/htmx.min.js b/static/htmx.min.js
new file mode 100644
index 0000000..5ac9041
--- /dev/null
+++ b/static/htmx.min.js
@@ -0,0 +1 @@
+(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Y={onLoad:t,process:Pt,on:Z,off:K,trigger:fe,ajax:wr,find:E,findAll:f,closest:v,values:function(e,t){var r=nr(e,t||"post");return r.values},remove:U,addClass:B,removeClass:n,toggleClass:V,takeClass:j,defineExtension:qr,removeExtension:Hr,logAll:X,logNone:F,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false},parseInterval:d,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Y.config.wsBinaryType;return t},version:"1.9.6"};var r={addTriggerHandler:St,bodyContains:oe,canAccessLocalStorage:M,findThisElement:de,filterValues:lr,hasAttribute:o,getAttributeValue:ee,getClosestAttributeValue:re,getClosestMatch:c,getExpressionVars:xr,getHeaders:sr,getInputValues:nr,getInternalData:ie,getSwapSpecification:fr,getTriggerSpecs:Ze,getTarget:ge,makeFragment:l,mergeObjects:se,makeSettleInfo:T,oobSwap:ye,querySelectorExt:le,selectAndSwap:Fe,settleImmediately:Wt,shouldCancel:tt,triggerEvent:fe,triggerErrorEvent:ue,withExtensions:C};var b=["get","post","put","delete","patch"];var w=b.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}if(e.slice(-2)=="ms"){return parseFloat(e.slice(0,-2))||undefined}if(e.slice(-1)=="s"){return parseFloat(e.slice(0,-1))*1e3||undefined}if(e.slice(-1)=="m"){return parseFloat(e.slice(0,-1))*1e3*60||undefined}return parseFloat(e)||undefined}function Q(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function ee(e,t){return Q(e,t)||Q(e,"data-"+t)}function u(e){return e.parentElement}function te(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function O(e,t,r){var n=ee(t,r);var i=ee(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function re(t,r){var n=null;c(t,function(e){return n=O(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function q(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=te().createDocumentFragment()}return i}function H(e){return e.match(/