summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore6
-rw-r--r--agpl.c19
-rw-r--r--bin/.gitignore37
-rw-r--r--bin/LICENSE661
-rw-r--r--bin/Makefile142
-rw-r--r--bin/README.789
-rw-r--r--bin/beef.c141
-rw-r--r--bin/bibsort.pl69
-rw-r--r--bin/bit.y202
-rw-r--r--bin/c.sh121
-rw-r--r--bin/c11.l144
-rw-r--r--bin/dehtml.l150
-rw-r--r--bin/downgrade.c362
-rw-r--r--bin/dtch.c271
-rw-r--r--bin/enc.sh70
-rw-r--r--bin/ever.c119
-rw-r--r--bin/freecell.c388
-rw-r--r--bin/git-comment.pl92
-rw-r--r--bin/glitch.c605
-rw-r--r--bin/hilex.c406
-rw-r--r--bin/hilex.h50
-rw-r--r--bin/htagml.c223
-rw-r--r--bin/html.mk47
-rw-r--r--bin/html.sh66
-rw-r--r--bin/make.l127
-rw-r--r--bin/man1/beef.191
-rw-r--r--bin/man1/bibsort.140
-rw-r--r--bin/man1/bit.155
-rw-r--r--bin/man1/c.145
-rw-r--r--bin/man1/dehtml.138
-rw-r--r--bin/man1/downgrade.1122
-rw-r--r--bin/man1/dtch.167
-rw-r--r--bin/man1/enc.155
-rw-r--r--bin/man1/ever.151
-rw-r--r--bin/man1/git-comment.1117
-rw-r--r--bin/man1/glitch.177
-rw-r--r--bin/man1/hilex.1218
-rw-r--r--bin/man1/htagml.175
-rw-r--r--bin/man1/modem.131
-rw-r--r--bin/man1/mtags.176
-rw-r--r--bin/man1/nudge.144
-rw-r--r--bin/man1/order.138
-rw-r--r--bin/man1/pbd.166
-rw-r--r--bin/man1/pngo.164
-rw-r--r--bin/man1/psf2png.153
-rw-r--r--bin/man1/ptee.151
-rw-r--r--bin/man1/qf.171
-rw-r--r--bin/man1/quick.166
-rw-r--r--bin/man1/relay.148
-rw-r--r--bin/man1/scheme.159
-rw-r--r--bin/man1/shotty.1115
-rw-r--r--bin/man1/sup.151
-rw-r--r--bin/man1/title.151
-rw-r--r--bin/man1/up.177
-rw-r--r--bin/man1/when.1100
-rw-r--r--bin/man1/xx.168
-rw-r--r--bin/man3/png.390
-rw-r--r--bin/man6/freecell.650
-rw-r--r--bin/mdoc.l60
-rw-r--r--bin/modem.c102
-rw-r--r--bin/mtags.c105
-rw-r--r--bin/nudge.c78
-rw-r--r--bin/order.y195
-rw-r--r--bin/pbd.c151
-rw-r--r--bin/png.h108
-rw-r--r--bin/pngo.c941
-rw-r--r--bin/psf2png.c107
-rw-r--r--bin/ptee.c151
-rw-r--r--bin/qf.c294
-rw-r--r--bin/quick.c163
-rw-r--r--bin/relay.c218
-rw-r--r--bin/scheme.c278
-rw-r--r--bin/sh.l181
-rw-r--r--bin/shotty.l597
-rw-r--r--bin/sup.sh283
-rw-r--r--bin/title.c211
-rw-r--r--bin/up.sh94
-rw-r--r--bin/when.y353
-rw-r--r--bin/xx.c142
-rw-r--r--doc/rfc/.gitignore2
-rw-r--r--doc/rfc/Makefile38
-rw-r--r--doc/rfc/rfc.160
-rw-r--r--doc/rfc/rfc.in41
-rw-r--r--doc/rfc/rfctags.pl21
-rw-r--r--doc/zlib/Makefile87
-rw-r--r--doc/zlib/adler32.365
-rw-r--r--doc/zlib/adler32_combine.363
-rw-r--r--doc/zlib/compress.384
-rw-r--r--doc/zlib/compressBound.344
-rw-r--r--doc/zlib/crc32.366
-rw-r--r--doc/zlib/crc32_combine.354
-rw-r--r--doc/zlib/deflate.3370
-rw-r--r--doc/zlib/deflateBound.371
-rw-r--r--doc/zlib/deflateCopy.366
-rw-r--r--doc/zlib/deflateEnd.350
-rw-r--r--doc/zlib/deflateGetDictionary.379
-rw-r--r--doc/zlib/deflateInit.3178
-rw-r--r--doc/zlib/deflateInit2.3227
-rw-r--r--doc/zlib/deflateParams.3123
-rw-r--r--doc/zlib/deflatePending.356
-rw-r--r--doc/zlib/deflatePrime.364
-rw-r--r--doc/zlib/deflateReset.357
-rw-r--r--doc/zlib/deflateSetDictionary.3142
-rw-r--r--doc/zlib/deflateSetHeader.3180
-rw-r--r--doc/zlib/deflateTune.370
-rw-r--r--doc/zlib/gzbuffer.359
-rw-r--r--doc/zlib/gzclose.397
-rw-r--r--doc/zlib/gzdirect.385
-rw-r--r--doc/zlib/gzeof.363
-rw-r--r--doc/zlib/gzerror.375
-rw-r--r--doc/zlib/gzflush.373
-rw-r--r--doc/zlib/gzfread.3107
-rw-r--r--doc/zlib/gzfwrite.375
-rw-r--r--doc/zlib/gzgetc.355
-rw-r--r--doc/zlib/gzgets.367
-rw-r--r--doc/zlib/gzoffset.351
-rw-r--r--doc/zlib/gzopen.3261
-rw-r--r--doc/zlib/gzprintf.371
-rw-r--r--doc/zlib/gzputc.343
-rw-r--r--doc/zlib/gzputs.341
-rw-r--r--doc/zlib/gzread.3115
-rw-r--r--doc/zlib/gzseek.3108
-rw-r--r--doc/zlib/gzsetparams.351
-rw-r--r--doc/zlib/gzungetc.367
-rw-r--r--doc/zlib/gzwrite.339
-rw-r--r--doc/zlib/inflate.3398
-rw-r--r--doc/zlib/inflateBack.3285
-rw-r--r--doc/zlib/inflateBackEnd.343
-rw-r--r--doc/zlib/inflateBackInit.384
-rw-r--r--doc/zlib/inflateCopy.359
-rw-r--r--doc/zlib/inflateEnd.344
-rw-r--r--doc/zlib/inflateGetDictionary.369
-rw-r--r--doc/zlib/inflateGetHeader.3170
-rw-r--r--doc/zlib/inflateInit.3101
-rw-r--r--doc/zlib/inflateInit2.3181
-rw-r--r--doc/zlib/inflateMark.388
-rw-r--r--doc/zlib/inflatePrime.373
-rw-r--r--doc/zlib/inflateReset.381
-rw-r--r--doc/zlib/inflateSetDictionary.385
-rw-r--r--doc/zlib/inflateSync.372
-rw-r--r--doc/zlib/uncompress.392
-rw-r--r--doc/zlib/zlibCompileFlags.3163
-rw-r--r--doc/zlib/zlibVersion.345
-rw-r--r--etc/CodeQWERTY.bundle/Contents/Info.plist19
-rw-r--r--etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout1178
-rw-r--r--etc/Dark.terminal1654
-rw-r--r--etc/Go-Mono-Bold-Italic.ttfbin0 -> 176832 bytes
-rw-r--r--etc/Go-Mono-Bold.ttfbin0 -> 168340 bytes
-rw-r--r--etc/Go-Mono-Italic.ttfbin0 -> 173548 bytes
-rw-r--r--etc/Go-Mono.ttfbin0 -> 164200 bytes
-rw-r--r--etc/README.Go-Mono36
-rw-r--r--etc/code.map20
-rw-r--r--etc/daticns.c62
-rw-r--r--etc/layout.txt9
-rw-r--r--etc/wsconsctl.conf26
-rw-r--r--gpl.c19
-rw-r--r--home/.config/X/modmap16
-rw-r--r--home/.config/X/resources52
-rw-r--r--home/.config/cwm/cwmrc87
-rw-r--r--home/.config/git/config34
-rw-r--r--home/.config/git/ignore2
-rw-r--r--home/.config/htop/htoprc38
-rw-r--r--home/.editrc1
-rw-r--r--home/.gdbinit1
-rw-r--r--home/.hushlogin0
-rw-r--r--home/.inputrc1
-rw-r--r--home/.lldbinit1
-rwxr-xr-xhome/.local/bin/aes7
-rwxr-xr-xhome/.local/bin/clock17
-rwxr-xr-xhome/.local/bin/def47
-rwxr-xr-xhome/.local/bin/deg6
-rwxr-xr-xhome/.local/bin/git-password7
-rwxr-xr-xhome/.local/bin/mdate2
-rwxr-xr-xhome/.local/bin/mins4
-rwxr-xr-xhome/.local/bin/nasd14
-rwxr-xr-xhome/.local/bin/notify-send9
-rwxr-xr-xhome/.local/bin/np7
-rwxr-xr-xhome/.local/bin/open19
-rwxr-xr-xhome/.local/bin/pbcopy11
-rwxr-xr-xhome/.local/bin/pbpaste11
-rwxr-xr-xhome/.local/bin/versions9
-rwxr-xr-xhome/.local/bin/whinclude11
-rw-r--r--home/.profile28
-rw-r--r--home/.shrc54
-rw-r--r--home/.ssh/config17
-rw-r--r--home/.xsession14
-rw-r--r--install.sh39
-rw-r--r--link.sh23
-rw-r--r--port/caesar/.gitignore2
-rw-r--r--port/caesar/Makefile19
-rw-r--r--port/caesar/caesar.673
-rw-r--r--port/caesar/caesar.c159
-rw-r--r--port/caesar/rot13.sh33
-rw-r--r--port/cgram/.gitignore1
-rw-r--r--port/cgram/Makefile17
-rw-r--r--port/cgram/cgram.665
-rw-r--r--port/cgram/cgram.c344
-rw-r--r--port/cgram/pathnames.h30
-rw-r--r--port/ddate/.gitignore1
-rw-r--r--port/ddate/Makefile15
-rw-r--r--port/ddate/ddate.1117
-rw-r--r--port/ddate/ddate.c399
-rw-r--r--port/file2c/.gitignore1
-rw-r--r--port/file2c/Makefile15
-rw-r--r--port/file2c/file2c.175
-rw-r--r--port/file2c/file2c.c92
-rw-r--r--prune.sh7
-rw-r--r--txt/.notemap3
-rw-r--r--txt/books.txt171
-rw-r--r--txt/diminishing-shine.txt32
-rw-r--r--txt/music.txt294
-rw-r--r--txt/shows.txt34
-rw-r--r--txt/trouble-at-jinx-hotel.txt236
-rw-r--r--www/causal.agency/.gitignore4
-rw-r--r--www/causal.agency/Makefile23
-rw-r--r--www/causal.agency/alpha.html92
-rw-r--r--www/causal.agency/index.774
-rw-r--r--www/causal.agency/lands.html176
-rw-r--r--www/causal.agency/style.css28
-rw-r--r--www/git.causal.agency/.gitignore13
-rw-r--r--www/git.causal.agency/Makefile53
-rw-r--r--www/git.causal.agency/cgitrc30
-rw-r--r--www/git.causal.agency/custom.css86
-rw-r--r--www/git.causal.agency/filter.c158
-rw-r--r--www/git.causal.agency/index.781
-rw-r--r--www/photo.causal.agency/.gitignore2
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0832.txt (renamed from 2024-04-10/IMG_0832.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0850.txt (renamed from 2024-04-10/IMG_0850.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0852.txt (renamed from 2024-04-10/IMG_0852.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0858.txt (renamed from 2024-04-10/IMG_0858.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0859.txt (renamed from 2024-04-10/IMG_0859.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0865.txt (renamed from 2024-04-10/IMG_0865.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-10/IMG_0890.txt (renamed from 2024-04-10/IMG_0890.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-14/IMG_1054.txt (renamed from 2024-04-14/IMG_1054.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-14/IMG_1058.txt (renamed from 2024-04-14/IMG_1058.txt)0
-rw-r--r--www/photo.causal.agency/2024-04-14/IMG_1066.txt (renamed from 2024-04-14/IMG_1066.txt)0
-rw-r--r--www/photo.causal.agency/generate.sh (renamed from generate.sh)0
-rw-r--r--www/photo.causal.agency/rsync.sh (renamed from rsync.sh)0
-rw-r--r--www/temp.causal.agency/.gitignore1
-rw-r--r--www/temp.causal.agency/Makefile15
-rw-r--r--www/temp.causal.agency/up.c193
-rw-r--r--www/text.causal.agency/.gitignore4
-rw-r--r--www/text.causal.agency/001-make.7159
-rw-r--r--www/text.causal.agency/002-writing-mdoc.7138
-rw-r--r--www/text.causal.agency/003-pleasant-c.7120
-rw-r--r--www/text.causal.agency/004-uloc.764
-rw-r--r--www/text.causal.agency/005-testing-c.773
-rw-r--r--www/text.causal.agency/006-some-libs.796
-rw-r--r--www/text.causal.agency/007-cgit-setup.7271
-rw-r--r--www/text.causal.agency/008-how-irc.7193
-rw-r--r--www/text.causal.agency/009-casual-update.7127
-rw-r--r--www/text.causal.agency/010-irc-suite.7409
-rw-r--r--www/text.causal.agency/011-libretls.7220
-rw-r--r--www/text.causal.agency/012-inability.739
-rw-r--r--www/text.causal.agency/013-hot-tips.7156
-rw-r--r--www/text.causal.agency/014-using-vi.7135
-rw-r--r--www/text.causal.agency/015-reusing-tags.7155
-rw-r--r--www/text.causal.agency/016-using-openbsd.7505
-rw-r--r--www/text.causal.agency/017-unpasswords.7153
-rw-r--r--www/text.causal.agency/018-operating-systems.786
-rw-r--r--www/text.causal.agency/019-mailing-list.7286
-rw-r--r--www/text.causal.agency/020-c-style.7172
-rw-r--r--www/text.causal.agency/021-time-machine.7144
-rw-r--r--www/text.causal.agency/022-swans-are-dead.7164
-rw-r--r--www/text.causal.agency/023-sparse-checkout.7144
-rw-r--r--www/text.causal.agency/024-seprintf.7137
-rw-r--r--www/text.causal.agency/025-v6-pwd.7330
-rw-r--r--www/text.causal.agency/026-git-comment.7190
-rw-r--r--www/text.causal.agency/027-openbsd-linode.7202
-rw-r--r--www/text.causal.agency/028-names.781
-rw-r--r--www/text.causal.agency/029-topics.7116
-rw-r--r--www/text.causal.agency/030-discs.7114
-rw-r--r--www/text.causal.agency/031-books-2021.7127
-rw-r--r--www/text.causal.agency/032-albums-2021.7173
-rw-r--r--www/text.causal.agency/033-jorts.7485
-rw-r--r--www/text.causal.agency/034-voices.756
-rw-r--r--www/text.causal.agency/035-addendum-2021.7111
-rw-r--r--www/text.causal.agency/036-compassion.7105
-rw-r--r--www/text.causal.agency/037-care.7167
-rw-r--r--www/text.causal.agency/038-agency.785
-rw-r--r--www/text.causal.agency/039-apologies.781
-rw-r--r--www/text.causal.agency/040-sound-memory.7165
-rw-r--r--www/text.causal.agency/041-albums-2022.7185
-rw-r--r--www/text.causal.agency/042-comfort-music.762
-rw-r--r--www/text.causal.agency/043-little-blessings.778
-rw-r--r--www/text.causal.agency/Makefile66
-rw-r--r--www/text.causal.agency/colb.c16
-rw-r--r--www/text.causal.agency/feed.sh58
288 files changed, 31910 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index a5f66a9d..3bcf7b3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
-static/
-*.JPG
+/build
+/clone
+/git
+/trash
diff --git a/agpl.c b/agpl.c
new file mode 100644
index 00000000..e7682757
--- /dev/null
+++ b/agpl.c
@@ -0,0 +1,19 @@
+/* Copyright (C) 2024  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
diff --git a/bin/.gitignore b/bin/.gitignore
new file mode 100644
index 00000000..42269bac
--- /dev/null
+++ b/bin/.gitignore
@@ -0,0 +1,37 @@
+*.html
+*.o
+beef
+bibsort
+bit
+c
+config.mk
+dehtml
+downgrade
+dtch
+enc
+ever
+freecell
+git-comment
+glitch
+hilex
+htagml
+htmltags
+modem
+mtags
+nudge
+order
+pbd
+pngo
+psf2png
+ptee
+qf
+quick
+relay
+scheme
+shotty
+sup
+tags
+title
+up
+when
+xx
diff --git a/bin/LICENSE b/bin/LICENSE
new file mode 100644
index 00000000..dba13ed2
--- /dev/null
+++ b/bin/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 00000000..bb1535d6
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,142 @@
+PREFIX ?= ~/.local
+MANDIR ?= ${PREFIX}/share/man
+
+LIBS_PREFIX ?= /usr/local
+CFLAGS += -I${LIBS_PREFIX}/include
+LDFLAGS += -L${LIBS_PREFIX}/lib
+
+CFLAGS += -Wall -Wextra -Wpedantic -Wno-gnu-case-range
+
+BINS += beef
+BINS += bibsort
+BINS += bit
+BINS += c
+BINS += dehtml
+BINS += dtch
+BINS += enc
+BINS += git-comment
+BINS += glitch
+BINS += hilex
+BINS += htagml
+BINS += modem
+BINS += mtags
+BINS += nudge
+BINS += order
+BINS += pbd
+BINS += pngo
+BINS += psf2png
+BINS += ptee
+BINS += qf
+BINS += quick
+BINS += scheme
+BINS += shotty
+BINS += sup
+BINS += title
+BINS += up
+BINS += when
+BINS += xx
+
+BSD += ever
+
+GAMES += freecell
+
+TLS += downgrade
+TLS += relay
+
+MANS = ${BINS:%=man1/%.1}
+MANS.BSD = ${BSD:%=man1/%.1}
+MANS.GAMES = ${GAMES:%=man6/%.6}
+MANS.TLS = ${TLS:%=man1/%.1}
+
+LDLIBS.downgrade = -ltls
+LDLIBS.dtch = -lutil
+LDLIBS.fbclock = -lz
+LDLIBS.freecell = -lcurses
+LDLIBS.glitch = -lz
+LDLIBS.modem = -lutil
+LDLIBS.pngo = -lz
+LDLIBS.ptee = -lutil
+LDLIBS.qf = -lcurses
+LDLIBS.relay = -ltls
+LDLIBS.scheme = -lm
+LDLIBS.title = -lcurl
+LDLIBS.typer = -ltls
+
+ALL ?= meta any
+
+-include config.mk
+
+all: ${ALL}
+
+meta: .gitignore tags
+
+any: ${BINS}
+
+bsd: ${BSD}
+
+games: ${GAMES}
+
+tls: ${TLS}
+
+IGNORE = *.o *.html
+IGNORE += ${BINS} ${BSD} ${GAMES} ${TLS}
+IGNORE += tags htmltags
+
+.gitignore: Makefile
+	echo config.mk '${IGNORE}' | tr ' ' '\n' | sort > $@
+
+tags: *.[chly]
+	ctags -w *.[chly]
+
+clean:
+	rm -f ${IGNORE}
+
+install: ${ALL:%=install-%}
+
+install-meta:
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+
+install-any: install-meta ${BINS} ${MANS}
+	install ${BINS} ${PREFIX}/bin
+	install -m 644 ${MANS} ${MANDIR}/man1
+
+install-bsd: install-meta ${BSD} ${MANS.BSD}
+	install ${BSD} ${PREFIX}/bin
+	install -m 644 ${MANS.BSD} ${MANDIR}/man1
+
+install-games: install-meta ${GAMES} ${MANS.GAMES}
+	install ${GAMES} ${PREFIX}/bin
+	install -m 644 ${MANS.GAMES} ${MANDIR}/man6
+
+install-tls: install-meta ${TLS} ${MANS.TLS}
+	install ${TLS} ${PREFIX}/bin
+	install -m 644 ${MANS.TLS} ${MANDIR}/man1
+
+uninstall:
+	rm -f ${BINS:%=${PREFIX}/bin/%} ${MANS:%=${MANDIR}/%}
+	rm -f ${BSD:%=${PREFIX}/bin/%} ${MANS.BSD:%=${MANDIR}/%}
+	rm -f ${GAMES:%=${PREFIX}/bin/%} ${MANS.GAMES:%=${MANDIR}/%}
+	rm -f ${TLS:%=${PREFIX}/bin/%} ${MANS.TLS:%=${MANDIR}/%}
+
+.SUFFIXES: .pl
+
+.c:
+	${CC} ${CFLAGS} ${LDFLAGS} $< ${LDLIBS.$@} -o $@
+
+.o:
+	${CC} ${LDFLAGS} $< ${LDLIBS.$@} -o $@
+
+.pl:
+	cp -f $< $@
+	chmod a+x $@
+
+OBJS.hilex = c11.o hilex.o make.o mdoc.o sh.o
+
+hilex: ${OBJS.hilex}
+	${CC} ${LDFLAGS} ${OBJS.$@} ${LDLIBS.$@} -o $@
+
+${OBJS.hilex}: hilex.h
+
+psf2png.o scheme.o: png.h
+
+include html.mk
diff --git a/bin/README.7 b/bin/README.7
new file mode 100644
index 00000000..100e183e
--- /dev/null
+++ b/bin/README.7
@@ -0,0 +1,89 @@
+.Dd June  2, 2022
+.Dt BIN 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm bin
+.Nd various utilities
+.
+.Sh DESCRIPTION
+Various tools primarily targeting
+.Fx ,
+.Ox
+and macOS.
+.
+.Pp
+.Bl -tag -width "git-comment(1)" -compact
+.It Xr beef 1
+Befunge-93 interpreter
+.It Xr bibsort 1
+reformat bibliography
+.It Xr bit 1
+calculator
+.It Xr c 1
+run C statements
+.It Xr dehtml 1
+extract text from HTML
+.It Xr downgrade 1
+IRC features for all
+.It Xr dtch 1
+detached sessions
+.It Xr enc 1
+encrypt and decrypt files
+.It Xr ever 1
+watch files
+.It Xr freecell 6
+patience game
+.It Xr git-comment 1
+add commit comments
+.It Xr glitch 1
+PNG glitcher
+.It Xr hilex 1
+syntax highlighter
+.It Xr htagml 1
+tags HTMLizer
+.It Xr modem 1
+fixed baud rate wrapper
+.It Xr mtags 1
+miscellaneous tags
+.It Xr nudge 1
+terminal vibrator
+.It Xr order 1
+operator precedence
+.It Xr pbd 1
+macOS pasteboard daemon
+.It Xr pngo 1
+PNG optimizer
+.It Xr psf2png 1
+PSF2 to PNG renderer
+.It Xr ptee 1
+tee for PTYs
+.It Xr qf 1
+grep pager
+.It Xr quick 1
+terrible HTTP/CGI server
+.It Xr relay 1
+IRC relay bot
+.It Xr scheme 1
+color scheme
+.It Xr shotty 1
+terminal capture
+.It Xr sup 1
+single-use passwords
+.It Xr title 1
+page titles
+.It Xr up 1
+upload file
+.It Xr when 1
+date calculator
+.It Xr xx 1
+hexdump
+.El
+.
+.Pp
+One piece of reused code.
+.Pp
+.Bl -tag -width "png(3)" -compact
+.It Xr png 3
+basic PNG output
+.El
diff --git a/bin/beef.c b/bin/beef.c
new file mode 100644
index 00000000..556f3088
--- /dev/null
+++ b/bin/beef.c
@@ -0,0 +1,141 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+enum {
+	Cols = 80,
+	Rows = 25,
+};
+static char page[Rows][Cols];
+
+static char get(int y, int x) {
+	if (y < 0 || y >= Rows) return 0;
+	if (x < 0 || x >= Cols) return 0;
+	return page[y][x];
+}
+static void put(int y, int x, char v) {
+	if (y < 0 || y >= Rows) return;
+	if (x < 0 || x >= Cols) return;
+	page[y][x] = v;
+}
+
+enum { StackLen = 1024 };
+static long stack[StackLen];
+static size_t top = StackLen;
+
+static void push(long val) {
+	if (!top) errx(EX_SOFTWARE, "stack overflow");
+	stack[--top] = val;
+}
+static long pop(void) {
+	if (top == StackLen) return 0;
+	return stack[top++];
+}
+
+static struct {
+	int y, x;
+	int dy, dx;
+} pc = { .dx = 1 };
+
+static void inc(void) {
+	pc.y += pc.dy;
+	pc.x += pc.dx;
+	if (pc.y == -1) pc.y += Rows;
+	if (pc.x == -1) pc.x += Cols;
+	if (pc.y == Rows) pc.y -= Rows;
+	if (pc.x == Cols) pc.x -= Cols;
+}
+
+static bool string;
+
+static bool step(void) {
+	char ch = page[pc.y][pc.x];
+
+	if (ch == '"') {
+		string ^= true;
+	} else if (string) {
+		push(ch);
+		inc();
+		return true;
+	}
+
+	if (ch == '?') ch = "><^v"[rand() % 4];
+
+	long x, y, v;
+	switch (ch) {
+		break; case '+': push(pop() + pop());
+		break; case '-': y = pop(); x = pop(); push(x - y);
+		break; case '*': push(pop() * pop());
+		break; case '/': y = pop(); x = pop(); push(x / y);
+		break; case '%': y = pop(); x = pop(); push(x % y);
+		break; case '!': push(!pop());
+		break; case '`': y = pop(); x = pop(); push(x > y);
+		break; case '>': pc.dy = 0; pc.dx = +1;
+		break; case '<': pc.dy = 0; pc.dx = -1;
+		break; case '^': pc.dy = -1; pc.dx = 0;
+		break; case 'v': pc.dy = +1; pc.dx = 0;
+		break; case '_': pc.dy = 0; pc.dx = (pop() ? -1 : +1);
+		break; case '|': pc.dx = 0; pc.dy = (pop() ? -1 : +1);
+		break; case ':': x = pop(); push(x); push(x);
+		break; case '\\': y = pop(); x = pop(); push(y); push(x);
+		break; case '$': pop();
+		break; case '.': printf("%ld ", pop()); fflush(stdout);
+		break; case ',': printf("%c", (char)pop()); fflush(stdout);
+		break; case '#': inc();
+		break; case 'g': y = pop(); x = pop(); push(get(y, x));
+		break; case 'p': y = pop(); x = pop(); v = pop(); put(y, x, v);
+		break; case '&': x = EOF; scanf("%ld", &x); push(x);
+		break; case '~': push(getchar());
+		break; case '@': return false;
+		break; default:  if (ch >= '0' && ch <= '9') push(ch - '0');
+	}
+
+	inc();
+	return true;
+}
+
+int main(int argc, char *argv[]) {
+	srand(time(NULL));
+	memset(page, ' ', sizeof(page));
+
+	FILE *file = stdin;
+	if (argc > 1) {
+		file = fopen(argv[1], "r");
+		if (!file) err(EX_NOINPUT, "%s", argv[1]);
+	}
+
+	int y = 0;
+	char *line = NULL;
+	size_t cap = 0;
+	while (y < Rows && 0 < getline(&line, &cap, file)) {
+		for (int x = 0; x < Cols; ++x) {
+			if (line[x] == '\n' || !line[x]) break;
+			page[y][x] = line[x];
+		}
+		y++;
+	}
+	free(line);
+
+	while (step());
+	return pop();
+}
diff --git a/bin/bibsort.pl b/bin/bibsort.pl
new file mode 100644
index 00000000..a4a8956a
--- /dev/null
+++ b/bin/bibsort.pl
@@ -0,0 +1,69 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+while (<>) {
+	print;
+	last if /^[.]Sh STANDARDS$/;
+}
+
+my ($ref, @refs);
+while (<>) {
+	next if /^[.](Bl|It|$)/;
+	last if /^[.]El$/;
+	if (/^[.]Rs$/) {
+		$ref = {};
+	} elsif (/^[.]%(.) (.*)/) {
+		$ref->{$1} = [] unless $ref->{$1};
+		push @{$ref->{$1}}, $2;
+	} elsif (/^[.]Re$/) {
+		push @refs, $ref;
+	} else {
+		print;
+	}
+}
+
+sub byLast {
+	my ($af, $al) = split /\s(\S+)(,.*)?$/, $a;
+	my ($bf, $bl) = split /\s(\S+)(,.*)?$/, $b;
+	($al // $af) cmp ($bl // $bf) || $af cmp $bf;
+}
+
+foreach $ref (@refs) {
+	@{$ref->{A}} = sort byLast @{$ref->{A}};
+	@{$ref->{Q}} = sort @{$ref->{Q}} if $ref->{Q};
+	if ($ref->{N} && $ref->{N}[0] =~ /RFC/) {
+		$ref->{R} = $ref->{N};
+		delete $ref->{N};
+	}
+	if ($ref->{R} && $ref->{R}[0] =~ /RFC (\d+)/ && !$ref->{U}) {
+		$ref->{U} = ["https://tools.ietf.org/html/rfc${1}"];
+	}
+}
+
+sub byAuthor {
+	my ($ta, $tb) = ($a->{T}[0], $b->{T}[0]);
+	local ($a, $b) = ($a->{A}[0], $b->{A}[0]);
+	byLast() || $ta cmp $tb;
+}
+
+{
+	local ($,, $\) = (' ', "\n");
+	print '.Bl', '-item';
+	foreach $ref (sort byAuthor @refs) {
+		print '.It';
+		print '.Rs';
+		foreach my $key (qw(A T B I J R N V U P Q C D O)) {
+			next unless $ref->{$key};
+			foreach (@{$ref->{$key}}) {
+				print ".%${key}", $_;
+			}
+		}
+		print '.Re';
+	}
+	print '.El';
+}
+
+while (<>) {
+	print;
+}
diff --git a/bin/bit.y b/bin/bit.y
new file mode 100644
index 00000000..1119bce6
--- /dev/null
+++ b/bin/bit.y
@@ -0,0 +1,202 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+
+#define MASK(b) ((1ULL << (b)) - 1)
+
+#define YYSTYPE uint64_t
+
+static int yylex(void);
+static void yyerror(const char *str);
+static void print(uint64_t val);
+
+static uint64_t vars[128];
+
+%}
+
+%token Int Var
+
+%left '$'
+%right '='
+%left '|'
+%left '^'
+%left '&'
+%left Shl Shr Sar
+%left '+' '-'
+%left '*' '/' '%'
+%right '~'
+%left 'K' 'M' 'G' 'T'
+
+%%
+
+stmt:
+	| stmt expr '\n' { print(vars['_'] = $2); printf("\n"); }
+	| stmt expr ',' { print(vars['_'] = $2); }
+	| stmt '\n'
+	;
+
+expr:
+	Int
+	| Var { $$ = vars[$1]; }
+	| '(' expr ')' { $$ = $2; }
+	| expr 'K' { $$ = $1 << 10; }
+	| expr 'M' { $$ = $1 << 20; }
+	| expr 'G' { $$ = $1 << 30; }
+	| expr 'T' { $$ = $1 << 40; }
+	| '~' expr { $$ = ~$2; }
+	| '&' expr %prec '~' { $$ = MASK($2); }
+	| '+' expr %prec '~' { $$ = +$2; }
+	| '-' expr %prec '~' { $$ = -$2; }
+	| expr '*' expr { $$ = $1 * $3; }
+	| expr '/' expr { $$ = $1 / $3; }
+	| expr '%' expr { $$ = $1 % $3; }
+	| expr '+' expr { $$ = $1 + $3; }
+	| expr '-' expr { $$ = $1 - $3; }
+	| expr Shl expr { $$ = $1 << $3; }
+	| expr Shr expr { $$ = $1 >> $3; }
+	| expr Sar expr { $$ = (int64_t)$1 >> $3; }
+	| expr '&' expr { $$ = $1 & $3; }
+	| expr '^' expr { $$ = $1 ^ $3; }
+	| expr '|' expr { $$ = $1 | $3; }
+	| Var '=' expr { $$ = vars[$1] = $3; }
+	| expr '$' { $$ = $1; }
+	;
+
+%%
+
+static int lexInt(uint64_t base) {
+	yylval = 0;
+	for (int ch; EOF != (ch = getchar());) {
+		uint64_t digit = base;
+		if (ch == '_') {
+			continue;
+		} else if (isdigit(ch)) {
+			digit = ch - '0';
+		} else if (isxdigit(ch)) {
+			digit = 0xA + toupper(ch) - 'A';
+		}
+		if (digit >= base) {
+			ungetc(ch, stdin);
+			return Int;
+		}
+		yylval *= base;
+		yylval += digit;
+	}
+	return Int;
+}
+
+static int yylex(void) {
+	int ch;
+	while (isblank(ch = getchar()));
+	if (ch == '\'') {
+		yylval = 0;
+		while (EOF != (ch = getchar()) && ch != '\'') {
+			yylval <<= 8;
+			yylval |= ch;
+		}
+		return Int;
+	} else if (ch == '0') {
+		ch = getchar();
+		if (ch == 'b') {
+			return lexInt(2);
+		} else if (ch == 'x') {
+			return lexInt(16);
+		} else {
+			ungetc(ch, stdin);
+			return lexInt(8);
+		}
+	} else if (isdigit(ch)) {
+		ungetc(ch, stdin);
+		return lexInt(10);
+	} else if (ch == '_' || islower(ch)) {
+		yylval = ch;
+		return Var;
+	} else if (ch == '<') {
+		char ne = getchar();
+		if (ne == '<') {
+			return Shl;
+		} else {
+			ungetc(ne, stdin);
+			return ch;
+		}
+	} else if (ch == '-' || ch == '>') {
+		char ne = getchar();
+		if (ne == '>') {
+			return (ch == '-' ? Sar : Shr);
+		} else {
+			ungetc(ne, stdin);
+			return ch;
+		}
+	} else {
+		return ch;
+	}
+}
+
+static void yyerror(const char *str) {
+	warnx("%s", str);
+}
+
+static const char *Codes[128] = {
+	"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
+	"BS",  "HT",  "NL",  "VT",  "NP",  "CR",  "SO",  "SI",
+	"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
+	"CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
+	[127] = "DEL",
+};
+
+static void print(uint64_t val) {
+	int bits = val > UINT32_MAX ? 64
+		: val > UINT16_MAX ? 32
+		: val > UINT8_MAX ? 16
+		: 8;
+	printf("0x%0*"PRIX64" %"PRId64"", bits >> 2, val, (int64_t)val);
+	if (bits == 8) {
+		char bin[9] = {0};
+		for (int i = 0; i < 8; ++i) {
+			bin[i] = '0' + (val >> (7 - i) & 1);
+		}
+		printf(" %#"PRIo64" 0b%s", val, bin);
+	}
+	if (val < 128) {
+		if (isprint(val)) printf(" '%c'", (char)val);
+		if (Codes[val]) printf(" %s", Codes[val]);
+	}
+	if (val) {
+		if (!(val & MASK(40))) {
+			printf(" %"PRIu64"T", val >> 40);
+		} else if (!(val & MASK(30))) {
+			printf(" %"PRIu64"G", val >> 30);
+		} else if (!(val & MASK(20))) {
+			printf(" %"PRIu64"M", val >> 20);
+		} else if (!(val & MASK(10))) {
+			printf(" %"PRIu64"K", val >> 10);
+		}
+	}
+	printf("\n");
+}
+
+int main(void) {
+	while (yyparse());
+}
diff --git a/bin/c.sh b/bin/c.sh
new file mode 100644
index 00000000..ff059437
--- /dev/null
+++ b/bin/c.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+set -eu
+
+temp=$(mktemp -d)
+trap 'rm -r "${temp}"' EXIT
+
+exec 3>>"${temp}/run.c"
+
+cat >&3 <<EOF
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+EOF
+
+expr=
+type=
+while getopts 'e:i:t' opt; do
+	case "${opt}" in
+		(e) expr=$OPTARG;;
+		(i) echo "#include <${OPTARG}>" >&3;;
+		(t) type=1;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+
+cat >&3 <<EOF
+int main(int argc, char *argv[]) {
+	(void)argc;
+	(void)argv;
+	$*;
+EOF
+
+if [ -n "${type}" ]; then
+	cat >&3 <<EOF
+	printf(
+		_Generic(
+			${expr},
+			char: "(char) ",
+			char *: "(char *) ",
+			const char *: "(const char *) ",
+			wchar_t *: "(wchar_t *) ",
+			const wchar_t *: "(const wchar_t *) ",
+			signed char: "(signed char) ",
+			short: "(short) ",
+			int: "(int) ",
+			long: "(long) ",
+			long long: "(long long) ",
+			unsigned char: "(unsigned char) ",
+			unsigned short: "(unsigned short) ",
+			unsigned int: "(unsigned int) ",
+			unsigned long: "(unsigned long) ",
+			unsigned long long: "(unsigned long long) ",
+			float: "(float) ",
+			double: "(double) ",
+			long double: "(long double) ",
+			default: "(void *) "
+		)
+	);
+EOF
+fi
+
+if [ -n "${expr}" ]; then
+	cat >&3 <<EOF
+	printf(
+		_Generic(
+			${expr},
+			char: "%c\n",
+			char *: "%s\n",
+			const char *: "%s\n",
+			wchar_t *: "%ls\n",
+			const wchar_t *: "%ls\n",
+			signed char: "%hhd\n",
+			short: "%hd\n",
+			int: "%d\n",
+			long: "%ld\n",
+			long long: "%lld\n",
+			unsigned char: "%hhu\n",
+			unsigned short: "%hu\n",
+			unsigned int: "%u\n",
+			unsigned long: "%lu\n",
+			unsigned long long: "%llu\n",
+			float: "%g\n",
+			double: "%g\n",
+			long double: "%Lg\n",
+			default: "%p\n"
+		),
+		${expr}
+	);
+EOF
+fi
+
+if [ $# -eq 0 -a -z "${expr}" ]; then
+	cat >&3
+fi
+
+echo '}' >&3
+
+cat >"${temp}/Makefile" <<EOF
+CFLAGS += -Wall -Wextra -Wpedantic
+EOF
+
+make -s -C "${temp}" run
+"${temp}/run"
diff --git a/bin/c11.l b/bin/c11.l
new file mode 100644
index 00000000..b1f0b960
--- /dev/null
+++ b/bin/c11.l
@@ -0,0 +1,144 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option prefix="c11"
+%option noinput nounput noyywrap
+
+%{
+#include "hilex.h"
+%}
+
+%s MacroLine MacroInclude
+%x CharLiteral StringLiteral
+
+ident [_[:alpha:]][_[:alnum:]]*
+width "*"|[0-9]+
+
+%%
+	static int pop = INITIAL;
+
+[[:blank:]]+ { return Normal; }
+
+^"%"[%{}]? {
+	BEGIN(pop = MacroLine);
+	return Macro;
+}
+
+([-+*/%&|^=!<>]|"<<"|">>")"="? |
+[=~.?:]|"["|"]"|"++"|"--"|"&&"|"||"|"->" |
+sizeof|(_A|alignof) {
+	return Operator;
+}
+
+([1-9][0-9]*|"0"[0-7]*|"0x"[[:xdigit:]]+)([ulUL]{0,3}) |
+([0-9]+("."[0-9]*)?|[0-9]*"."[0-9]+)([eE][+-]?[0-9]+)?[flFL]? |
+"0x"[[:xdigit:]]*("."[[:xdigit:]]*)?([pP][+-]?[0-9]+)[flFL]? {
+	return Number;
+}
+
+auto|break|case|const|continue|default|do|else|enum|extern|for|goto|if|inline |
+register|restrict|return|static|struct|switch|typedef|union|volatile|while |
+(_A|a)lignas|_Atomic|_Generic|(_N|n)oreturn|(_S|s)tatic_assert |
+(_T|t)hread_local {
+	return Keyword;
+}
+
+^"#"[[:blank:]]*(include|import) {
+	BEGIN(pop = MacroInclude);
+	return Macro;
+}
+^"#"[[:blank:]]*{ident} {
+	BEGIN(pop = MacroLine);
+	return Macro;
+}
+<MacroInclude>"<"[^>]+">" {
+	return String;
+}
+<MacroLine,MacroInclude>{
+	"\n" {
+		BEGIN(pop = INITIAL);
+		return Normal;
+	}
+	"\\\n" { return Macro; }
+	{ident} { return Macro; }
+}
+
+{ident} { return Ident; }
+
+"//"([^\n]|"\\\n")* |
+"/*"([^*]|"*"+[^*/])*"*"+"/" {
+	return Comment;
+}
+
+[LUu]?"'"/[^\\] {
+	BEGIN(CharLiteral);
+	yymore();
+}
+[LUu]?"'" {
+	BEGIN(CharLiteral);
+	return String;
+}
+([LU]|u8?)?"\""/[^\\%] {
+	BEGIN(StringLiteral);
+	yymore();
+}
+([LU]|u8?)?"\"" {
+	BEGIN(StringLiteral);
+	return String;
+}
+
+<CharLiteral,StringLiteral>{
+	"\\\n" |
+	"\\"[''""?\\abfnrtv] |
+	"\\"([0-7]{1,3}) |
+	"\\x"([[:xdigit:]]{2}) |
+	"\\u"([[:xdigit:]]{4}) |
+	"\\U"([[:xdigit:]]{8}) {
+		return Escape;
+	}
+}
+<StringLiteral>{
+	"%%" |
+	"%"[EO]?[ABCDFGHIMRSTUVWXYZabcdeghjmnprtuwxyz] |
+	"%"[ #+-0]*{width}?("."{width})?([Lhjltz]|hh|ll)?[AEFGXacdefginopsux] {
+		return Format;
+	}
+}
+
+<CharLiteral>{
+	[^\\'']*"'" {
+		BEGIN(pop);
+		return String;
+	}
+	[^\\'']+|. { return String; }
+}
+<StringLiteral>{
+	[^%\\""]*"\"" {
+		BEGIN(pop);
+		return String;
+	}
+	[^%\\""]+|. { return String; }
+}
+
+<MacroLine,MacroInclude>. {
+	return Macro;
+}
+
+.|\n { return Normal; }
+
+%%
+
+const struct Lexer LexC = { yylex, &yyin, &yytext };
diff --git a/bin/dehtml.l b/bin/dehtml.l
new file mode 100644
index 00000000..799f0926
--- /dev/null
+++ b/bin/dehtml.l
@@ -0,0 +1,150 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option noinput nounput noyywrap
+
+%{
+enum Token {
+	Doctype = 1,
+	Comment,
+	TagOpen,
+	TagClose,
+	Entity,
+	Text,
+	Space,
+};
+%}
+
+%%
+
+"<!DOCTYPE "[^>]*">" { return Doctype; }
+"<!--"([^-]|-[^-]|--[^>])*"-->" { return Comment; }
+"</"[^>]*">" { return TagClose; }
+"<"[^>]*">" { return TagOpen; }
+"&"[^;]*";" { return Entity; }
+[^<&[:space:]]+ { return Text; }
+[[:space:]]+ { return Space; }
+
+%%
+
+#include <err.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+
+static const struct {
+	wchar_t ch;
+	const char *name;
+} Entities[] = {
+	{ L'&', "&amp;" },
+	{ L'<', "&lt;" },
+	{ L'>', "&gt;" },
+	{ L'"', "&quot;" },
+	{ L' ', "&nbsp;" },
+	{ L'\u00A9', "&copy;" },
+	{ L'\u00B7', "&middot;" },
+	{ L'\u00BB', "&raquo;" },
+	{ L'\u200F', "&rlm;" },
+	{ L'\u2014', "&mdash;" },
+	{ L'\u2191', "&uarr;" },
+};
+
+static void entity(void) {
+	wchar_t ch = 0;
+	if (yytext[1] == '#') {
+		if (yytext[2] == 'x') {
+			ch = strtoul(&yytext[3], NULL, 16);
+		} else {
+			ch = strtoul(&yytext[2], NULL, 10);
+		}
+	} else {
+		for (size_t i = 0; i < sizeof(Entities) / sizeof(Entities[0]); ++i) {
+			if (strcmp(Entities[i].name, yytext)) continue;
+			ch = Entities[i].ch;
+			break;
+		}
+	}
+	if (ch) {
+		printf("%lc", (wint_t)ch);
+	} else {
+		warnx("unknown entity %s", yytext);
+		printf("%s", yytext);
+	}
+}
+
+static bool isTag(const char *tag) {
+	const char *ptr = &yytext[1];
+	if (*ptr == '/') ptr++;
+	size_t len = strlen(tag);
+	if (strncasecmp(ptr, tag, len)) return false;
+	ptr += len;
+	return *ptr == ' ' || *ptr == '>';
+}
+
+int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
+	bool collapse = 0;
+	for (int opt; 0 < (opt = getopt(argc, argv, "s"));) {
+		switch (opt) {
+			break; case 's': collapse = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (!argc) argc++;
+	for (int i = 0; i < argc; ++i) {
+		yyin = (argv[i] ? fopen(argv[i], "r") : stdin);
+		if (!yyin) err(EX_NOINPUT, "%s", argv[i]);
+
+		bool space = true;
+		bool discard = false;
+		bool pre = false;
+		for (enum Token tok; (tok = yylex());) {
+			if (tok == TagOpen || tok == TagClose) {
+				if (isTag("title") || isTag("style") || isTag("script")) {
+					discard = (tok == TagOpen);
+				} else if (isTag("pre")) {
+					pre = (tok == TagOpen);
+				}
+			} else if (discard) {
+				continue;
+			} else if (tok == Entity) {
+				entity();
+				space = false;
+			} else if (tok == Text) {
+				printf("%s", yytext);
+				space = false;
+			} else if (tok == Space) {
+				if (collapse && !pre) {
+					if (space) continue;
+					printf("%c", yytext[0]);
+				} else {
+					printf("%s", yytext);
+				}
+				space = true;
+			}
+		}
+	}
+}
diff --git a/bin/downgrade.c b/bin/downgrade.c
new file mode 100644
index 00000000..31019714
--- /dev/null
+++ b/bin/downgrade.c
@@ -0,0 +1,362 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <tls.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#include <capsicum_helpers.h>
+#endif
+
+enum { BufferCap = 8192 + 512 };
+
+static bool verbose;
+static struct tls *client;
+
+static void clientWrite(const char *ptr, size_t len) {
+	if (verbose) printf("%.*s", (int)len, ptr);
+	while (len) {
+		ssize_t ret = tls_write(client, ptr, len);
+		if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue;
+		if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client));
+		ptr += ret;
+		len -= ret;
+	}
+}
+
+static void format(const char *format, ...) {
+	char buf[BufferCap];
+	va_list ap;
+	va_start(ap, format);
+	int len = vsnprintf(buf, sizeof(buf), format, ap);
+	va_end(ap);
+	assert((size_t)len < sizeof(buf));
+	clientWrite(buf, len);
+}
+
+static bool invite;
+static const char *join;
+
+enum { Cap = 1024 };
+static struct Message {
+	char *id;
+	char *nick;
+	char *chan;
+	char *mesg;
+} msgs[Cap];
+static size_t m;
+
+static void push(struct Message msg) {
+	struct Message *dst = &msgs[m++ % Cap];
+	free(dst->id);
+	free(dst->nick);
+	free(dst->chan);
+	free(dst->mesg);
+	dst->id = strdup(msg.id);
+	dst->nick = strdup(msg.nick);
+	dst->chan = strdup(msg.chan);
+	if (!dst->id || !dst->nick || !dst->chan) err(EX_OSERR, "strdup");
+	dst->mesg = NULL;
+	if (msg.mesg) {
+		dst->mesg = strdup(msg.mesg);
+		if (!dst->mesg) err(EX_OSERR, "strdup");
+	}
+}
+
+static struct Message *find(const char *id) {
+	for (size_t i = 0; i < Cap; ++i) {
+		if (!msgs[i].id) return NULL;
+		if (!strcmp(msgs[i].id, id)) return &msgs[i];
+	}
+	return NULL;
+}
+
+static void handle(char *ptr) {
+	char *tags = NULL;
+	char *origin = NULL;
+	if (ptr && *ptr == '@') tags = 1 + strsep(&ptr, " ");
+	if (ptr && *ptr == ':') origin = 1 + strsep(&ptr, " ");
+	char *cmd = strsep(&ptr, " ");
+	if (!cmd) return;
+	if (!strcmp(cmd, "CAP")) {
+		strsep(&ptr, " ");
+		char *sub = strsep(&ptr, " ");
+		if (!sub) errx(EX_PROTOCOL, "CAP without subcommand");
+		if (!strcmp(sub, "NAK")) {
+			errx(EX_CONFIG, "server does not support %s", ptr);
+		} else if (!strcmp(sub, "ACK")) {
+			if (!ptr) errx(EX_PROTOCOL, "CAP ACK without caps");
+			if (*ptr == ':') ptr++;
+			if (!strcmp(ptr, "sasl")) format("AUTHENTICATE EXTERNAL\r\n");
+		}
+	} else if (!strcmp(cmd, "AUTHENTICATE")) {
+		format("AUTHENTICATE +\r\nCAP END\r\n");
+	} else if (!strcmp(cmd, "433")) {
+		strsep(&ptr, " ");
+		char *nick = strsep(&ptr, " ");
+		if (!nick) errx(EX_PROTOCOL, "ERR_NICKNAMEINUSE missing nick");
+		format("NICK %s_\r\n", nick);
+	} else if (!strcmp(cmd, "001")) {
+		if (join) format("JOIN %s\r\n", join);
+	} else if (!strcmp(cmd, "005")) {
+		char *self = strsep(&ptr, " ");
+		if (!self) errx(EX_PROTOCOL, "RPL_ISUPPORT missing nick");
+		while (ptr && *ptr != ':') {
+			char *tok = strsep(&ptr, " ");
+			char *key = strsep(&tok, "=");
+			if (!strcmp(key, "BOT") && tok) {
+				format("MODE %s +%s\r\n", self, tok);
+			}
+		}
+	} else if (!strcmp(cmd, "INVITE") && invite) {
+		strsep(&ptr, " ");
+		if (!ptr) errx(EX_PROTOCOL, "INVITE missing channel");
+		if (*ptr == ':') ptr++;
+		format("JOIN %s\r\n", ptr);
+	} else if (!strcmp(cmd, "PING")) {
+		if (!ptr) errx(EX_PROTOCOL, "PING missing parameter");
+		format("PONG %s\r\n", ptr);
+	} else if (!strcmp(cmd, "ERROR")) {
+		if (!ptr) errx(EX_PROTOCOL, "ERROR missing parameter");
+		if (*ptr == ':') ptr++;
+		errx(EX_UNAVAILABLE, "%s", ptr);
+	}
+
+	if (
+		strcmp(cmd, "PRIVMSG") &&
+		strcmp(cmd, "NOTICE") &&
+		strcmp(cmd, "TAGMSG")
+	) return;
+	if (!origin) errx(EX_PROTOCOL, "%s missing origin", cmd);
+
+	struct Message msg = {
+		.nick = strsep(&origin, "!"),
+		.chan = strsep(&ptr, " "),
+	};
+	if (!msg.chan) errx(EX_PROTOCOL, "%s missing target", cmd);
+	if (msg.chan[0] == ':') msg.chan++;
+	if (msg.chan[0] != '#') return;
+	if (strcmp(cmd, "TAGMSG")) msg.mesg = (*ptr == ':' ? &ptr[1] : ptr);
+
+	if (msg.mesg) {
+		if (!strncmp(msg.mesg, "\1ACTION ", 8)) msg.mesg += 8;
+		size_t len = strlen(msg.mesg);
+		if (msg.mesg[len-1] == '\1') msg.mesg[len-1] = '\0';
+	}
+
+	char *reply = NULL;
+	char *react = NULL;
+	char *typing = NULL;
+	if (!tags) return;
+	while (tags) {
+		char *tag = strsep(&tags, ";");
+		char *key = strsep(&tag, "=");
+		if (!strcmp(key, "msgid")) {
+			if (tag) msg.id = tag;
+		} else if (!strcmp(key, "+draft/reply")) {
+			if (tag) reply = tag;
+		} else if (!strcmp(key, "+draft/react")) {
+			if (!tag) continue;
+			for (char *ptr = tag; (ptr = strchr(ptr, '\\')); ptr += !!*ptr) {
+				switch (ptr[1]) {
+					break; case ':': ptr[1] = ';';
+					break; case 's': ptr[1] = ' ';
+					//break; case 'r': ptr[1] = '\r';
+					//break; case 'n': ptr[1] = '\n';
+				}
+				memmove(ptr, &ptr[1], strlen(&ptr[1]) + 1);
+			}
+			react = tag;
+		} else if (!strcmp(key, "+typing") || !strcmp(key, "+draft/typing")) {
+			if (tag) typing = tag;
+		}
+	}
+	if (msg.id) push(msg);
+
+	if (typing) {
+		if (!strcmp(typing, "active")) {
+			format("NOTICE %s :* %s is typing...\r\n", msg.chan, msg.nick);
+		} else if (!strcmp(typing, "paused")) {
+			format(
+				"NOTICE %s :* %s is thinking hard...\r\n", msg.chan, msg.nick
+			);
+		} else if (!strcmp(typing, "done")) {
+			format("NOTICE %s :* %s has given up :(\r\n", msg.chan, msg.nick);
+		} else {
+			format(
+				"NOTICE %s :* %s is doing some wacky %s typing!\r\n",
+				msg.chan, msg.nick, typing
+			);
+		}
+	} else if (react && reply) {
+		struct Message *to = find(reply);
+		format("NOTICE %s :* %s reacted to ", msg.chan, msg.nick);
+		if (to && strcmp(to->chan, msg.chan)) {
+			format("a message in another channel");
+		} else if (to && to->mesg) {
+			size_t len = 0;
+			for (size_t n; to->mesg[len]; len += n) {
+				n = 1 + strcspn(&to->mesg[len+1], " ");
+				if (len + n > 50) break;
+			}
+			format(
+				"%s's message (\"%.*s\"%s)",
+				to->nick, (int)len, to->mesg, (to->mesg[len] ? "..." : "")
+			);
+		} else if (to) {
+			format("%s's reaction", to->nick);
+		} else {
+			format("an unknown message");
+		}
+		format(" with \"%s\"\r\n", react);
+	} else if (react) {
+		format(
+			"NOTICE %s :* %s reacted to nothing with \"%s\"\r\n",
+			msg.chan, msg.nick, react
+		);
+	} else if (reply) {
+		struct Message *to = find(reply);
+		format("NOTICE %s :* %s was replying to ", msg.chan, msg.nick);
+		if (to && strcmp(to->chan, msg.chan)) {
+			format("a message in another channel!\r\n");
+		} else if (to && to->mesg) {
+			size_t len = 0;
+			for (size_t n; to->mesg[len]; len += n) {
+				n = 1 + strcspn(&to->mesg[len+1], " ");
+				if (len + n > 50) break;
+			}
+			format(
+				"%s's message (\"%.*s\"%s)\r\n",
+				to->nick, (int)len, to->mesg, (to->mesg[len] ? "..." : "")
+			);
+		} else if (to) {
+			format("%s's reaction\r\n", to->nick);
+		} else {
+			format("an unknown message!\r\n");
+		}
+	}
+}
+
+static void quit(int sig) {
+	(void)sig;
+	format("QUIT\r\n");
+	tls_close(client);
+	exit(EX_OK);
+}
+
+int main(int argc, char *argv[]) {
+	const char *host = NULL;
+	const char *port = "6697";
+	const char *nick = "downgrade";
+	const char *cert = NULL;
+	const char *priv = NULL;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "c:ij:k:n:p:v"));) {
+		switch (opt) {
+			break; case 'c': cert = optarg;
+			break; case 'i': invite = true;
+			break; case 'j': join = optarg;
+			break; case 'k': priv = optarg;
+			break; case 'n': nick = optarg;
+			break; case 'p': port = optarg;
+			break; case 'v': verbose = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind == argc) errx(EX_USAGE, "host required");
+	host = argv[optind];
+
+	client = tls_client();
+	if (!client) errx(EX_SOFTWARE, "tls_client");
+
+	struct tls_config *config = tls_config_new();
+	if (!config) errx(EX_SOFTWARE, "tls_config_new");
+
+	if (cert) {
+		if (!priv) priv = cert;
+		int error = tls_config_set_keypair_file(config, cert, priv);
+		if (error) errx(EX_NOINPUT, "%s: %s", cert, tls_config_error(config));
+	}
+
+	int error = tls_configure(client, config);
+	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
+
+	error = tls_connect(client, host, port);
+	if (error) errx(EX_UNAVAILABLE, "tls_connect: %s", tls_error(client));
+
+	do {
+		error = tls_handshake(client);
+	} while (error == TLS_WANT_POLLIN || error == TLS_WANT_POLLOUT);
+	if (error) errx(EX_PROTOCOL, "tls_handshake: %s", tls_error(client));
+	tls_config_clear_keys(config);
+
+#ifdef __OpenBSD__
+	error = pledge("stdio", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+#ifdef __FreeBSD__
+	error = caph_enter() || caph_limit_stdio();
+	if (error) err(EX_OSERR, "caph_enter");
+#endif
+
+	signal(SIGHUP, quit);
+	signal(SIGINT, quit);
+	signal(SIGTERM, quit);
+	format(
+		"CAP REQ :echo-message message-tags\r\n"
+		"NICK %s\r\n"
+		"USER %s 0 * :https://causal.agency/bin/downgrade.html\r\n",
+		nick, nick
+	);
+	if (cert) {
+		format("CAP REQ sasl\r\n");
+	} else {
+		format("CAP END\r\n");
+	}
+
+	size_t len = 0;
+	char buf[BufferCap];
+	for (;;) {
+		ssize_t n = tls_read(client, &buf[len], sizeof(buf) - len);
+		if (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT) continue;
+		if (n < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
+		if (!n) errx(EX_UNAVAILABLE, "disconnected");
+		len += n;
+
+		char *ptr = buf;
+		for (
+			char *crlf;
+			(crlf = memmem(ptr, &buf[len] - ptr, "\r\n", 2));
+			ptr = crlf + 2
+		) {
+			*crlf = '\0';
+			if (verbose) printf("%s\n", ptr);
+			handle(ptr);
+		}
+		len -= ptr - buf;
+		memmove(buf, ptr, len);
+	}
+}
diff --git a/bin/dtch.c b/bin/dtch.c
new file mode 100644
index 00000000..026493dd
--- /dev/null
+++ b/bin/dtch.c
@@ -0,0 +1,271 @@
+/* Copyright (C) 2017-2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+static char _;
+static struct iovec iov = { .iov_base = &_, .iov_len = 1 };
+
+static ssize_t sendfd(int sock, int fd) {
+	size_t len = CMSG_SPACE(sizeof(int));
+	char buf[len];
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = buf,
+		.msg_controllen = len,
+	};
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	*(int *)CMSG_DATA(cmsg) = fd;
+
+	return sendmsg(sock, &msg, 0);
+}
+
+static int recvfd(int sock) {
+	size_t len = CMSG_SPACE(sizeof(int));
+	char buf[len];
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = buf,
+		.msg_controllen = len,
+	};
+	if (0 > recvmsg(sock, &msg, 0)) return -1;
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+	if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) {
+		errno = ENOMSG;
+		return -1;
+	}
+	return *(int *)CMSG_DATA(cmsg);
+}
+
+static struct sockaddr_un addr = { .sun_family = AF_UNIX };
+
+static void handler(int sig) {
+	unlink(addr.sun_path);
+	_exit(-sig);
+}
+
+static void detach(int server, bool sink, char *argv[]) {
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, NULL);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[0], argv);
+		err(EX_NOINPUT, "%s", argv[0]);
+	}
+
+	signal(SIGINT, handler);
+	signal(SIGTERM, handler);
+
+	int error = listen(server, 0);
+	if (error) err(EX_OSERR, "listen");
+
+	struct pollfd fds[] = {
+		{ .events = POLLIN, .fd = server },
+		{ .events = POLLIN, .fd = pty },
+	};
+	while (0 < poll(fds, (sink ? 2 : 1), -1)) {
+		if (fds[0].revents) {
+			int client = accept(server, NULL, NULL);
+			if (client < 0) err(EX_IOERR, "accept");
+
+			ssize_t len = sendfd(client, pty);
+			if (len < 0) warn("sendfd");
+
+			len = recv(client, &_, sizeof(_), 0);
+			if (len < 0) warn("recv");
+
+			close(client);
+		}
+
+		if (fds[1].revents) {
+			char buf[4096];
+			ssize_t len = read(pty, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+		}
+
+		int status;
+		pid_t dead = waitpid(pid, &status, WNOHANG);
+		if (dead < 0) err(EX_OSERR, "waitpid");
+		if (dead) {
+			unlink(addr.sun_path);
+			exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status));
+		}
+	}
+	err(EX_IOERR, "poll");
+}
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+	fprintf(stderr, "\33c"); // RIS
+	warnx("detached");
+}
+
+static void nop(int sig) {
+	(void)sig;
+}
+
+static void attach(int client) {
+	int error;
+
+	int pty = recvfd(client);
+	if (pty < 0) err(EX_IOERR, "recvfd");
+	warnx("attached");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	struct winsize redraw = { .ws_row = 1, .ws_col = 1 };
+	error = ioctl(pty, TIOCSWINSZ, &redraw);
+	if (error) err(EX_IOERR, "ioctl");
+
+	error = ioctl(pty, TIOCSWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	signal(SIGWINCH, nop);
+
+	char buf[4096];
+	struct pollfd fds[] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	for (;;) {
+		int nfds = poll(fds, 2, -1);
+		if (nfds < 0) {
+			if (errno != EINTR) err(EX_IOERR, "poll");
+
+			error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+			if (error) err(EX_IOERR, "ioctl");
+
+			error = ioctl(pty, TIOCSWINSZ, &window);
+			if (error) err(EX_IOERR, "ioctl");
+
+			continue;
+		}
+
+		if (fds[0].revents) {
+			ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+			if (!len) break;
+
+			if (len == 1 && buf[0] == CTRL('Q')) break;
+
+			len = write(pty, buf, len);
+			if (len < 0) err(EX_IOERR, "write");
+		}
+
+		if (fds[1].revents) {
+			ssize_t len = read(pty, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+			if (!len) break;
+
+			len = write(STDOUT_FILENO, buf, len);
+			if (len < 0) err(EX_IOERR, "write");
+		}
+	}
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	bool atch = false;
+	bool sink = false;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "as"))) {
+		switch (opt) {
+			break; case 'a': atch = true;
+			break; case 's': sink = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind == argc) errx(EX_USAGE, "no session name");
+	const char *name = argv[optind++];
+
+	if (optind == argc) {
+		argv[--optind] = getenv("SHELL");
+		if (!argv[optind]) errx(EX_CONFIG, "SHELL unset");
+	}
+
+	const char *home = getenv("HOME");
+	if (!home) errx(EX_CONFIG, "HOME unset");
+
+	int fd = open(home, 0);
+	if (fd < 0) err(EX_CANTCREAT, "%s", home);
+
+	error = mkdirat(fd, ".dtch", 0700);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "%s/.dtch", home);
+
+	close(fd);
+
+	int sock = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (sock < 0) err(EX_OSERR, "socket");
+	fcntl(sock, F_SETFD, FD_CLOEXEC);
+
+	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.dtch/%s", home, name);
+
+	if (atch) {
+		error = connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
+		if (error) err(EX_NOINPUT, "%s", addr.sun_path);
+		attach(sock);
+	} else {
+		error = bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
+		if (error) err(EX_CANTCREAT, "%s", addr.sun_path);
+		detach(sock, sink, &argv[optind]);
+	}
+}
diff --git a/bin/enc.sh b/bin/enc.sh
new file mode 100644
index 00000000..4233f0a3
--- /dev/null
+++ b/bin/enc.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+set -eu
+
+readonly Command='openssl enc -ChaCha20 -pbkdf2'
+
+base64=
+stdout=false
+mode=encrypt
+force=false
+
+while getopts 'acdef' opt; do
+	case $opt in
+		(a) base64=-a;;
+		(c) stdout=true;;
+		(d) mode=decrypt;;
+		(e) mode=encrypt;;
+		(f) force=true;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+
+confirm() {
+	$force && return 0
+	while :; do
+		printf '%s: overwrite %s? [y/N] ' "$0" "$1" >&2
+		read -r confirm
+		case "$confirm" in
+			(Y*|y*) return 0;;
+			(N*|n*|'') return 1;;
+		esac
+	done
+}
+
+encrypt() {
+	if test -z "${1:-}"; then
+		$Command -e $base64
+	elif $stdout; then
+		$Command -e $base64 -in "$1"
+	else
+		input=$1
+		output="${1}.enc"
+		if test -e "$output" && ! confirm "$output"; then
+			return
+		fi
+		$Command -e $base64 -in "$input" -out "$output"
+	fi
+}
+
+decrypt() {
+	if test -z "${1:-}"; then
+		$Command -d $base64
+	elif $stdout || [ "${1%.enc}" = "$1" ]; then
+		$Command -d $base64 -in "$1"
+	else
+		input=$1
+		output=${1%.enc}
+		if test -e "$output" && ! confirm "$output"; then
+			return
+		fi
+		$Command -d $base64 -in "$input" -out "$output"
+	fi
+}
+
+for input; do
+	$mode "$input"
+done
+if [ $# -eq 0 ]; then
+	$mode
+fi
diff --git a/bin/ever.c b/bin/ever.c
new file mode 100644
index 00000000..f8ff943b
--- /dev/null
+++ b/bin/ever.c
@@ -0,0 +1,119 @@
+/* Copyright (C) 2017  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/event.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static int watch(int kq, char *path) {
+	int fd = open(path, O_CLOEXEC);
+	if (fd < 0) err(EX_NOINPUT, "%s", path);
+
+	struct kevent event;
+	EV_SET(
+		&event,
+		fd,
+		EVFILT_VNODE,
+		EV_ADD | EV_CLEAR,
+		NOTE_WRITE | NOTE_DELETE,
+		0,
+		path
+	);
+	int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
+	if (nevents < 0) err(EX_OSERR, "kevent");
+
+	return fd;
+}
+
+static bool quiet;
+static void exec(int fd, char *const argv[]) {
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+
+	if (!pid) {
+		dup2(fd, STDIN_FILENO);
+		execvp(*argv, argv);
+		err(EX_NOINPUT, "%s", *argv);
+	}
+
+	int status;
+	pid = wait(&status);
+	if (pid < 0) err(EX_OSERR, "wait");
+
+	if (quiet) return;
+	if (WIFEXITED(status)) {
+		warnx("exit %d\n", WEXITSTATUS(status));
+	} else if (WIFSIGNALED(status)) {
+		warnx("signal %d\n", WTERMSIG(status));
+	} else {
+		warnx("status %d\n", status);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	bool input = false;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "iq"));) {
+		switch (opt) {
+			break; case 'i': input = true;
+			break; case 'q': quiet = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+	if (argc < 2) return EX_USAGE;
+
+	int kq = kqueue();
+	if (kq < 0) err(EX_OSERR, "kqueue");
+
+	int i;
+	for (i = 0; i < argc - 1; ++i) {
+		if (argv[i][0] == '-') {
+			i++;
+			break;
+		}
+		watch(kq, argv[i]);
+	}
+
+	if (!input) {
+		exec(STDIN_FILENO, &argv[i]);
+	}
+
+	for (;;) {
+		struct kevent event;
+		int nevents = kevent(kq, NULL, 0, &event, 1, NULL);
+		if (nevents < 0) err(EX_OSERR, "kevent");
+
+		if (event.fflags & NOTE_DELETE) {
+			close(event.ident);
+			sleep(1);
+			event.ident = watch(kq, (char *)event.udata);
+		} else if (input) {
+			off_t off = lseek(event.ident, 0, SEEK_SET);
+			if (off < 0) err(EX_IOERR, "lseek");
+		}
+
+		exec((input ? event.ident : STDIN_FILENO), &argv[i]);
+	}
+}
diff --git a/bin/freecell.c b/bin/freecell.c
new file mode 100644
index 00000000..fbc0fe22
--- /dev/null
+++ b/bin/freecell.c
@@ -0,0 +1,388 @@
+/* Copyright (C) 2019, 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <curses.h>
+#include <err.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+typedef unsigned uint;
+typedef unsigned char byte;
+
+typedef byte Card;
+enum {
+	A = 1,
+	J = 11,
+	Q = 12,
+	K = 13,
+	Rank = 0x0F,
+	Suit = 0x30,
+	Color = 0x10,
+	Club = 0x00,
+	Diamond = 0x10,
+	Spade = 0x20,
+	Heart = 0x30,
+};
+
+enum { StackCap = 52 };
+struct Stack {
+	byte len;
+	Card cards[StackCap];
+};
+static void push(struct Stack *stack, Card card) {
+	assert(stack->len < StackCap);
+	stack->cards[stack->len++] = card;
+}
+static Card pop(struct Stack *stack) {
+	if (!stack->len) return 0;
+	return stack->cards[--stack->len];
+}
+static Card peek(struct Stack *stack) {
+	if (!stack->len) return 0;
+	return stack->cards[stack->len-1];
+}
+
+enum {
+	Foundation,
+	Cell = Foundation + 4,
+	Tableau = Cell + 4,
+	Stacks = Tableau + 8,
+};
+static struct Stack stacks[Stacks];
+
+struct Move {
+	byte dst;
+	byte src;
+};
+
+enum { QCap = 16 };
+static struct {
+	struct Move moves[QCap];
+	uint r, w, u;
+} q;
+static void enq(byte dst, byte src) {
+	q.moves[q.w % QCap].dst = dst;
+	q.moves[q.w % QCap].src = src;
+	q.w++;
+}
+static void deq(void) {
+	struct Move move = q.moves[q.r++ % QCap];
+	push(&stacks[move.dst], pop(&stacks[move.src]));
+}
+static void undo(void) {
+	uint len = q.w - q.u;
+	if (!len || len > QCap) return;
+	for (uint i = len-1; i < len; --i) {
+		struct Move move = q.moves[(q.u+i) % QCap];
+		push(&stacks[move.src], pop(&stacks[move.dst]));
+	}
+	q.r = q.w = q.u;
+}
+
+// https://rosettacode.org/wiki/Deal_cards_for_FreeCell
+static uint lcgState;
+static uint lcg(void) {
+	lcgState = (214013 * lcgState + 2531011) % (1 << 31);
+	return lcgState >> 16;
+}
+static void deal(uint game) {
+	lcgState = game;
+	struct Stack deck = {0};
+	for (Card i = A; i <= K; ++i) {
+		push(&deck, Club | i);
+		push(&deck, Diamond | i);
+		push(&deck, Heart | i);
+		push(&deck, Spade | i);
+	}
+	for (uint stack = 0; deck.len; ++stack) {
+		uint i = lcg() % deck.len;
+		Card card = deck.cards[i];
+		deck.cards[i] = deck.cards[--deck.len];
+		push(&stacks[Tableau + stack%8], card);
+	}
+}
+
+static bool win(void) {
+	for (uint i = Foundation; i < Cell; ++i) {
+		if (stacks[i].len != 13) return false;
+	}
+	return true;
+}
+
+static bool valid(uint dst, Card card) {
+	Card top = peek(&stacks[dst]);
+	if (dst < Cell) {
+		if (!top) return (card & Rank) == A;
+		return (card & Suit) == (top & Suit)
+			&& (card & Rank) == (top & Rank) + 1;
+	}
+	if (!top) return true;
+	if (dst >= Tableau) {
+		return (card & Color) != (top & Color)
+			&& (card & Rank) == (top & Rank) - 1;
+	}
+	return false;
+}
+
+static void autoEnq(void) {
+	Card min[] = { K, K };
+	for (uint i = Cell; i < Stacks; ++i) {
+		for (uint j = 0; j < stacks[i].len; ++j) {
+			Card card = stacks[i].cards[j];
+			if ((card & Rank) < min[!!(card & Color)]) {
+				min[!!(card & Color)] = card & Rank;
+			}
+		}
+	}
+	for (uint src = Cell; src < Stacks; ++src) {
+		Card card = peek(&stacks[src]);
+		if (!card) continue;
+		if (min[!(card & Color)] < (card & Rank)-1) continue;
+		for (uint dst = Foundation; dst < Cell; ++dst) {
+			if (valid(dst, card)) {
+				enq(dst, src);
+				return;
+			}
+		}
+	}
+}
+
+static void moveSingle(uint dst, uint src) {
+	if (!valid(dst, peek(&stacks[src]))) return;
+	q.u = q.w;
+	enq(dst, src);
+}
+
+static uint freeCells(uint cells[static 4]) {
+	uint len = 0;
+	for (uint i = Cell; i < Tableau; ++i) {
+		if (!stacks[i].len) cells[len++] = i;
+	}
+	return len;
+}
+
+static uint moveDepth(uint src) {
+	struct Stack stack = stacks[src];
+	if (stack.len < 2) return stack.len;
+	uint n = 1;
+	for (uint i = stack.len-2; i < stack.len; --i, ++n) {
+		if ((stack.cards[i] & Color) == (stack.cards[i+1] & Color)) break;
+		if ((stack.cards[i] & Rank) != (stack.cards[i+1] & Rank) + 1) break;
+	}
+	return n;
+}
+
+static void moveColumn(uint dst, uint src) {
+	uint depth;
+	uint cells[4];
+	uint free = freeCells(cells);
+	for (depth = moveDepth(src); depth; --depth) {
+		if (free < depth-1) continue;
+		if (valid(dst, stacks[src].cards[stacks[src].len-depth])) break;
+	}
+	if (depth < 2 || dst < Tableau) {
+		moveSingle(dst, src);
+		return;
+	}
+	q.u = q.w;
+	for (uint i = 0; i < depth-1; ++i) {
+		enq(cells[i], src);
+	}
+	enq(dst, src);
+	for (uint i = depth-2; i < depth-1; --i) {
+		enq(dst, cells[i]);
+	}
+}
+
+static void curse(void) {
+	setlocale(LC_CTYPE, "");
+	initscr();
+	cbreak();
+	noecho();
+	curs_set(0);
+	start_color();
+	use_default_colors();
+	init_pair(1, COLOR_BLACK, COLOR_WHITE);
+	init_pair(2, COLOR_RED, COLOR_WHITE);
+	init_pair(3, COLOR_GREEN, -1);
+}
+
+static void drawCard(bool hi, int y, int x, Card card) {
+	if (!card) return;
+	move(y, x);
+	attr_set(hi ? A_REVERSE : A_NORMAL, (card & Color) ? 2 : 1, NULL);
+	switch (card & Suit) {
+		break; case Club: addstr("\u2663");
+		break; case Diamond: addstr("\u2666");
+		break; case Spade: addstr("\u2660");
+		break; case Heart: addstr("\u2665");
+		break; default:;
+	}
+	switch (card & Rank) {
+		break; case A: addstr(" A");
+		break; case 10: addstr("10");
+		break; case J: addstr(" J");
+		break; case Q: addstr(" Q");
+		break; case K: addstr(" K");
+		break; default: {
+			addch(' ');
+			addch('0' + (card & Rank));
+		}
+	}
+	attr_set(A_NORMAL, 0, NULL);
+}
+
+static void drawStack(bool hi, int y, int x, const struct Stack *stack) {
+	for (uint i = 0; i < stack->len; ++i) {
+		drawCard(hi && i == stack->len-1, y++, x, stack->cards[i]);
+	}
+}
+
+enum {
+	Padding = 1,
+	CardWidth = 3,
+	CardHeight = 1,
+	CellX = Padding,
+	CellY = 2*CardHeight,
+	FoundationX = CellX + 4*(CardWidth+Padding),
+	FoundationY = CellY,
+	TableauX = CellX,
+	TableauY = CellY + 2*CardHeight,
+};
+
+static uint game;
+static uint srcStack = Stacks;
+
+static void draw(void) {
+	erase();
+	static char buf[256];
+	if (!buf[0]) snprintf(buf, sizeof(buf), "Game #%u", game);
+	attr_set(A_NORMAL, 3, NULL);
+	mvaddstr(0, Padding, buf);
+	for (uint i = 0; i < Stacks; ++i) {
+		int y, x;
+		char key;
+		if (i < Cell) {
+			y = FoundationY;
+			x = FoundationX + (3-(i-Foundation)) * (CardWidth+Padding);
+			key = '_';
+		} else if (i < Tableau) {
+			y = CellY;
+			x = CellX + (i-Cell) * (CardWidth+Padding);
+			key = '1' + i-Cell;
+		} else {
+			y = TableauY;
+			x = TableauX + (i-Tableau) * (CardWidth+Padding);
+			key = "QWERASDF"[i-Tableau];
+		}
+		if (i < Tableau) {
+			mvaddch(y, x+1, COLOR_PAIR(3) | key);
+		} else {
+			mvaddch(y + 8*CardHeight, x+1, COLOR_PAIR(3) | key);
+		}
+		if (i < Cell) {
+			drawCard(false, y, x, peek(&stacks[i]));
+		} else {
+			drawStack(i == srcStack, y, x, &stacks[i]);
+		}
+	}
+}
+
+static void input(void) {
+	char ch = getch();
+	uint stack = Stacks;
+	switch (tolower(ch)) {
+		break; case '\33': srcStack = Stacks;
+		break; case 'u': case '\b': case '\177': undo();
+		break; case '1': case '!': stack = Cell+0;
+		break; case '2': case '@': stack = Cell+1;
+		break; case '3': case '#': stack = Cell+2;
+		break; case '4': case '$': stack = Cell+3;
+		break; case '_': case ' ': stack = Foundation;
+		break; case 'q': stack = Tableau+0;
+		break; case 'w': stack = Tableau+1;
+		break; case 'e': stack = Tableau+2;
+		break; case 'r': stack = Tableau+3;
+		break; case 'a': stack = Tableau+4;
+		break; case 's': stack = Tableau+5;
+		break; case 'd': stack = Tableau+6;
+		break; case 'f': stack = Tableau+7;
+	}
+	if (stack == Stacks) return;
+
+	if (srcStack < Stacks) {
+		Card card = peek(&stacks[srcStack]);
+		if (stack == Foundation) {
+			for (; stack < Cell; ++stack) {
+				if (valid(stack, card)) break;
+			}
+			if (stack == Cell) return;
+		}
+		if (stack == srcStack) {
+			for (stack = Cell; stack < Stacks; ++stack) {
+				if (!stacks[stack].len) break;
+			}
+			if (stack == Stacks) return;
+		}
+		if (isupper(ch)) {
+			moveSingle(stack, srcStack);
+		} else {
+			moveColumn(stack, srcStack);
+		}
+		srcStack = Stacks;
+
+	} else if (stack >= Cell && stacks[stack].len) {
+		srcStack = stack;
+	}
+}
+
+static void status(void) {
+	printf("Game #%u %s!\n", game, win() ? "win" : "lose");
+}
+
+int main(int argc, char *argv[]) {
+	game = 1 + time(NULL) % 32000;
+	uint delay = 50;
+	for (int opt; 0 < (opt = getopt(argc, argv, "d:n:"));) {
+		switch (opt) {
+			break; case 'd': delay = strtoul(optarg, NULL, 10);
+			break; case 'n': game = strtoul(optarg, NULL, 10);
+			break; default:  return EX_USAGE;
+		}
+	}
+	curse();
+	deal(game);
+	atexit(status);
+	while (!win()) {
+		while (q.r < q.w) {
+			deq();
+			draw();
+			refresh();
+			usleep(delay * 1000);
+			if (q.r == q.w) autoEnq();
+		}
+		draw();
+		input();
+	}
+	endwin();
+}
diff --git a/bin/git-comment.pl b/bin/git-comment.pl
new file mode 100644
index 00000000..5352702d
--- /dev/null
+++ b/bin/git-comment.pl
@@ -0,0 +1,92 @@
+#!/usr/bin/env perl
+# Copyright (C) 2021  June McEnroe <june@causal.agency>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use lib (split(/:/, $ENV{GITPERLLIB} || '/usr/local/share/perl5'));
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config pass_through);
+use Git;
+
+my $repo = Git->repository();
+
+my ($all, $minGroup, $minRepeat, $noRepeat) = (0, 2, 30, 0);
+my $commentStart = $repo->config('comment.start') // "/*";
+my $commentLead = $repo->config('comment.lead') // " *";
+my $commentEnd = $repo->config('comment.end') // " */";
+my $pretty = $repo->config('comment.pretty') // 'format:%h %s%n%n%-b';
+GetOptions(
+	'all' => \$all,
+	'comment-start=s' => \$commentStart,
+	'comment-lead=s' => \$commentLead,
+	'comment-end:s' => \$commentEnd,
+	'min-group=i' => \$minGroup,
+	'min-repeat=i' => \$minRepeat,
+	'no-repeat' => \$noRepeat,
+	'pretty=s' => \$pretty,
+) or die;
+
+sub printComment {
+	my ($indent, $summary, @body) = @_;
+	print "$indent$commentStart $summary";
+	if (@body) {
+		print "\n";
+		foreach (@body) {
+			print "$indent$commentLead";
+			print " $_" if $_;
+			print "\n";
+		}
+		print "$indent$commentEnd\n" if $commentEnd;
+	} else {
+		print "$commentEnd\n";
+	}
+}
+
+my ($pipe, $ctx) = $repo->command_output_pipe('blame', '--porcelain', @ARGV);
+
+my ($commit, $nr, $group, $printed, %message, %nrs);
+while (<$pipe>) {
+	chomp;
+	if (/^([[:xdigit:]]+) \d+ (\d+) (\d+)/) {
+		($commit, $nr, $group, $printed) = ($1, $2, $3, 0);
+		next if $message{$commit};
+		if ($commit =~ /^0+$/) {
+			$message{$commit} = ['Not committed yet'];
+			next;
+		}
+		my @message = $repo->command(
+			'show', '--no-patch', "--pretty=$pretty", $commit
+		);
+		$message{$commit} = \@message;
+	} elsif (/^\t(\s*)(.*)/) {
+		my ($indent, $line) = ($1, $2);
+		unless ($printed || $line =~ /^[})]?;?$/) {
+			$printed = 1;
+			if (
+				$group >= $minGroup &&
+				!($noRepeat && $nrs{$commit}) &&
+				!($nrs{$commit} && $nr < $nrs{$commit} + $minRepeat) &&
+				($all || @{$message{$commit}} > 1)
+			) {
+				$nrs{$commit} = $nr;
+				printComment($indent, @{$message{$commit}});
+			}
+		}
+		print "$indent$line\n";
+	}
+}
+
+$repo->command_close_pipe($pipe, $ctx);
diff --git a/bin/glitch.c b/bin/glitch.c
new file mode 100644
index 00000000..d0c926f9
--- /dev/null
+++ b/bin/glitch.c
@@ -0,0 +1,605 @@
+/* Copyright (C) 2018, 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+static const char *path;
+static FILE *file;
+static uint32_t crc;
+
+static void pngRead(void *ptr, size_t len, const char *desc) {
+	size_t n = fread(ptr, len, 1, file);
+	if (!n && ferror(file)) err(EX_IOERR, "%s", path);
+	if (!n) errx(EX_DATAERR, "%s: missing %s", path, desc);
+	crc = crc32(crc, ptr, len);
+}
+
+static void pngWrite(const void *ptr, size_t len) {
+	size_t n = fwrite(ptr, len, 1, file);
+	if (!n) err(EX_IOERR, "%s", path);
+	crc = crc32(crc, ptr, len);
+}
+
+static const uint8_t Sig[8] = "\x89PNG\r\n\x1A\n";
+
+static void sigRead(void) {
+	uint8_t sig[sizeof(Sig)];
+	pngRead(sig, sizeof(sig), "signature");
+	if (memcmp(sig, Sig, sizeof(sig))) {
+		errx(EX_DATAERR, "%s: invalid signature", path);
+	}
+}
+
+static void sigWrite(void) {
+	pngWrite(Sig, sizeof(Sig));
+}
+
+static uint32_t u32Read(const char *desc) {
+	uint8_t b[4];
+	pngRead(b, sizeof(b), desc);
+	return (uint32_t)b[0] << 24 | (uint32_t)b[1] << 16
+		| (uint32_t)b[2] << 8 | (uint32_t)b[3];
+}
+
+static void u32Write(uint32_t x) {
+	uint8_t b[4] = { x >> 24 & 0xFF, x >> 16 & 0xFF, x >> 8 & 0xFF, x & 0xFF };
+	pngWrite(b, sizeof(b));
+}
+
+struct Chunk {
+	uint32_t len;
+	char type[5];
+};
+
+static struct Chunk chunkRead(void) {
+	struct Chunk chunk;
+	chunk.len = u32Read("chunk length");
+	crc = crc32(0, Z_NULL, 0);
+	pngRead(chunk.type, 4, "chunk type");
+	chunk.type[4] = 0;
+	return chunk;
+}
+
+static void chunkWrite(struct Chunk chunk) {
+	u32Write(chunk.len);
+	crc = crc32(0, Z_NULL, 0);
+	pngWrite(chunk.type, 4);
+}
+
+static void crcRead(void) {
+	uint32_t expect = crc;
+	uint32_t actual = u32Read("CRC32");
+	if (actual == expect) return;
+	errx(
+		EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
+		path, expect, actual
+	);
+}
+
+static void crcWrite(void) {
+	u32Write(crc);
+}
+
+static void chunkSkip(struct Chunk chunk) {
+	if (!(chunk.type[0] & 0x20)) {
+		errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, chunk.type);
+	}
+	uint8_t buf[4096];
+	while (chunk.len > sizeof(buf)) {
+		pngRead(buf, sizeof(buf), "chunk data");
+		chunk.len -= sizeof(buf);
+	}
+	if (chunk.len) pngRead(buf, chunk.len, "chunk data");
+	crcRead();
+}
+
+enum Color {
+	Grayscale = 0,
+	Truecolor = 2,
+	Indexed = 3,
+	GrayscaleAlpha = 4,
+	TruecolorAlpha = 6,
+};
+enum Compression {
+	Deflate,
+};
+enum FilterMethod {
+	Adaptive,
+};
+enum Interlace {
+	Progressive,
+	Adam7,
+};
+
+enum { HeaderLen = 13 };
+static struct {
+	uint32_t width;
+	uint32_t height;
+	uint8_t depth;
+	uint8_t color;
+	uint8_t compression;
+	uint8_t filter;
+	uint8_t interlace;
+} header;
+
+static size_t pixelLen;
+static size_t lineLen;
+static size_t dataLen;
+
+static void recalc(void) {
+	size_t pixelBits = header.depth;
+	switch (header.color) {
+		break; case GrayscaleAlpha: pixelBits *= 2;
+		break; case Truecolor: pixelBits *= 3;
+		break; case TruecolorAlpha: pixelBits *= 4;
+	}
+	pixelLen = (pixelBits + 7) / 8;
+	lineLen = (header.width * pixelBits + 7) / 8;
+	dataLen = (1 + lineLen) * header.height;
+}
+
+static void headerRead(struct Chunk chunk) {
+	if (chunk.len != HeaderLen) {
+		errx(
+			EX_DATAERR, "%s: expected %s length %" PRIu32 ", found %" PRIu32,
+			path, chunk.type, (uint32_t)HeaderLen, chunk.len
+		);
+	}
+	header.width = u32Read("header width");
+	header.height = u32Read("header height");
+	pngRead(&header.depth, 1, "header depth");
+	pngRead(&header.color, 1, "header color");
+	pngRead(&header.compression, 1, "header compression");
+	pngRead(&header.filter, 1, "header filter");
+	pngRead(&header.interlace, 1, "header interlace");
+	crcRead();
+	recalc();
+}
+
+static void headerWrite(void) {
+	struct Chunk ihdr = { HeaderLen, "IHDR" };
+	chunkWrite(ihdr);
+	u32Write(header.width);
+	u32Write(header.height);
+	pngWrite(&header.depth, 1);
+	pngWrite(&header.color, 1);
+	pngWrite(&header.compression, 1);
+	pngWrite(&header.filter, 1);
+	pngWrite(&header.interlace, 1);
+	crcWrite();
+}
+
+static struct {
+	uint32_t len;
+	uint8_t rgb[256][3];
+} pal;
+
+static struct {
+	uint32_t len;
+	uint8_t a[256];
+} trans;
+
+static void palClear(void) {
+	pal.len = 0;
+	trans.len = 0;
+}
+
+static void palRead(struct Chunk chunk) {
+	if (chunk.len % 3) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " not divisible by 3",
+			path, chunk.type, chunk.len
+		);
+	}
+	pal.len = chunk.len / 3;
+	if (pal.len > 256) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " > 256",
+			path, chunk.type, pal.len
+		);
+	}
+	pngRead(pal.rgb, chunk.len, "palette data");
+	crcRead();
+}
+
+static void palWrite(void) {
+	struct Chunk plte = { 3 * pal.len, "PLTE" };
+	chunkWrite(plte);
+	pngWrite(pal.rgb, plte.len);
+	crcWrite();
+}
+
+static void transRead(struct Chunk chunk) {
+	trans.len = chunk.len;
+	if (trans.len > 256) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " > 256",
+			path, chunk.type, trans.len
+		);
+	}
+	pngRead(trans.a, chunk.len, "transparency data");
+	crcRead();
+}
+
+static void transWrite(void) {
+	struct Chunk trns = { trans.len, "tRNS" };
+	chunkWrite(trns);
+	pngWrite(trans.a, trns.len);
+	crcWrite();
+}
+
+static uint8_t *data;
+
+static void dataAlloc(void) {
+	data = malloc(dataLen);
+	if (!data) err(EX_OSERR, "malloc");
+}
+
+static void dataRead(struct Chunk chunk) {
+	z_stream stream = { .next_out = data, .avail_out = dataLen };
+	int error = inflateInit(&stream);
+	if (error != Z_OK) errx(EX_SOFTWARE, "inflateInit: %s", stream.msg);
+
+	for (;;) {
+		if (strcmp(chunk.type, "IDAT")) {
+			errx(EX_DATAERR, "%s: missing IDAT chunk", path);
+		}
+
+		uint8_t *idat = malloc(chunk.len);
+		if (!idat) err(EX_OSERR, "malloc");
+
+		pngRead(idat, chunk.len, "image data");
+		crcRead();
+		
+		stream.next_in = idat;
+		stream.avail_in = chunk.len;
+		error = inflate(&stream, Z_SYNC_FLUSH);
+		free(idat);
+
+		if (error == Z_STREAM_END) break;
+		if (error != Z_OK) {
+			errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
+		}
+
+		chunk = chunkRead();
+	}
+	inflateEnd(&stream);
+	if ((size_t)stream.total_out != dataLen) {
+		errx(
+			EX_DATAERR, "%s: expected data length %zu, found %zu",
+			path, dataLen, (size_t)stream.total_out
+		);
+	}
+}
+
+static void dataWrite(void) {
+	z_stream stream = {
+		.next_in = data,
+		.avail_in = dataLen,
+	};
+	int error = deflateInit2(
+		&stream, Z_BEST_COMPRESSION, Z_DEFLATED, 15, 8, Z_FILTERED
+	);
+	if (error != Z_OK) errx(EX_SOFTWARE, "deflateInit2: %s", stream.msg);
+
+	uLong bound = deflateBound(&stream, dataLen);
+	uint8_t *buf = malloc(bound);
+	if (!buf) err(EX_OSERR, "malloc");
+
+	stream.next_out = buf;
+	stream.avail_out = bound;
+	deflate(&stream, Z_FINISH);
+	deflateEnd(&stream);
+
+	struct Chunk idat = { stream.total_out, "IDAT" };
+	chunkWrite(idat);
+	pngWrite(buf, stream.total_out);
+	crcWrite();
+	free(buf);
+
+	struct Chunk iend = { 0, "IEND" };
+	chunkWrite(iend);
+	crcWrite();
+}
+
+enum Filter {
+	None,
+	Sub,
+	Up,
+	Average,
+	Paeth,
+	FilterCap,
+};
+
+struct Bytes {
+	uint8_t x, a, b, c;
+};
+
+static bool brokenPaeth;
+static uint8_t paethPredictor(struct Bytes f) {
+	int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
+	int32_t pa = labs(p - (int32_t)f.a);
+	int32_t pb = labs(p - (int32_t)f.b);
+	int32_t pc = labs(p - (int32_t)f.c);
+	if (pa <= pb && pa <= pc) return f.a;
+	if (brokenPaeth) {
+		if (pb < pc) return f.b;
+	} else {
+		if (pb <= pc) return f.b;
+	}
+	return f.c;
+}
+
+static uint8_t recon(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x + f.a;
+		case Up:      return f.x + f.b;
+		case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x + paethPredictor(f);
+		default: abort();
+	}
+}
+
+static uint8_t filt(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x - f.a;
+		case Up:      return f.x - f.b;
+		case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x - paethPredictor(f);
+		default: abort();
+	}
+}
+
+static uint8_t *lineType(uint32_t y) {
+	return &data[y * (1 + lineLen)];
+}
+static uint8_t *lineData(uint32_t y) {
+	return 1 + lineType(y);
+}
+
+static struct Bytes origBytes(uint32_t y, size_t i) {
+	bool a = (i >= pixelLen), b = (y > 0), c = (a && b);
+	return (struct Bytes) {
+		.x = lineData(y)[i],
+		.a = (a ? lineData(y)[i-pixelLen] : 0),
+		.b = (b ? lineData(y-1)[i] : 0),
+		.c = (c ? lineData(y-1)[i-pixelLen] : 0),
+	};
+}
+
+static bool reconFilter;
+static void dataRecon(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0; i < lineLen; ++i) {
+			if (reconFilter) {
+				lineData(y)[i] = filt(*lineType(y), origBytes(y, i));
+			} else {
+				lineData(y)[i] = recon(*lineType(y), origBytes(y, i));
+			}
+		}
+		*lineType(y) = None;
+	}
+}
+
+static bool filterRecon;
+static size_t applyFilter;
+static enum Filter applyFilters[256];
+static size_t declFilter;
+static enum Filter declFilters[256];
+
+static void dataFilter(void) {
+	uint8_t *filter[FilterCap];
+	for (enum Filter i = None; i < FilterCap; ++i) {
+		filter[i] = malloc(lineLen);
+		if (!filter[i]) err(EX_OSERR, "malloc");
+	}
+	for (uint32_t y = header.height-1; y < header.height; --y) {
+		uint32_t heuristic[FilterCap] = {0};
+		enum Filter minType = None;
+		for (enum Filter type = None; type < FilterCap; ++type) {
+			for (size_t i = 0; i < lineLen; ++i) {
+				if (filterRecon) {
+					filter[type][i] = recon(type, origBytes(y, i));
+				} else {
+					filter[type][i] = filt(type, origBytes(y, i));
+				}
+				heuristic[type] += abs((int8_t)filter[type][i]);
+			}
+			if (heuristic[type] < heuristic[minType]) minType = type;
+		}
+		if (declFilter) {
+			*lineType(y) = declFilters[y % declFilter];
+		} else {
+			*lineType(y) = minType;
+		}
+		if (applyFilter) {
+			memcpy(lineData(y), filter[applyFilters[y % applyFilter]], lineLen);
+		} else {
+			memcpy(lineData(y), filter[minType], lineLen);
+		}
+	}
+	for (enum Filter i = None; i < FilterCap; ++i) {
+		free(filter[i]);
+	}
+}
+
+static bool invertData;
+static bool mirrorData;
+static bool zeroX;
+static bool zeroY;
+
+static void glitch(const char *inPath, const char *outPath) {
+	if (inPath) {
+		path = inPath;
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+	} else {
+		path = "stdin";
+		file = stdin;
+	}
+
+	sigRead();
+	struct Chunk ihdr = chunkRead();
+	if (strcmp(ihdr.type, "IHDR")) {
+		errx(EX_DATAERR, "%s: expected IHDR, found %s", path, ihdr.type);
+	}
+	headerRead(ihdr);
+	if (header.interlace != Progressive) {
+		errx(EX_CONFIG, "%s: unsupported interlacing", path);
+	}
+
+	palClear();
+	dataAlloc();
+	for (;;) {
+		struct Chunk chunk = chunkRead();
+		if (!strcmp(chunk.type, "PLTE")) {
+			palRead(chunk);
+		} else if (!strcmp(chunk.type, "tRNS")) {
+			transRead(chunk);
+		} else if (!strcmp(chunk.type, "IDAT")) {
+			dataRead(chunk);
+		} else if (!strcmp(chunk.type, "IEND")) {
+			break;
+		} else {
+			chunkSkip(chunk);
+		}
+	}
+	fclose(file);
+
+	dataRecon();
+	dataFilter();
+
+	if (invertData) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			for (size_t i = 0; i < lineLen; ++i) {
+				lineData(y)[i] ^= 0xFF;
+			}
+		}
+	}
+	if (mirrorData) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			for (size_t i = 0, j = lineLen-1; i < j; ++i, --j) {
+				uint8_t x = lineData(y)[i];
+				lineData(y)[i] = lineData(y)[j];
+				lineData(y)[j] = x;
+			}
+		}
+	}
+	if (zeroX) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			memset(lineData(y), 0, pixelLen);
+		}
+	}
+	if (zeroY) {
+		memset(lineData(0), 0, lineLen);
+	}
+
+	char buf[PATH_MAX];
+	if (outPath) {
+		path = outPath;
+		if (outPath == inPath) {
+			snprintf(buf, sizeof(buf), "%sg", outPath);
+			file = fopen(buf, "wx");
+			if (!file) err(EX_CANTCREAT, "%s", buf);
+		} else {
+			file = fopen(path, "w");
+			if (!file) err(EX_CANTCREAT, "%s", outPath);
+		}
+	} else {
+		path = "stdout";
+		file = stdout;
+	}
+
+	sigWrite();
+	headerWrite();
+	if (header.color == Indexed) {
+		palWrite();
+		if (trans.len) transWrite();
+	}
+	dataWrite();
+	free(data);
+	int error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+
+	if (outPath && outPath == inPath) {
+		error = rename(buf, outPath);
+		if (error) err(EX_CANTCREAT, "%s", outPath);
+	}
+}
+
+static enum Filter parseFilter(const char *str) {
+	switch (str[0]) {
+		case 'N': case 'n': return None;
+		case 'S': case 's': return Sub;
+		case 'U': case 'u': return Up;
+		case 'A': case 'a': return Average;
+		case 'P': case 'p': return Paeth;
+		default: errx(EX_USAGE, "invalid filter type %s", str);
+	}
+}
+
+static size_t parseFilters(enum Filter *filters, char *str) {
+	size_t len = 0;
+	while (str) {
+		char *filt = strsep(&str, ",");
+		filters[len++] = parseFilter(filt);
+	}
+	return len;
+}
+
+int main(int argc, char *argv[]) {
+	bool stdio = false;
+	char *outPath = NULL;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"));) {
+		switch (opt) {
+			break; case 'a': applyFilter = parseFilters(applyFilters, optarg);
+			break; case 'c': stdio = true;
+			break; case 'd': declFilter = parseFilters(declFilters, optarg);
+			break; case 'f': reconFilter = true;
+			break; case 'i': invertData = true;
+			break; case 'm': mirrorData = true;
+			break; case 'o': outPath = optarg;
+			break; case 'p': brokenPaeth = true;
+			break; case 'r': filterRecon = true;
+			break; case 'x': zeroX = true;
+			break; case 'y': zeroY = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	if (optind < argc) {
+		for (int i = optind; i < argc; ++i) {
+			glitch(argv[i], (stdio ? NULL : outPath ? outPath : argv[i]));
+		}
+	} else {
+		glitch(NULL, outPath);
+	}
+}
diff --git a/bin/hilex.c b/bin/hilex.c
new file mode 100644
index 00000000..7d7b3f2d
--- /dev/null
+++ b/bin/hilex.c
@@ -0,0 +1,406 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "hilex.h"
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+static const char *Class[] = {
+#define X(class) [class] = #class,
+	ENUM_CLASS
+#undef X
+};
+
+static FILE *yyin;
+static char *yytext;
+static int yylex(void) {
+	static size_t cap = 0;
+	return (getline(&yytext, &cap, yyin) < 0 ? None : Normal);
+}
+static const struct Lexer LexText = { yylex, &yyin, &yytext };
+
+static const struct {
+	const struct Lexer *lexer;
+	const char *name;
+	const char *namePatt;
+	const char *linePatt;
+} Lexers[] = {
+	{ &LexC, "c", "[.][chlmy]$", NULL },
+	{ &LexMake, "make", "[.](mk|am)$|^Makefile$", NULL },
+	{ &LexMdoc, "mdoc", "[.][1-9]$", "^[.]Dd" },
+	{ &LexSh, "sh", "[.]sh$|^[.](profile|shrc)$", "^#![ ]?/bin/k?sh" },
+	{ &LexText, "text", "[.]txt$", NULL },
+};
+
+static const struct Lexer *parseLexer(const char *name) {
+	for (size_t i = 0; i < ARRAY_LEN(Lexers); ++i) {
+		if (!strcmp(name, Lexers[i].name)) return Lexers[i].lexer;
+	}
+	errx(EX_USAGE, "unknown lexer %s", name);
+}
+
+static void ungets(const char *str, FILE *file) {
+	size_t len = strlen(str);
+	for (size_t i = len-1; i < len; --i) {
+		int ch = ungetc(str[i], file);
+		if (ch == EOF) errx(EX_IOERR, "cannot push back string");
+	}
+}
+
+static const struct Lexer *matchLexer(const char *name, FILE *file) {
+	char buf[256];
+	regex_t regex;
+	for (size_t i = 0; i < ARRAY_LEN(Lexers); ++i) {
+		int error = regcomp(
+			&regex, Lexers[i].namePatt, REG_EXTENDED | REG_NOSUB
+		);
+		assert(!error);
+		error = regexec(&regex, name, 0, NULL, 0);
+		regfree(&regex);
+		if (!error) return Lexers[i].lexer;
+	}
+	char *line = fgets(buf, sizeof(buf), file);
+	if (!line) return NULL;
+	for (size_t i = 0; i < ARRAY_LEN(Lexers); ++i) {
+		if (!Lexers[i].linePatt) continue;
+		int error = regcomp(
+			&regex, Lexers[i].linePatt, REG_EXTENDED | REG_NOSUB
+		);
+		assert(!error);
+		error = regexec(&regex, line, 0, NULL, 0);
+		regfree(&regex);
+		if (!error) {
+			ungets(line, file);
+			return Lexers[i].lexer;
+		}
+	}
+	ungets(line, file);
+	return NULL;
+}
+
+#define ENUM_OPTION \
+	X(Document, "document") \
+	X(Inline, "inline") \
+	X(Monospace, "monospace") \
+	X(Pre, "pre") \
+	X(Style, "style") \
+	X(Tab, "tab") \
+	X(Title, "title")
+
+enum Option {
+#define X(option, key) option,
+	ENUM_OPTION
+#undef X
+	OptionCap,
+};
+
+typedef void Header(const char *opts[]);
+typedef void Output(const char *opts[], enum Class class, const char *text);
+
+static bool pager;
+static void ansiHeader(const char *opts[]) {
+	(void)opts;
+	if (!pager) return;
+	const char *shell = getenv("SHELL");
+	const char *pager = getenv("PAGER");
+	if (!shell) shell = "/bin/sh";
+	if (!pager) pager = "less";
+	setenv("LESS", "FRX", 0);
+
+	int rw[2];
+	int error = pipe(rw);
+	if (error) err(EX_OSERR, "pipe");
+
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+	if (!pid) {
+		dup2(rw[0], STDIN_FILENO);
+		close(rw[0]);
+		close(rw[1]);
+		execl(shell, shell, "-c", pager, NULL);
+		err(EX_CONFIG, "%s", shell);
+	}
+	dup2(rw[1], STDOUT_FILENO);
+	close(rw[0]);
+	close(rw[1]);
+	setlinebuf(stdout);
+
+#ifdef __OpenBSD__
+	error = pledge("stdio", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+}
+
+static void ansiFooter(const char *opts[]) {
+	(void)opts;
+	if (!pager) return;
+	int status;
+	fclose(stdout);
+	wait(&status);
+}
+
+static const char *SGR[ClassCap] = {
+	[Keyword] = "37",
+	[Macro]   = "32",
+	[Comment] = "34",
+	[String]  = "36",
+	[Format]  = "36;1;96",
+	[Subst]   = "33",
+};
+
+static void ansiFormat(const char *opts[], enum Class class, const char *text) {
+	(void)opts;
+	if (!SGR[class]) {
+		printf("%s", text);
+		return;
+	}
+	// Set color on each line for piping to less -R:
+	for (const char *nl; (nl = strchr(text, '\n')); text = &nl[1]) {
+		printf("\33[%sm%.*s\33[m\n", SGR[class], (int)(nl - text), text);
+	}
+	if (*text) printf("\33[%sm%s\33[m", SGR[class], text);
+}
+
+static void
+debugFormat(const char *opts[], enum Class class, const char *text) {
+	if (class != Normal) {
+		printf("%s(", Class[class]);
+		ansiFormat(opts, class, text);
+		printf(")");
+	} else {
+		printf("%s", text);
+	}
+}
+
+static const char *IRC[ClassCap] = {
+	[Keyword] = "\00315",
+	[Macro]   = "\0033",
+	[Comment] = "\0032",
+	[String]  = "\00310",
+	[Format]  = "\00311",
+	[Subst]   = "\0037",
+};
+
+static void ircHeader(const char *opts[]) {
+	if (opts[Monospace]) printf("\21");
+}
+
+static const char *stop(const char *text) {
+	return (*text == ',' || isdigit(*text) ? "\2\2" : "");
+}
+
+static void ircFormat(const char *opts[], enum Class class, const char *text) {
+	for (const char *nl; (nl = strchr(text, '\n')); text = &nl[1]) {
+		if (IRC[class]) printf("%s%s", IRC[class], stop(text));
+		printf("%.*s\n", (int)(nl - text), text);
+		if (opts[Monospace]) printf("\21");
+	}
+	if (*text) {
+		if (IRC[class]) {
+			printf("%s%s%s\17", IRC[class], stop(text), text);
+			if (opts[Monospace]) printf("\21");
+		} else {
+			printf("%s", text);
+		}
+	}
+}
+
+static void htmlEscape(const char *text) {
+	while (*text) {
+		switch (*text) {
+			break; case '"': text++; printf("&quot;");
+			break; case '&': text++; printf("&amp;");
+			break; case '<': text++; printf("&lt;");
+		}
+		size_t len = strcspn(text, "\"&<");
+		if (len) fwrite(text, len, 1, stdout);
+		text += len;
+	}
+}
+
+static const char *Styles[ClassCap] = {
+	[Keyword] = "color: dimgray;",
+	[Macro]   = "color: green;",
+	[Comment] = "color: navy;",
+	[String]  = "color: teal;",
+	[Format]  = "color: teal; font-weight: bold;",
+	[Subst]   = "color: olive;",
+};
+
+static void styleTabSize(const char *tab) {
+	printf("-moz-tab-size: ");
+	htmlEscape(tab);
+	printf("; tab-size: ");
+	htmlEscape(tab);
+	printf(";");
+}
+
+static void htmlHeader(const char *opts[]) {
+	if (!opts[Document]) goto body;
+
+	printf("<!DOCTYPE html>\n<title>");
+	if (opts[Title]) htmlEscape(opts[Title]);
+	printf("</title>\n");
+
+	if (opts[Style]) {
+		printf("<link rel=\"stylesheet\" href=\"");
+		htmlEscape(opts[Style]);
+		printf("\">\n");
+	} else if (!opts[Inline]) {
+		printf("<style>\n");
+		if (opts[Tab]) {
+			printf("pre.hilex { ");
+			styleTabSize(opts[Tab]);
+			printf(" }\n");
+		}
+		for (enum Class class = 0; class < ClassCap; ++class) {
+			if (!Styles[class]) continue;
+			printf("pre.hilex .%.2s { %s }\n", Class[class], Styles[class]);
+		}
+		printf("</style>\n");
+	}
+
+body:
+	if ((opts[Document] || opts[Pre]) && opts[Inline] && opts[Tab]) {
+		printf("<pre class=\"hilex\" style=\"");
+		styleTabSize(opts[Tab]);
+		printf("\">");
+	} else if (opts[Document] || opts[Pre]) {
+		printf("<pre class=\"hilex\">");
+	}
+}
+
+static void htmlFooter(const char *opts[]) {
+	if (opts[Document] || opts[Pre]) printf("</pre>");
+	if (opts[Document]) printf("\n");
+}
+
+static void htmlFormat(const char *opts[], enum Class class, const char *text) {
+	if (class != Normal) {
+		if (opts[Inline]) {
+			printf("<span style=\"%s\">", Styles[class] ? Styles[class] : "");
+		} else {
+			printf("<span class=\"%.2s\">", Class[class]);
+		}
+		htmlEscape(text);
+		printf("</span>");
+	} else {
+		htmlEscape(text);
+	}
+}
+
+static const struct Formatter {
+	const char *name;
+	Header *header;
+	Output *format;
+	Header *footer;
+} Formatters[] = {
+	{ "ansi", ansiHeader, ansiFormat, ansiFooter },
+	{ "debug", NULL, debugFormat, NULL },
+	{ "html", htmlHeader, htmlFormat, htmlFooter },
+	{ "irc", ircHeader, ircFormat, NULL },
+};
+
+static const struct Formatter *parseFormatter(const char *name) {
+	for (size_t i = 0; i < ARRAY_LEN(Formatters); ++i) {
+		if (!strcmp(name, Formatters[i].name)) return &Formatters[i];
+	}
+	errx(EX_USAGE, "unknown formatter %s", name);
+}
+
+static char *const OptionKeys[OptionCap + 1] = {
+#define X(option, key) [option] = key,
+	ENUM_OPTION
+#undef X
+	NULL,
+};
+
+int main(int argc, char *argv[]) {
+	bool text = false;
+	const char *name = NULL;
+	const struct Lexer *lexer = NULL;
+	const struct Formatter *formatter = &Formatters[0];
+	const char *opts[OptionCap] = {0};
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "f:l:n:o:t"));) {
+		switch (opt) {
+			break; case 'f': formatter = parseFormatter(optarg);
+			break; case 'l': lexer = parseLexer(optarg);
+			break; case 'n': name = optarg;
+			break; case 'o': {
+				while (*optarg) {
+					char *val;
+					int key = getsubopt(&optarg, OptionKeys, &val);
+					if (key < 0) errx(EX_USAGE, "no such option %s", val);
+					opts[key] = (val ? val : "");
+				}
+			}
+			break; case 't': text = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	const char *path = "(stdin)";
+	FILE *file = stdin;
+	if (optind < argc) {
+		path = argv[optind];
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+		pager = isatty(STDOUT_FILENO);
+	}
+
+#ifdef __OpenBSD__
+	int error;
+	if (formatter->header == ansiHeader && pager) {
+		error = pledge("stdio proc exec", NULL);
+	} else {
+		error = pledge("stdio", NULL);
+	}
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	if (!name) {
+		if (NULL != (name = strrchr(path, '/'))) {
+			name++;
+		} else {
+			name = path;
+		}
+	}
+	if (!opts[Title]) opts[Title] = name;
+	if (!lexer) lexer = matchLexer(name, file);
+	if (!lexer && text) lexer = &LexText;
+	if (!lexer) errx(EX_USAGE, "cannot infer lexer for %s", name);
+
+	*lexer->in = file;
+	if (formatter->header) formatter->header(opts);
+	for (enum Class class; None != (class = lexer->lex());) {
+		assert(class < ClassCap);
+		formatter->format(opts, class, *lexer->text);
+	}
+	if (formatter->footer) formatter->footer(opts);
+}
diff --git a/bin/hilex.h b/bin/hilex.h
new file mode 100644
index 00000000..b57fc8cc
--- /dev/null
+++ b/bin/hilex.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#define ENUM_CLASS \
+	X(None) \
+	X(Normal) \
+	X(Operator) \
+	X(Number) \
+	X(Keyword) \
+	X(Ident) \
+	X(Macro) \
+	X(Comment) \
+	X(String) \
+	X(Escape) \
+	X(Format) \
+	X(Subst)
+
+enum Class {
+#define X(class) class,
+	ENUM_CLASS
+#undef X
+	ClassCap,
+};
+
+typedef int Lex(void);
+struct Lexer {
+	Lex *lex;
+	FILE **in;
+	char **text;
+};
+
+extern const struct Lexer LexC;
+extern const struct Lexer LexMake;
+extern const struct Lexer LexMdoc;
+extern const struct Lexer LexSh;
diff --git a/bin/htagml.c b/bin/htagml.c
new file mode 100644
index 00000000..1f547be6
--- /dev/null
+++ b/bin/htagml.c
@@ -0,0 +1,223 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static char *deregex(const char *patt) {
+	char *buf = malloc(strlen(patt) + 1);
+	if (!buf) err(EX_OSERR, "malloc");
+	char *ptr = buf;
+	if (*patt == '^') patt++;
+	for (; *patt; ++patt) {
+		if (patt[0] == '$' && !patt[1]) {
+			*ptr++ = '\n';
+			break;
+		}
+		if (patt[0] == '\\' && patt[1]) patt++;
+		*ptr++ = *patt;
+	}
+	*ptr = '\0';
+	return buf;
+}
+
+static size_t escape(bool esc, const char *ptr, size_t len) {
+	if (!esc) {
+		fwrite(ptr, len, 1, stdout);
+		return len;
+	}
+	for (size_t i = 0; i < len; ++i) {
+		switch (ptr[i]) {
+			break; case '&': printf("&amp;");
+			break; case '<': printf("&lt;");
+			break; case '"': printf("&quot;");
+			break; default:  putchar(ptr[i]);
+		}
+	}
+	return len;
+}
+
+static void id(const char *tag) {
+	for (const char *ch = tag; *ch; ++ch) {
+		if (isalnum(*ch) || strchr("-._", *ch)) {
+			putchar(*ch);
+		} else {
+			putchar('_');
+		}
+	}
+}
+
+static char *hstrstr(const char *haystack, const char *needle) {
+	while (haystack) {
+		char *elem = strchr(haystack, '<');
+		char *match = strstr(haystack, needle);
+		if (!match) return NULL;
+		if (!elem || match < elem) return match;
+		haystack = strchr(elem, '>');
+	}
+	return NULL;
+}
+
+static int isident(int c) {
+	return isalnum(c) || c == '_';
+}
+
+int main(int argc, char *argv[]) {
+	bool pre = false;
+	bool pipe = false;
+	bool main = false;
+	bool index = false;
+	const char *tagsPath = "tags";
+	for (int opt; 0 < (opt = getopt(argc, argv, "f:impx"));) {
+		switch (opt) {
+			break; case 'f': tagsPath = optarg;
+			break; case 'i': pipe = true;
+			break; case 'm': main = true;
+			break; case 'p': pre = true;
+			break; case 'x': index = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind == argc) errx(EX_USAGE, "name required");
+	const char *name = argv[optind];
+
+	FILE *file = fopen(name, "r");
+	if (!file) err(EX_NOINPUT, "%s", name);
+
+	FILE *tagsFile = fopen(tagsPath, "r");
+	if (!tagsFile) err(EX_NOINPUT, "%s", tagsPath);
+
+#ifdef __OpenBSD__
+	int error = pledge("stdio", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	size_t len = 0;
+	size_t cap = 256;
+	struct Tag {
+		char *tag;
+		int num;
+		char *str;
+		size_t len;
+	} *tags = malloc(cap * sizeof(*tags));
+	if (!tags) err(EX_OSERR, "malloc");
+
+	char *buf = NULL;
+	size_t bufCap = 0;
+	while (0 < getline(&buf, &bufCap, tagsFile)) {
+		char *line = buf;
+		char *tag = strsep(&line, "\t");
+		char *file = strsep(&line, "\t");
+		char *def = strsep(&line, "\n");
+		if (!tag || !file || !def) errx(EX_DATAERR, "malformed tags file");
+
+		if (strcmp(file, name)) continue;
+		if (len == cap) {
+			tags = realloc(tags, (cap *= 2) * sizeof(*tags));
+			if (!tags) err(EX_OSERR, "realloc");
+		}
+		tags[len].tag = strdup(tag);
+		if (!tags[len].tag) err(EX_OSERR, "strdup");
+
+		tags[len].num = 0;
+		if (def[0] == '/' || def[0] == '?') {
+			def++;
+			def[strlen(def)-1] = '\0';
+			if (def[0] != '^') {
+				warnx("unanchored regex for tag %s: %s", tag, def);
+			}
+			tags[len].str = deregex(def);
+			tags[len].len = strlen(tags[len].str);
+		} else {
+			tags[len].num = strtol(def, &def, 10);
+			if (*def) {
+				warnx("invalid line number for tag %s: %s", tag, def);
+				continue;
+			}
+		}
+		len++;
+	}
+	fclose(tagsFile);
+
+	int num = 0;
+	printf(pre ? "<pre>" : index ? "<ul class=\"index\">\n" : "");
+	while (0 < getline(&buf, &bufCap, file) && ++num) {
+		char *tag = NULL;
+		for (size_t i = 0; i < len; ++i) {
+			if (tags[i].num) {
+				if (num != tags[i].num) continue;
+			} else {
+				if (strncmp(tags[i].str, buf, tags[i].len)) continue;
+			}
+			tag = tags[i].tag;
+			tags[i] = tags[--len];
+			break;
+		}
+		if (index) {
+			if (!tag) continue;
+			printf("<li><a class=\"tag\" href=\"#");
+			id(tag);
+			printf("\">");
+			escape(true, tag, strlen(tag));
+			printf("</a></li>\n");
+			continue;
+		}
+		if (pipe) {
+			ssize_t len = getline(&buf, &bufCap, stdin);
+			if (len < 0) {
+				errx(EX_DATAERR, "missing line %d on standard input", num);
+			}
+		}
+		if (!tag) {
+			escape(!pipe, buf, strlen(buf));
+			continue;
+		}
+
+		size_t mlen = strlen(tag);
+		char *match = (pipe ? hstrstr : strstr)(buf, tag);
+		while (
+			match &&
+			((match > buf && isident(match[-1])) || isident(match[mlen]))
+		) {
+			match = (pipe ? hstrstr : strstr)(&match[mlen], tag);
+		}
+		if (!match && tag[0] == 'M') {
+			mlen = 4;
+			match = (pipe ? hstrstr : strstr)(buf, "main");
+			if (main) tag = "main";
+		}
+		if (!match) {
+			mlen = strlen(buf) - 1;
+			match = buf;
+		}
+		escape(!pipe, buf, match - buf);
+		printf("<a class=\"tag\" id=\"");
+		id(tag);
+		printf("\" href=\"#");
+		id(tag);
+		printf("\">");
+		match += escape(!pipe, match, mlen);
+		printf("</a>");
+		escape(!pipe, match, strlen(match));
+	}
+	printf(pre ? "</pre>" : index ? "</ul>\n" : "");
+}
diff --git a/bin/html.mk b/bin/html.mk
new file mode 100644
index 00000000..818c6cf5
--- /dev/null
+++ b/bin/html.mk
@@ -0,0 +1,47 @@
+WEBROOT ?= /var/www/causal.agency
+
+HTMLS = index.html png.html
+HTMLS += ${BINS:=.html}
+HTMLS += ${BSD:=.html}
+HTMLS += ${GAMES:=.html}
+HTMLS += ${TLS:=.html}
+
+html: ${HTMLS}
+	@true
+
+install-html: ${HTMLS}
+	install -d ${WEBROOT}/bin
+	install -C -m 644 ${HTMLS} ${WEBROOT}/bin
+
+${HTMLS}: html.sh scheme hilex htagml htmltags
+
+htmltags: *.[chly] mtags Makefile html.mk *.sh
+	rm -f $@
+	for f in *.[chly]; do ctags -aw -f $@ $$f; done
+	./mtags -a -f $@ Makefile html.mk *.sh
+
+index.html: README.7 Makefile html.mk html.sh
+	sh html.sh README.7 Makefile html.mk html.sh > $@
+
+.SUFFIXES: .html
+
+.c.html:
+	sh html.sh man1/${<:.c=.1} $< > $@
+
+.h.html:
+	sh html.sh man3/${<:.h=.3} $< > $@
+
+.l.html:
+	sh html.sh man1/${<:.l=.1} $< > $@
+
+.y.html:
+	sh html.sh man1/${<:.y=.1} $< > $@
+
+.sh.html:
+	sh html.sh man1/${<:.sh=.1} $< > $@
+
+.pl.html:
+	sh html.sh man1/${<:.pl=.1} $< > $@
+
+freecell.html: freecell.c man6/freecell.6
+	sh html.sh man6/freecell.6 freecell.c > $@
diff --git a/bin/html.sh b/bin/html.sh
new file mode 100644
index 00000000..3223120b
--- /dev/null
+++ b/bin/html.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+set -eu
+
+readonly GitURL='https://git.causal.agency/src/tree/bin'
+
+man=$1
+shift
+title=${man##*/}
+title=${title%.[1-9]}
+
+cat <<EOF
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+<title>${title}</title>
+<style>
+html { line-height: 1.25em; font-family: monospace; }
+body { max-width: 80ch; margin: 1em auto; padding: 0 1ch; }
+
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
+
+table { border-collapse: collapse; }
+table.Nm code.Nm { padding-right: 1ch; }
+table.foot { margin-top: 1em; }
+
+ul.index { padding: 0; }
+ul.index li { display: inline; list-style-type: none; }
+pre { -moz-tab-size: 4; tab-size: 4; }
+
+$(./scheme -st)
+html { background-color: var(--ansi16); color: var(--ansi17); }
+a { color: var(--ansi4); }
+a:visited { color: var(--ansi5); }
+a.permalink, a.tag { color: var(--ansi3); text-decoration: none; }
+a.permalink > code:target, *:target > a.permalink,
+a.tag:target { color: var(--ansi11); }
+pre .Ke { color: var(--ansi7); }
+pre .Ma { color: var(--ansi2); }
+pre .Co { color: var(--ansi4); }
+pre .St { color: var(--ansi6); }
+pre .Fo { color: var(--ansi14); }
+pre .Su { color: var(--ansi1); }
+</style>
+EOF
+
+opts=fragment
+[ "${man}" = "README.7" ] && opts=${opts},man=%N.html
+mandoc -T html -O ${opts} "${man}"
+
+for src; do
+	cat <<-EOF
+	<p>
+	<a href="${GitURL}/${src}">${src} in git</a>
+	EOF
+	./htagml -x -f htmltags "${src}"
+	./hilex -t -f html "${src}" | ./htagml -ip -f htmltags "${src}"
+done
diff --git a/bin/make.l b/bin/make.l
new file mode 100644
index 00000000..6296716d
--- /dev/null
+++ b/bin/make.l
@@ -0,0 +1,127 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option prefix="make"
+%option noinput nounput noyywrap
+
+%{
+#include "hilex.h"
+%}
+
+%s Assign Preproc
+%x Variable Shell
+
+ident [._[:alnum:]]+
+assign [+?:!]?=
+target [-._/?*\[\][:alnum:]]+
+operator [:!]|::
+
+%%
+	static int pop = INITIAL;
+	static int depth = 0;
+
+^"\t"+ {
+	BEGIN(pop = Shell);
+	return Normal;
+}
+<Shell>{
+	"\n" {
+		BEGIN(pop = INITIAL);
+		return Normal;
+	}
+	"\\\n" { return Normal; }
+	[^\\\n$]+|. { return Normal; }
+}
+
+[[:blank:]]+ { return Normal; }
+
+{operator} { return Operator; }
+
+"."(PHONY|PRECIOUS|SUFFIXES)/{operator}? {
+	return Keyword;
+}
+
+{target}/{operator} { return Ident; }
+
+^"."{ident} |
+^"-"?include {
+	BEGIN(pop = Preproc);
+	return Macro;
+}
+<Preproc>{
+	"\n" {
+		BEGIN(pop = INITIAL);
+		return Normal;
+	}
+	"\\\n""\t"? { return Normal; }
+
+	"\""[^""]*"\"" |
+	"<"[^>]*">" {
+		return String;
+	}
+
+	[!<>=]"="?|"||"|"&&" { return Operator; }
+	[0-9]+|"0x"[[:xdigit:]]+ { return Number; }
+	defined|make|empty|exists|target|commands|in { return Keyword; }
+}
+
+^{ident}/[[:blank:]]*{assign} {
+	return Ident;
+}
+
+{assign} {
+	BEGIN(pop = Assign);
+	return Operator;
+}
+<Assign>{
+	"\n" {
+		BEGIN(pop = INITIAL);
+		return Normal;
+	}
+	"\\\n""\t"? { return Escape; }
+	[^\\$[:space:]]+|. { return String; }
+}
+
+{target} { return Ident; }
+
+"#"([^\\\n]|"\\"[^\n]|"\\\n")* { return Comment; }
+
+<*>{
+	"$"("{"|"(")/[^$] {
+		depth++;
+		BEGIN(Variable);
+		yymore();
+	}
+	"$"("{"|"(") {
+		depth++;
+		BEGIN(Variable);
+		return Subst;
+	}
+	"$". { return Subst; }
+}
+<Variable>{
+	[^${}()]*"}"|")" {
+		if (!--depth) BEGIN(pop);
+		return Subst;
+	}
+	[^${}()]+ { return Subst; }
+}
+
+.|\n { return Normal; }
+
+%%
+
+const struct Lexer LexMake = { yylex, &yyin, &yytext };
diff --git a/bin/man1/beef.1 b/bin/man1/beef.1
new file mode 100644
index 00000000..ea52cfa0
--- /dev/null
+++ b/bin/man1/beef.1
@@ -0,0 +1,91 @@
+.Dd August 28, 2019
+.Dt BEEF 1
+.Os
+.
+.Sh NAME
+.Nm beef
+.Nd Befunge-93 interpreter
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+is a Befunge-93 interpreter.
+If no
+.Ar file
+is provided,
+the program is read from standard input.
+.
+.Ss Befunge-93 Command Summary
+.Bl -tag -width "0-9" -compact
+.It \(dq
+toggle string mode
+.It 0-9
+push value
+.It +
+add
+.It -
+subtract
+.It *
+multiply
+.It /
+divide
+.It %
+modulo
+.It !
+not
+.It `
+greater than
+.It >
+right
+.It <
+left
+.It ^
+up
+.It v
+down
+.It ?
+random
+.It _
+horizontal (left) if
+.It |
+vertical (up) if
+.It :
+duplicate
+.It \e
+swap
+.It $
+drop
+.It .
+output integer
+.It ,
+output ASCII
+.It #
+bridge
+.It g
+get (y, x)
+.It p
+put (y, x) = v
+.It &
+input integer
+.It ~
+input ASCII
+.It @
+exit
+.El
+.
+.Sh EXIT STATUS
+.Nm
+exits with the top value left on the stack,
+or 0 if the stack is left empty.
+.
+.Sh STANDARDS
+.Rs
+.%A Chris Pressey
+.%Q Cat's Eye Technologies
+.%T Befunge-93
+.%D September, 1993
+.%U https://github.com/catseye/Befunge-93/blob/master/doc/Befunge-93.markdown
+.Re
diff --git a/bin/man1/bibsort.1 b/bin/man1/bibsort.1
new file mode 100644
index 00000000..07ed91ef
--- /dev/null
+++ b/bin/man1/bibsort.1
@@ -0,0 +1,40 @@
+.Dd February 16, 2021
+.Dt BIBSORT 1
+.Os
+.
+.Sh NAME
+.Nm bibsort
+.Nd reformat bibliography
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+reformats on standard output
+the
+.Em STANDARDS
+section of the
+.Xr mdoc 7
+manual page
+.Ar file
+or standard input.
+Bibliographic references
+are sorted by author last names,
+and formatted in an item list
+with macro lines appearing
+in the order they are formatted by
+.Xr mandoc 1 .
+Additionally,
+.Ic \&%N
+macros referencing RFC numbers
+are rewritten to
+.Ic \&%R
+macros
+and missing
+.Ic \&%U
+macros are added for RFCs.
+.
+.Sh EXAMPLES
+.Dl :%!bibsort
diff --git a/bin/man1/bit.1 b/bin/man1/bit.1
new file mode 100644
index 00000000..b91a10e1
--- /dev/null
+++ b/bin/man1/bit.1
@@ -0,0 +1,55 @@
+.Dd December 30, 2020
+.Dt BIT 1
+.Os
+.
+.Sh NAME
+.Nm bit
+.Nd a calculator
+.
+.Sh SYNOPSIS
+.Nm
+.
+.Sh DESCRIPTION
+.Nm
+is an integer calculator.
+Its syntax resembles that of C expressions,
+with the following changes:
+.
+.Bl -bullet
+.It
+Underscores are allowed in integer literals.
+.It
+The
+.Sy 0b
+prefix is used for binary literals.
+.It
+The
+.Sy ->
+operator is used for arithmetic shift.
+.It
+The unary
+.Sy &
+operator is equivalent to
+.Sy (1 << x) - 1 .
+.It
+The postfix operators
+.Sy K ,
+.Sy M ,
+.Sy G ,
+.Sy T
+are used as constant multipliers.
+.It
+The postfix operator
+.Sy $
+is of lowest precedence and is equivalent to
+wrapping the preceding expression in parentheses.
+.It
+Single-letter (lower case) variables
+can be assigned.
+The variable
+.Sy _
+stores the previous result.
+.El
+.
+.Sh SEE ALSO
+.Xr operator 7
diff --git a/bin/man1/c.1 b/bin/man1/c.1
new file mode 100644
index 00000000..97384ebe
--- /dev/null
+++ b/bin/man1/c.1
@@ -0,0 +1,45 @@
+.Dd January  9, 2021
+.Dt C 1
+.Os
+.
+.Sh NAME
+.Nm c
+.Nd run C
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl t
+.Op Fl e Ar expr
+.Op Fl i Ar include
+.Op Ar stmts ...
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility compiles and runs
+C statements wrapped in
+.Fn main
+with common includes.
+If no
+.Ar expr
+or
+.Ar stmts
+are provided,
+statements are read from standard input.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl e Ar expr
+Print the result of the C expression
+.Ar expr
+after executing
+.Ar stmts .
+.It Fl i Ar include
+Add the include file
+.Ar include .
+.It Fl t
+With
+.Fl e ,
+print the type of the expression.
+.El
diff --git a/bin/man1/dehtml.1 b/bin/man1/dehtml.1
new file mode 100644
index 00000000..c55c35d4
--- /dev/null
+++ b/bin/man1/dehtml.1
@@ -0,0 +1,38 @@
+.Dd September  7, 2021
+.Dt DEHTML 1
+.Os
+.
+.Sh NAME
+.Nm dehtml
+.Nd extract text from HTML
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Op Ar
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility extracts text
+from HTML documents.
+Text inside
+.Sy <title> ,
+.Sy <style>
+and
+.Sy <script>
+tags is discarded.
+Numeric and common named HTML entities
+are converted.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl s
+Collapse whitespace outside of
+.Sy <pre>
+tags.
+.El
+.
+.Sh BUGS
+There is no way to extract image alt text.
diff --git a/bin/man1/downgrade.1 b/bin/man1/downgrade.1
new file mode 100644
index 00000000..e1a594b7
--- /dev/null
+++ b/bin/man1/downgrade.1
@@ -0,0 +1,122 @@
+.Dd September 14, 2021
+.Dt DOWNGRADE 1
+.Os
+.
+.Sh NAME
+.Nm downgrade
+.Nd IRC features for all
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl iv
+.Op Fl c Ar cert
+.Op Fl j Ar join
+.Op Fl k Ar priv
+.Op Fl n Ar nick
+.Op Fl p Ar port
+.Ar host
+.
+.Sh DESCRIPTION
+The
+.Nm
+IRC bot downgrades new IRC
+.Dq features
+so
+.Em everyone
+can see them.
+It supports typing notifications,
+message reactions
+and message replies.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar cert
+Load the TLS client certificate from
+.Ar cert
+and authenticate using SASL EXTERNAL.
+.It Fl i
+Accept invites to channels.
+.It Fl j Ar join
+Join the channel list
+.Ar join .
+.It Fl k Ar priv
+Load the TLS client private key from
+.Ar priv .
+The default is the same path as
+.Ar cert .
+.It Fl n Ar nick
+Set the nickname and username to
+.Ar nick .
+The default is
+.Nm .
+.It Fl p Ar port
+Connect to
+.Ar port .
+The default is 6697.
+.It Fl v
+Log IRC protocol.
+.It Ar host
+Connect to
+.Ar host .
+.El
+.
+.Sh EXAMPLES
+.Bd -literal
+-downgrade- * guest-n4 is typing...
+<guest-n4> wtf
+-downgrade- * june reacted to guest-n4's message ("wtf") with "\[u1F44D]"
+-downgrade- * guest-n4 is typing...
+-downgrade- * guest-n4 has given up :(
+.Ed
+.Bd -literal
+<june> ,bef
+-downgrade- * tildebot is typing...
+<tildebot> [Ducks] june: There was no duck!
+-downgrade- * tildebot was replying to june's message (",bef")
+.Ed
+.
+.Sh STANDARDS
+.Bl -item
+.It
+.Rs
+.%A Kiyoshi Aman
+.%A Kyle Fuller
+.%A St\('ephan Kochen
+.%A Alexey Sokolov
+.%A James Wheare
+.%T Message Tags
+.%U https://ircv3.net/specs/extensions/message-tags
+.Re
+.It
+.Rs
+.%A MuffinMedic
+.%A James Wheare
+.%T typing client tag
+.%U https://ircv3.net/specs/client-tags/typing
+.Re
+.It
+.Rs
+.%A Daniel Oaks
+.%T Bot Mode
+.%U https://ircv3.net/specs/extensions/bot-mode
+.Re
+.It
+.Rs
+.%A James Wheare
+.%T Message IDs
+.%U https://ircv3.net/specs/extensions/message-ids
+.Re
+.It
+.Rs
+.%A James Wheare
+.%T react client tag
+.%U https://ircv3.net/specs/client-tags/react
+.Re
+.It
+.Rs
+.%A James Wheare
+.%T reply client tag
+.%U https://ircv3.net/specs/client-tags/reply
+.Re
+.El
diff --git a/bin/man1/dtch.1 b/bin/man1/dtch.1
new file mode 100644
index 00000000..e27713e1
--- /dev/null
+++ b/bin/man1/dtch.1
@@ -0,0 +1,67 @@
+.Dd August 12, 2019
+.Dt DTCH 1
+.Os
+.
+.Sh NAME
+.Nm dtch
+.Nd detached sessions
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Ar name
+.Op Ar command ...
+.Nm
+.Fl a
+.Ar name
+.
+.Sh DESCRIPTION
+.Nm
+spawns a
+.Ar command
+in a detachable session.
+If no
+.Ar command
+is given,
+the value of
+.Ev SHELL
+is used.
+The
+.Nm
+process
+should be run as a background job
+or with
+.Xr nohup 1 .
+.
+.Pp
+To attach to an existing session,
+pass the
+.Fl a
+flag.
+To detach from the session,
+type
+.Ic ^Q .
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Attach to an existing session.
+.It Fl s
+Sink the output of
+.Ar command
+while detached.
+.El
+.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa ~/.dtch
+Directory of UNIX-domain sockets
+for each session.
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+dtch foo vim &
+dtch -a foo
+.Ed
diff --git a/bin/man1/enc.1 b/bin/man1/enc.1
new file mode 100644
index 00000000..32845847
--- /dev/null
+++ b/bin/man1/enc.1
@@ -0,0 +1,55 @@
+.Dd January 30, 2022
+.Dt ENC 1
+.Os
+.
+.Sh NAME
+.Nm enc
+.Nd encrypt and decrypt files
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl acdef
+.Op Ar
+.
+.Sh DESCRIPTION
+.Nm
+encrypts and decrypts files
+using ChaCha20 via
+.Xr openssl 1 .
+When encrypting files,
+the
+.Pa .enc
+extension is added.
+When decrypting files,
+the
+.Pa .enc
+extension is removed,
+if possible.
+Otherwise output is written
+to standard output.
+Input files are not removed.
+If no files are provided,
+standard input is encrypted or decrypted.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Encrypted data is Base64-encoded.
+.It Fl c
+Always write to standard output.
+.It Fl d
+Decrypt.
+.It Fl e
+Encrypt.
+This is the default.
+.It Fl f
+Do not ask to confirm overwriting files.
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+$ enc secret.txt
+$ rm secret.txt
+$ enc -d secret.txt.enc
+.Ed
diff --git a/bin/man1/ever.1 b/bin/man1/ever.1
new file mode 100644
index 00000000..8cdab99b
--- /dev/null
+++ b/bin/man1/ever.1
@@ -0,0 +1,51 @@
+.Dd February 24, 2021
+.Dt EVER 1
+.Os
+.
+.Sh NAME
+.Nm ever
+.Nd watch files
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl iq
+.Ar
+.Ar command
+.Nm
+.Op Fl i
+.Ar
+.Fl -
+.Ar command
+.Op Ar argument ...
+.
+.Sh DESCRIPTION
+.Nm
+executes the
+.Ar command
+whenever
+.Ar file
+is modified.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl i
+Attach the
+.Ar file
+which was modified
+to the standard input of
+.Ar command .
+.It Fl q
+Suppress exit status output.
+.El
+.
+.Sh EXAMPLES
+.Dl ever ever.c make
+.Dl ever when.y ever.c -- make when ever
+.Dl ever -i ever.1 mandoc
+.
+.Sh CAVEATS
+.Nm
+does not support Linux
+since it uses
+.Xr kqueue 2 .
diff --git a/bin/man1/git-comment.1 b/bin/man1/git-comment.1
new file mode 100644
index 00000000..8e958f30
--- /dev/null
+++ b/bin/man1/git-comment.1
@@ -0,0 +1,117 @@
+.Dd September 10, 2021
+.Dt GIT-COMMENT 1
+.Os
+.
+.Sh NAME
+.Nm git-comment
+.Nd add comments from commit messages
+.
+.Sh SYNOPSIS
+.Nm git comment
+.Op Fl \-all
+.Op Fl \-comment-start Ar string
+.Op Fl \-comment-lead Ar string
+.Op Fl \-comment-end Ar string
+.Op Fl \-min-group Ar lines
+.Op Fl \-min-repeat Ar lines
+.Op Fl \-no-repeat
+.Op Fl \-pretty Ar format
+.Op Ar options ...
+.Op Fl \-
+.Ar file
+.
+.Sh DESCRIPTION
+The
+.Nm
+command
+adds comments to a file
+showing the commit messages
+which last modified
+each group of lines.
+By default only commit messages with bodies
+and which modified groups of at least 2 lines
+are added.
+Each comment contains
+the abbreviated commit hash
+and the commit summary,
+followed by the commit body.
+.
+.Pp
+.Nm
+accepts all the options of
+.Xr git-blame 1
+in addition to the following:
+.Bl -tag -width Ds
+.It Fl \-all
+Include all commit messages.
+The default is to include
+only commit messages with bodies
+(lines after the summary).
+.
+.It Fl \-comment-start Ar string
+Start comments with
+.Ar string .
+The default is the value of
+.Cm comment.start
+or
+.Ql /* .
+.
+.It Fl \-comment-lead Ar string
+Continue comments with the leading
+.Ar string .
+The default is the value of
+.Cm comment.lead
+or
+.Ql " *" .
+.
+.It Fl \-comment-end Ar string
+End comments with
+.Ar string .
+The default is the value of
+.Cm comment.end
+or
+.Ql " */" .
+.
+.It Fl \-min-group Ar lines
+Add comments only for groups of at least
+.Ar lines .
+The default is 2 lines.
+.
+.It Fl \-min-repeat Ar lines
+Avoid repeating a comment
+if it occurred in the last
+.Ar lines .
+The default is 30 lines.
+.
+.It Fl \-no-repeat
+Avoid repeating comments entirely.
+.
+.It Fl \-pretty Ar format
+Set the pretty-print format
+to use for commit messages.
+The default is the value of
+.Cm comment.pretty
+or
+.Ql format:%h\ %s%n%n%-b .
+See
+.Xr git-show 1 .
+.El
+.
+.Sh EXAMPLES
+For files with
+.Ql #
+comments:
+.Bd -literal -offset indent
+git config comment.start '#'
+git config comment.lead '#'
+git config comment.end ''
+.Ed
+.
+.Pp
+Add as many comments as possible:
+.Bd -literal -offset indent
+git comment --all --min-group 1 --min-repeat 1
+.Ed
+.
+.Sh SEE ALSO
+.Xr git-blame 1
diff --git a/bin/man1/glitch.1 b/bin/man1/glitch.1
new file mode 100644
index 00000000..6562c4dc
--- /dev/null
+++ b/bin/man1/glitch.1
@@ -0,0 +1,77 @@
+.Dd September 7, 2018
+.Dt GLITCH 1
+.Os
+.
+.Sh NAME
+.Nm glitch
+.Nd PNG glitcher
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl cfimprxy
+.Op Fl a Ar filters
+.Op Fl d Ar filters
+.Op Fl o Ar file
+.Op Ar
+.
+.Sh DESCRIPTION
+.Nm
+misinterprets PNG files
+according to the options given
+to create natural glitch effects.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a Ar filters
+Apply a pattern of comma-separated filters.
+Filters are
+.Cm none ,
+.Cm sub ,
+.Cm up ,
+.Cm average ,
+.Cm paeth .
+.
+.It Fl c
+Write to standard output.
+.
+.It Fl d Ar filters
+Declare a pattern of comma-separated filters.
+See
+.Fl a
+for list of filters.
+.
+.It Fl f
+Apply filtering in place of reconstruction.
+.
+.It Fl i
+Invert image data after filtering.
+.
+.It Fl m
+Mirror scanlines after filtering.
+.
+.It Fl o Ar file
+Write to
+.Ar file .
+.
+.It Fl p
+Use a broken Paeth predictor function.
+.
+.It Fl r
+Apply reconstruction in place of filtering.
+.
+.It Fl x
+Zero first pixel of each scanline after filtering.
+.
+.It Fl y
+Zero first scanline after filtering.
+.El
+.
+.Sh EXAMPLES
+.Dl glitch -m -a sub -d sub
+.
+.Sh SEE ALSO
+.Xr pngo 1
+.
+.Sh BUGS
+More wanted.
diff --git a/bin/man1/hilex.1 b/bin/man1/hilex.1
new file mode 100644
index 00000000..80b3155b
--- /dev/null
+++ b/bin/man1/hilex.1
@@ -0,0 +1,218 @@
+.Dd January 20, 2021
+.Dt HILEX 1
+.Os
+.
+.Sh NAME
+.Nm hilex
+.Nd syntax highlighter
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl t
+.Op Fl f Ar format
+.Op Fl l Ar lexer
+.Op Fl n Ar name
+.Op Fl o Ar opts
+.Op Ar file
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility
+syntax highlights
+the contents of
+.Ar file
+or standard input
+and formats it on standard output.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-f format"
+.It Fl f Ar format
+Set the output format.
+See
+.Sx Output Formats .
+The default format is
+.Cm ansi .
+.
+.It Fl l Ar lexer
+Set the input lexer.
+See
+.Sx Input Lexers .
+The default input lexer is inferred from
+.Ar name
+or the first line of input.
+.
+.It Fl n Ar name
+Set the name used to infer the input lexer.
+The default is the final component of
+.Ar file .
+.
+.It Fl o Ar opts
+Set output format options.
+.Ar opts
+is a comma-separated list of options.
+Options for each output format are documented in
+.Sx Output Formats .
+.
+.It Fl t
+Default to the
+.Cm text
+input lexer if one cannot be inferred.
+.El
+.
+.Ss Output Formats
+.Bl -tag -width Ds
+.It Cm ansi
+Output ANSI terminal control sequences.
+If standard output is a terminal
+and standard input is not being read,
+output is piped to
+.Ev PAGER
+with
+.Ev LESS=FRX
+if it is not already set.
+.
+.It Cm html
+Output HTML
+.Sy span
+elements
+with the following classes:
+.Pp
+.Bl -hang -width "\&Op" -compact
+.It Sy \&Op
+operators
+.It Sy \&Nu
+numbers
+.It Sy \&Ke
+keywords
+.It Sy \&Id
+identifiers
+.It Sy \&Ma
+macros
+.It Sy \&Co
+comments
+.It Sy \&St
+strings
+.It Sy \&Es
+character escapes
+.It Sy \&Fo
+format strings
+.It Sy \&Su
+variable substitutions
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width "title=..."
+.It Cm document
+Output an HTML document containing a
+.Sy pre
+element.
+.It Cm inline
+Output inline style attributes
+rather than classes.
+.It Cm pre
+Wrap the output in a
+.Sy pre
+element with the class
+.Sy hilex .
+.It Cm style Ns = Ns Ar url
+With
+.Cm document ,
+use the external stylesheet
+.Ar url .
+If unset,
+default styles are included in a
+.Sy style
+element.
+.It Cm tab Ns = Ns Ar n
+With
+.Cm document ,
+.Cm inline
+or
+.Cm pre ,
+set the
+.Sy tab-size
+property to
+.Ar n .
+.It Cm title Ns = Ns Ar ...
+With
+.Cm document ,
+set the
+.Sy title
+element text.
+The default title is the same as
+.Ar name .
+.El
+.
+.It Cm irc
+Output IRC formatting codes.
+The options are as follows:
+.Bl -tag -width "monospace"
+.It Cm monospace
+Use the IRCCloud monospace formatting code.
+.El
+.El
+.
+.Ss Input Lexers
+.Bl -tag -width Ds
+.It Cm c
+The C11 language,
+with minimal support for
+.Xr lex 1 ,
+.Xr yacc 1
+and Objective-C input.
+Inferred for
+.Pa *.[chlmy]
+files.
+.
+.It Cm make
+BSD
+.Xr make 1 .
+Inferred for
+.Pa Makefile ,
+.Pa *.mk
+and
+.Pa *.am
+files.
+.
+.It Cm mdoc
+The
+.Xr mdoc 7
+language.
+Inferred for
+.Pa *.[1-9]
+files
+and files starting with
+.Dq .Dd .
+.
+.It Cm sh
+POSIX
+.Xr sh 1 .
+Since lexical analysis of
+the shell command language
+is effectively impossible,
+this is best-effort only.
+Inferred for
+.Pa *.sh ,
+.Pa .profile ,
+.Pa .shrc
+files
+and files starting with
+.Dq #!/bin/sh .
+.
+.It Cm text
+Plain text.
+Inferred for
+.Pa *.txt
+files.
+.El
+.
+.Sh ENVIRONMENT
+.Bl -tag -width "PAGER"
+.It Ev PAGER
+The command to pipe ANSI output to
+if standard output is a terminal.
+The default is
+.Ev PAGER=less .
+.El
diff --git a/bin/man1/htagml.1 b/bin/man1/htagml.1
new file mode 100644
index 00000000..d8cf6441
--- /dev/null
+++ b/bin/man1/htagml.1
@@ -0,0 +1,75 @@
+.Dd October  1, 2021
+.Dt HTAGML 1
+.Os
+.
+.Sh NAME
+.Nm htagml
+.Nd format tagged file as HTML
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl imp | x
+.Op Fl f Ar tagsfile
+.Ar file
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility formats a file tagged with
+.Xr ctags 1
+as HTML.
+Tags are output as fragment hyperlinks
+with the class
+.Qq tag .
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar tagsfile
+Read the tag descriptions from a file called
+.Ar tagsfile .
+The default behavior is
+to read them from a file called
+.Pa tags .
+.It Fl i
+Assume
+.Ar file
+has been pre-formatted
+on standard input,
+such as by a syntax highlighter.
+Only tag hyperlinks are added.
+.It Fl m
+Rename the
+.Xr ctags 1
+.Sq M
+tag to
+.Sy main .
+.It Fl p
+Wrap the output in a
+.Sy pre
+element.
+.It Fl x
+Instead produce an index of tags
+ordered by their occurrence in
+.Ar file .
+The index is formatted as a
+.Sy ul
+element with the class
+.Qq index .
+.El
+.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa tags
+default input tags file
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+ctags htagml.c && htagml htagml.c
+hilex -f html htagml.c | htagml -i htagml.c
+.Ed
+.
+.Sh SEE ALSO
+.Xr ctags 1 ,
+.Xr hilex 1
diff --git a/bin/man1/modem.1 b/bin/man1/modem.1
new file mode 100644
index 00000000..a4bbc3f1
--- /dev/null
+++ b/bin/man1/modem.1
@@ -0,0 +1,31 @@
+.Dd December  8, 2020
+.Dt MODEM 1
+.Os
+.
+.Sh NAME
+.Nm modem
+.Nd fixed baud rate wrapper
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl r Ar rate
+.Ar command ...
+.
+.Sh DESCRIPTION
+.Nm
+runs the
+.Ar command
+in a new PTY
+with a fixed baud rate.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl r Ar rate
+Set the baud rate.
+The default is 19200.
+.El
+.
+.Sh BUGS
+Window size changes are not propagated
+to the child PTY.
diff --git a/bin/man1/mtags.1 b/bin/man1/mtags.1
new file mode 100644
index 00000000..57856ba0
--- /dev/null
+++ b/bin/man1/mtags.1
@@ -0,0 +1,76 @@
+.Dd January 20, 2021
+.Dt MTAGS 1
+.Os
+.
+.Sh NAME
+.Nm mtags
+.Nd miscellaneous tags
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl f Ar tagsfile
+.Ar
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility
+makes a
+.Pa tags
+file for
+.Xr ex 1
+from the specified
+.Xr make 1 ,
+.Xr mdoc 7
+.Xr sh 1
+sources.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Append to
+.Pa tags
+file.
+.It Fl f Ar tagsfile
+Place the tag descriptions
+in a file called
+.Ar tagsfile .
+The default behaviour is
+to place them in a file called
+.Pa tags .
+.El
+.
+.Pp
+Files whose names are
+.Pa Makefile
+or end in
+.Pa .mk
+are assumed to be
+.Xr make 1
+files.
+Files whose names end in
+.Pa .[1-9]
+are assumed to be
+.Xr mdoc 7
+files.
+Files whose names are
+.Pa .profile ,
+.Pa .shrc
+or end in
+.Pa .sh
+are assumed to be
+.Xr sh 1
+files.
+.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa tags
+default output tags file
+.El
+.
+.Sh SEE ALSO
+.Xr ctags 1 ,
+.Xr ex 1 ,
+.Xr vi 1
diff --git a/bin/man1/nudge.1 b/bin/man1/nudge.1
new file mode 100644
index 00000000..3ca4294a
--- /dev/null
+++ b/bin/man1/nudge.1
@@ -0,0 +1,44 @@
+.Dd September  4, 2020
+.Dt NUDGE 1
+.Os
+.
+.Sh NAME
+.Nm nudge
+.Nd terminal vibrator
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Op Fl n Ar count
+.Op Fl s Ar shake
+.Op Fl t Ar delay
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility
+nudges the terminal.
+An
+.Xr xterm 1
+compatible terminal is required.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar file
+Open the terminal named by
+.Ar file .
+The default is
+.Pa /dev/tty .
+.It Fl n Ar count
+Set the number of times
+to nudge the terminal.
+The default is 2.
+.It Fl s Ar shake
+Set the shake intensity in pixels.
+The default is 10.
+.It Fl t Ar delay
+Set the interval between shakes
+in milliseconds.
+The default is 20.
+.El
diff --git a/bin/man1/order.1 b/bin/man1/order.1
new file mode 100644
index 00000000..89fcbda5
--- /dev/null
+++ b/bin/man1/order.1
@@ -0,0 +1,38 @@
+.Dd July 18, 2020
+.Dt ORDER 1
+.Os
+.
+.Sh NAME
+.Nm order
+.Nd operator precedence
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar expr ...
+.
+.Sh DESCRIPTION
+.Nm
+parses C expressions
+and prints them with parentheses
+according to the precedence rules in
+.Xr operator 7 .
+If no
+.Ar expr
+are given,
+an expression is read
+from standard input.
+.
+.Sh EXAMPLES
+.Bd -literal
+$ order 'a & b << 1'
+(a & (b << 1))
+.Ed
+.
+.Sh SEE ALSO
+.Xr operator 7
+.
+.Sh CAVEATS
+.Nm
+does not support the
+.Sy (type)
+operator.
diff --git a/bin/man1/pbd.1 b/bin/man1/pbd.1
new file mode 100644
index 00000000..f0665891
--- /dev/null
+++ b/bin/man1/pbd.1
@@ -0,0 +1,66 @@
+.Dd February  9, 2021
+.Dt PBD 1
+.Os
+.
+.Sh NAME
+.Nm pbd
+.Nd macOS pasteboard daemon
+.
+.Sh SYNOPSIS
+.Nm Op Fl s | c | p | o Ar url
+.
+.Sh DESCRIPTION
+.Nm
+is a daemon which pipes into macOS
+.Xr pbcopy 1 ,
+from
+.Xr pbpaste 1
+and invokes
+.Xr open 1
+in response to messages
+sent over TCP port 7062.
+.
+.Pp
+The socket can be forwarded through
+.Xr ssh 1
+and the flags can be used remotely
+to access the local pasteboard
+and open URLs.
+.
+.Pp
+Forwarding can be configured with:
+.Pp
+.Dl RemoteForward 7062 127.0.0.1:7062
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Behave as
+.Xr pbcopy 1 .
+.It Fl o Ar url
+Behave as
+.Xr open 1 .
+.It Fl p
+Behave as
+.Xr pbpaste 1 .
+.It Fl s
+Run the server.
+This is the default.
+.El
+.Pp
+ACAB.
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+pbd &
+ssh -R 7062:127.0.0.1:7062 tux.local
+pbd -p
+.Ed
+.
+.Sh SEE ALSO
+.Xr open 1 ,
+.Xr pbcopy 1 ,
+.Xr pbpaste 1 ,
+.Xr ssh 1 ,
+.Xr ssh_config 5
diff --git a/bin/man1/pngo.1 b/bin/man1/pngo.1
new file mode 100644
index 00000000..a235355b
--- /dev/null
+++ b/bin/man1/pngo.1
@@ -0,0 +1,64 @@
+.Dd September 21, 2021
+.Dt PNGO 1
+.Os
+.
+.Sh NAME
+.Nm pngo
+.Nd PNG optimizer
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl acgv
+.Op Fl b Ar depth
+.Op Fl o Ar file
+.Op Ar
+.
+.Sh DESCRIPTION
+.Nm
+optimizes PNG files for size
+by performing the following:
+.Pp
+.Bl -enum -compact
+.It
+Discard ancillary chunks.
+.It
+Discard unnecessary alpha channel.
+.It
+Convert unnecessary truecolor to grayscale.
+.It
+Palletize color if possible.
+.It
+Reduce unnecessary bit depth.
+.It
+Apply a simple filter type heuristic.
+.It
+Apply zlib's best compression.
+.El
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Always discard the alpha channel.
+.It Fl b Ar depth
+Reduce bit depth to
+.Ar depth
+or lower.
+.It Fl c
+Write to standard output.
+.It Fl g
+Convert to grayscale.
+.It Fl o Ar file
+Write to
+.Ar file .
+.It Fl v
+Print header information and sizes
+to standard error.
+.El
+.
+.Sh SEE ALSO
+.Xr glitch 1
+.
+.Sh BUGS
+.Nm
+does not support interlaced PNGs.
diff --git a/bin/man1/psf2png.1 b/bin/man1/psf2png.1
new file mode 100644
index 00000000..db74c6e2
--- /dev/null
+++ b/bin/man1/psf2png.1
@@ -0,0 +1,53 @@
+.Dd September 28, 2018
+.Dt PSF2PNG 1
+.Os
+.
+.Sh NAME
+.Nm psf2png
+.Nd PSF2 to PNG renderer
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl b Ar bg
+.Op Fl c Ar cols
+.Op Fl f Ar fg
+.Op Fl s Ar str
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+renders the PSF2 font
+.Ar file
+or standard input
+to PNG
+on standard output.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar bg
+Use
+.Ar bg
+(hexadecimal RGB)
+as background color.
+The default background color is black.
+.It Fl c Ar cols
+Arrange glyphs in
+.Ar cols
+columns.
+The default number of columns is 32.
+.It Fl f Ar fg
+Use
+.Ar fg
+(hexadecimal RGB)
+as foreground color.
+The default foreground color is white.
+.It Fl s Ar str
+Render glyphs for string
+.Ar str
+rather than all glyphs.
+.El
+.
+.Sh SEE ALSO
+.Xr pngo 1 ,
+.Xr psfed 1
diff --git a/bin/man1/ptee.1 b/bin/man1/ptee.1
new file mode 100644
index 00000000..bb381ecb
--- /dev/null
+++ b/bin/man1/ptee.1
@@ -0,0 +1,51 @@
+.Dd October 18, 2021
+.Dt PTEE 1
+.Os
+.
+.Sh NAME
+.Nm ptee
+.Nd tee for PTYs
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl t Ar ms
+.Ar command ...
+.Cm >
+.Ar file
+.
+.Sh DESCRIPTION
+.Nm
+runs
+.Ar command
+in a new PTY
+which is mirrored to
+the current PTY
+and standard output.
+Standard output must be redirected
+to a file or pipe.
+.
+.Pp
+Type
+.Ic ^S
+to write a media copy sequence
+to standard output.
+Type
+.Ic ^Q
+to toggle writing to standard output.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl t Ar ms
+Write a media copy sequence
+to standard output every
+.Ar ms
+milliseconds.
+.El
+.
+.Sh SEE ALSO
+.Xr script 1
+.
+.Sh BUGS
+Window size changes are not propagated
+to the child PTY.
diff --git a/bin/man1/qf.1 b/bin/man1/qf.1
new file mode 100644
index 00000000..8828d723
--- /dev/null
+++ b/bin/man1/qf.1
@@ -0,0 +1,71 @@
+.Dd June  2, 2022
+.Dt QF 1
+.Os
+.
+.Sh NAME
+.Nm qf
+.Nd grep pager
+.
+.Sh SYNOPSIS
+.Nm Op Ar pattern
+.
+.Sh DESCRIPTION
+.Nm
+is a pager for
+.Xr grep 1 ,
+.Xr ag 1 ,
+.Xr rg 1 ,
+etc.\&
+which allows
+jumping to matches in
+.Ev $EDITOR .
+It parses any input
+prefixed by path
+and line number
+separated by a colon
+.Ql ":"
+followed by either a colon
+or a hyphen
+.Ql "-" .
+It otherwise operates similar to
+.Xr less 1 .
+.
+.Pp
+If
+.Ar pattern
+is given,
+the first match on each line
+will be highlighted.
+The
+.Ar pattern
+is interpreted as
+an extended regular expression
+and is matched case-insensitively
+unless it contains an uppercase letter.
+.
+.Pp
+The keys are as follows:
+.Bl -tag -width Ds
+.It Ic Enter
+Open the currently selected line in
+.Ev $EDITOR .
+When the editor exits,
+.Nm
+resumes.
+.It Ic {}
+Jump between files.
+.It Ic gG
+Jump to first or last line.
+.It Ic jk
+Move to next or previous line.
+.It Ic nN
+Jump to next or previous match line.
+.It Ic q
+Exit.
+.It Ic r
+Refresh the display.
+.El
+.
+.Sh EXAMPLES
+.Dl $ ag -C open | qf
+.Dl $ git grep -n open | qf
diff --git a/bin/man1/quick.1 b/bin/man1/quick.1
new file mode 100644
index 00000000..96f1766a
--- /dev/null
+++ b/bin/man1/quick.1
@@ -0,0 +1,66 @@
+.Dd September 23, 2021
+.Dt QUICK 1
+.Os
+.
+.Sh NAME
+.Nm quick
+.Nd (and dirty) HTTP/CGI server
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl p Ar port
+.Ar script
+.Op Ar args ...
+.
+.Sh DESCRIPTION
+.Nm
+is a barely functional HTTP server
+for running CGI scripts.
+It listens only on localhost,
+on a randomly assigned port,
+unless
+.Fl p
+is used.
+The URL of the server
+is printed to standard output.
+.
+.Sh EXAMPLES
+.Dl quick cgit | xargs -n1 open
+.
+.Sh STANDARDS
+.Nm
+does
+.Em not
+implement the following:
+.Bl -item
+.It
+.Rs
+.%A T. Berners-Lee
+.%A R. Fielding
+.%A H. Frystyk
+.%A J. Gettys
+.%A J. Mogul
+.%T Hypertext Transfer Protocol -- HTTP/1.1
+.%R RFC 2068
+.%U https://tools.ietf.org/html/rfc2068
+.%D January 1997
+.Re
+.It
+.Rs
+.%A K. Coar
+.%A D. Robinson
+.%T The Common Gateway Interface (CGI) Version 1.1
+.%R RFC 3875
+.%U https://tools.ietf.org/html/rfc3875
+.%D October 2004
+.Re
+.El
+.
+.Sh CAVEATS
+Oh, so many.
+No error handling,
+no validation,
+no security.
+This is a local testing tool only.
+.Pp
+Every response is served as a 200 OK.
diff --git a/bin/man1/relay.1 b/bin/man1/relay.1
new file mode 100644
index 00000000..402c4726
--- /dev/null
+++ b/bin/man1/relay.1
@@ -0,0 +1,48 @@
+.Dd April 28, 2019
+.Dt RELAY 1
+.Os
+.
+.Sh NAME
+.Nm relay
+.Nd IRC relay bot
+.
+.Sh SYNOPSIS
+.Nm
+.Ar host
+.Ar port
+.Ar nick
+.Ar chan
+.
+.Sh DESCRIPTION
+.Nm
+is one half of an IRC relay pair.
+It connects to
+.Ar host Ns : Ns Ar port
+over TLS
+as
+.Ar nick
+and joins
+.Ar chan .
+.
+.Pp
+.Nm
+outputs messages from
+.Ar chan
+to standard output
+and sends messages to
+.Ar chan
+from standard input.
+Two
+.Nm
+processes can be connected with
+.Xr mkfifo 1 .
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+mkfifo a b
+relay a.example.com 6697 relay '#example' <>a >b
+relay b.example.com 6697 relay '#example' <>b >a
+.Ed
+.
+.Sh SEE ALSO
+.Xr mkfifo 1
diff --git a/bin/man1/scheme.1 b/bin/man1/scheme.1
new file mode 100644
index 00000000..9f72d945
--- /dev/null
+++ b/bin/man1/scheme.1
@@ -0,0 +1,59 @@
+.Dd February  6, 2021
+.Dt SCHEME 1
+.Os
+.
+.Sh NAME
+.Nm scheme
+.Nd color scheme
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl Xacghilmstx
+.Op Fl p Ar n
+.
+.Sh DESCRIPTION
+.Nm
+generates a color scheme
+and outputs it in a number of formats.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl X
+Output Xresources for
+.Xr xterm 1 .
+.It Fl a
+Generate the 16 ANSI colors.
+This is the default.
+.It Fl c
+Output a C enum.
+.It Fl g
+Output a swatch PNG.
+.It Fl h
+Output floating point HSV.
+.It Fl i
+Swap black and white.
+.It Fl l
+Output Linux console OSC sequences.
+.It Fl m
+Output a
+.Xr mintty 1
+theme.
+Use with
+.Fl t .
+.It Fl p Ar n
+Generate only the color
+.Ar n .
+.It Fl s
+Output CSS
+for classes named
+.Sy fg Ns Ar n
+and
+.Sy bg Ns Ar n .
+.It Fl t
+Generate the 16 ANSI colors as well as
+background, foreground, bold, selection and cursor colors.
+.It Fl x
+Output hexadecimal RGB.
+This is the default.
+.El
diff --git a/bin/man1/shotty.1 b/bin/man1/shotty.1
new file mode 100644
index 00000000..0a3bd127
--- /dev/null
+++ b/bin/man1/shotty.1
@@ -0,0 +1,115 @@
+.Dd October 18, 2021
+.Dt SHOTTY 1
+.Os
+.
+.Sh NAME
+.Nm shotty
+.Nd HTML terminal renderer
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl Bdins
+.Op Fl b Ar bg
+.Op Fl f Ar fg
+.Op Fl h Ar rows
+.Op Fl w Ar cols
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+renders a terminal session
+captured with
+.Xr ptee 1
+or
+.Xr script 1
+from
+.Ar file
+or standard input
+and renders one or more HTML snapshots.
+One snapshot is rendered
+for each media copy sequence,
+or a single snapshot is rendered
+at the end of the session.
+.Nm
+targets compatibility with
+.Ev TERM=xterm
+and
+.Ev TERM=xterm-256color
+as used by
+.Xr ncurses 3 .
+.
+.Pp
+HTML output uses
+.Sy bg Ns Va n
+and
+.Sy fg Ns Va n
+classes for colors,
+and inline styles for
+bold, italic and underline.
+CSS for colors
+can be generated by
+.Xr scheme 1 .
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-w cols"
+.It Fl B
+Replace bold with bright colors.
+.
+.It Fl b Ar bg
+Set the default background color.
+The default is 0 (black).
+.
+.It Fl d
+Render a snapshot
+after each control sequence.
+.
+.It Fl f Ar fg
+Set the default foreground color.
+The default is 7 (white).
+.
+.It Fl h Ar rows
+Set the terminal height.
+The default is 24.
+.
+.It Fl i
+Output inline color attributes.
+.
+.It Fl n
+Hide the cursor.
+.
+.It Fl s
+Copy the terminal size
+from the current terminal.
+.
+.It Fl w Ar cols
+Set the terminal width.
+The default is 80.
+.El
+.
+.Sh EXAMPLES
+.Dl $ ptee htop | shotty -Bis >htop.html
+.
+.Sh SEE ALSO
+.Xr ptee 1 ,
+.Xr script 1
+.
+.Sh STANDARDS
+.Bl -item
+.It
+.Rs
+.%A Thomas Dickey
+.%A Stephen Gildea
+.%A Edward Moy
+.%T XTerm Control Sequences
+.%U https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+.Re
+.It
+.Rs
+.%A F. Yergeau
+.%T UTF-8
+.%R RFC 2044
+.%U https://tools.ietf.org/html/rfc2044
+.%D October 1996
+.Re
+.El
diff --git a/bin/man1/sup.1 b/bin/man1/sup.1
new file mode 100644
index 00000000..bd88ad47
--- /dev/null
+++ b/bin/man1/sup.1
@@ -0,0 +1,51 @@
+.Dd January 12, 2022
+.Dt SUP 1
+.Os
+.
+.Sh NAME
+.Nm sup
+.Nd single-use password
+.
+.Sh SYNOPSIS
+.Nm
+.Ar service
+.Op Ar email
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility
+sets a random single-use password
+for a service using the
+.Dq forgot password
+or
+.Dq password reset
+flow.
+The password is copied to the clipboard
+and the service login page is opened.
+For passwordless services
+with email-based authentication,
+the emailed login link is opened.
+.
+.Pp
+The following services are supported:
+.Cm asciinema ,
+.Cm discogs ,
+.Cm freebsdbugzilla ,
+.Cm liberapay ,
+.Cm lobsters ,
+.Cm lwn ,
+.Cm patreon ,
+.Cm tildegit ,
+.Cm tildenews .
+.
+.Pp
+The
+.Nm
+utility requires
+.Xr curl 1 ,
+.Xr git-fetch-email 1 ,
+.Xr openssl 1 ,
+.Xr pbcopy 1
+and
+.Xr open 1 .
diff --git a/bin/man1/title.1 b/bin/man1/title.1
new file mode 100644
index 00000000..43ecc5e2
--- /dev/null
+++ b/bin/man1/title.1
@@ -0,0 +1,51 @@
+.Dd September 10, 2019
+.Dt TITLE 1
+.Os
+.
+.Sh NAME
+.Nm title
+.Nd page titles
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl x Ar pattern
+.Op Ar url
+.
+.Sh DESCRIPTION
+.Nm
+fetches HTML page titles
+over HTTP and HTTPS.
+.Nm
+scans standard input for URLs
+and writes their titles to standard output.
+If a
+.Ar url
+argument is given,
+.Nm
+exits after fetching its title.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl x Ar pattern
+Exclude URLs matching
+.Ar pattern ,
+which is a modern regular expression.
+See
+.Xr re_format 7 .
+.It Fl v
+Enable
+.Xr libcurl 3
+verbose output.
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+mkfifo snarf titles
+relay irc.example.org 6697 snarf '#example' <>titles >snarf
+title <snarf >titles
+.Ed
+.
+.Sh SEE ALSO
+.Xr relay 1
diff --git a/bin/man1/up.1 b/bin/man1/up.1
new file mode 100644
index 00000000..aece79bd
--- /dev/null
+++ b/bin/man1/up.1
@@ -0,0 +1,77 @@
+.Dd July 26, 2022
+.Dt UP 1
+.Os
+.
+.Sh NAME
+.Nm up
+.Nd upload file
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl c | h | s | t
+.Op Fl w Ar warn
+.Op Ar file | command
+.
+.Sh DESCRIPTION
+.Nm
+uploads a file
+to temp.causal.agency with
+.Xr scp 1 .
+If no
+.Ar file
+is provided,
+standard input is read
+and uploaded as text.
+.
+.Pp
+The destination file name
+is chosen using
+.Xr date 1
+and
+.Xr openssl 1
+.Cm rand .
+The URL of the uploaded file is printed
+and copied to the pasteboard with
+.Xr pbcopy 1
+if available.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Run a command
+to produce a text file for upload.
+.It Fl h
+Use
+.Xr hilex 1
+to produce an HTML file for upload.
+.It Fl s
+Use
+.Xr screencapture 1
+or
+.Xr scrot 1
+to produce a PNG file for upload.
+The file is optimized by
+.Xr pngo 1 .
+.It Fl t
+Run a command with
+.Xr ptee 1
+and
+.Xr shotty 1
+to produce an HTML file for upload.
+.It Fl w Ar warn
+Create an HTML redirect with
+.Ar warn
+in its title.
+.El
+.
+.Pp
+Any arguments after
+.Ql \-\-
+are passed to
+.Xr hilex 1
+and
+.Xr screencapture 1
+or
+.Xr scrot 1 ,
+respectively.
diff --git a/bin/man1/when.1 b/bin/man1/when.1
new file mode 100644
index 00000000..3f2735f7
--- /dev/null
+++ b/bin/man1/when.1
@@ -0,0 +1,100 @@
+.Dd September 19, 2022
+.Dt WHEN 1
+.Os
+.
+.Sh NAME
+.Nm when
+.Nd date calculator
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar expr
+.Nm
+.Cm -
+.
+.Sh DESCRIPTION
+.Nm
+is a date calculator.
+If no
+.Ar expr
+is given,
+expressions are read
+from standard input.
+If
+.Cm -
+is given,
+the intervals between each named date
+and today are printed.
+.
+.Pp
+The grammar is as follows:
+.Bl -tag -width Ds
+.It Sy \&.
+Today's date.
+The empty expression is equivalent.
+.
+.It Ar name Op Sy = Ar date
+A named date.
+Names are alphanumeric including underscores.
+.
+.It Ar month Ar date Op Ar year
+A full date,
+or a date in the current year.
+Months can be abbreviated to three letters.
+.
+.It Ar day
+A day of the week
+in the current week.
+Days can be abbreviated to three letters.
+.
+.It Sy < Ar date
+The date one week before.
+.
+.It Sy > Ar date
+The date one week after.
+.
+.It Ar date Sy + Ar interval
+The date after some interval.
+.
+.It Ar date Sy - Ar interval
+The date before some interval.
+.
+.It Ar date Sy - Ar date
+The interval between two dates.
+.
+.It Ar num Sy d
+A number of days.
+.
+.It Ar num Sy w
+A number of weeks.
+.
+.It Ar num Sy m
+A number of months.
+.
+.It Ar num Sy y
+A number of years.
+.El
+.
+.Sh FILES
+The file
+.Pa $XDG_CONFIG_HOME/when/dates
+or
+.Pa ~/.config/when/dates
+is read before any other expressions,
+if it exists.
+.
+.Sh EXAMPLES
+.Bl -tag -width "Dec 25 - ."
+.It Ic Dec 25 - \&.
+How long until Christmas.
+.It Ic >Fri
+The date next Friday.
+.It Ic \&. + 2w
+Your last day at work.
+.El
+.Pp
+Checking a milestone:
+.Bd -literal -offset indent
+$ echo 'hrt = oct 15 2021' >> ~/.config/when/dates
+$ when -hrt
+.Ed
diff --git a/bin/man1/xx.1 b/bin/man1/xx.1
new file mode 100644
index 00000000..d38789a7
--- /dev/null
+++ b/bin/man1/xx.1
@@ -0,0 +1,68 @@
+.Dd September 7, 2018
+.Dt XX 1
+.Os
+.
+.Sh NAME
+.Nm xx
+.Nd hexdump
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl arsz
+.Op Fl c Ar cols
+.Op Fl g Ar group
+.Op Fl p Ar count
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+dumps the contents of a
+.Ar file
+or standard input
+in hexadecimal format.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Toggle ASCII output.
+.
+.It Fl c Ar cols
+Output
+.Ar cols
+bytes per line.
+The default
+.Ar cols
+is 16.
+.
+.It Fl g Ar group
+Output extra space after every
+.Ar group
+bytes.
+The default
+.Ar group
+is 8.
+.
+.It Fl p Ar count
+Output a blank line after every
+.Ar count
+bytes.
+.Ar count
+must be a multiple of
+.Ar cols .
+.
+.It Fl r
+Reverse hexdump.
+Read hexadecimal input
+and write byte output.
+.
+.It Fl s
+Toggle offset output.
+.
+.It Fl z
+Skip output of lines containing only zeros.
+.El
+.
+.Sh SEE ALSO
+.Xr hexdump 1 ,
+.Xr xxd 1
diff --git a/bin/man3/png.3 b/bin/man3/png.3
new file mode 100644
index 00000000..accffbd7
--- /dev/null
+++ b/bin/man3/png.3
@@ -0,0 +1,90 @@
+.Dd July 25, 2019
+.Dt PNG 3
+.Os
+.
+.Sh NAME
+.Nm png
+.Nd basic PNG output
+.
+.Sh SYNOPSIS
+.In png.h
+.
+.Ft void
+.Fo pngHead
+.Fa "FILE *file"
+.Fa "uint32_t width"
+.Fa "uint32_t height"
+.Fa "uint8_t depth"
+.Fa "uint8_t color"
+.Fc
+.
+.Ft void
+.Fn pngPalette "FILE *file" "const uint8_t *pal" "uint32_t len"
+.
+.Ft void
+.Fn pngData "FILE *file" "const uint8_t *data" "uint32_t len"
+.
+.Ft void
+.Fn pngTail "FILE *file"
+.
+.Sh DESCRIPTION
+The
+.Fn pngHead
+function
+writes the
+.Sy IHDR
+chunk to
+.Fa file .
+The
+.Fa color
+parameter can be one of
+.Dv PNGGrayscale ,
+.Dv PNGTruecolor
+optionally
+.Em or Ns 'ed
+with
+.Dv PNGAlpha ,
+or
+.Dv PNGIndexed .
+.
+.Pp
+The
+.Fn pngPalette
+function
+writes the
+.Sy PLTE
+chunk to
+.Fa file .
+.
+.Pp
+The
+.Fn pngData
+function
+writes the
+.Sy IDAT
+chunk to
+.Fa file
+without compression.
+The constants
+.Dv PNGNone ,
+.Dv PNGSub ,
+.Dv PNGUp ,
+.Dv PNGAverage ,
+.Dv PNGPaeth
+are defined
+for use in PNG data.
+.
+.Pp
+The
+.Fn pngTail
+function
+writes the
+.Sy IEND
+chunk to
+.Fa file .
+.
+.Sh ERRORS
+Any errors from writing to
+.Fa file
+are handled by calling
+.Xr err 3 .
diff --git a/bin/man6/freecell.6 b/bin/man6/freecell.6
new file mode 100644
index 00000000..0e485a16
--- /dev/null
+++ b/bin/man6/freecell.6
@@ -0,0 +1,50 @@
+.Dd April 17, 2021
+.Dt FREECELL 6
+.Os
+.
+.Sh NAME
+.Nm freecell
+.Nd patience game
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar delay
+.Op Fl n Ar game
+.
+.Sh DESCRIPTION
+.Nm
+is a terminal FreeCell patience game.
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl d Ar delay
+Set the delay in milliseconds
+between queued moves.
+The default is 50.
+.It Fl n Ar game
+Select the game number to play.
+.El
+.
+.Pp
+Moves are performed
+by typing a sequence of two keys.
+To automatically move a card
+to a free cell,
+type the same key twice.
+The keys are as follows:
+.Bl -tag -width Ds
+.It Ic Escape
+Cancel a pending move.
+.It Ic u , Backspace
+Undo the previous move.
+.It Ic 1 , 2 , 3 , 4
+Select the cells.
+.It Ic q , w , e , r , a , s , d , f
+Select the tableau cascades.
+.It Ic _ , Space
+Manually move
+the selected card
+to the foundations.
+.It Ic Shift
+Move a single card
+to an empty tableau cascade.
+.El
diff --git a/bin/mdoc.l b/bin/mdoc.l
new file mode 100644
index 00000000..b6deacbe
--- /dev/null
+++ b/bin/mdoc.l
@@ -0,0 +1,60 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option prefix="mdoc"
+%option noinput nounput noyywrap
+
+%{
+#include "hilex.h"
+%}
+
+%s MacroLine
+
+%%
+
+[[:blank:]]+ { return Normal; }
+
+^"." {
+	BEGIN(MacroLine);
+	return Keyword;
+}
+
+^".\\\"".* { return Comment; }
+
+<MacroLine>{
+	"\n" {
+		BEGIN(0);
+		return Normal;
+	}
+
+	%[ABCDIJNOPQRTUV]|A[cdnopqrt]|B[cdfkloqtx]|Br[coq]|Bsx|C[dm]|D[1bcdloqtvx] |
+	E[cdfklmnorsvx]|F[acdlnortx]|Hf|I[cnt]|L[bikp]|M[st]|N[dmosx]|O[copstx] |
+	P[acfopq]|Q[cloq]|R[esv]|S[chmoqstxy]|T[an]|U[dx]|V[at]|X[cor] {
+		return Keyword;
+	}
+
+	"\""([^""]|"\\\"")*"\"" { return String; }
+}
+
+"\\"(.|"("..|"["[^]]*"]") { return String; }
+
+[^.\\""[:space:]]+ { return Normal; }
+
+.|\n { return Normal; }
+
+%%
+
+const struct Lexer LexMdoc = { yylex, &yyin, &yytext };
diff --git a/bin/modem.c b/bin/modem.c
new file mode 100644
index 00000000..4392e071
--- /dev/null
+++ b/bin/modem.c
@@ -0,0 +1,102 @@
+/* Copyright (C) 2018  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+typedef unsigned uint;
+typedef unsigned char byte;
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	uint baudRate = 19200;
+	for (int opt; 0 < (opt = getopt(argc, argv, "r:"));) {
+		switch (opt) {
+			break; case 'r': baudRate = strtoul(optarg, NULL, 10);
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (argc - optind < 1) return EX_USAGE;
+
+	error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "TIOCGWINSZ");
+
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, &window);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[optind], &argv[optind]);
+		err(EX_NOINPUT, "%s", argv[optind]);
+	}
+
+	byte c;
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	while (usleep(8 * 1000000 / baudRate), 0 < poll(fds, 2, -1)) {
+		if (fds[0].revents) {
+			ssize_t size = read(STDIN_FILENO, &c, 1);
+			if (size < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO);
+			size = write(pty, &c, 1);
+			if (size < 0) err(EX_IOERR, "write(%d)", pty);
+		}
+
+		if (fds[1].revents) {
+			ssize_t size = read(pty, &c, 1);
+			if (size < 0) err(EX_IOERR, "read(%d)", pty);
+			if (!size) break;
+			size = write(STDOUT_FILENO, &c, 1);
+			if (size < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO);
+		}
+	}
+
+	int status;
+	pid_t dead = waitpid(pid, &status, 0);
+	if (dead < 0) err(EX_OSERR, "waitpid");
+	return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE;
+}
diff --git a/bin/mtags.c b/bin/mtags.c
new file mode 100644
index 00000000..5c1a057e
--- /dev/null
+++ b/bin/mtags.c
@@ -0,0 +1,105 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void escape(FILE *file, const char *str, size_t len) {
+	for (size_t i = 0; i < len; ++i) {
+		if (str[i] == '\\' || str[i] == '/') {
+			putc('\\', file);
+		}
+		putc(str[i], file);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+	bool append = false;
+	const char *path = "tags";
+	for (int opt; 0 < (opt = getopt(argc, argv, "af:"));) {
+		switch (opt) {
+			break; case 'a': append = true;
+			break; case 'f': path = optarg;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	FILE *tags = fopen(path, (append ? "a" : "w"));
+	if (!tags) err(EX_CANTCREAT, "%s", path);
+
+#ifdef __OpenBSD__
+	error = pledge("stdio rpath", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	regex_t makeFile, makeLine;
+	regex_t mdocFile, mdocLine;
+	regex_t shFile, shLine;
+	error = 0
+		|| regcomp(&makeFile, "(^|/)Makefile|[.]mk$", REG_EXTENDED | REG_NOSUB)
+		|| regcomp(
+			&makeLine,
+			"^([.][^:$A-Z][^:$[:space:]]*|[^.:$][^:$[:space:]]*):",
+			REG_EXTENDED
+		)
+		|| regcomp(&mdocFile, "[.][1-9]$", REG_EXTENDED | REG_NOSUB)
+		|| regcomp(&mdocLine, "^[.]S[hs] ([^\t\n]+)", REG_EXTENDED)
+		|| regcomp(
+			&shFile, "(^|/)[.](profile|shrc)|[.]sh$", REG_EXTENDED | REG_NOSUB
+		)
+		|| regcomp(&shLine, "^([_[:alnum:]]+)[[:blank:]]*[(][)]", REG_EXTENDED);
+	assert(!error);
+
+	size_t cap = 0;
+	char *buf = NULL;
+	for (int i = optind; i < argc; ++i) {
+		const regex_t *regex;
+		if (!regexec(&makeFile, argv[i], 0, NULL, 0)) {
+			regex = &makeLine;
+		} else if (!regexec(&mdocFile, argv[i], 0, NULL, 0)) {
+			regex = &mdocLine;
+		} else if (!regexec(&shFile, argv[i], 0, NULL, 0)) {
+			regex = &shLine;
+		} else {
+			warnx("skipping unknown file type %s", argv[i]);
+			continue;
+		}
+
+		FILE *file = fopen(argv[i], "r");
+		if (!file) err(EX_NOINPUT, "%s", argv[i]);
+
+		while (0 < getline(&buf, &cap, file)) {
+			regmatch_t match[2];
+			if (regexec(regex, buf, 2, match, 0)) continue;
+			fprintf(
+				tags, "%.*s\t%s\t/^",
+				(int)(match[1].rm_eo - match[1].rm_so), &buf[match[1].rm_so],
+				argv[i]
+			);
+			escape(tags, buf, match[0].rm_eo);
+			fprintf(tags, "/\n");
+		}
+		fclose(file);
+	}
+}
diff --git a/bin/nudge.c b/bin/nudge.c
new file mode 100644
index 00000000..8ae916eb
--- /dev/null
+++ b/bin/nudge.c
@@ -0,0 +1,78 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+static int shake = 10;
+static int delay = 20000;
+static int count = 2;
+
+static void move(int tty, int x, int y) {
+	dprintf(tty, "\33[3;%d;%dt", x, y);
+	usleep(delay);
+}
+
+int main(int argc, char *argv[]) {
+	const char *path = "/dev/tty";
+	for (int opt; 0 < (opt = getopt(argc, argv, "f:n:s:t:"));) {
+		switch (opt) {
+			break; case 'f': path = optarg;
+			break; case 'n': count = atoi(optarg);
+			break; case 's': shake = atoi(optarg);
+			break; case 't': delay = atoi(optarg) * 1000;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	int tty = open(path, O_RDWR);
+	if (tty < 0) err(EX_OSFILE, "%s", path);
+
+	struct termios save;
+	int error = tcgetattr(tty, &save);
+	if (error) err(EX_IOERR, "tcgetattr");
+
+	struct termios raw = save;
+	cfmakeraw(&raw);
+	error = tcsetattr(tty, TCSAFLUSH, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	char buf[256];
+	dprintf(tty, "\33[13t");
+	ssize_t len = read(tty, buf, sizeof(buf) - 1);
+	buf[(len < 0 ? 0 : len)] = '\0';
+
+	int x, y;
+	int n = sscanf(buf, "\33[3;%d;%dt", &x, &y);
+
+	error = tcsetattr(tty, TCSANOW, &save);
+	if (error) err(EX_IOERR, "tcsetattr");
+	if (n < 2) return EX_CONFIG;
+
+	dprintf(tty, "\33[5t");
+	for (int i = 0; i < count; ++i) {
+		move(tty, x - shake, y);
+		move(tty, x, y + shake);
+		move(tty, x + shake, y);
+		move(tty, x, y - shake);
+		move(tty, x, y);
+	}
+}
diff --git a/bin/order.y b/bin/order.y
new file mode 100644
index 00000000..b3cbf2df
--- /dev/null
+++ b/bin/order.y
@@ -0,0 +1,195 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#define YYSTYPE char *
+
+static char *fmt(const char *format, ...) {
+	char *str = NULL;
+	va_list ap;
+	va_start(ap, format);
+	vasprintf(&str, format, ap);
+	va_end(ap);
+	if (!str) err(EX_OSERR, "vasprintf");
+	return str;
+}
+
+static int yylex(void);
+static void yyerror(const char *str);
+
+%}
+
+%token Ident
+
+%left ','
+%right '=' MulAss DivAss ModAss AddAss SubAss ShlAss ShrAss AndAss XorAss OrAss
+%right '?' ':'
+%left Or
+%left And
+%left '|'
+%left '^'
+%left '&'
+%left Eq Ne
+%left '<' Le '>' Ge
+%left Shl Shr
+%left '+' '-'
+%left '*' '/' '%'
+%right '!' '~' Inc Dec Sizeof
+%left '(' ')' '[' ']' Arr '.'
+
+%%
+
+start:
+	expr { printf("%s\n", $1); }
+	;
+
+expr:
+	Ident
+	| '(' expr ')' { $$ = $2; }
+	| expr '[' expr ']' { $$ = fmt("(%s[%s])", $1, $3); }
+	| expr Arr Ident { $$ = fmt("(%s->%s)", $1, $3); }
+	| expr '.' Ident { $$ = fmt("(%s.%s)", $1, $3); }
+	| '!' expr { $$ = fmt("(!%s)", $2); }
+	| '~' expr { $$ = fmt("(~%s)", $2); }
+	| Inc expr { $$ = fmt("(++%s)", $2); }
+	| Dec expr { $$ = fmt("(--%s)", $2); }
+	| expr Inc { $$ = fmt("(%s++)", $1); }
+	| expr Dec { $$ = fmt("(%s--)", $1); }
+	| '+' expr %prec '!' { $$ = fmt("(+%s)", $2); }
+	| '-' expr %prec '!' { $$ = fmt("(-%s)", $2); }
+	| '*' expr %prec '!' { $$ = fmt("(*%s)", $2); }
+	| '&' expr %prec '!' { $$ = fmt("(&%s)", $2); }
+	| Sizeof expr { $$ = fmt("(sizeof %s)", $2); }
+	| expr '*' expr { $$ = fmt("(%s * %s)", $1, $3); }
+	| expr '/' expr { $$ = fmt("(%s / %s)", $1, $3); }
+	| expr '%' expr { $$ = fmt("(%s %% %s)", $1, $3); }
+	| expr '+' expr { $$ = fmt("(%s + %s)", $1, $3); }
+	| expr '-' expr { $$ = fmt("(%s - %s)", $1, $3); }
+	| expr Shl expr { $$ = fmt("(%s << %s)", $1, $3); }
+	| expr Shr expr { $$ = fmt("(%s >> %s)", $1, $3); }
+	| expr '<' expr { $$ = fmt("(%s < %s)", $1, $3); }
+	| expr Le expr { $$ = fmt("(%s <= %s)", $1, $3); }
+	| expr '>' expr { $$ = fmt("(%s > %s)", $1, $3); }
+	| expr Ge expr { $$ = fmt("(%s >= %s)", $1, $3); }
+	| expr Eq expr { $$ = fmt("(%s == %s)", $1, $3); }
+	| expr Ne expr { $$ = fmt("(%s != %s)", $1, $3); }
+	| expr '&' expr { $$ = fmt("(%s & %s)", $1, $3); }
+	| expr '^' expr { $$ = fmt("(%s ^ %s)", $1, $3); }
+	| expr '|' expr { $$ = fmt("(%s | %s)", $1, $3); }
+	| expr And expr { $$ = fmt("(%s && %s)", $1, $3); }
+	| expr Or expr { $$ = fmt("(%s || %s)", $1, $3); }
+	| expr '?' expr ':' expr { $$ = fmt("(%s ? %s : %s)", $1, $3, $5); }
+	| expr ass expr %prec '=' { $$ = fmt("(%s %s %s)", $1, $2, $3); }
+	| expr ',' expr { $$ = fmt("(%s, %s)", $1, $3); }
+	;
+
+ass:
+	'=' { $$ = "="; }
+	| MulAss { $$ = "*="; }
+	| DivAss { $$ = "/="; }
+	| ModAss { $$ = "%="; }
+	| AddAss { $$ = "+="; }
+	| SubAss { $$ = "-="; }
+	| ShlAss { $$ = "<<="; }
+	| ShrAss { $$ = ">>="; }
+	| AndAss { $$ = "&="; }
+	| XorAss { $$ = "^="; }
+	| OrAss { $$ = "|="; }
+	;
+
+%%
+
+#define T(a, b) ((int)(a) << 8 | (int)(b))
+
+static FILE *in;
+
+static int yylex(void) {
+	char ch;
+	while (isspace(ch = getc(in)));
+
+	if (isalnum(ch)) {
+		char ident[64] = { ch, '\0' };
+		for (size_t i = 1; i < sizeof(ident) - 1; ++i) {
+			ch = getc(in);
+			if (!isalnum(ch) && ch != '_') break;
+			ident[i] = ch;
+		}
+		ungetc(ch, in);
+		if (!strcmp(ident, "sizeof")) return Sizeof;
+		yylval = fmt("%s", ident);
+		return Ident;
+	}
+
+	char ne = getc(in);
+	switch (T(ch, ne)) {
+		case T('-', '>'): return Arr;
+		case T('+', '+'): return Inc;
+		case T('-', '-'): return Dec;
+		case T('<', '='): return Le;
+		case T('>', '='): return Ge;
+		case T('=', '='): return Eq;
+		case T('!', '='): return Ne;
+		case T('&', '&'): return And;
+		case T('|', '|'): return Or;
+		case T('*', '='): return MulAss;
+		case T('/', '='): return DivAss;
+		case T('%', '='): return ModAss;
+		case T('+', '='): return AddAss;
+		case T('-', '='): return SubAss;
+		case T('&', '='): return AndAss;
+		case T('^', '='): return XorAss;
+		case T('|', '='): return OrAss;
+		case T('<', '<'): {
+			if ('=' == (ne = getc(in))) return ShlAss;
+			ungetc(ne, in);
+			return Shl;
+		}
+		case T('>', '>'): {
+			if ('=' == (ne = getc(in))) return ShrAss;
+			ungetc(ne, in);
+			return Shr;
+		}
+		default: {
+			ungetc(ne, in);
+			return ch;
+		}
+	}
+}
+
+static void yyerror(const char *str) {
+	errx(EX_DATAERR, "%s", str);
+}
+
+int main(int argc, char *argv[]) {
+	for (int i = 1; i < argc; ++i) {
+		in = fmemopen(argv[i], strlen(argv[i]), "r");
+		if (!in) err(EX_OSERR, "fmemopen");
+		yyparse();
+		fclose(in);
+	}
+	if (argc > 1) return EX_OK;
+	in = stdin;
+	yyparse();
+}
diff --git a/bin/pbd.c b/bin/pbd.c
new file mode 100644
index 00000000..9f47b63e
--- /dev/null
+++ b/bin/pbd.c
@@ -0,0 +1,151 @@
+/* Copyright (C) 2017  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+typedef unsigned char byte;
+
+static void spawn(const char *cmd, const char *arg, int dest, int src) {
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+
+	if (pid) {
+		int status;
+		pid_t dead = waitpid(pid, &status, 0);
+		if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid);
+		if (status) warnx("%s: status %d", cmd, status);
+
+	} else {
+		int fd = dup2(src, dest);
+		if (fd < 0) err(EX_OSERR, "dup2");
+
+		execlp(cmd, cmd, arg, NULL);
+		err(EX_UNAVAILABLE, "%s", cmd);
+	}
+}
+
+static int pbd(void) {
+	int error;
+
+	int server = socket(PF_INET, SOCK_STREAM, 0);
+	if (server < 0) err(EX_OSERR, "socket");
+
+	error = fcntl(server, F_SETFD, FD_CLOEXEC);
+	if (error) err(EX_IOERR, "fcntl");
+
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_port = htons(7062),
+		.sin_addr = { .s_addr = htonl(0x7F000001) },
+	};
+	error = bind(server, (struct sockaddr *)&addr, sizeof(addr));
+	if (error) err(EX_UNAVAILABLE, "bind");
+
+	error = listen(server, 0);
+	if (error) err(EX_UNAVAILABLE, "listen");
+
+	for (;;) {
+		int client = accept(server, NULL, NULL);
+		if (client < 0) err(EX_IOERR, "accept");
+
+		error = fcntl(client, F_SETFD, FD_CLOEXEC);
+		if (error) err(EX_IOERR, "fcntl");
+
+		char c = 0;
+		ssize_t size = read(client, &c, 1);
+		if (size < 0) warn("read");
+
+		switch (c) {
+			break; case 'p': spawn("pbpaste", NULL, STDOUT_FILENO, client);
+			break; case 'c': spawn("pbcopy", NULL, STDIN_FILENO, client);
+			break; case 'o': spawn("xargs", "open", STDIN_FILENO, client);
+		}
+
+		close(client);
+	}
+}
+
+static int pbdClient(char c) {
+	int client = socket(PF_INET, SOCK_STREAM, 0);
+	if (client < 0) err(EX_OSERR, "socket");
+
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_port = htons(7062),
+		.sin_addr = { .s_addr = htonl(0x7F000001) },
+	};
+	int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
+	if (error) err(EX_UNAVAILABLE, "connect");
+
+	ssize_t size = write(client, &c, 1);
+	if (size < 0) err(EX_IOERR, "write");
+
+	return client;
+}
+
+static void copy(int out, int in) {
+	byte buf[4096];
+	ssize_t readSize;
+	while (0 < (readSize = read(in, buf, sizeof(buf)))) {
+		ssize_t writeSize = write(out, buf, readSize);
+		if (writeSize < 0) err(EX_IOERR, "write(%d)", out);
+	}
+	if (readSize < 0) err(EX_IOERR, "read(%d)", in);
+}
+
+static int pbcopy(void) {
+	int client = pbdClient('c');
+	copy(client, STDIN_FILENO);
+	return EX_OK;
+}
+
+static int pbpaste(void) {
+	int client = pbdClient('p');
+	copy(STDOUT_FILENO, client);
+	return EX_OK;
+}
+
+static int open1(const char *url) {
+	if (!url) return EX_USAGE;
+	int client = pbdClient('o');
+	ssize_t size = write(client, url, strlen(url));
+	if (size < 0) err(EX_IOERR, "write");
+	return EX_OK;
+}
+
+int main(int argc, char *argv[]) {
+	for (int opt; 0 < (opt = getopt(argc, argv, "co:ps"));) {
+		switch (opt) {
+			case 'c': return pbcopy();
+			case 'o': return open1(optarg);
+			case 'p': return pbpaste();
+			case 's': return pbd();
+			default:  return EX_USAGE;
+		}
+	}
+	return pbd();
+}
diff --git a/bin/png.h b/bin/png.h
new file mode 100644
index 00000000..0df4699b
--- /dev/null
+++ b/bin/png.h
@@ -0,0 +1,108 @@
+/* Copyright (C) 2018  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+
+static inline uint32_t pngCRCTable(uint8_t n) {
+	static uint32_t table[256];
+	if (table[1]) return table[n];
+	for (int i = 0; i < 256; ++i) {
+		table[i] = i;
+		for (int j = 0; j < 8; ++j) {
+			table[i] = (table[i] >> 1) ^ (table[i] & 1 ? 0xEDB88320 : 0);
+		}
+	}
+	return table[n];
+}
+
+static uint32_t pngCRC;
+
+static inline void pngWrite(FILE *file, const uint8_t *ptr, uint32_t len) {
+	if (!fwrite(ptr, len, 1, file)) err(EX_IOERR, "pngWrite");
+	for (uint32_t i = 0; i < len; ++i) {
+		pngCRC = pngCRCTable(pngCRC ^ ptr[i]) ^ (pngCRC >> 8);
+	}
+}
+static inline void pngInt32(FILE *file, uint32_t n) {
+	pngWrite(file, (uint8_t []) { n >> 24, n >> 16, n >> 8, n }, 4);
+}
+static inline void pngChunk(FILE *file, char type[static 4], uint32_t len) {
+	pngInt32(file, len);
+	pngCRC = ~0;
+	pngWrite(file, (uint8_t *)type, 4);
+}
+
+enum {
+	PNGGrayscale,
+	PNGTruecolor = 2,
+	PNGIndexed,
+	PNGAlpha,
+};
+
+static inline void pngHead(
+	FILE *file, uint32_t width, uint32_t height, uint8_t depth, uint8_t color
+) {
+	pngWrite(file, (uint8_t *)"\x89PNG\r\n\x1A\n", 8);
+	pngChunk(file, "IHDR", 13);
+	pngInt32(file, width);
+	pngInt32(file, height);
+	pngWrite(file, &depth, 1);
+	pngWrite(file, &color, 1);
+	pngWrite(file, (uint8_t []) { 0, 0, 0 }, 3);
+	pngInt32(file, ~pngCRC);
+}
+
+static inline void pngPalette(FILE *file, const uint8_t *pal, uint32_t len) {
+	pngChunk(file, "PLTE", len);
+	pngWrite(file, pal, len);
+	pngInt32(file, ~pngCRC);
+}
+
+enum {
+	PNGNone,
+	PNGSub,
+	PNGUp,
+	PNGAverage,
+	PNGPaeth,
+};
+
+static inline void pngData(FILE *file, const uint8_t *data, uint32_t len) {
+	uint32_t adler1 = 1, adler2 = 0;
+	for (uint32_t i = 0; i < len; ++i) {
+		adler1 = (adler1 + data[i]) % 65521;
+		adler2 = (adler1 + adler2) % 65521;
+	}
+	uint32_t zlen = 2 + 5 * ((len + 0xFFFE) / 0xFFFF) + len + 4;
+	pngChunk(file, "IDAT", zlen);
+	pngWrite(file, (uint8_t []) { 0x08, 0x1D }, 2);
+	for (; len > 0xFFFF; data += 0xFFFF, len -= 0xFFFF) {
+		pngWrite(file, (uint8_t []) { 0x00, 0xFF, 0xFF, 0x00, 0x00 }, 5);
+		pngWrite(file, data, 0xFFFF);
+	}
+	pngWrite(file, (uint8_t []) { 0x01, len, len >> 8, ~len, ~len >> 8 }, 5);
+	pngWrite(file, data, len);
+	pngInt32(file, adler2 << 16 | adler1);
+	pngInt32(file, ~pngCRC);
+}
+
+static inline void pngTail(FILE *file) {
+	pngChunk(file, "IEND", 0);
+	pngInt32(file, ~pngCRC);
+}
diff --git a/bin/pngo.c b/bin/pngo.c
new file mode 100644
index 00000000..eb51ccc2
--- /dev/null
+++ b/bin/pngo.c
@@ -0,0 +1,941 @@
+/* Copyright (C) 2018, 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+static bool verbose;
+static const char *path;
+static FILE *file;
+static uint32_t crc;
+
+static void pngRead(void *ptr, size_t len, const char *desc) {
+	size_t n = fread(ptr, len, 1, file);
+	if (!n && ferror(file)) err(EX_IOERR, "%s", path);
+	if (!n) errx(EX_DATAERR, "%s: missing %s", path, desc);
+	crc = crc32(crc, ptr, len);
+}
+
+static void pngWrite(const void *ptr, size_t len) {
+	size_t n = fwrite(ptr, len, 1, file);
+	if (!n) err(EX_IOERR, "%s", path);
+	crc = crc32(crc, ptr, len);
+}
+
+static const uint8_t Sig[8] = "\x89PNG\r\n\x1A\n";
+
+static void sigRead(void) {
+	uint8_t sig[sizeof(Sig)];
+	pngRead(sig, sizeof(sig), "signature");
+	if (memcmp(sig, Sig, sizeof(sig))) {
+		errx(EX_DATAERR, "%s: invalid signature", path);
+	}
+}
+
+static void sigWrite(void) {
+	pngWrite(Sig, sizeof(Sig));
+}
+
+static uint32_t u32Read(const char *desc) {
+	uint8_t b[4];
+	pngRead(b, sizeof(b), desc);
+	return (uint32_t)b[0] << 24 | (uint32_t)b[1] << 16
+		| (uint32_t)b[2] << 8 | (uint32_t)b[3];
+}
+
+static void u32Write(uint32_t x) {
+	uint8_t b[4] = { x >> 24 & 0xFF, x >> 16 & 0xFF, x >> 8 & 0xFF, x & 0xFF };
+	pngWrite(b, sizeof(b));
+}
+
+struct Chunk {
+	uint32_t len;
+	char type[5];
+};
+
+static struct Chunk chunkRead(void) {
+	struct Chunk chunk;
+	chunk.len = u32Read("chunk length");
+	crc = crc32(0, Z_NULL, 0);
+	pngRead(chunk.type, 4, "chunk type");
+	chunk.type[4] = 0;
+	return chunk;
+}
+
+static void chunkWrite(struct Chunk chunk) {
+	u32Write(chunk.len);
+	crc = crc32(0, Z_NULL, 0);
+	pngWrite(chunk.type, 4);
+}
+
+static void crcRead(void) {
+	uint32_t expect = crc;
+	uint32_t actual = u32Read("CRC32");
+	if (actual == expect) return;
+	errx(
+		EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
+		path, expect, actual
+	);
+}
+
+static void crcWrite(void) {
+	u32Write(crc);
+}
+
+static void chunkSkip(struct Chunk chunk) {
+	if (!(chunk.type[0] & 0x20)) {
+		errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, chunk.type);
+	}
+	uint8_t buf[4096];
+	while (chunk.len > sizeof(buf)) {
+		pngRead(buf, sizeof(buf), "chunk data");
+		chunk.len -= sizeof(buf);
+	}
+	if (chunk.len) pngRead(buf, chunk.len, "chunk data");
+	crcRead();
+}
+
+enum Color {
+	Grayscale = 0,
+	Truecolor = 2,
+	Indexed = 3,
+	GrayscaleAlpha = 4,
+	TruecolorAlpha = 6,
+};
+enum Compression {
+	Deflate,
+};
+enum FilterMethod {
+	Adaptive,
+};
+enum Interlace {
+	Progressive,
+	Adam7,
+};
+
+enum { HeaderLen = 13 };
+static struct {
+	uint32_t width;
+	uint32_t height;
+	uint8_t depth;
+	uint8_t color;
+	uint8_t compression;
+	uint8_t filter;
+	uint8_t interlace;
+} header;
+
+static size_t pixelLen;
+static size_t lineLen;
+static size_t dataLen;
+
+static void recalc(void) {
+	size_t pixelBits = header.depth;
+	switch (header.color) {
+		break; case GrayscaleAlpha: pixelBits *= 2;
+		break; case Truecolor: pixelBits *= 3;
+		break; case TruecolorAlpha: pixelBits *= 4;
+	}
+	pixelLen = (pixelBits + 7) / 8;
+	lineLen = (header.width * pixelBits + 7) / 8;
+	dataLen = (1 + lineLen) * header.height;
+}
+
+static void headerPrint(void) {
+	static const char *String[] = {
+		[Grayscale] = "grayscale",
+		[Truecolor] = "truecolor",
+		[Indexed] = "indexed",
+		[GrayscaleAlpha] = "grayscale alpha",
+		[TruecolorAlpha] = "truecolor alpha",
+	};
+	fprintf(
+		stderr, "%s: %" PRIu32 "x%" PRIu32 " %" PRIu8 "-bit %s\n",
+		path, header.width, header.height, header.depth, String[header.color]
+	);
+}
+
+static void headerRead(struct Chunk chunk) {
+	if (chunk.len != HeaderLen) {
+		errx(
+			EX_DATAERR, "%s: expected %s length %" PRIu32 ", found %" PRIu32,
+			path, chunk.type, (uint32_t)HeaderLen, chunk.len
+		);
+	}
+	header.width = u32Read("header width");
+	header.height = u32Read("header height");
+	pngRead(&header.depth, 1, "header depth");
+	pngRead(&header.color, 1, "header color");
+	pngRead(&header.compression, 1, "header compression");
+	pngRead(&header.filter, 1, "header filter");
+	pngRead(&header.interlace, 1, "header interlace");
+	crcRead();
+	recalc();
+
+	if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path);
+	if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path);
+	static const struct {
+		uint8_t color;
+		uint8_t depth;
+	} Valid[] = {
+		{ Grayscale, 1 },
+		{ Grayscale, 2 },
+		{ Grayscale, 4 },
+		{ Grayscale, 8 },
+		{ Grayscale, 16 },
+		{ Truecolor, 8 },
+		{ Truecolor, 16 },
+		{ Indexed, 1 },
+		{ Indexed, 2 },
+		{ Indexed, 4 },
+		{ Indexed, 8 },
+		{ Indexed, 16 },
+		{ GrayscaleAlpha, 8 },
+		{ GrayscaleAlpha, 16 },
+		{ TruecolorAlpha, 8 },
+		{ TruecolorAlpha, 16 },
+	};
+	bool valid = false;
+	for (size_t i = 0; i < ARRAY_LEN(Valid); ++i) {
+		valid = (
+			header.color == Valid[i].color &&
+			header.depth == Valid[i].depth
+		);
+		if (valid) break;
+	}
+	if (!valid) {
+		errx(
+			EX_DATAERR,
+			"%s: invalid color type %" PRIu8 " and bit depth %" PRIu8,
+			path, header.color, header.depth
+		);
+	}
+	if (header.compression != Deflate) {
+		errx(
+			EX_DATAERR, "%s: invalid compression method %" PRIu8,
+			path, header.compression
+		);
+	}
+	if (header.filter != Adaptive) {
+		errx(
+			EX_DATAERR, "%s: invalid filter method %" PRIu8,
+			path, header.filter
+		);
+	}
+	if (header.interlace > Adam7) {
+		errx(
+			EX_DATAERR, "%s: invalid interlace method %" PRIu8,
+			path, header.interlace
+		);
+	}
+
+	if (verbose) headerPrint();
+}
+
+static void headerWrite(void) {
+	if (verbose) headerPrint();
+
+	struct Chunk ihdr = { HeaderLen, "IHDR" };
+	chunkWrite(ihdr);
+	u32Write(header.width);
+	u32Write(header.height);
+	pngWrite(&header.depth, 1);
+	pngWrite(&header.color, 1);
+	pngWrite(&header.compression, 1);
+	pngWrite(&header.filter, 1);
+	pngWrite(&header.interlace, 1);
+	crcWrite();
+}
+
+static struct {
+	uint32_t len;
+	uint8_t rgb[256][3];
+} pal;
+
+static struct {
+	uint32_t len;
+	uint8_t a[256];
+} trans;
+
+static void palClear(void) {
+	pal.len = 0;
+	trans.len = 0;
+}
+
+static uint32_t palIndex(bool alpha, const uint8_t *rgba) {
+	uint32_t i;
+	for (i = 0; i < pal.len; ++i) {
+		if (alpha && i < trans.len && trans.a[i] != rgba[3]) continue;
+		if (!memcmp(pal.rgb[i], rgba, 3)) break;
+	}
+	return i;
+}
+
+static bool palAdd(bool alpha, const uint8_t *rgba) {
+	uint32_t i = palIndex(alpha, rgba);
+	if (i < pal.len) return true;
+	if (i == 256) return false;
+	memcpy(pal.rgb[i], rgba, 3);
+	pal.len++;
+	if (alpha) {
+		trans.a[i] = rgba[3];
+		trans.len++;
+	}
+	return true;
+}
+
+static void transCompact(void) {
+	uint32_t i;
+	for (i = 0; i < trans.len; ++i) {
+		if (trans.a[i] == 0xFF) break;
+	}
+	if (i == trans.len) return;
+
+	for (uint32_t j = i+1; j < trans.len; ++j) {
+		if (trans.a[j] == 0xFF) continue;
+		uint8_t a = trans.a[i];
+		trans.a[i] = trans.a[j];
+		trans.a[j] = a;
+		uint8_t rgb[3];
+		memcpy(rgb, pal.rgb[i], 3);
+		memcpy(pal.rgb[i], pal.rgb[j], 3);
+		memcpy(pal.rgb[j], rgb, 3);
+		i++;
+	}
+	trans.len = i;
+}
+
+static void palRead(struct Chunk chunk) {
+	if (chunk.len % 3) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " not divisible by 3",
+			path, chunk.type, chunk.len
+		);
+	}
+	pal.len = chunk.len / 3;
+	if (pal.len > 256) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " > 256",
+			path, chunk.type, pal.len
+		);
+	}
+	pngRead(pal.rgb, chunk.len, "palette data");
+	crcRead();
+	if (verbose) {
+		fprintf(stderr, "%s: palette length %" PRIu32 "\n", path, pal.len);
+	}
+}
+
+static void palWrite(void) {
+	if (verbose) {
+		fprintf(stderr, "%s: palette length %" PRIu32 "\n", path, pal.len);
+	}
+	struct Chunk plte = { 3 * pal.len, "PLTE" };
+	chunkWrite(plte);
+	pngWrite(pal.rgb, plte.len);
+	crcWrite();
+}
+
+static void transRead(struct Chunk chunk) {
+	trans.len = chunk.len;
+	if (trans.len > 256) {
+		errx(
+			EX_DATAERR, "%s: %s length %" PRIu32 " > 256",
+			path, chunk.type, trans.len
+		);
+	}
+	pngRead(trans.a, chunk.len, "transparency data");
+	crcRead();
+	if (verbose) {
+		fprintf(stderr, "%s: trans length %" PRIu32 "\n", path, trans.len);
+	}
+}
+
+static void transWrite(void) {
+	if (verbose) {
+		fprintf(stderr, "%s: trans length %" PRIu32 "\n", path, trans.len);
+	}
+	struct Chunk trns = { trans.len, "tRNS" };
+	chunkWrite(trns);
+	pngWrite(trans.a, trns.len);
+	crcWrite();
+}
+
+static uint8_t *data;
+
+static void dataAlloc(void) {
+	data = malloc(dataLen);
+	if (!data) err(EX_OSERR, "malloc");
+}
+
+static const char *humanize(size_t n) {
+	static char buf[64];
+	if (n >> 10) {
+		snprintf(buf, sizeof(buf), "%zuK", n >> 10);
+	} else {
+		snprintf(buf, sizeof(buf), "%zuB", n);
+	}
+	return buf;
+}
+
+static void dataRead(struct Chunk chunk) {
+	if (verbose) {
+		fprintf(stderr, "%s: data size %s\n", path, humanize(dataLen));
+	}
+
+	z_stream stream = { .next_out = data, .avail_out = dataLen };
+	int error = inflateInit(&stream);
+	if (error != Z_OK) errx(EX_SOFTWARE, "inflateInit: %s", stream.msg);
+
+	for (;;) {
+		if (strcmp(chunk.type, "IDAT")) {
+			errx(EX_DATAERR, "%s: missing IDAT chunk", path);
+		}
+
+		uint8_t *idat = malloc(chunk.len);
+		if (!idat) err(EX_OSERR, "malloc");
+
+		pngRead(idat, chunk.len, "image data");
+		crcRead();
+		
+		stream.next_in = idat;
+		stream.avail_in = chunk.len;
+		error = inflate(&stream, Z_SYNC_FLUSH);
+		free(idat);
+
+		if (error == Z_STREAM_END) break;
+		if (error != Z_OK) {
+			errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
+		}
+
+		chunk = chunkRead();
+	}
+	inflateEnd(&stream);
+	if ((size_t)stream.total_out != dataLen) {
+		errx(
+			EX_DATAERR, "%s: expected data length %zu, found %zu",
+			path, dataLen, (size_t)stream.total_out
+		);
+	}
+
+	if (verbose) {
+		fprintf(
+			stderr, "%s: deflate size %s\n",
+			path, humanize(stream.total_in)
+		);
+	}
+}
+
+static void dataWrite(void) {
+	if (verbose) {
+		fprintf(stderr, "%s: data size %s\n", path, humanize(dataLen));
+	}
+
+	z_stream stream = {
+		.next_in = data,
+		.avail_in = dataLen,
+	};
+	int error = deflateInit2(
+		&stream, Z_BEST_COMPRESSION, Z_DEFLATED, 15, 8, Z_FILTERED
+	);
+	if (error != Z_OK) errx(EX_SOFTWARE, "deflateInit2: %s", stream.msg);
+
+	uLong bound = deflateBound(&stream, dataLen);
+	uint8_t *buf = malloc(bound);
+	if (!buf) err(EX_OSERR, "malloc");
+
+	stream.next_out = buf;
+	stream.avail_out = bound;
+	deflate(&stream, Z_FINISH);
+	deflateEnd(&stream);
+
+	struct Chunk idat = { stream.total_out, "IDAT" };
+	chunkWrite(idat);
+	pngWrite(buf, stream.total_out);
+	crcWrite();
+	free(buf);
+
+	struct Chunk iend = { 0, "IEND" };
+	chunkWrite(iend);
+	crcWrite();
+
+	if (verbose) {
+		fprintf(
+			stderr, "%s: deflate size %s\n",
+			path, humanize(stream.total_out)
+		);
+	}
+}
+
+enum Filter {
+	None,
+	Sub,
+	Up,
+	Average,
+	Paeth,
+	FilterCap,
+};
+
+struct Bytes {
+	uint8_t x, a, b, c;
+};
+
+static uint8_t paethPredictor(struct Bytes f) {
+	int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
+	int32_t pa = labs(p - (int32_t)f.a);
+	int32_t pb = labs(p - (int32_t)f.b);
+	int32_t pc = labs(p - (int32_t)f.c);
+	if (pa <= pb && pa <= pc) return f.a;
+	if (pb <= pc) return f.b;
+	return f.c;
+}
+
+static uint8_t recon(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x + f.a;
+		case Up:      return f.x + f.b;
+		case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x + paethPredictor(f);
+		default: abort();
+	}
+}
+
+static uint8_t filt(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x - f.a;
+		case Up:      return f.x - f.b;
+		case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x - paethPredictor(f);
+		default: abort();
+	}
+}
+
+static uint8_t *lineType(uint32_t y) {
+	return &data[y * (1 + lineLen)];
+}
+static uint8_t *lineData(uint32_t y) {
+	return 1 + lineType(y);
+}
+
+static struct Bytes origBytes(uint32_t y, size_t i) {
+	bool a = (i >= pixelLen), b = (y > 0), c = (a && b);
+	return (struct Bytes) {
+		.x = lineData(y)[i],
+		.a = (a ? lineData(y)[i-pixelLen] : 0),
+		.b = (b ? lineData(y-1)[i] : 0),
+		.c = (c ? lineData(y-1)[i-pixelLen] : 0),
+	};
+}
+
+static void dataRecon(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0; i < lineLen; ++i) {
+			lineData(y)[i] = recon(*lineType(y), origBytes(y, i));
+		}
+		*lineType(y) = None;
+	}
+}
+
+static void dataFilter(void) {
+	if (header.color == Indexed || header.depth < 8) return;
+	uint8_t *filter[FilterCap];
+	for (enum Filter i = None; i < FilterCap; ++i) {
+		filter[i] = malloc(lineLen);
+		if (!filter[i]) err(EX_OSERR, "malloc");
+	}
+	for (uint32_t y = header.height-1; y < header.height; --y) {
+		uint32_t heuristic[FilterCap] = {0};
+		enum Filter minType = None;
+		for (enum Filter type = None; type < FilterCap; ++type) {
+			for (size_t i = 0; i < lineLen; ++i) {
+				filter[type][i] = filt(type, origBytes(y, i));
+				heuristic[type] += abs((int8_t)filter[type][i]);
+			}
+			if (heuristic[type] < heuristic[minType]) minType = type;
+		}
+		*lineType(y) = minType;
+		memcpy(lineData(y), filter[minType], lineLen);
+	}
+	for (enum Filter i = None; i < FilterCap; ++i) {
+		free(filter[i]);
+	}
+}
+
+static bool alphaUnused(void) {
+	if (header.color != GrayscaleAlpha && header.color != TruecolorAlpha) {
+		return false;
+	}
+	size_t sampleLen = header.depth / 8;
+	size_t colorLen = pixelLen - sampleLen;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (uint32_t x = 0; x < header.width; ++x)
+	for (size_t i = 0; i < sampleLen; ++i) {
+		if (lineData(y)[x * pixelLen + colorLen + i] != 0xFF) return false;
+	}
+	return true;
+}
+
+static void alphaDiscard(void) {
+	if (header.color != GrayscaleAlpha && header.color != TruecolorAlpha) {
+		return;
+	}
+	size_t sampleLen = header.depth / 8;
+	size_t colorLen = pixelLen - sampleLen;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (uint32_t x = 0; x < header.width; ++x) {
+			memmove(ptr, &lineData(y)[x * pixelLen], colorLen);
+			ptr += colorLen;
+		}
+	}
+	header.color = (header.color == GrayscaleAlpha ? Grayscale : Truecolor);
+	recalc();
+}
+
+static bool depth16Unused(void) {
+	if (header.color != Grayscale && header.color != Truecolor) return false;
+	if (header.depth != 16) return false;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (size_t i = 0; i < lineLen; i += 2) {
+		if (lineData(y)[i] != lineData(y)[i+1]) return false;
+	}
+	return true;
+}
+
+static void depth16Reduce(void) {
+	if (header.depth != 16) return;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (size_t i = 0; i < lineLen / 2; ++i) {
+			*ptr++ = lineData(y)[i*2];
+		}
+	}
+	header.depth = 8;
+	recalc();
+}
+
+static bool colorUnused(void) {
+	if (header.color != Truecolor && header.color != TruecolorAlpha) {
+		return false;
+	}
+	if (header.depth != 8) return false;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (uint32_t x = 0; x < header.width; ++x) {
+		uint8_t r = lineData(y)[x * pixelLen + 0];
+		uint8_t g = lineData(y)[x * pixelLen + 1];
+		uint8_t b = lineData(y)[x * pixelLen + 2];
+		if (r != g || g != b) return false;
+	}
+	return true;
+}
+
+static void colorDiscard(void) {
+	if (header.color != Truecolor && header.color != TruecolorAlpha) return;
+	if (header.depth != 8) return;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (uint32_t x = 0; x < header.width; ++x) {
+			uint8_t r = lineData(y)[x * pixelLen + 0];
+			uint8_t g = lineData(y)[x * pixelLen + 1];
+			uint8_t b = lineData(y)[x * pixelLen + 2];
+			*ptr++ = ((uint32_t)r + (uint32_t)g + (uint32_t)b) / 3;
+			if (header.color == TruecolorAlpha) {
+				*ptr++ = lineData(y)[x * pixelLen + 3];
+			}
+		}
+	}
+	header.color = (header.color == Truecolor ? Grayscale : GrayscaleAlpha);
+	recalc();
+}
+
+static void colorIndex(void) {
+	if (header.color != Truecolor && header.color != TruecolorAlpha) return;
+	if (header.depth != 8) return;
+	bool alpha = (header.color == TruecolorAlpha);
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (uint32_t x = 0; x < header.width; ++x) {
+		if (!palAdd(alpha, &lineData(y)[x * pixelLen])) return;
+	}
+
+	transCompact();
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (uint32_t x = 0; x < header.width; ++x) {
+			*ptr++ = palIndex(alpha, &lineData(y)[x * pixelLen]);
+		}
+	}
+	header.color = Indexed;
+	recalc();
+}
+
+static bool depth8Unused(void) {
+	if (header.depth != 8) return false;
+	if (header.color == Indexed) return pal.len <= 16;
+	if (header.color != Grayscale) return false;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (size_t i = 0; i < lineLen; ++i) {
+		if ((lineData(y)[i] >> 4) != (lineData(y)[i] & 0x0F)) return false;
+	}
+	return true;
+}
+
+static void depth8Reduce(void) {
+	if (header.color != Grayscale && header.color != Indexed) return;
+	if (header.depth != 8) return;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (size_t i = 0; i < lineLen; i += 2) {
+			uint8_t a, b;
+			uint8_t aa = lineData(y)[i];
+			uint8_t bb = (i+1 < lineLen ? lineData(y)[i+1] : 0);
+			if (header.color == Grayscale) {
+				a = aa >> 4;
+				b = bb >> 4;
+			} else {
+				a = aa & 0x0F;
+				b = bb & 0x0F;
+			}
+			*ptr++ = a << 4 | b;
+		}
+	}
+	header.depth = 4;
+	recalc();
+}
+
+static bool depth4Unused(void) {
+	if (header.depth != 4) return false;
+	if (header.color == Indexed) return pal.len <= 4;
+	if (header.color != Grayscale) return false;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (size_t i = 0; i < lineLen; ++i) {
+		uint8_t a = lineData(y)[i] >> 4;
+		uint8_t b = lineData(y)[i] & 0x0F;
+		if ((a >> 2) != (a & 0x03)) return false;
+		if ((b >> 2) != (b & 0x03)) return false;
+	}
+	return true;
+}
+
+static void depth4Reduce(void) {
+	if (header.color != Grayscale && header.color != Indexed) return;
+	if (header.depth != 4) return;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (size_t i = 0; i < lineLen; i += 2) {
+			uint8_t a, b, c, d;
+			uint8_t aabb = lineData(y)[i];
+			uint8_t ccdd = (i+1 < lineLen ? lineData(y)[i+1] : 0);
+			if (header.color == Grayscale) {
+				a = aabb >> 6;
+				c = ccdd >> 6;
+				b = aabb >> 2 & 0x03;
+				d = ccdd >> 2 & 0x03;
+			} else {
+				a = aabb >> 4 & 0x03;
+				c = ccdd >> 4 & 0x03;
+				b = aabb & 0x03;
+				d = ccdd & 0x03;
+			}
+			*ptr++ = a << 6 | b << 4 | c << 2 | d;
+		}
+	}
+	header.depth = 2;
+	recalc();
+}
+
+static bool depth2Unused(void) {
+	if (header.depth != 2) return false;
+	if (header.color == Indexed) return pal.len <= 2;
+	if (header.color != Grayscale) return false;
+	for (uint32_t y = 0; y < header.height; ++y)
+	for (size_t i = 0; i < lineLen; ++i) {
+		uint8_t a = lineData(y)[i] >> 6;
+		uint8_t b = lineData(y)[i] >> 4 & 0x03;
+		uint8_t c = lineData(y)[i] >> 2 & 0x03;
+		uint8_t d = lineData(y)[i] & 0x03;
+		if ((a >> 1) != (a & 1)) return false;
+		if ((b >> 1) != (b & 1)) return false;
+		if ((c >> 1) != (c & 1)) return false;
+		if ((d >> 1) != (d & 1)) return false;
+	}
+	return true;
+}
+
+static void depth2Reduce(void) {
+	if (header.color != Grayscale && header.color != Indexed) return;
+	if (header.depth != 2) return;
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = *lineType(y);
+		for (size_t i = 0; i < lineLen; i += 2) {
+			uint8_t a, b, c, d, e, f, g, h;
+			uint8_t aabbccdd = lineData(y)[i];
+			uint8_t eeffgghh = (i+1 < lineLen ? lineData(y)[i+1] : 0);
+			if (header.color == Grayscale) {
+				a = aabbccdd >> 7;
+				b = aabbccdd >> 5 & 1;
+				c = aabbccdd >> 3 & 1;
+				d = aabbccdd >> 1 & 1;
+				e = eeffgghh >> 7;
+				f = eeffgghh >> 5 & 1;
+				g = eeffgghh >> 3 & 1;
+				h = eeffgghh >> 1 & 1;
+			} else {
+				a = aabbccdd >> 6 & 1;
+				b = aabbccdd >> 4 & 1;
+				c = aabbccdd >> 2 & 1;
+				d = aabbccdd & 1;
+				e = eeffgghh >> 6 & 1;
+				f = eeffgghh >> 4 & 1;
+				g = eeffgghh >> 2 & 1;
+				h = eeffgghh & 1;
+			}
+			*ptr++ = 0
+				| a << 7 | b << 6 | c << 5 | d << 4
+				| e << 3 | f << 2 | g << 1 | h;
+		}
+	}
+	header.depth = 1;
+	recalc();
+}
+
+static bool discardAlpha;
+static bool discardColor;
+static uint8_t reduceDepth = 16;
+
+static void optimize(const char *inPath, const char *outPath) {
+	if (inPath) {
+		path = inPath;
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+	} else {
+		path = "stdin";
+		file = stdin;
+	}
+
+	sigRead();
+	struct Chunk ihdr = chunkRead();
+	if (strcmp(ihdr.type, "IHDR")) {
+		errx(EX_DATAERR, "%s: expected IHDR, found %s", path, ihdr.type);
+	}
+	headerRead(ihdr);
+	if (header.interlace != Progressive) {
+		errx(EX_CONFIG, "%s: unsupported interlacing", path);
+	}
+
+	palClear();
+	dataAlloc();
+	for (;;) {
+		struct Chunk chunk = chunkRead();
+		if (!strcmp(chunk.type, "PLTE")) {
+			palRead(chunk);
+		} else if (!strcmp(chunk.type, "tRNS")) {
+			transRead(chunk);
+		} else if (!strcmp(chunk.type, "IDAT")) {
+			dataRead(chunk);
+		} else if (!strcmp(chunk.type, "IEND")) {
+			break;
+		} else {
+			chunkSkip(chunk);
+		}
+	}
+	fclose(file);
+
+	dataRecon();
+	if (discardAlpha || alphaUnused()) alphaDiscard();
+	if (reduceDepth < 16 || depth16Unused()) depth16Reduce();
+	if (discardColor || colorUnused()) colorDiscard();
+	colorIndex();
+	if (reduceDepth < 8 || depth8Unused()) depth8Reduce();
+	if (reduceDepth < 4 || depth4Unused()) depth4Reduce();
+	if (reduceDepth < 2 || depth2Unused()) depth2Reduce();
+	dataFilter();
+
+	char buf[PATH_MAX];
+	if (outPath) {
+		path = outPath;
+		if (outPath == inPath) {
+			snprintf(buf, sizeof(buf), "%so", outPath);
+			file = fopen(buf, "wx");
+			if (!file) err(EX_CANTCREAT, "%s", buf);
+		} else {
+			file = fopen(path, "w");
+			if (!file) err(EX_CANTCREAT, "%s", outPath);
+		}
+	} else {
+		path = "stdout";
+		file = stdout;
+	}
+
+	sigWrite();
+	headerWrite();
+	if (header.color == Indexed) {
+		palWrite();
+		if (trans.len) transWrite();
+	}
+	dataWrite();
+	free(data);
+	int error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+
+	if (outPath && outPath == inPath) {
+		error = rename(buf, outPath);
+		if (error) err(EX_CANTCREAT, "%s", outPath);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	bool stdio = false;
+	char *outPath = NULL;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "ab:cgo:v"));) {
+		switch (opt) {
+			break; case 'a': discardAlpha = true;
+			break; case 'b': reduceDepth = strtoul(optarg, NULL, 10);
+			break; case 'c': stdio = true;
+			break; case 'g': discardColor = true;
+			break; case 'o': outPath = optarg;
+			break; case 'v': verbose = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	if (optind < argc) {
+		for (int i = optind; i < argc; ++i) {
+			optimize(argv[i], (stdio ? NULL : outPath ? outPath : argv[i]));
+		}
+	} else {
+		optimize(NULL, outPath);
+	}
+}
diff --git a/bin/psf2png.c b/bin/psf2png.c
new file mode 100644
index 00000000..c36238a0
--- /dev/null
+++ b/bin/psf2png.c
@@ -0,0 +1,107 @@
+/* Copyright (C) 2018  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "png.h"
+
+int main(int argc, char *argv[]) {
+	uint32_t cols = 32;
+	const char *str = NULL;
+	uint32_t fg = 0xFFFFFF;
+	uint32_t bg = 0x000000;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "b:c:f:s:"))) {
+		switch (opt) {
+			break; case 'b': bg = strtoul(optarg, NULL, 16);
+			break; case 'c': cols = strtoul(optarg, NULL, 0);
+			break; case 'f': fg = strtoul(optarg, NULL, 16);
+			break; case 's': str = optarg;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (!cols && str) cols = strlen(str);
+	if (!cols) return EX_USAGE;
+
+	const char *path = NULL;
+	if (optind < argc) path = argv[optind];
+	
+	FILE *file = path ? fopen(path, "r") : stdin;
+	if (!file) err(EX_NOINPUT, "%s", path);
+	if (!path) path = "(stdin)";
+
+	struct {
+		uint32_t magic;
+		uint32_t version;
+		uint32_t size;
+		uint32_t flags;
+		struct {
+			uint32_t len;
+			uint32_t size;
+			uint32_t height;
+			uint32_t width;
+		} glyph;
+	} header;
+	size_t len = fread(&header, sizeof(header), 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (len < 1) errx(EX_DATAERR, "%s: truncated header", path);
+
+	uint32_t widthBytes = (header.glyph.width + 7) / 8;
+	uint8_t glyphs[header.glyph.len][header.glyph.height][widthBytes];
+	len = fread(glyphs, header.glyph.size, header.glyph.len, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (len < header.glyph.len) {
+		errx(EX_DATAERR, "%s: truncated glyphs", path);
+	}
+	fclose(file);
+
+	uint32_t count = (str ? strlen(str) : header.glyph.len);
+	uint32_t width = header.glyph.width * cols;
+	uint32_t rows = (count + cols - 1) / cols;
+	uint32_t height = header.glyph.height * rows;
+
+	pngHead(stdout, width, height, 8, PNGIndexed);
+	uint8_t pal[] = {
+		bg >> 16, bg >> 8, bg,
+		fg >> 16, fg >> 8, fg,
+	};
+	pngPalette(stdout, pal, sizeof(pal));
+
+	uint8_t data[height][1 + width];
+	memset(data, PNGNone, sizeof(data));
+
+	for (uint32_t i = 0; i < count; ++i) {
+		uint32_t row = header.glyph.height * (i / cols);
+		uint32_t col = 1 + header.glyph.width * (i % cols);
+		uint32_t g = (str ? str[i] : i);
+		for (uint32_t y = 0; y < header.glyph.height; ++y) {
+			for (uint32_t x = 0; x < header.glyph.width; ++x) {
+				uint8_t bit = glyphs[g][y][x / 8] >> (7 - x % 8) & 1;
+				data[row + y][col + x] = bit;
+			}
+		}
+	}
+
+	pngData(stdout, (uint8_t *)data, sizeof(data));
+	pngTail(stdout);
+}
diff --git a/bin/ptee.c b/bin/ptee.c
new file mode 100644
index 00000000..52350a21
--- /dev/null
+++ b/bin/ptee.c
@@ -0,0 +1,151 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+typedef unsigned char byte;
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+static void handler(int sig) {
+	(void)sig;
+}
+
+int main(int argc, char *argv[]) {
+	int timer = 0;
+	for (int opt; 0 < (opt = getopt(argc, argv, "t:"));) {
+		switch (opt) {
+			break; case 't': timer = atoi(optarg);
+			break; default:  return EX_USAGE;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) return EX_USAGE;
+	if (isatty(STDOUT_FILENO)) errx(EX_USAGE, "stdout is not redirected");
+
+	int error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, &window);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[0], argv);
+		err(EX_NOINPUT, "%s", argv[0]);
+	}
+
+	if (timer) {
+		signal(SIGALRM, handler);
+		struct timeval tv = {
+			.tv_sec = timer / 1000,
+			.tv_usec = timer % 1000 * 1000,
+		};
+		struct itimerval itv = { tv, tv };
+		setitimer(ITIMER_REAL, &itv, NULL);
+	}
+
+	char mc[] = "\x1B[10i";
+	bool stop = false;
+
+	byte buf[4096];
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	for (;;) {
+		int nfds = poll(fds, 2, -1);
+		if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll");
+
+		if (nfds < 0) {
+			ssize_t wlen = write(STDOUT_FILENO, mc, sizeof(mc) - 1);
+			if (wlen < 0) err(EX_IOERR, "write");
+			continue;
+		}
+
+		if (fds[0].revents & POLLIN) {
+			ssize_t rlen = read(STDIN_FILENO, buf, sizeof(buf));
+			if (rlen < 0) err(EX_IOERR, "read");
+
+			if (rlen == 1 && buf[0] == CTRL('Q')) {
+				stop ^= true;
+				continue;
+			}
+
+			if (rlen == 1 && buf[0] == CTRL('S')) {
+				ssize_t wlen = write(STDOUT_FILENO, mc, sizeof(mc) - 1);
+				if (wlen < 0) err(EX_IOERR, "write");
+				continue;
+			}
+
+			ssize_t wlen = write(pty, buf, rlen);
+			if (wlen < 0) err(EX_IOERR, "write");
+		}
+
+		if (fds[1].revents & POLLIN) {
+			ssize_t rlen = read(pty, buf, sizeof(buf));
+			if (rlen < 0) err(EX_IOERR, "read");
+
+			ssize_t wlen = write(STDIN_FILENO, buf, rlen);
+			if (wlen < 0) err(EX_IOERR, "write");
+
+			if (!stop) {
+				wlen = write(STDOUT_FILENO, buf, rlen);
+				if (wlen < 0) err(EX_IOERR, "write");
+			}
+		}
+
+		int status;
+		pid_t dead = waitpid(pid, &status, WNOHANG);
+		if (dead < 0) err(EX_OSERR, "waitpid");
+		if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE;
+	}
+}
diff --git a/bin/qf.c b/bin/qf.c
new file mode 100644
index 00000000..1fbf48b9
--- /dev/null
+++ b/bin/qf.c
@@ -0,0 +1,294 @@
+/* Copyright (C) 2022  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <curses.h>
+#include <err.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+enum Type {
+	File,
+	Match,
+	Context,
+	Text,
+};
+
+struct Line {
+	enum Type type;
+	char *path;
+	unsigned nr;
+	char *text;
+	regmatch_t match;
+};
+
+static struct {
+	struct Line *ptr;
+	size_t len, cap;
+} lines;
+
+static void push(struct Line line) {
+	if (lines.len == lines.cap) {
+		lines.cap = (lines.cap ? lines.cap * 2 : 256);
+		lines.ptr = realloc(lines.ptr, sizeof(*lines.ptr) * lines.cap);
+		if (!lines.ptr) err(EX_OSERR, "realloc");
+	}
+	lines.ptr[lines.len++] = line;
+}
+
+static const char *pattern;
+static regex_t regex;
+
+static void parse(struct Line line) {
+	line.path = strsep(&line.text, ":");
+	if (!line.text) {
+		line.type = Text;
+		line.text = line.path;
+		if (lines.len) line.path = lines.ptr[lines.len-1].path;
+		push(line);
+		return;
+	}
+	char *rest;
+	line.nr = strtoul(line.text, &rest, 10);
+	struct Line prev = {0};
+	if (lines.len) prev = lines.ptr[lines.len-1];
+	if (!prev.path || strcmp(line.path, prev.path)) {
+		if (lines.len) push((struct Line) { .type = Text, .text = " " });
+		line.type = File;
+		push(line);
+	}
+	if (rest > line.text && rest[0] == ':') {
+		line.type = Match;
+		line.text = &rest[1];
+	} else if (rest > line.text && rest[0] == '-') {
+		line.type = Context;
+		line.text = &rest[1];
+	} else {
+		line.type = Text;
+	}
+	if (line.type == Match && pattern) {
+		regexec(&regex, line.text, 1, &line.match, 0);
+	}
+	push(line);
+}
+
+enum {
+	Path = 1,
+	Number = 2,
+	Highlight = 3,
+};
+
+static void curse(void) {
+	set_term(newterm(NULL, stdout, stderr));
+	cbreak();
+	noecho();
+	nodelay(stdscr, true);
+	TABSIZE = 4;
+	curs_set(0);
+	start_color();
+	use_default_colors();
+	init_pair(Path, COLOR_GREEN, -1);
+	init_pair(Number, COLOR_YELLOW, -1);
+	init_pair(Highlight, COLOR_MAGENTA, -1);
+}
+
+static size_t top;
+static size_t cur;
+static bool reading = true;
+
+static void draw(void) {
+	int y = 0, x = 0;
+	for (int i = 0; i < LINES; ++i) {
+		move(i, 0);
+		clrtoeol();
+		if (top + i >= lines.len) {
+			addstr(reading ? "..." : !lines.len ? "No results" : "");
+			break;
+		}
+		struct Line line = lines.ptr[top + i];
+		if (top + i == cur) {
+			getyx(stdscr, y, x);
+			attron(A_REVERSE);
+		} else {
+			attroff(A_REVERSE);
+		}
+		switch (line.type) {
+			break; case File: {
+				color_set(Path, NULL);
+				addstr(line.path);
+				color_set(0, NULL);
+			}
+			break; case Match: {
+				color_set(Number, NULL);
+				printw("%u", line.nr);
+				color_set(0, NULL);
+				addch(':');
+				if (line.match.rm_so == line.match.rm_eo) {
+					addstr(line.text);
+					break;
+				}
+				addnstr(line.text, line.match.rm_so);
+				color_set(Highlight, NULL);
+				addnstr(
+					&line.text[line.match.rm_so],
+					line.match.rm_eo - line.match.rm_so
+				);
+				color_set(0, NULL);
+				addstr(&line.text[line.match.rm_eo]);
+			}
+			break; case Context: {
+				color_set(Number, NULL);
+				printw("%u", line.nr);
+				color_set(0, NULL);
+				addch('-');
+				addstr(line.text);
+			}
+			break; case Text: addstr(line.text);
+		}
+	}
+	move(y, x);
+	refresh();
+}
+
+static void edit(struct Line line) {
+	char cmd[32];
+	snprintf(cmd, sizeof(cmd), "+%u", (line.nr ? line.nr : 1));
+	const char *editor = getenv("EDITOR");
+	if (!editor) editor = "vi";
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+	if (!pid) {
+		dup2(STDERR_FILENO, STDIN_FILENO);
+		execlp(editor, editor, cmd, line.path, NULL);
+		err(EX_CONFIG, "%s", editor);
+	}
+	int status;
+	pid = waitpid(pid, &status, 0);
+	if (pid < 0) err(EX_OSERR, "waitpid");
+}
+
+static void toPrev(enum Type type) {
+	if (!cur) return;
+	size_t prev = cur - 1;
+	while (prev && lines.ptr[prev].type != type) {
+		prev--;
+	}
+	if (lines.ptr[prev].type == type) {
+		cur = prev;
+	}
+}
+
+static void toNext(enum Type type) {
+	size_t next = cur + 1;
+	while (next < lines.len && lines.ptr[next].type != type) {
+		next++;
+	}
+	if (next < lines.len && lines.ptr[next].type == type) {
+		cur = next;
+	}
+}
+
+static void input(void) {
+	char ch;
+	while (ERR != (ch = getch())) {
+		switch (ch) {
+			break; case '\n': {
+				if (lines.ptr[cur].type == Text) break;
+				endwin();
+				edit(lines.ptr[cur]);
+				refresh();
+			}
+			break; case '{': toPrev(File);
+			break; case '}': toNext(File);
+			break; case 'G': cur = lines.len - 1;
+			break; case 'N': toPrev(Match);
+			break; case 'g': cur = 0;
+			break; case 'j': if (cur + 1 < lines.len) cur++;
+			break; case 'k': if (cur) cur--;
+			break; case 'n': toNext(Match);
+			break; case 'q': {
+				endwin();
+				exit(EX_OK);
+			}
+			break; case 'r': clearok(stdscr, true);
+		}
+	}
+	if (cur < top) top = cur;
+	if (cur >= top + LINES) top = cur - LINES + 1;
+}
+
+int main(int argc, char *argv[]) {
+	if (isatty(STDIN_FILENO)) errx(EX_USAGE, "no input");
+	if (argc > 1) {
+		pattern = argv[1];
+		int flags = REG_EXTENDED | REG_ICASE;
+		for (const char *ch = pattern; *ch; ++ch) {
+			if (isupper(*ch)) {
+				flags &= ~REG_ICASE;
+				break;
+			}
+		}
+		int error = regcomp(&regex, pattern, flags);
+		if (error) errx(EX_USAGE, "invalid pattern");
+	}
+	curse();
+	draw();
+	struct pollfd fds[2] = {
+		{ .fd = STDERR_FILENO, .events = POLLIN },
+		{ .fd = STDIN_FILENO, .events = POLLIN },
+	};
+	size_t len = 0;
+	size_t cap = 4096;
+	char *buf = malloc(cap);
+	if (!buf) err(EX_OSERR, "malloc");
+	while (poll(fds, (reading ? 2 : 1), -1)) {
+		if (fds[0].revents) {
+			input();
+		}
+		if (reading && fds[1].revents) {
+			ssize_t n = read(fds[1].fd, &buf[len], cap - len);
+			if (n < 0) err(EX_IOERR, "read");
+			if (!n) reading = false;
+			len += n;
+			char *ptr = buf;
+			for (
+				char *nl;
+				(nl = memchr(ptr, '\n', &buf[len] - ptr));
+				ptr = &nl[1]
+			) {
+				struct Line line = { .text = strndup(ptr, nl - ptr) };
+				if (!line.text) err(EX_OSERR, "strndup");
+				parse(line);
+			}
+			len -= ptr - buf;
+			memmove(buf, ptr, len);
+			if (len == cap) {
+				cap *= 2;
+				buf = realloc(buf, cap);
+				if (!buf) err(EX_OSERR, "realloc");
+			}
+		}
+		draw();
+	}
+	err(EX_IOERR, "poll");
+}
diff --git a/bin/quick.c b/bin/quick.c
new file mode 100644
index 00000000..d814873d
--- /dev/null
+++ b/bin/quick.c
@@ -0,0 +1,163 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void request(int sock, char *argv[]) {
+	struct pollfd pfd = { .fd = sock, .events = POLLIN };
+	int nfds = poll(&pfd, 1, -1);
+	if (nfds < 0) err(EX_OSERR, "poll");
+
+	char buf[4096];
+	ssize_t len = recv(sock, buf, sizeof(buf)-1, MSG_PEEK);
+	if (len < 0) {
+		warn("recv");
+		return;
+	}
+	char *blank = memmem(buf, len, "\r\n\r\n", 4);
+	if (!blank) {
+		warnx("can't find end of request headers in peek");
+		return;
+	}
+	len = recv(sock, buf, &blank[4] - buf, 0);
+	if (len < 0) {
+		warn("recv");
+		return;
+	}
+	buf[len] = '\0';
+
+	char *ptr = buf;
+	char *req = strsep(&ptr, "\r\n");
+	char *method = strsep(&req, " ");
+	char *query = strsep(&req, " ");
+	char *path = strsep(&query, "?");
+	char *proto = strsep(&req, " ");
+	if (!method || !path || !proto) {
+		warnx("invalid request line");
+		return;
+	}
+	setenv("REQUEST_METHOD", method, 1);
+	setenv("PATH_INFO", path, 1);
+	setenv("QUERY_STRING", (query ? query : ""), 1);
+	setenv("SERVER_PROTOCOL", proto, 1);
+
+	unsetenv("CONTENT_TYPE");
+	unsetenv("CONTENT_LENGTH");
+	unsetenv("HTTP_HOST");
+	while (ptr) {
+		char *value = strsep(&ptr, "\r\n");
+		if (!value[0]) continue;
+		char *header = strsep(&value, ":");
+		if (!header || !value++) {
+			warnx("invalid header");
+			return;
+		}
+		if (!strcasecmp(header, "Content-Type")) {
+			setenv("CONTENT_TYPE", value, 1);
+		} else if (!strcasecmp(header, "Content-Length")) {
+			setenv("CONTENT_LENGTH", value, 1);
+		} else if (!strcasecmp(header, "Host")) {
+			setenv("HTTP_HOST", value, 1);
+		}
+	}
+
+	dprintf(sock, "HTTP/1.1 200 OK\nConnection: close\n");
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+	if (!pid) {
+		dup2(sock, STDIN_FILENO);
+		dup2(sock, STDOUT_FILENO);
+		execv(argv[0], argv);
+		warn("%s", argv[0]);
+		_exit(127);
+	}
+
+	int status;
+	pid = wait(&status);
+	if (pid < 0) err(EX_OSERR, "wait");
+	if (WIFEXITED(status) && WEXITSTATUS(status)) {
+		warnx("%s exited %d", argv[0], WEXITSTATUS(status));
+	} else if (WIFSIGNALED(status)) {
+		warnx("%s killed %d", argv[0], WTERMSIG(status));
+	}
+}
+
+int main(int argc, char *argv[]) {
+	short port = 0;
+	for (int opt; 0 < (opt = getopt(argc, argv, "p:"));) {
+		switch (opt) {
+			break; case 'p': port = atoi(optarg);
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind == argc) errx(EX_USAGE, "script required");
+
+	int server = socket(AF_INET, SOCK_STREAM, 0);
+	if (server < 0) err(EX_OSERR, "socket");
+	fcntl(server, F_SETFD, FD_CLOEXEC);
+
+	int on = 1;
+	setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_port = htons(port),
+		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+	};
+	socklen_t addrlen = sizeof(addr);
+	int error = 0
+		|| bind(server, (struct sockaddr *)&addr, addrlen)
+		|| getsockname(server, (struct sockaddr *)&addr, &addrlen)
+		|| listen(server, -1);
+	if (error) err(EX_UNAVAILABLE, "%hd", port);
+
+	char host[NI_MAXHOST], serv[NI_MAXSERV];
+	error = getnameinfo(
+		(struct sockaddr *)&addr, addrlen,
+		host, sizeof(host), serv, sizeof(serv),
+		NI_NOFQDN | NI_NUMERICSERV
+	);
+	if (error) errx(EX_UNAVAILABLE, "getnameinfo: %s", gai_strerror(error));
+	printf("http://%s:%s/\n", host, serv);
+	fflush(stdout);
+
+	setenv("SERVER_SOFTWARE", "quick (and dirty)", 1);
+	setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
+	setenv("SERVER_NAME", host, 1);
+	setenv("SERVER_PORT", serv, 1);
+	setenv("REMOTE_ADDR", "127.0.0.1", 1);
+	setenv("REMOTE_HOST", host, 1);
+	setenv("SCRIPT_NAME", "/", 1);
+
+	for (int sock; 0 <= (sock = accept(server, NULL, NULL)); close(sock)) {
+		fcntl(sock, F_SETFD, FD_CLOEXEC);
+		request(sock, &argv[optind]);
+	}
+	err(EX_IOERR, "accept");
+}
diff --git a/bin/relay.c b/bin/relay.c
new file mode 100644
index 00000000..fd799462
--- /dev/null
+++ b/bin/relay.c
@@ -0,0 +1,218 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with LibreSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of LibreSSL used as well as that of the
+ * covered work.
+ */
+
+#include <err.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sysexits.h>
+#include <tls.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#include <sys/capsicum.h>
+#endif
+
+static void clientWrite(struct tls *client, const char *ptr, size_t len) {
+	while (len) {
+		ssize_t ret = tls_write(client, ptr, len);
+		if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue;
+		if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client));
+		ptr += ret;
+		len -= ret;
+	}
+}
+
+static void clientFormat(struct tls *client, const char *format, ...) {
+	char buf[1024];
+	va_list ap;
+	va_start(ap, format);
+	int len = vsnprintf(buf, sizeof(buf), format, ap);
+	va_end(ap);
+	if ((size_t)len > sizeof(buf) - 1) errx(EX_DATAERR, "message too large");
+	clientWrite(client, buf, len);
+}
+
+static void clientHandle(struct tls *client, const char *chan, char *line) {
+	char *prefix = NULL;
+	if (line[0] == ':') {
+		prefix = strsep(&line, " ") + 1;
+		if (!line) errx(EX_PROTOCOL, "unexpected eol");
+	}
+
+	char *command = strsep(&line, " ");
+	if (!strcmp(command, "001") || !strcmp(command, "INVITE")) {
+		clientFormat(client, "JOIN :%s\r\n", chan);
+	} else if (!strcmp(command, "PING")) {
+		clientFormat(client, "PONG %s\r\n", line);
+	}
+	if (strcmp(command, "PRIVMSG") && strcmp(command, "NOTICE")) return;
+
+	if (!prefix) errx(EX_PROTOCOL, "message without prefix");
+	char *nick = strsep(&prefix, "!");
+
+	if (!line) errx(EX_PROTOCOL, "message without destination");
+	char *dest = strsep(&line, " ");
+	if (strcmp(dest, chan)) return;
+
+	if (!line || line[0] != ':') errx(EX_PROTOCOL, "message without message");
+	line = &line[1];
+
+	if (!strncmp(line, "\1ACTION ", 8)) {
+		line = &line[8];
+		size_t len = strcspn(line, "\1");
+		printf("* %c\u200C%s %.*s\n", nick[0], &nick[1], (int)len, line);
+	} else if (command[0] == 'N') {
+		printf("-%c\u200C%s- %s\n", nick[0], &nick[1], line);
+	} else {
+		printf("<%c\u200C%s> %s\n", nick[0], &nick[1], line);
+	}
+}
+
+#ifdef __FreeBSD__
+static void limit(int fd, const cap_rights_t *rights) {
+	int error = cap_rights_limit(fd, rights);
+	if (error) err(EX_OSERR, "cap_rights_limit");
+}
+#endif
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	if (argc < 5) return EX_USAGE;
+	const char *host = argv[1];
+	const char *port = argv[2];
+	const char *nick = argv[3];
+	const char *chan = argv[4];
+
+	setlinebuf(stdout);
+	signal(SIGPIPE, SIG_IGN);
+
+	struct tls_config *config = tls_config_new();
+	if (!config) errx(EX_SOFTWARE, "tls_config_new");
+
+	error = tls_config_set_ciphers(config, "compat");
+	if (error) {
+		errx(EX_SOFTWARE, "tls_config_set_ciphers: %s", tls_config_error(config));
+	}
+
+	struct tls *client = tls_client();
+	if (!client) errx(EX_SOFTWARE, "tls_client");
+
+	error = tls_configure(client, config);
+	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
+	tls_config_free(config);
+
+	struct addrinfo *head;
+	struct addrinfo hints = {
+		.ai_family = AF_UNSPEC,
+		.ai_socktype = SOCK_STREAM,
+		.ai_protocol = IPPROTO_TCP,
+	};
+	error = getaddrinfo(host, port, &hints, &head);
+	if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error));
+
+	int sock = -1;
+	for (struct addrinfo *ai = head; ai; ai = ai->ai_next) {
+		sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+		if (sock < 0) err(EX_OSERR, "socket");
+
+		error = connect(sock, ai->ai_addr, ai->ai_addrlen);
+		if (!error) break;
+
+		close(sock);
+		sock = -1;
+	}
+	if (sock < 0) err(EX_UNAVAILABLE, "connect");
+	freeaddrinfo(head);
+
+	error = tls_connect_socket(client, sock, host);
+	if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client));
+
+#ifdef __FreeBSD__
+	error = cap_enter();
+	if (error) err(EX_OSERR, "cap_enter");
+
+	cap_rights_t rights;
+	cap_rights_init(&rights, CAP_WRITE);
+	limit(STDOUT_FILENO, &rights);
+	limit(STDERR_FILENO, &rights);
+
+	cap_rights_init(&rights, CAP_EVENT, CAP_READ);
+	limit(STDIN_FILENO, &rights);
+
+	cap_rights_set(&rights, CAP_WRITE);
+	limit(sock, &rights);
+#endif
+
+	clientFormat(client, "NICK :%s\r\nUSER %s 0 * :%s\r\n", nick, nick, nick);
+
+	char *input = NULL;
+	size_t cap = 0;
+
+	char buf[4096];
+	size_t len = 0;
+
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = sock },
+	};
+	while (0 < poll(fds, 2, -1)) {
+		if (fds[0].revents) {
+			ssize_t len = getline(&input, &cap, stdin);
+			if (len < 0) err(EX_IOERR, "getline");
+			input[len - 1] = '\0';
+			clientFormat(client, "NOTICE %s :%s\r\n", chan, input);
+		}
+		if (!fds[1].revents) continue;
+
+		ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len);
+		if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) continue;
+		if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
+		if (!read) return EX_UNAVAILABLE;
+		len += read;
+
+		char *crlf;
+		char *line = buf;
+		for (;;) {
+			crlf = memmem(line, &buf[len] - line, "\r\n", 2);
+			if (!crlf) break;
+			crlf[0] = '\0';
+			clientHandle(client, chan, line);
+			line = &crlf[2];
+		}
+		len -= line - buf;
+		memmove(buf, line, len);
+	}
+	err(EX_IOERR, "poll");
+}
diff --git a/bin/scheme.c b/bin/scheme.c
new file mode 100644
index 00000000..2bae8f82
--- /dev/null
+++ b/bin/scheme.c
@@ -0,0 +1,278 @@
+/* Copyright (C) 2018, 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "png.h"
+
+typedef unsigned uint;
+typedef unsigned char byte;
+
+struct HSV {
+	double h, s, v;
+};
+
+struct RGB {
+	byte r, g, b;
+};
+
+static struct RGB convert(struct HSV o) {
+	double c = o.v * o.s;
+	double h = o.h / 60.0;
+	double x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
+	double m = o.v - c;
+	double r = m, g = m, b = m;
+	if      (h <= 1.0) { r += c; g += x; }
+	else if (h <= 2.0) { r += x; g += c; }
+	else if (h <= 3.0) { g += c; b += x; }
+	else if (h <= 4.0) { g += x; b += c; }
+	else if (h <= 5.0) { r += x; b += c; }
+	else if (h <= 6.0) { r += c; b += x; }
+	return (struct RGB) { r * 255.0, g * 255.0, b * 255.0 };
+}
+
+static const struct HSV
+R = {   0.0, 1.0, 1.0 },
+Y = {  60.0, 1.0, 1.0 },
+G = { 120.0, 1.0, 1.0 },
+C = { 180.0, 1.0, 1.0 },
+B = { 240.0, 1.0, 1.0 },
+M = { 300.0, 1.0, 1.0 };
+
+static struct HSV x(struct HSV o, double hd, double sf, double vf) {
+	return (struct HSV) {
+		fmod(o.h + hd, 360.0),
+		fmin(o.s * sf, 1.0),
+		fmin(o.v * vf, 1.0),
+	};
+}
+
+enum {
+	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White,
+	Dark = 0,
+	Light = 8,
+	Background = 16,
+	Foreground,
+	Bold,
+	Selection,
+	Cursor,
+	SchemeLen,
+};
+static struct HSV scheme[SchemeLen];
+static struct HSV *dark = &scheme[Dark];
+static struct HSV *light = &scheme[Light];
+
+static void generate(void) {
+	light[Black]   = x(R, +45.0, 0.3, 0.3);
+	light[Red]     = x(R, +10.0, 0.9, 0.8);
+	light[Green]   = x(G, -55.0, 0.8, 0.6);
+	light[Yellow]  = x(Y, -20.0, 0.8, 0.8);
+	light[Blue]    = x(B, -55.0, 0.4, 0.5);
+	light[Magenta] = x(M, +45.0, 0.4, 0.6);
+	light[Cyan]    = x(C, -60.0, 0.3, 0.6);
+	light[White]   = x(R, +45.0, 0.3, 0.8);
+
+	dark[Black] = x(light[Black], 0.0, 1.0, 0.3);
+	dark[White] = x(light[White], 0.0, 1.0, 0.75);
+	for (uint i = Red; i < White; ++i) {
+		dark[i] = x(light[i], 0.0, 1.0, 0.8);
+	}
+
+	scheme[Background] = x(dark[Black],  0.0, 1.0, 0.9);
+	scheme[Foreground] = x(light[White], 0.0, 1.0, 0.9);
+	scheme[Bold]       = x(light[White], 0.0, 1.0, 1.0);
+	scheme[Selection]  = x(light[Red], +10.0, 1.0, 0.8);
+	scheme[Cursor]     = x(dark[White],  0.0, 1.0, 0.8);
+}
+
+static void swap(struct HSV *a, struct HSV *b) {
+	struct HSV c = *a;
+	*a = *b;
+	*b = c;
+}
+
+static void invert(void) {
+	swap(&dark[Black], &light[White]);
+	swap(&dark[White], &light[Black]);
+}
+
+typedef void OutputFn(const struct HSV *hsv, uint len);
+
+static void outputHSV(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		printf("%g,%g,%g\n", hsv[i].h, hsv[i].s, hsv[i].v);
+	}
+}
+
+#define FORMAT_RGB "%02hhX%02hhX%02hhX"
+
+static void outputRGB(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf(FORMAT_RGB "\n", rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static void outputLinux(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\x1B]P%X" FORMAT_RGB, i, rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static const char *Enum[SchemeLen] = {
+	"DarkBlack", "DarkRed", "DarkGreen", "DarkYellow",
+	"DarkBlue", "DarkMagenta", "DarkCyan", "DarkWhite",
+	"LightBlack", "LightRed", "LightGreen", "LightYellow",
+	"LightBlue", "LightMagenta", "LightCyan", "LightWhite",
+	"Background", "Foreground", "Bold", "Selection", "Cursor",
+};
+
+static void outputEnum(const struct HSV *hsv, uint len) {
+	printf("enum {\n");
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\t%s = 0x" FORMAT_RGB ",\n", Enum[i], rgb.r, rgb.g, rgb.b);
+	}
+	printf("};\n");
+}
+
+#define FORMAT_X "rgb:%02hhX/%02hhX/%02hhX"
+
+static const char *Resources[SchemeLen] = {
+	[Background] = "background",
+	[Foreground] = "foreground",
+	[Bold] = "colorBD",
+	[Selection] = "highlightColor",
+	[Cursor] = "cursorColor",
+};
+
+static void outputXTerm(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		if (Resources[i]) {
+			printf("XTerm*%s: " FORMAT_X "\n", Resources[i], rgb.r, rgb.g, rgb.b);
+		} else {
+			printf("XTerm*color%u: " FORMAT_X "\n", i, rgb.r, rgb.g, rgb.b);
+		}
+	}
+}
+
+static const char *Mintty[SchemeLen] = {
+	"Black", "Red", "Green", "Yellow",
+	"Blue", "Magenta", "Cyan", "White",
+	"BoldBlack", "BoldRed", "BoldGreen", "BoldYellow",
+	"BoldBlue", "BoldMagenta", "BoldCyan", "BoldWhite",
+	[Background] = "BackgroundColour",
+	[Foreground] = "ForegroundColour",
+	[Cursor]     = "CursorColour",
+};
+
+static void outputMintty(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		if (!Mintty[i]) continue;
+		struct RGB rgb = convert(hsv[i]);
+		printf("%s=%hhu,%hhu,%hhu\n", Mintty[i], rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static void outputCSS(const struct HSV *hsv, uint len) {
+	printf(":root {\n");
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\t--ansi%u: #" FORMAT_RGB ";\n", i, rgb.r, rgb.g, rgb.b);
+	}
+	printf("}\n");
+	for (uint i = 0; i < len; ++i) {
+		printf(
+			".fg%u { color: var(--ansi%u); }\n"
+			".bg%u { background-color: var(--ansi%u); }\n",
+			i, i, i, i
+		);
+	}
+}
+
+enum {
+	SwatchWidth = 64,
+	SwatchHeight = 64,
+	SwatchCols = 8,
+};
+
+static void outputPNG(const struct HSV *hsv, uint len) {
+	uint rows = (len + SwatchCols - 1) / SwatchCols;
+	uint width = SwatchWidth * SwatchCols;
+	uint height = SwatchHeight * rows;
+	pngHead(stdout, width, height, 8, PNGIndexed);
+
+	struct RGB pal[len];
+	for (uint i = 0; i < len; ++i) {
+		pal[i] = convert(hsv[i]);
+	}
+	pngPalette(stdout, (byte *)pal, sizeof(pal));
+
+	byte data[height][1 + width];
+	memset(data, 0, sizeof(data));
+	for (uint y = 0; y < height; ++y) {
+		data[y][0] = (y % SwatchHeight ? PNGUp : PNGSub);
+	}
+	for (uint i = 0; i < len; ++i) {
+		uint y = SwatchHeight * (i / SwatchCols);
+		uint x = SwatchWidth * (i % SwatchCols);
+		data[y][1 + x] = (x ? 1 : i);
+	}
+	pngData(stdout, (byte *)data, sizeof(data));
+	pngTail(stdout);
+}
+
+int main(int argc, char *argv[]) {
+	generate();
+
+	OutputFn *output = outputRGB;
+	const struct HSV *hsv = scheme;
+	uint len = 16;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "Xacghilmp:stx"))) {
+		switch (opt) {
+			break; case 'X': output = outputXTerm;
+			break; case 'a': len = 16;
+			break; case 'c': output = outputEnum;
+			break; case 'g': output = outputPNG;
+			break; case 'h': output = outputHSV;
+			break; case 'i': invert();
+			break; case 'l': output = outputLinux;
+			break; case 'm': output = outputMintty;
+			break; case 'p': {
+				uint p = strtoul(optarg, NULL, 0);
+				if (p >= SchemeLen) return EX_USAGE;
+				hsv = &scheme[p];
+				len = 1;
+			}
+			break; case 's': output = outputCSS;
+			break; case 't': len = SchemeLen;
+			break; case 'x': output = outputRGB;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	output(hsv, len);
+}
diff --git a/bin/sh.l b/bin/sh.l
new file mode 100644
index 00000000..8f0f7723
--- /dev/null
+++ b/bin/sh.l
@@ -0,0 +1,181 @@
+/* Copyright (C) 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option prefix="sh"
+%option noinput nounput noyywrap
+
+%{
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include "hilex.h"
+
+enum { Cap = 64 };
+static int len = 1;
+static int stack[Cap];
+static int push(int val) {
+	if (len < Cap) stack[len++] = val;
+	return val;
+}
+static int pop(void) {
+	if (len > 1) len--;
+	return stack[len-1];
+}
+%}
+
+%s Param Command Arith Backtick Subshell
+%x DQuote HereDocDel HereDoc HereDocLit
+
+word [[:alnum:]_.-]+
+param [^:=?+%#{}-]+
+reserved [!{}]|else|do|elif|for|done|fi|then|until|while|if|case|esac
+
+%%
+	static bool first;
+	static char *delimiter;
+
+[[:blank:]]+ { return Normal; }
+
+"\\". { return Escape; }
+
+<INITIAL,DQuote,HereDoc,Param,Command,Arith,Subshell>{
+	"$"[*@#?$!0-9-] |
+	"$"[_[:alpha:][_[:alnum:]]* |
+	"${"[#]?{param}"}" {
+		return Subst;
+	}
+	"${"{param} {
+		BEGIN(push(Param));
+		return Subst;
+	}
+	"$(" {
+		BEGIN(push(Command));
+		return Subst;
+	}
+	"$((" {
+		BEGIN(push(Arith));
+		return Subst;
+	}
+	"`" {
+		BEGIN(push(Backtick));
+		return Subst;
+	}
+	"(" {
+		BEGIN(push(Subshell));
+		return Normal;
+	}
+}
+<Param>"}" |
+<Command>")" |
+<Arith>"))" |
+<Backtick>"`" {
+	BEGIN(pop());
+	return Subst;
+}
+<Subshell>")" {
+	BEGIN(pop());
+	return Normal;
+}
+
+"\n" {
+	first = true;
+	return Normal;
+}
+[&();|]|"&&"|";;"|"||" {
+	first = true;
+	return Operator;
+}
+[0-9]?([<>]"&"?|">|"|">>"|"<>") {
+	return Operator;
+}
+
+{reserved} {
+	if (first) {
+		first = false;
+		return Keyword;
+	}
+	return Normal;
+}
+
+{word}/[[:blank:]]*"()" { return Ident; }
+
+[0-9]?("<<"|"<<-") {
+	BEGIN(push(HereDocDel));
+	return Operator;
+}
+<HereDocDel>{
+	[[:blank:]]+ { return Normal; }
+	{word} {
+		delimiter = strdup(yytext);
+		assert(delimiter);
+		BEGIN(pop(), push(HereDoc));
+		return Ident;
+	}
+	"'"{word}"'" {
+		delimiter = strndup(&yytext[1], strlen(yytext)-2);
+		assert(delimiter);
+		BEGIN(pop(), push(HereDocLit));
+		return Ident;
+	}
+}
+<HereDoc,HereDocLit>{
+	^"\t"*{word} {
+		if (strcmp(&yytext[strspn(yytext, "\t")], delimiter)) REJECT;
+		free(delimiter);
+		BEGIN(pop());
+		return Ident;
+	}
+}
+<HereDoc>{
+	[^$`\n]+ { return String; }
+	.|\n { return String; }
+}
+<HereDocLit>{
+	.*\n { return String; }
+}
+
+"'"[^'']*"'" { return String; }
+
+"\""/[^$`\\] {
+	BEGIN(push(DQuote));
+	yymore();
+}
+"\"" {
+	BEGIN(push(DQuote));
+	return String;
+}
+
+<DQuote>{
+	[^\\$`""]*"\"" {
+		BEGIN(pop());
+		return String;
+	}
+	"\\"[$`""\\\n] { return Escape; }
+	[^\\$`""]+|. { return String; }
+}
+
+<INITIAL,Command,Backtick,Arith>"#".* { return Comment; }
+
+{word} {
+	first = false;
+	return Normal;
+}
+
+.|\n { return Normal; }
+
+%%
+
+const struct Lexer LexSh = { yylex, &yyin, &yytext };
diff --git a/bin/shotty.l b/bin/shotty.l
new file mode 100644
index 00000000..dcac43ec
--- /dev/null
+++ b/bin/shotty.l
@@ -0,0 +1,597 @@
+/* Copyright (C) 2019, 2021  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%option noinput nounput noyywrap
+
+%{
+
+#include <assert.h>
+#include <err.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#define Q(...) #__VA_ARGS__
+#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+
+#define ENUM_CODE \
+	X(BS) \
+	X(CHA) \
+	X(CNL) \
+	X(CPL) \
+	X(CR) \
+	X(CUB) \
+	X(CUD) \
+	X(CUF) \
+	X(CUP) \
+	X(CUU) \
+	X(DCH) \
+	X(DECRC) \
+	X(DECRST) \
+	X(DECSC) \
+	X(DECSET) \
+	X(DECSTBM) \
+	X(DL) \
+	X(ECH) \
+	X(ED) \
+	X(EL) \
+	X(HT) \
+	X(ICH) \
+	X(IL) \
+	X(MC) \
+	X(NL) \
+	X(RI) \
+	X(RM) \
+	X(SD) \
+	X(SGR) \
+	X(SM) \
+	X(SU) \
+	X(VPA)
+
+enum Code {
+	Data = 1,
+#define X(code) code,
+	ENUM_CODE
+#undef X
+};
+
+static enum {
+	USASCII,
+	DECSpecial,
+} g0;
+
+static const wchar_t AltCharset[128] = {
+	['`'] = L'\u25C6', ['a'] = L'\u2592', ['f'] = L'\u00B0', ['g'] = L'\u00B1',
+	['i'] = L'\u240B', ['j'] = L'\u2518', ['k'] = L'\u2510', ['l'] = L'\u250C',
+	['m'] = L'\u2514', ['n'] = L'\u253C', ['o'] = L'\u23BA', ['p'] = L'\u23BB',
+	['q'] = L'\u2500', ['r'] = L'\u23BC', ['s'] = L'\u23BD', ['t'] = L'\u251C',
+	['u'] = L'\u2524', ['v'] = L'\u2534', ['w'] = L'\u252C', ['x'] = L'\u2502',
+	['y'] = L'\u2264', ['z'] = L'\u2265', ['{'] = L'\u03C0', ['|'] = L'\u2260',
+	['}'] = L'\u00A3', ['~'] = L'\u00B7',
+};
+
+static int pn;
+static int ps[16];
+static wchar_t ch;
+
+%}
+
+ESC \x1B
+
+%x CSI CSI_LT CSI_EQ CSI_GT CSI_QM
+%x OSC
+
+%%
+	pn = 0;
+
+{ESC}"["	BEGIN(CSI);
+{ESC}"[<"	BEGIN(CSI_LT);
+{ESC}"[="	BEGIN(CSI_EQ);
+{ESC}"[>"	BEGIN(CSI_GT);
+{ESC}"[?"	BEGIN(CSI_QM);
+{ESC}"]"	BEGIN(OSC);
+
+<CSI,CSI_LT,CSI_EQ,CSI_GT,CSI_QM>{
+	[0-9]+;?	if (pn < 16) ps[pn++] = atoi(yytext);
+	;			if (pn < 16) ps[pn++] = 0;
+}
+
+<OSC>{
+	\x07	BEGIN(0);
+	{ESC}\\	BEGIN(0);
+	.|\n	;
+}
+
+\b	return BS;
+\t	return HT;
+\n	return NL;
+\r	return CR;
+
+{ESC}7	return DECSC;
+{ESC}8	return DECRC;
+{ESC}=	// DECKPAM
+{ESC}>	// DECKPNM
+{ESC}M	return RI;
+
+{ESC}"(0"	g0 = DECSpecial;
+{ESC}"(B"	g0 = USASCII;
+
+<CSI>@	BEGIN(0); return ICH;
+<CSI>A	BEGIN(0); return CUU;
+<CSI>B	BEGIN(0); return CUD;
+<CSI>C	BEGIN(0); return CUF;
+<CSI>D	BEGIN(0); return CUB;
+<CSI>E	BEGIN(0); return CNL;
+<CSI>F	BEGIN(0); return CPL;
+<CSI>G	BEGIN(0); return CHA;
+<CSI>H	BEGIN(0); return CUP;
+<CSI>J	BEGIN(0); return ED;
+<CSI>K	BEGIN(0); return EL;
+<CSI>L	BEGIN(0); return IL;
+<CSI>M	BEGIN(0); return DL;
+<CSI>P	BEGIN(0); return DCH;
+<CSI>S	BEGIN(0); return SU;
+<CSI>T	BEGIN(0); return SD;
+<CSI>X	BEGIN(0); return ECH;
+<CSI>d	BEGIN(0); return VPA;
+<CSI>h	BEGIN(0); return SM;
+<CSI>i	BEGIN(0); return MC;
+<CSI>l	BEGIN(0); return RM;
+<CSI>m	BEGIN(0); return SGR;
+<CSI>r	BEGIN(0); return DECSTBM;
+<CSI>t	BEGIN(0); // XTWINOPS
+
+<CSI_QM>h	BEGIN(0); return DECSET;
+<CSI_QM>l	BEGIN(0); return DECRST;
+
+<CSI>[ -/]*.	BEGIN(0); warnx("unhandled CSI %s", yytext);
+<CSI_LT>[ -/]*.	BEGIN(0); warnx("unhandled CSI < %s", yytext);
+<CSI_EQ>[ -/]*.	BEGIN(0); warnx("unhandled CSI = %s", yytext);
+<CSI_GT>[ -/]*.	BEGIN(0); warnx("unhandled CSI > %s", yytext);
+<CSI_QM>[ -/]*.	BEGIN(0); warnx("unhandled CSI ? %s", yytext);
+
+[\x00-\x7F] {
+	ch = yytext[0];
+	if (g0 == DECSpecial && AltCharset[ch]) {
+		ch = AltCharset[ch];
+	}
+	return Data;
+}
+[\xC0-\xDF][\x80-\xBF] {
+	ch = (wchar_t)(yytext[0] & 0x1F) << 6
+		| (wchar_t)(yytext[1] & 0x3F);
+	return Data;
+}
+[\xE0-\xEF]([\x80-\xBF]{2}) {
+	ch = (wchar_t)(yytext[0] & 0x0F) << 12
+		| (wchar_t)(yytext[1] & 0x3F) << 6
+		| (wchar_t)(yytext[2] & 0x3F);
+	return Data;
+}
+[\xF0-\xF7]([\x80-\xBF]{3}) {
+	ch = (wchar_t)(yytext[0] & 0x07) << 18
+		| (wchar_t)(yytext[1] & 0x3F) << 12
+		| (wchar_t)(yytext[2] & 0x3F) << 6
+		| (wchar_t)(yytext[3] & 0x3F);
+	return Data;
+}
+
+.	ch = yytext[0]; return Data;
+
+%%
+
+static int rows = 24;
+static int cols = 80;
+
+static struct Cell {
+	enum {
+		BIT(Bold),
+		BIT(Italic),
+		BIT(Underline),
+		BIT(Reverse),
+	} attr;
+	int bg, fg;
+	wchar_t ch;
+} *cells;
+
+static int y, x;
+static struct {
+	int y, x;
+} sc;
+static struct {
+	int top, bot;
+} scr;
+
+static enum Mode {
+	BIT(Insert),
+	BIT(Wrap),
+	BIT(Cursor),
+} mode = Wrap | Cursor;
+
+static struct Cell sgr = {
+	.bg = -1,
+	.fg = -1,
+	.ch = L' ',
+};
+
+static struct Cell *cell(int y, int x) {
+	assert(y <= rows);
+	assert(x <= cols);
+	assert(y * cols + x <= rows * cols);
+	return &cells[y * cols + x];
+}
+
+static int p(int i, int d) {
+	return (i < pn ? ps[i] : d);
+}
+
+static int bound(int a, int x, int b) {
+	if (x < a) return a;
+	if (x > b) return b;
+	return x;
+}
+
+static void move(struct Cell *dst, struct Cell *src, size_t len) {
+	memmove(dst, src, sizeof(*dst) * len);
+}
+static void erase(struct Cell *at, struct Cell *to) {
+	for (; at < to; ++at) {
+		*at = sgr;
+	}
+}
+
+static void scrup(int top, int n) {
+	n = bound(0, n, scr.bot - top);
+	move(cell(top, 0), cell(top+n, 0), cols * (scr.bot-top-n));
+	erase(cell(scr.bot-n, 0), cell(scr.bot, 0));
+}
+static void scrdn(int top, int n) {
+	n = bound(0, n, scr.bot - top);
+	move(cell(top+n, 0), cell(top, 0), cols * (scr.bot-top-n));
+	erase(cell(top, 0), cell(top+n, 0));
+}
+
+static enum Mode pmode(void) {
+	enum Mode mode = 0;
+	for (int i = 0; i < pn; ++i) {
+		switch (ps[i]) {
+			break; case 4: mode |= Insert;
+			break; default: warnx("unhandled SM/RM %d", ps[i]);
+		}
+	}
+	return mode;
+}
+static enum Mode pdmode(void) {
+	enum Mode mode = 0;
+	for (int i = 0; i < pn; ++i) {
+		switch (ps[i]) {
+			break; case 1: // DECCKM
+			break; case 7: mode |= Wrap;
+			break; case 12: // "Start Blinking Cursor"
+			break; case 25: mode |= Cursor;
+			break; default: {
+				if (ps[i] < 1000) warnx("unhandled DECSET/DECRST %d", ps[i]);
+			}
+		}
+	}
+	return mode;
+}
+
+static void update(enum Code cc) {
+	switch (cc) {
+		break; case BS: x--;
+		break; case HT: x = x - x % 8 + 8;
+		break; case CR: x = 0;
+		break; case CUU: y -= p(0, 1);
+		break; case CUD: y += p(0, 1);
+		break; case CUF: x += p(0, 1);
+		break; case CUB: x -= p(0, 1);
+		break; case CNL: x = 0; y += p(0, 1);
+		break; case CPL: x = 0; y -= p(0, 1);
+		break; case CHA: x = p(0, 1) - 1;
+		break; case VPA: y = p(0, 1) - 1;
+		break; case CUP: y = p(0, 1) - 1; x = p(1, 1) - 1;
+		break; case DECSC: sc.y = y; sc.x = x;
+		break; case DECRC: y = sc.y; x = sc.x;
+
+		break; case ED: erase(
+			(p(0, 0) == 0 ? cell(y, x) : cell(0, 0)),
+			(p(0, 0) == 1 ? cell(y, x) : cell(rows-1, cols))
+		);
+		break; case EL: erase(
+			(p(0, 0) == 0 ? cell(y, x) : cell(y, 0)),
+			(p(0, 0) == 1 ? cell(y, x) : cell(y, cols))
+		);
+		break; case ECH: erase(
+			cell(y, x), cell(y, bound(0, x + p(0, 1), cols))
+		);
+
+		break; case DCH: {
+			int n = bound(0, p(0, 1), cols-x);
+			move(cell(y, x), cell(y, x+n), cols-x-n);
+			erase(cell(y, cols-n), cell(y, cols));
+		}
+		break; case ICH: {
+			int n = bound(0, p(0, 1), cols-x);
+			move(cell(y, x+n), cell(y, x), cols-x-n);
+			erase(cell(y, x), cell(y, x+n));
+		}
+
+		break; case DECSTBM: {
+			scr.bot = bound(0, p(1, rows), rows);
+			scr.top = bound(0, p(0, 1) - 1, scr.bot);
+		}
+		break; case SU: scrup(scr.top, p(0, 1));
+		break; case SD: scrdn(scr.top, p(0, 1));
+		break; case DL: scrup(bound(0, y, scr.bot), p(0, 1));
+		break; case IL: scrdn(bound(0, y, scr.bot), p(0, 1));
+
+		break; case NL: {
+			if (y+1 == scr.bot) {
+				scrup(scr.top, 1);
+			} else {
+				y++;
+			}
+		}
+		break; case RI: {
+			if (y == scr.top) {
+				scrdn(scr.top, 1);
+			} else {
+				y--;
+			}
+		}
+
+		break; case SM: mode |= pmode();
+		break; case RM: mode &= ~pmode();
+		break; case DECSET: mode |= pdmode();
+		break; case DECRST: mode &= ~pdmode();
+
+		break; case SGR: {
+			if (!pn) ps[pn++] = 0;
+			for (int i = 0; i < pn; ++i) {
+				switch (ps[i]) {
+					break; case 0: sgr.attr = 0; sgr.bg = -1; sgr.fg = -1;
+					break; case 1: sgr.attr |= Bold;
+					break; case 3: sgr.attr |= Italic;
+					break; case 4: sgr.attr |= Underline;
+					break; case 7: sgr.attr |= Reverse;
+					break; case 22: sgr.attr &= ~Bold;
+					break; case 23: sgr.attr &= ~Italic;
+					break; case 24: sgr.attr &= ~Underline;
+					break; case 27: sgr.attr &= ~Reverse;
+					break; case 30 ... 37: sgr.fg = ps[i] - 30;
+					break; case 38: {
+						if (++i < pn && ps[i] == 5) {
+							if (++i < pn) sgr.fg = ps[i];
+						}
+					}
+					break; case 39: sgr.fg = -1;
+					break; case 40 ... 47: sgr.bg = ps[i] - 40;
+					break; case 48: {
+						if (++i < pn && ps[i] == 5) {
+							if (++i < pn) sgr.bg = ps[i];
+						}
+					}
+					break; case 49: sgr.bg = -1;
+					break; case 90 ... 97: sgr.fg = 8 + ps[i] - 90;
+					break; case 100 ... 107: sgr.bg = 8 + ps[i] - 100;
+					break; default: warnx("unhandled SGR %d", ps[i]);
+				}
+			}
+		}
+
+		break; case Data: {
+			int w = wcwidth(ch);
+			if (w < 0) {
+				warnx("unhandled \\u%04X", ch);
+				return;
+			}
+			if (mode & Insert) {
+				int n = bound(0, w, cols-x);
+				move(cell(y, x+n), cell(y, x), cols-x-n);
+			}
+			if (mode & Wrap && x+w > cols) {
+				update(CR);
+				update(NL);
+			}
+			*cell(y, x) = sgr;
+			cell(y, x)->ch = ch;
+			for (int i = 1; i < w && x+i < cols; ++i) {
+				*cell(y, x+i) = sgr;
+				cell(y, x+i)->ch = L'\0';
+			}
+			x = bound(0, x+w, (mode & Wrap ? cols : cols-1));
+			return;
+		}
+		break; case MC:;
+	}
+
+	x = bound(0, x, cols-1);
+	y = bound(0, y, rows-1);
+}
+
+static bool bright;
+static bool colors;
+static int defaultBg = 0;
+static int defaultFg = 7;
+
+static const unsigned Palette[256] = {
+	0x000000, 0xCD0000, 0x00CD00, 0xCDCD00, 0x0000EE, 0xCD00CD, 0x00CDCD,
+	0xE5E5E5, 0x7F7F7F, 0xFF0000, 0x00FF00, 0xFFFF00, 0x5C5CFF, 0xFF00FF,
+	0x00FFFF, 0xFFFFFF, 0x000000, 0x00005F, 0x000087, 0x0000AF, 0x0000D7,
+	0x0000FF, 0x005F00, 0x005F5F, 0x005F87, 0x005FAF, 0x005FD7, 0x005FFF,
+	0x008700, 0x00875F, 0x008787, 0x0087AF, 0x0087D7, 0x0087FF, 0x00AF00,
+	0x00AF5F, 0x00AF87, 0x00AFAF, 0x00AFD7, 0x00AFFF, 0x00D700, 0x00D75F,
+	0x00D787, 0x00D7AF, 0x00D7D7, 0x00D7FF, 0x00FF00, 0x00FF5F, 0x00FF87,
+	0x00FFAF, 0x00FFD7, 0x00FFFF, 0x5F0000, 0x5F005F, 0x5F0087, 0x5F00AF,
+	0x5F00D7, 0x5F00FF, 0x5F5F00, 0x5F5F5F, 0x5F5F87, 0x5F5FAF, 0x5F5FD7,
+	0x5F5FFF, 0x5F8700, 0x5F875F, 0x5F8787, 0x5F87AF, 0x5F87D7, 0x5F87FF,
+	0x5FAF00, 0x5FAF5F, 0x5FAF87, 0x5FAFAF, 0x5FAFD7, 0x5FAFFF, 0x5FD700,
+	0x5FD75F, 0x5FD787, 0x5FD7AF, 0x5FD7D7, 0x5FD7FF, 0x5FFF00, 0x5FFF5F,
+	0x5FFF87, 0x5FFFAF, 0x5FFFD7, 0x5FFFFF, 0x870000, 0x87005F, 0x870087,
+	0x8700AF, 0x8700D7, 0x8700FF, 0x875F00, 0x875F5F, 0x875F87, 0x875FAF,
+	0x875FD7, 0x875FFF, 0x878700, 0x87875F, 0x878787, 0x8787AF, 0x8787D7,
+	0x8787FF, 0x87AF00, 0x87AF5F, 0x87AF87, 0x87AFAF, 0x87AFD7, 0x87AFFF,
+	0x87D700, 0x87D75F, 0x87D787, 0x87D7AF, 0x87D7D7, 0x87D7FF, 0x87FF00,
+	0x87FF5F, 0x87FF87, 0x87FFAF, 0x87FFD7, 0x87FFFF, 0xAF0000, 0xAF005F,
+	0xAF0087, 0xAF00AF, 0xAF00D7, 0xAF00FF, 0xAF5F00, 0xAF5F5F, 0xAF5F87,
+	0xAF5FAF, 0xAF5FD7, 0xAF5FFF, 0xAF8700, 0xAF875F, 0xAF8787, 0xAF87AF,
+	0xAF87D7, 0xAF87FF, 0xAFAF00, 0xAFAF5F, 0xAFAF87, 0xAFAFAF, 0xAFAFD7,
+	0xAFAFFF, 0xAFD700, 0xAFD75F, 0xAFD787, 0xAFD7AF, 0xAFD7D7, 0xAFD7FF,
+	0xAFFF00, 0xAFFF5F, 0xAFFF87, 0xAFFFAF, 0xAFFFD7, 0xAFFFFF, 0xD70000,
+	0xD7005F, 0xD70087, 0xD700AF, 0xD700D7, 0xD700FF, 0xD75F00, 0xD75F5F,
+	0xD75F87, 0xD75FAF, 0xD75FD7, 0xD75FFF, 0xD78700, 0xD7875F, 0xD78787,
+	0xD787AF, 0xD787D7, 0xD787FF, 0xD7AF00, 0xD7AF5F, 0xD7AF87, 0xD7AFAF,
+	0xD7AFD7, 0xD7AFFF, 0xD7D700, 0xD7D75F, 0xD7D787, 0xD7D7AF, 0xD7D7D7,
+	0xD7D7FF, 0xD7FF00, 0xD7FF5F, 0xD7FF87, 0xD7FFAF, 0xD7FFD7, 0xD7FFFF,
+	0xFF0000, 0xFF005F, 0xFF0087, 0xFF00AF, 0xFF00D7, 0xFF00FF, 0xFF5F00,
+	0xFF5F5F, 0xFF5F87, 0xFF5FAF, 0xFF5FD7, 0xFF5FFF, 0xFF8700, 0xFF875F,
+	0xFF8787, 0xFF87AF, 0xFF87D7, 0xFF87FF, 0xFFAF00, 0xFFAF5F, 0xFFAF87,
+	0xFFAFAF, 0xFFAFD7, 0xFFAFFF, 0xFFD700, 0xFFD75F, 0xFFD787, 0xFFD7AF,
+	0xFFD7D7, 0xFFD7FF, 0xFFFF00, 0xFFFF5F, 0xFFFF87, 0xFFFFAF, 0xFFFFD7,
+	0xFFFFFF, 0x080808, 0x121212, 0x1C1C1C, 0x262626, 0x303030, 0x3A3A3A,
+	0x444444, 0x4E4E4E, 0x585858, 0x626262, 0x6C6C6C, 0x767676, 0x808080,
+	0x8A8A8A, 0x949494, 0x9E9E9E, 0xA8A8A8, 0xB2B2B2, 0xBCBCBC, 0xC6C6C6,
+	0xD0D0D0, 0xDADADA, 0xE4E4E4, 0xEEEEEE,
+};
+
+static void span(const struct Cell *prev, const struct Cell *cell) {
+	if (
+		!prev ||
+		cell->attr != prev->attr ||
+		cell->bg != prev->bg ||
+		cell->fg != prev->fg
+	) {
+		if (prev) printf("</span>");
+		int attr = cell->attr;
+		int bg = (attr & Reverse ? cell->fg : cell->bg);
+		int fg = (attr & Reverse ? cell->bg : cell->fg);
+		if (bg < 0) bg = (attr & Reverse ? defaultFg : defaultBg);
+		if (fg < 0) fg = (attr & Reverse ? defaultBg : defaultFg);
+		if (bright && cell->attr & Bold) {
+			if (fg < 8) fg += 8;
+			attr &= ~Bold;
+		}
+		printf(Q(<span class="bg%d fg%d"), bg, fg);
+		if (attr || colors) printf(" style=\"");
+		if (attr & Bold) printf("font-weight:bold;");
+		if (attr & Italic) printf("font-style:italic;");
+		if (attr & Underline) printf("text-decoration:underline;");
+		if (colors && bg < 256 && fg < 256) {
+			printf(
+				"background-color:#%06X;color:#%06X;",
+				Palette[bg], Palette[fg]
+			);
+		}
+		printf("%s>", (attr || colors ? "\"" : ""));
+	}
+	switch (cell->ch) {
+		break; case L'&': printf("&amp;");
+		break; case L'<': printf("&lt;");
+		break; case L'>': printf("&gt;");
+		break; case L'"': printf("&quot;");
+		break; default: printf("%lc", (wint_t)cell->ch);
+	}
+}
+
+static void html(void) {
+	if (mode & Cursor) cell(y, x)->attr ^= Reverse;
+	printf(
+		Q(<pre style="width: %dch;" class="bg%d fg%d">),
+		cols, defaultBg, defaultFg
+	);
+	for (int y = 0; y < rows; ++y) {
+		for (int x = 0; x < cols; ++x) {
+			if (!cell(y, x)->ch) continue;
+			span((x ? cell(y, x-1) : NULL), cell(y, x));
+		}
+		printf("</span>\n");
+	}
+	printf("</pre>\n");
+	if (mode & Cursor) cell(y, x)->attr ^= Reverse;
+}
+
+static const char *Debug[] = {
+#define X(code) [code] = #code,
+	ENUM_CODE
+#undef X
+};
+
+int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
+	bool debug = false;
+	bool size = false;
+	bool hide = false;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "Bb:df:h:insw:"));) {
+		switch (opt) {
+			break; case 'B': bright = true;
+			break; case 'b': defaultBg = atoi(optarg);
+			break; case 'd': debug = true;
+			break; case 'f': defaultFg = atoi(optarg);
+			break; case 'h': rows = atoi(optarg);
+			break; case 'i': colors = true;
+			break; case 'n': hide = true;
+			break; case 's': size = true;
+			break; case 'w': cols = atoi(optarg);
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind < argc) {
+		yyin = fopen(argv[optind], "r");
+		if (!yyin) err(EX_NOINPUT, "%s", argv[optind]);
+	}
+
+	if (size) {
+		struct winsize win;
+		int error = ioctl(STDERR_FILENO, TIOCGWINSZ, &win);
+		if (error) err(EX_IOERR, "ioctl");
+		cols = win.ws_col;
+		rows = win.ws_row;
+	}
+	scr.bot = rows;
+
+	cells = calloc(cols * rows, sizeof(*cells));
+	if (!cells) err(EX_OSERR, "calloc");
+	erase(cell(0, 0), cell(rows-1, cols));
+
+	bool mc = false;
+	for (int cc; (cc = yylex());) {
+		if (cc == MC) {
+			mc = true;
+			html();
+		} else {
+			update(cc);
+		}
+		if (debug && cc != Data) {
+			printf("%s", Debug[cc]);
+			for (int i = 0; i < pn; ++i) {
+				printf("%s%d", (i ? ", " : " "), ps[i]);
+			}
+			printf("\n");
+			html();
+		}
+	}
+	if (hide) mode &= ~Cursor;
+	if (!mc) html();
+}
diff --git a/bin/sup.sh b/bin/sup.sh
new file mode 100644
index 00000000..32e282d1
--- /dev/null
+++ b/bin/sup.sh
@@ -0,0 +1,283 @@
+#!/bin/sh
+set -eu
+
+service=$1
+email=${2:-$(git config fetchemail.imapUser)}
+
+generate() {
+	openssl rand -base64 33
+}
+copy() {
+	printf '%s' "$1" | pbcopy
+}
+unwrap() {
+	sed '
+		:x
+		/=$/ {
+			N
+			s/=\n//g
+			bx
+		}
+	'
+}
+
+asciinema() {
+	echo 'Fetching CSRF token...'
+	jar=$(mktemp -t sup)
+	trap 'rm "${jar}"' EXIT
+	csrf=$(
+		curl -Ss -c "${jar}" 'https://asciinema.org/login/new' |
+		sed -n 's/.*name="_csrf_token".*value="\([^"]*\)".*/\1/p'
+	)
+	echo 'Submitting form...'
+	curl -Ss -X POST -b "${jar}" \
+		-F "_csrf_token=${csrf}" -F "login[email]=${email}" \
+		'https://asciinema.org/login' \
+		>/dev/null
+	echo 'Waiting for email...'
+	url=$(
+		git fetch-email -i -M Trash \
+			-F 'hello@asciinema.org' -T "${email}" \
+			-S 'Login to asciinema.org' |
+		grep -m 1 '^https://asciinema\.org/session/new'
+	)
+	open "${url}"
+}
+
+bugzilla() {
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss "${bugzillaBase}/" |
+		sed -n '
+			/name="token"/N
+			s/.*name="token"[[:space:]]*value="\([^"]*\)".*/\1/p
+		' | head -n 1
+	)
+	echo 'Submitting form...'
+	curl -Ss -X POST \
+		-F "loginname=${email}" -F "token=${csrf}" -F 'a=reqpw' \
+		"${bugzillaBase}/token.cgi" \
+		>/dev/null
+	echo 'Waiting for email...'
+	token=$(
+		git fetch-email -i -M Trash \
+			-F "${bugzillaFrom}" -T "${email}" \
+			-S 'Bugzilla Change Password Request' |
+		sed -n 's/.*t=3D\([^&]*\).*/\1/p' |
+		head -n 1
+	)
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST \
+		-F "t=${token}" -F 'a=chgpw' \
+		-F "password=${password}" -F "matchpassword=${password}" \
+		"${bugzillaBase}/token.cgi" \
+		>/dev/null
+	copy "${password}"
+	open "${bugzillaBase}/"
+}
+
+freebsdbugzilla() {
+	bugzillaBase='https://bugs.freebsd.org/bugzilla'
+	bugzillaFrom='bugzilla-noreply@freebsd.org'
+	bugzilla
+}
+
+discogs() {
+	echo 'Submitting form...'
+	curl -Ss -X POST \
+		-F "email=${email}" -F 'Action.EmailResetInstructions=submit' \
+		'https://www.discogs.com/users/forgot_password' \
+		>/dev/null
+	echo 'Waiting for email...'
+	url=$(
+		git fetch-email -i -M Trash \
+			-F 'noreply@discogs.com' -T "${email}" \
+			-S 'Discogs Account Password Reset Instructions' |
+		sed -n 's/^To proceed, follow the instructions here: \(.*\)/\1/p'
+	)
+	echo 'Fetching token...'
+	token=$(curl -ISs --url "${url}" | sed -n 's/.*[?]token=\([^&]*\).*/\1/p')
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST \
+		-F "token=${token}" \
+		-F "password0=${password}" -F "password1=${password}" \
+		-F 'Action.ChangePassword=submit' \
+		'https://www.discogs.com/users/forgot_password' \
+		>/dev/null
+	copy "${password}"
+	open 'https://discogs.com/login'
+}
+
+gitea() {
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss "${giteaBase}/user/forgot_password" |
+		sed -n 's/.*name="_csrf" value="\([^"]*\)".*/\1/p'
+	)
+	echo 'Submitting form...'
+	curl -Ss -X POST \
+		-F "email=${email}" -F "_csrf=${csrf}" \
+		"${giteaBase}/user/forgot_password" \
+		>/dev/null
+	echo 'Waiting for email...'
+	code=$(
+		git fetch-email -i -M Trash \
+			-F "${giteaFrom}" -T "${email}" -S 'Recover your account' |
+		unwrap | sed -n 's/.*code=3D\(.*\)/\1/p' | head -n 1
+	)
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss "${giteaBase}/user/recover_account" |
+		sed -n 's/.*name="_csrf" value="\([^"]*\)".*/\1/p'
+	)
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST \
+		-F "_csrf=${csrf}" -F "code=${code}" \
+		-F "password=${password}" \
+		"${giteaBase}/user/recover_account" \
+		>/dev/null
+	copy "${password}"
+	open "${giteaBase}/user/login"
+}
+
+liberapay() {
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss 'https://liberapay.com/sign-in' |
+		sed -n 's/.*name="csrf_token".*value="\([^"]*\)".*/\1/p'
+	)
+	echo 'Submitting form...'
+	curl -Ss -X POST \
+		-b "csrf_token=${csrf}" -F "csrf_token=${csrf}" \
+		-F "log-in.id=${email}" \
+		'https://liberapay.com/sign-in' \
+		>/dev/null
+	echo 'Waiting for email...'
+	url=$(
+		git fetch-email -i -M Trash \
+			-F 'support@liberapay.com' -T "${email}" \
+			-S 'Log in to Liberapay' |
+		grep -m 1 '^https://liberapay\.com/'
+	)
+	open "${url}"
+}
+
+lobsters() {
+	: ${lobstersBase:=https://lobste.rs}
+	: ${lobstersFrom:=nobody@lobste.rs}
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss "${lobstersBase}/login/forgot_password" |
+		sed -n 's/.*name="authenticity_token" value="\([^"]*\)".*/\1/p'
+	)
+	echo 'Submitting form...'
+	curl -Ss -X POST \
+		-F "authenticity_token=${csrf}" \
+		-F "email=${email}" -F 'commit=submit' \
+		"${lobstersBase}/login/reset_password" \
+		>/dev/null
+	echo 'Waiting for email...'
+	token=$(
+		git fetch-email -i -M Trash \
+			-F "${lobstersFrom}" -T "${email}" \
+			-S 'Reset your password' |
+		sed -n 's|^https://.*[?]token=\([^&]*\).*|\1|p'
+	)
+	echo 'Fetching CSRF token...'
+	csrf=$(
+		curl -Ss "${lobstersBase}/login/set_new_password?token=${token}" |
+		sed -n 's/.*name="authenticity_token" value="\([^"]*\)".*/\1/p'
+	)
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST \
+		-F "authenticity_token=${csrf}" -F "token=${token}" \
+		-F "password=${password}" -F "password_confirmation=${password}" \
+		-F 'commit=submit' \
+		"${lobstersBase}/login/set_new_password" \
+		>/dev/null
+	copy "${password}"
+	open "${lobstersBase}/login"
+}
+
+lwn() {
+	username=$email
+	echo 'Submitting form...'
+	curl -Ss -X POST -F "username=${username}" \
+		'https://lwn.net/Login/MailPWLink' \
+		>/dev/null
+	echo 'Waiting for email...'
+	key=$(
+		git fetch-email -i -M Trash \
+			-F 'lwn@lwn.net' -S 'A link to set your LWN.net password' |
+		sed -n 's|.*/Login/SetPassword/.*/\(.*\)|\1|p'
+	)
+	echo 'Retrieving UID...'
+	uid=$(
+		curl -Ss "https://lwn.net/Login/SetPassword/${username}/${key}" |
+		sed -n 's/.*name="uid" value="\([^"]*\)".*/\1/p'
+	)
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST \
+		-F "uid=${uid}" -F "key=${key}" \
+		-F "new1=${password}" -F "new2=${password}" \
+		'https://lwn.net/Login/DoSetPassword' \
+		>/dev/null
+	copy "${password}"
+	open 'https://lwn.net/Login/'
+}
+
+patreon() {
+	readonly patreonAPI='https://www.patreon.com/api'
+	echo 'Submitting form...'
+	curl -Ss -X POST -d @- \
+		-H 'Content-Type: application/vnd.api+json' \
+		"${patreonAPI}/auth/forgot-password?json-api-version=1.0" <<-EOF
+		{"data":{"email":"${email}"}}
+		EOF
+	echo 'Waiting for email...'
+	url=$(
+		git fetch-email -i -M Trash \
+			-F 'password@patreon.com' -T "${email}" \
+			-S 'Patreon Password Reset' |
+		unwrap |
+		grep -o -m 1 'https://email[.]mailgun[.]patreon[.]com/.*'
+	)
+	echo 'Fetching token...'
+	location=$(curl -ISs --url "${url}" | grep -i '^Location: ' | tr -d '\r')
+	u=$(echo "${location}" | sed 's/.*[?&]u=\([^&]*\).*/\1/')
+	sec=$(echo "${location}" | sed 's/.*[?&]sec=\([^&]*\).*/\1/')
+	password=$(generate)
+	echo 'Setting password...'
+	curl -Ss -X POST -d @- \
+		-H 'Content-Type: application/vnd.api+json' \
+		"${patreonAPI}/auth/forgot-password/change?json-api-version=1.0" <<-EOF
+		{
+			"data":{
+				"user_id":"${u}",
+				"security_token":"${sec}",
+				"password":"${password}"
+			}
+		}
+		EOF
+	copy "${password}"
+	open 'https://www.patreon.com/login'
+}
+
+tildegit() {
+	giteaBase='https://tildegit.org'
+	giteaFrom='git@tildegit.org'
+	gitea
+}
+
+tildenews() {
+	lobstersBase='https://tilde.news'
+	lobstersFrom='nobody@tilde.news'
+	lobsters
+}
+
+$service
diff --git a/bin/title.c b/bin/title.c
new file mode 100644
index 00000000..47ff720a
--- /dev/null
+++ b/bin/title.c
@@ -0,0 +1,211 @@
+/* Copyright (C) 2019  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <curl/curl.h>
+#include <err.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+
+static regex_t regex(const char *pattern, int flags) {
+	regex_t regex;
+	int error = regcomp(&regex, pattern, REG_EXTENDED | flags);
+	if (!error) return regex;
+
+	char buf[256];
+	regerror(error, &regex, buf, sizeof(buf));
+	errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern);
+}
+
+static const struct Entity {
+	wchar_t ch;
+	const char *name;
+} Entities[] = {
+	{ L'"', "&quot;" },
+	{ L'&', "&amp;" },
+	{ L'<', "&lt;" },
+	{ L'>', "&gt;" },
+	{ L'␤', "&#10;" },
+};
+
+static wchar_t entity(const char *name) {
+	for (size_t i = 0; i < sizeof(Entities) / sizeof(Entities[0]); ++i) {
+		struct Entity entity = Entities[i];
+		if (strncmp(name, entity.name, strlen(entity.name))) continue;
+		return entity.ch;
+	}
+	if (!strncmp(name, "&#x", 3)) return strtoul(&name[3], NULL, 16);
+	if (!strncmp(name, "&#", 2)) return strtoul(&name[2], NULL, 10);
+	return 0;
+}
+
+static const char EntityPattern[] = {
+	"[[:space:]]+|&([[:alpha:]]+|#([[:digit:]]+|x[[:xdigit:]]+));"
+};
+static regex_t EntityRegex;
+
+static void showTitle(const char *title) {
+	regmatch_t match = {0};
+	for (; *title; title += match.rm_eo) {
+		if (regexec(&EntityRegex, title, 1, &match, 0)) break;
+		if (title[match.rm_so] != '&') {
+			printf("%.*s ", (int)match.rm_so, title);
+			continue;
+		}
+		wchar_t ch = entity(&title[match.rm_so]);
+		if (ch) {
+			printf("%.*s%lc", (int)match.rm_so, title, (wint_t)ch);
+		} else {
+			printf("%.*s", (int)match.rm_eo, title);
+		}
+	}
+	printf("%s\n", title);
+}
+
+static CURL *curl;
+static bool title;
+static struct {
+	char buf[64 * 1024];
+	size_t len;
+} body;
+
+// HE COMES
+static const char TitlePattern[] = "<title>([^<]*)</title>";
+static regex_t TitleRegex;
+
+static size_t handleBody(char *buf, size_t size, size_t nitems, void *user) {
+	(void)user;
+	size_t len = size * nitems;
+	size_t cap = sizeof(body.buf) - body.len - 1;
+	size_t new = (len < cap ? len : cap);
+	if (title || !new) return len;
+
+	memcpy(&body.buf[body.len], buf, new);
+	body.len += new;
+	body.buf[body.len] = '\0';
+
+	regmatch_t match[2];
+	if (regexec(&TitleRegex, body.buf, 2, match, 0)) return len;
+	body.buf[match[1].rm_eo] = '\0';
+	showTitle(&body.buf[match[1].rm_so]);
+	title = true;
+
+	return len;
+}
+
+static CURLcode fetchTitle(const char *url) {
+	CURLcode code = curl_easy_setopt(curl, CURLOPT_URL, url);
+	if (code) return code;
+
+	curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+	code = curl_easy_perform(curl);
+	if (code) return code;
+
+	char *type;
+	code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &type);
+	if (code) return code;
+	if (!type || strncmp(type, "text/html", 9)) return CURLE_OK;
+
+	char *dest;
+	curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &dest);
+	dest = strdup(dest);
+	if (!dest) err(EX_OSERR, "strdup");
+
+	code = curl_easy_setopt(curl, CURLOPT_URL, dest);
+	if (code) return code;
+	free(dest);
+
+	body.len = 0;
+	title = false;
+	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+	code = curl_easy_perform(curl);
+	return code;
+}
+
+int main(int argc, char *argv[]) {
+	EntityRegex = regex(EntityPattern, 0);
+	TitleRegex = regex(TitlePattern, REG_ICASE);
+
+	setlocale(LC_CTYPE, "");
+	setlinebuf(stdout);
+
+	CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+	if (code) errx(EX_OSERR, "curl_global_init: %s", curl_easy_strerror(code));
+
+	curl = curl_easy_init();
+	if (!curl) errx(EX_SOFTWARE, "curl_easy_init");
+
+	static char error[CURL_ERROR_SIZE];
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
+
+	curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+	curl_easy_setopt(
+		curl, CURLOPT_USERAGENT,
+		"curl/7.54.0 facebookexternalhit/1.1 Twitterbot/1.0"
+	);
+	curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
+	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handleBody);
+
+	bool exclude = false;
+	regex_t excludeRegex;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "x:v"))) {
+		switch (opt) {
+			break; case 'x': {
+				exclude = true;
+				excludeRegex = regex(optarg, REG_NOSUB);
+			}
+			break; case 'v': curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	if (optind < argc) {
+		code = fetchTitle(argv[optind]);
+		if (!code) return EX_OK;
+		errx(EX_DATAERR, "curl_easy_perform: %s", error);
+	}
+
+	char *buf = NULL;
+	size_t cap = 0;
+
+	regex_t urlRegex = regex("https?://([^[:space:]>\"()]|[(][^)]*[)])+", 0);
+	while (0 < getline(&buf, &cap, stdin)) {
+		regmatch_t match = {0};
+		for (char *ptr = buf; *ptr; ptr += match.rm_eo) {
+			if (regexec(&urlRegex, ptr, 1, &match, 0)) break;
+			ptr[match.rm_eo] = '\0';
+			const char *url = &ptr[match.rm_so];
+			if (!exclude || regexec(&excludeRegex, url, 0, NULL, 0)) {
+				code = fetchTitle(url);
+				if (code) warnx("curl_easy_perform: %s", error);
+			}
+			ptr[match.rm_eo] = ' ';
+		}
+	}
+	if (ferror(stdin)) err(EX_IOERR, "getline");
+}
diff --git a/bin/up.sh b/bin/up.sh
new file mode 100644
index 00000000..6305b1ee
--- /dev/null
+++ b/bin/up.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+set -eu
+
+readonly Host='temp.causal.agency'
+readonly Root='/var/www'
+
+temp=
+temp() {
+	temp=$(mktemp -d)
+	trap 'rm -r "$temp"' EXIT
+}
+
+warn=
+upload() {
+	src=$1
+	ext=${src##*.}
+	name=$(printf '%x%s' "$(date +%s)" "$(openssl rand -hex 4)")
+	url="${Host}/${name}.${ext}"
+	scp -q "$src" "${Host}:${Root}/${url}"
+	if test -n "$warn"; then
+		test -n "$temp" || temp
+		cat >"${temp}/warn.html" <<-EOF
+			<!DOCTYPE html>
+			<title>${warn}</title>
+			<meta http-equiv="refresh" content="0;url=${name}.${ext}">
+		EOF
+		url="${Host}/${name}.html"
+		scp -q "${temp}/warn.html" "${Host}:${Root}/${url}"
+	fi
+	echo "https://${url}"
+}
+
+uploadText() {
+	temp
+	cat >"${temp}/input.txt"
+	upload "${temp}/input.txt"
+}
+
+uploadCommand() {
+	temp
+	echo "$ $1" >"${temp}/exec.txt"
+	$SHELL -c "$1" >>"${temp}/exec.txt" 2>&1 || true
+	upload "${temp}/exec.txt"
+}
+
+uploadHilex() {
+	temp
+	hilex -f html -o document,tab=4 "$@" >"${temp}/hilex.html"
+	upload "${temp}/hilex.html"
+}
+
+uploadScreen() {
+	temp
+	if command -v screencapture >/dev/null; then
+		screencapture -i "$@" "${temp}/capture.png"
+	else
+		scrot -s "$@" "${temp}/capture.png"
+	fi
+	pngo "${temp}/capture.png"
+	upload "${temp}/capture.png"
+}
+
+uploadTerminal() {
+	temp
+	cat >"${temp}/term.html" <<-EOF
+	<!DOCTYPE html>
+	<meta charset="utf-8">
+	<title>${1}</title>
+	<style>
+	$(scheme -s)
+	</style>
+	EOF
+	ptee $SHELL -c "$1" >"${temp}/term.pty"
+	shotty -Bs "${temp}/term.pty" >>"${temp}/term.html"
+	upload "${temp}/term.html"
+}
+
+while getopts 'chstw:' opt; do
+	case $opt in
+		(c) fn=uploadCommand;;
+		(h) fn=uploadHilex;;
+		(s) fn=uploadScreen;;
+		(t) fn=uploadTerminal;;
+		(w) warn=$OPTARG;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+[ $# -eq 0 ] && : ${fn:=uploadText}
+: ${fn:=upload}
+
+url=$($fn "$@")
+printf '%s' "$url" | pbcopy || true
+echo "$url"
diff --git a/bin/when.y b/bin/when.y
new file mode 100644
index 00000000..46651ebb
--- /dev/null
+++ b/bin/when.y
@@ -0,0 +1,353 @@
+/* Copyright (C) 2019, 2022  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <time.h>
+
+static void yyerror(const char *str);
+static int yylex(void);
+
+#define YYSTYPE struct tm
+
+static const char *Days[7] = {
+	"Sunday", "Monday", "Tuesday", "Wednesday",
+	"Thursday", "Friday", "Saturday",
+};
+
+static const char *Months[12] = {
+	"January", "February", "March", "April", "May", "June",
+	"July", "August", "September", "October", "November", "December",
+};
+
+static const struct tm Week = { .tm_mday = 7 };
+
+static struct tm normalize(struct tm date) {
+	time_t time = timegm(&date);
+	struct tm *norm = gmtime(&time);
+	if (!norm) err(EX_OSERR, "gmtime");
+	return *norm;
+}
+
+static struct tm today(void) {
+	time_t now = time(NULL);
+	struct tm *local = localtime(&now);
+	if (!local) err(EX_OSERR, "localtime");
+	struct tm date = {
+		.tm_year = local->tm_year,
+		.tm_mon = local->tm_mon,
+		.tm_mday = local->tm_mday,
+	};
+	return normalize(date);
+}
+
+static struct tm monthDay(int month, int day) {
+	struct tm date = today();
+	date.tm_mon = month;
+	date.tm_mday = day;
+	return normalize(date);
+}
+
+static struct tm monthDayYear(int month, int day, int year) {
+	struct tm date = today();
+	date.tm_mon = month;
+	date.tm_mday = day;
+	date.tm_year = year - 1900;
+	return normalize(date);
+}
+
+static struct tm weekDay(int day) {
+	struct tm date = today();
+	date.tm_mday += day - date.tm_wday;
+	return normalize(date);
+}
+
+static struct tm scalarAdd(struct tm a, struct tm b) {
+	a.tm_mday += b.tm_mday;
+	a.tm_mon += b.tm_mon;
+	a.tm_year += b.tm_year;
+	return a;
+}
+
+static struct tm scalarSub(struct tm a, struct tm b) {
+	a.tm_mday -= b.tm_mday;
+	a.tm_mon -= b.tm_mon;
+	a.tm_year -= b.tm_year;
+	return a;
+}
+
+static struct tm dateAdd(struct tm date, struct tm scalar) {
+	return normalize(scalarAdd(date, scalar));
+}
+
+static struct tm dateSub(struct tm date, struct tm scalar) {
+	return normalize(scalarSub(date, scalar));
+}
+
+static struct tm dateDiff(struct tm a, struct tm b) {
+	time_t atime = timegm(&a), btime = timegm(&b);
+	if (atime < btime) {
+		struct tm x = a;
+		a = b;
+		b = x;
+		time_t xtime = atime;
+		atime = btime;
+		btime = xtime;
+	}
+	struct tm diff = {
+		.tm_year = a.tm_year - b.tm_year,
+		.tm_mon = a.tm_mon - b.tm_mon,
+		.tm_mday = a.tm_mday - b.tm_mday,
+	};
+	if (
+		a.tm_mon < b.tm_mon ||
+		(a.tm_mon == b.tm_mon && a.tm_mday < b.tm_mday)
+	) {
+		diff.tm_year--;
+		diff.tm_mon += 12;
+	}
+	if (a.tm_mday < b.tm_mday) {
+		diff.tm_mon--;
+		diff.tm_mday = 0;
+		while (dateAdd(b, diff).tm_mday != a.tm_mday) diff.tm_mday++;
+	}
+	diff.tm_yday = (atime - btime) / 24 / 60 / 60;
+	return diff;
+}
+
+static struct {
+	size_t cap, len;
+	struct tm *ptr;
+} dates;
+
+static struct tm getDate(const char *name) {
+	for (size_t i = 0; i < dates.len; ++i) {
+		if (!strcmp(dates.ptr[i].tm_zone, name)) return dates.ptr[i];
+	}
+	return (struct tm) {0};
+}
+
+static void setDate(const char *name, struct tm date) {
+	for (size_t i = 0; i < dates.len; ++i) {
+		if (strcmp(dates.ptr[i].tm_zone, name)) continue;
+		char *tm_zone = dates.ptr[i].tm_zone;
+		dates.ptr[i] = date;
+		dates.ptr[i].tm_zone = tm_zone;
+		return;
+	}
+	if (dates.len == dates.cap) {
+		dates.cap = (dates.cap ? dates.cap * 2 : 8);
+		dates.ptr = realloc(dates.ptr, sizeof(*dates.ptr) * dates.cap);
+		if (!dates.ptr) err(EX_OSERR, "realloc");
+	}
+	dates.ptr[dates.len] = date;
+	dates.ptr[dates.len].tm_zone = strdup(name);
+	if (!dates.ptr[dates.len].tm_zone) err(EX_OSERR, "strdup");
+	dates.len++;
+}
+
+static bool silent;
+
+static void printDate(struct tm date) {
+	if (silent) return;
+	printf(
+		"%.3s %.3s %d %d\n",
+		Days[date.tm_wday], Months[date.tm_mon],
+		date.tm_mday, 1900 + date.tm_year
+	);
+}
+
+static void printScalar(struct tm scalar) {
+	if (silent) return;
+	if (scalar.tm_year) printf("%dy ", scalar.tm_year);
+	if (scalar.tm_mon) printf("%dm ", scalar.tm_mon);
+	if (scalar.tm_mday % 7) {
+		printf("%dd ", scalar.tm_mday);
+	} else if (scalar.tm_mday) {
+		printf("%dw ", scalar.tm_mday / 7);
+	}
+	if (scalar.tm_yday && scalar.tm_mon) {
+		if (scalar.tm_yday >= 7) {
+			printf("(%dw", scalar.tm_yday / 7);
+			if (scalar.tm_yday % 7) {
+				printf(" %dd", scalar.tm_yday % 7);
+			}
+			printf(") ");
+		}
+		printf("(%dd) ", scalar.tm_yday);
+	}
+	printf("\n");
+}
+
+%}
+
+%token Name Number Month Day
+%left '+' '-'
+%right '=' '<' '>'
+
+%%
+
+expr:
+	date { printDate($1); }
+	| scalar { printScalar($1); }
+	;
+
+date:
+	dateLit
+	| Name { $$ = getDate($1.tm_zone); free($1.tm_zone); }
+	| Name '=' date { setDate($1.tm_zone, $3); free($1.tm_zone); $$ = $3; }
+	| '(' date ')' { $$ = $2; }
+	| '<' date { $$ = dateSub($2, Week); }
+	| '>' date { $$ = dateAdd($2, Week); }
+	| date '+' scalar { $$ = dateAdd($1, $3); }
+	| date '-' scalar { $$ = dateSub($1, $3); }
+	;
+
+scalar:
+	scalarLit
+	| '(' scalar ')' { $$ = $2; }
+	| scalar '+' scalar { $$ = scalarAdd($1, $3); }
+	| scalar '-' scalar { $$ = scalarSub($1, $3); }
+	| date '-' date { $$ = dateDiff($1, $3); }
+	;
+
+dateLit:
+	{ $$ = today(); }
+	| '.' { $$ = today(); }
+	| Month Number { $$ = monthDay($1.tm_mon, $2.tm_sec); }
+	| Month Number Number { $$ = monthDayYear($1.tm_mon, $2.tm_sec, $3.tm_sec); }
+	| Day { $$ = weekDay($1.tm_wday); }
+	;
+
+scalarLit:
+	Number 'd' { $$ = (struct tm) { .tm_mday = $1.tm_sec }; }
+	| Number 'w' { $$ = (struct tm) { .tm_mday = 7 * $1.tm_sec }; }
+	| Number 'm' { $$ = (struct tm) { .tm_mon = $1.tm_sec }; }
+	| Number 'y' { $$ = (struct tm) { .tm_year = $1.tm_sec }; }
+	;
+
+%%
+
+static void yyerror(const char *str) {
+	warnx("%s", str);
+}
+
+static const char *input;
+
+static int yylex(void) {
+	while (isspace(*input)) input++;
+	if (!*input) return EOF;
+
+	if (isdigit(*input)) {
+		char *rest;
+		yylval.tm_sec = strtol(input, &rest, 10);
+		input = rest;
+		return Number;
+	}
+
+	size_t len;
+	for (len = 0; isalnum(input[len]) || input[len] == '_'; ++len);
+
+	if (len >= 3) {
+		for (int i = 0; i < 7; ++i) {
+			if (strncasecmp(input, Days[i], len)) continue;
+			yylval.tm_wday = i;
+			input += len;
+			return Day;
+		}
+
+		for (int i = 0; i < 12; ++i) {
+			if (strncasecmp(input, Months[i], len)) continue;
+			yylval.tm_mon = i;
+			input += len;
+			return Month;
+		}
+	}
+
+	if (len && (len != 1 || !strchr("dwmy", *input))) {
+		yylval.tm_zone = strndup(input, len);
+		if (!yylval.tm_zone) err(EX_OSERR, "strndup");
+		input += len;
+		return Name;
+	}
+
+	return *input++;
+}
+
+int main(int argc, char *argv[]) {
+	size_t cap = 0;
+	char *line = NULL;
+
+	char path[PATH_MAX];
+	const char *configHome = getenv("XDG_CONFIG_HOME");
+	if (configHome) {
+		snprintf(path, sizeof(path), "%s/when/dates", configHome);
+	} else {
+		snprintf(path, sizeof(path), "%s/.config/when/dates", getenv("HOME"));
+	}
+
+	FILE *file = fopen(path, "r");
+	if (file) {
+		silent = true;
+		while (0 < getline(&line, &cap, file)) {
+			input = line;
+			yyparse();
+		}
+		fclose(file);
+		silent = false;
+	} else if (errno != ENOENT) {
+		err(EX_CONFIG, "%s", path);
+	}
+
+	if (argc > 1) {
+		if (strcmp(argv[1], "-")) {
+			input = argv[1];
+			return yyparse();
+		} else {
+			for (size_t i = 0; i < dates.len; ++i) {
+				printf("%s: ", dates.ptr[i].tm_zone);
+				printScalar(dateDiff(today(), dates.ptr[i]));
+			}
+			return EX_OK;
+		}
+	}
+
+	struct tm date = today();
+	printDate(date);
+	printf("\n");
+
+	while (0 < getline(&line, &cap, stdin)) {
+		if (line[0] == '\n') continue;
+
+		if (today().tm_mday != date.tm_mday) {
+			warnx("the date has changed");
+			date = today();
+		}
+
+		input = line;
+		yyparse();
+		printf("\n");
+	}
+}
diff --git a/bin/xx.c b/bin/xx.c
new file mode 100644
index 00000000..39d7ec07
--- /dev/null
+++ b/bin/xx.c
@@ -0,0 +1,142 @@
+/* Copyright (C) 2017  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+typedef unsigned char byte;
+
+static bool zero(const byte *ptr, size_t size) {
+	for (size_t i = 0; i < size; ++i) {
+		if (ptr[i]) return false;
+	}
+	return true;
+}
+
+static struct {
+	size_t cols;
+	size_t group;
+	size_t blank;
+	bool ascii;
+	bool offset;
+	bool skip;
+} options = { 16, 8, 0, true, true, false };
+
+static void dump(FILE *file) {
+	bool skip = false;
+
+	byte buf[options.cols];
+	size_t offset = 0;
+	for (
+		size_t size;
+		(size = fread(buf, 1, sizeof(buf), file));
+		offset += size
+	) {
+		if (options.skip) {
+			if (zero(buf, size)) {
+				if (!skip) printf("*\n");
+				skip = true;
+				continue;
+			} else {
+				skip = false;
+			}
+		}
+
+		if (options.blank) {
+			if (offset && offset % options.blank == 0) {
+				printf("\n");
+			}
+		}
+
+		if (options.offset) {
+			printf("%08zX:  ", offset);
+		}
+
+		for (size_t i = 0; i < sizeof(buf); ++i) {
+			if (options.group) {
+				if (i && !(i % options.group)) {
+					printf(" ");
+				}
+			}
+			if (i < size) {
+				printf("%02hhX ", buf[i]);
+			} else {
+				printf("   ");
+			}
+		}
+
+		if (options.ascii) {
+			printf(" ");
+			for (size_t i = 0; i < size; ++i) {
+				if (options.group) {
+					if (i && !(i % options.group)) {
+						printf(" ");
+					}
+				}
+				printf("%c", isprint(buf[i]) ? buf[i] : '.');
+			}
+		}
+
+		printf("\n");
+	}
+}
+
+static void undump(FILE *file) {
+	byte c;
+	int match;
+	while (0 < (match = fscanf(file, " %hhx", &c))) {
+		printf("%c", c);
+	}
+	if (!match) errx(EX_DATAERR, "invalid input");
+}
+
+int main(int argc, char *argv[]) {
+	bool reverse = false;
+	const char *path = NULL;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "ac:g:p:rsz"))) {
+		switch (opt) {
+			break; case 'a': options.ascii ^= true;
+			break; case 'c': options.cols = strtoul(optarg, NULL, 0);
+			break; case 'g': options.group = strtoul(optarg, NULL, 0);
+			break; case 'p': options.blank = strtoul(optarg, NULL, 0);
+			break; case 'r': reverse = true;
+			break; case 's': options.offset ^= true;
+			break; case 'z': options.skip ^= true;
+			break; default: return EX_USAGE;
+		}
+	}
+	if (argc > optind) path = argv[optind];
+	if (!options.cols) return EX_USAGE;
+
+	FILE *file = path ? fopen(path, "r") : stdin;
+	if (!file) err(EX_NOINPUT, "%s", path);
+
+	if (reverse) {
+		undump(file);
+	} else {
+		dump(file);
+	}
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+
+	return EX_OK;
+}
diff --git a/doc/rfc/.gitignore b/doc/rfc/.gitignore
new file mode 100644
index 00000000..cc3245d4
--- /dev/null
+++ b/doc/rfc/.gitignore
@@ -0,0 +1,2 @@
+rfc
+rfctags
diff --git a/doc/rfc/Makefile b/doc/rfc/Makefile
new file mode 100644
index 00000000..3078dcd3
--- /dev/null
+++ b/doc/rfc/Makefile
@@ -0,0 +1,38 @@
+PREFIX ?= ~/.local
+MANDIR ?= ${PREFIX}/share/man
+
+MODULE = ftp.rfc-editor.org::rfcs
+RFCS = ${MODULE}/rfc-index.txt ${MODULE}/'rfc[1-9]*.txt' ${MODULE}/'rfc*.json'
+
+all: rfc rfctags
+
+.SUFFIXES: .in .pl
+
+.in:
+	sed 's|%%PREFIX%%|${PREFIX}|g' $< > $@
+	chmod a+x $@
+
+.pl:
+	cp -f $< $@
+	chmod a+x $@
+
+clean:
+	rm -f rfc rfctags
+
+install: rfc rfctags rfc.1
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+	install rfc rfctags ${PREFIX}/bin
+	install -m 644 rfc.1 ${MANDIR}/man1
+	ln -fs rfc.1 ${MANDIR}/man1/rfctags.1
+
+sync:
+	install -d ${PREFIX}/share
+	rsync -ptz ${RFCS} ${PREFIX}/share/rfc
+
+compress:
+	find ${PREFIX}/share/rfc -name '*.txt' | xargs gzip -9f
+
+uninstall:
+	rm -f ${PREFIX}/bin/rfc ${PREFIX}/bin/rfctags
+	rm -f ${MANDIR}/man1/rfc.1 ${MANDIR}/man1/rfctags.1
+	rm -fr ${PREFIX}/share/rfc
diff --git a/doc/rfc/rfc.1 b/doc/rfc/rfc.1
new file mode 100644
index 00000000..da393e8b
--- /dev/null
+++ b/doc/rfc/rfc.1
@@ -0,0 +1,60 @@
+.Dd January  3, 2022
+.Dt RFC 1
+.Os
+.
+.Sh NAME
+.Nm rfc ,
+.Nm rfctags
+.Nd view IETF RFCs
+.
+.Sh SYNOPSIS
+.Nm rfc
+.Op Ar number
+.Nm rfc
+.Fl b Ar number
+.Nm rfctags
+.Op Ar
+.
+.Sh DESCRIPTION
+The
+.Nm rfc
+utility displays
+an IETF RFC by number,
+or the RFC index if no number is specified.
+The RFC is displayed in the
+.Ev PAGER
+with a tags file generated by
+.Nm rfctags .
+The
+.Fl b
+option outputs an
+.Xr mdoc 7
+bibliographic block.
+.
+.Pp
+The
+.Nm rfctags
+utility generates tags
+for RFC text file
+section numbers,
+section names
+and bracketed references.
+.
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev PAGER
+The program used to display RFCs.
+It must accept the
+.Fl T
+flag for specifying
+the path of the tags file.
+The default is
+.Ev PAGER=less .
+.El
+.
+.Sh SEE ALSO
+.Xr ctags 1 ,
+.Xr less 1
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/doc/rfc/rfc.in b/doc/rfc/rfc.in
new file mode 100644
index 00000000..abeb293f
--- /dev/null
+++ b/doc/rfc/rfc.in
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -eu
+
+mktemp='mktemp -t rfc'
+[ "$(uname)" = 'OpenBSD' ] && mktemp="${mktemp}.XXXXXXXXXX"
+
+bib=
+while getopts 'b:' opt; do
+	case $opt in
+		(b) bib=$OPTARG;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+
+if test -n "${bib}"; then
+	exec jq -r '
+		".Rs",
+		(.authors[] | ".%A \(.)"),
+		".%T \(.title | ltrimstr(" "))",
+		".%I IETF",
+		".%R \(.doc_id)",
+		".%U https://tools.ietf.org/html/\(.doc_id | ascii_downcase)",
+		".%D \(.pub_date)",
+		".Re"
+	' %%PREFIX%%/share/rfc/"rfc${bib}.json"
+fi
+
+rfc=%%PREFIX%%/share/rfc/"rfc${1:--index}.txt"
+tags=$($mktemp)
+trap 'rm "${tags}"' EXIT
+
+if test -f "${rfc}.gz"; then
+	txt=$($mktemp)
+	trap 'rm "${txt}" "${tags}"' EXIT
+	gunzip -c "${rfc}.gz" >"${txt}"
+	rfc=$txt
+fi
+
+%%PREFIX%%/bin/rfctags "${rfc}" >"${tags}"
+${PAGER:-less} -T "${tags}" "${rfc}"
diff --git a/doc/rfc/rfctags.pl b/doc/rfc/rfctags.pl
new file mode 100644
index 00000000..05173d00
--- /dev/null
+++ b/doc/rfc/rfctags.pl
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use open ':encoding(ISO-8859-1)';
+
+($,, $\) = ("\t", "\n");
+while (<>) {
+	chomp;
+	# Section headings
+	if (/^([\d.]+|[A-Z][.])\s+([^\t]+)?/) {
+		print $1, $ARGV, $.;
+		print $2, $ARGV, $. if $2;
+		print $1, $ARGV, $. if $1 =~ /^([\d.]+)[.]$/;
+	}
+	# References
+	if (/^\s*(\[[\w-]+\])\s{2,}/) {
+		print $1, $ARGV, $.;
+		print "\\$1", $ARGV, $.; # vim ^] prepends \ to [
+	}
+	close ARGV if eof;
+}
diff --git a/doc/zlib/Makefile b/doc/zlib/Makefile
new file mode 100644
index 00000000..6cfd4a42
--- /dev/null
+++ b/doc/zlib/Makefile
@@ -0,0 +1,87 @@
+PREFIX ?= ~/.local
+MANDIR ?= ${PREFIX}/share/man
+
+MAN += adler32.3
+MAN += adler32_combine.3
+MAN += compress.3
+MAN += compressBound.3
+MAN += crc32.3
+MAN += crc32_combine.3
+MAN += deflate.3
+MAN += deflateBound.3
+MAN += deflateCopy.3
+MAN += deflateEnd.3
+MAN += deflateGetDictionary.3
+MAN += deflateInit.3
+MAN += deflateInit2.3
+MAN += deflateParams.3
+MAN += deflatePending.3
+MAN += deflatePrime.3
+MAN += deflateReset.3
+MAN += deflateSetDictionary.3
+MAN += deflateSetHeader.3
+MAN += deflateTune.3
+MAN += gzbuffer.3
+MAN += gzclose.3
+MAN += gzdirect.3
+MAN += gzeof.3
+MAN += gzerror.3
+MAN += gzflush.3
+MAN += gzfread.3
+MAN += gzfwrite.3
+MAN += gzgetc.3
+MAN += gzgets.3
+MAN += gzoffset.3
+MAN += gzopen.3
+MAN += gzprintf.3
+MAN += gzputc.3
+MAN += gzputs.3
+MAN += gzread.3
+MAN += gzseek.3
+MAN += gzsetparams.3
+MAN += gzungetc.3
+MAN += gzwrite.3
+MAN += inflate.3
+MAN += inflateBack.3
+MAN += inflateBackEnd.3
+MAN += inflateBackInit.3
+MAN += inflateCopy.3
+MAN += inflateEnd.3
+MAN += inflateGetDictionary.3
+MAN += inflateGetHeader.3
+MAN += inflateInit.3
+MAN += inflateInit2.3
+MAN += inflateMark.3
+MAN += inflatePrime.3
+MAN += inflateReset.3
+MAN += inflateSetDictionary.3
+MAN += inflateSync.3
+MAN += uncompress.3
+MAN += zlibCompileFlags.3
+MAN += zlibVersion.3
+
+MLINKS += adler32.3 adler32_z.3
+MLINKS += compress.3 compress2.3
+MLINKS += crc32.3 crc32_z.3
+MLINKS += gzclose.3 gzclose_r.3
+MLINKS += gzclose.3 gzclose_w.3
+MLINKS += gzerror.3 gzclearerr.3
+MLINKS += gzopen.3 gzdopen.3
+MLINKS += gzseek.3 gzrewind.3
+MLINKS += gzseek.3 gztell.3
+MLINKS += inflateReset.3 inflateReset2.3
+MLINKS += uncompress.3 uncompress2.3
+
+lint:
+	mandoc -T lint ${MAN} | grep -v 'referenced manual not found'
+
+install:
+	install -d ${MANDIR}/man3
+	install -m 644 ${MAN} ${MANDIR}/man3
+	set -- ${MLINKS}; while [ -n "$$*" ]; do \
+		ln -fs $$1 ${MANDIR}/man3/$$2; shift 2; done
+
+uninstall:
+	rm -f ${MAN:%=${MANDIR}/man3/%}
+	set -- ${MLINKS}; while [ -n "$$*" ]; do \
+		rm -f ${MANDIR}/man3/$$2; shift 2; done
diff --git a/doc/zlib/adler32.3 b/doc/zlib/adler32.3
new file mode 100644
index 00000000..c58a34e7
--- /dev/null
+++ b/doc/zlib/adler32.3
@@ -0,0 +1,65 @@
+.Dd January 15, 2017
+.Dt ADLER32 3
+.Os
+.
+.Sh NAME
+.Nm adler32 ,
+.Nm adler32_z
+.Nd update Adler-32 checksum
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn adler32 "uLong adler" "const Bytef *buf" "uInt len"
+.Ft uLong
+.Fn adler32_z "uLong adler" "const Bytef *buf" "z_size_t len"
+.
+.Sh DESCRIPTION
+Update a running Adler-32 checksum with the bytes
+.Fa "buf[0..len-1]"
+and return the updated checksum.
+If
+.Fa buf
+is
+.Dv Z_NULL ,
+this function returns
+the required initial value for the checksum.
+.
+.Pp
+An Adler-32 checksum is almost as reliable as a CRC-32
+but can be computed much faster.
+.
+.Pp
+.Fn adler32_z
+is the same as
+.Fn adler32 ,
+but with a
+.Vt size_t
+length.
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+uLong adler = adler32(0L, Z_NULL, 0);
+
+while (read_buffer(buffer, length) != EOF) {
+	adler = adler32(adler, buffer, length);
+}
+if (adler != original_adler) error();
+.Ed
+.
+.Sh SEE ALSO
+.Xr adler32_combine 3 ,
+.Xr crc32 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/adler32_combine.3 b/doc/zlib/adler32_combine.3
new file mode 100644
index 00000000..55e801e9
--- /dev/null
+++ b/doc/zlib/adler32_combine.3
@@ -0,0 +1,63 @@
+.Dd January 15, 2017
+.Dt ADLER32_COMBINE 3
+.Os
+.
+.Sh NAME
+.Nm adler32_combine
+.Nd combine Adler-32 checksums
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn adler32_combine "uLong adler1" "uLong adler2" "z_off_t len2"
+.
+.Sh DESCRIPTION
+Combine two Adler-32 checksums into one.
+For two sequences of bytes,
+.Va seq1
+and
+.Va seq2
+with lengths
+.Va len1
+and
+.Va len2 ,
+Adler-32 checksums were calculated for each,
+.Va adler1
+and
+.Va adler2 .
+.Fn adler32_combine
+returns the Adler-32 checksum of
+.Va seq1
+and
+.Va seq2
+concatenated,
+requiring only
+.Fa adler1 ,
+.Fa adler2 ,
+and
+.Fa len2 .
+Note that the
+.Vt z_off_t
+type
+.Pq like Vt off_t
+is a signed integer.
+If
+.Fa len2
+is negative,
+the result has no meaning or utility.
+.
+.Sh SEE ALSO
+.Xr adler32 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/compress.3 b/doc/zlib/compress.3
new file mode 100644
index 00000000..16445e2f
--- /dev/null
+++ b/doc/zlib/compress.3
@@ -0,0 +1,84 @@
+.Dd January 15, 2017
+.Dt COMPRESS 3
+.Os
+.
+.Sh NAME
+.Nm compress ,
+.Nm compress2
+.Nd compress source buffer into destination buffer
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.
+.Ft int
+.Fo compress
+.Fa "Bytef *dest"
+.Fa "uLongf *destLen"
+.Fa "const Bytef *source"
+.Fa "uLong sourceLen"
+.Fc
+.
+.Ft int
+.Fo compress2
+.Fa "Bytef *dest"
+.Fa "uLongf *destLen"
+.Fa "const Bytef *source"
+.Fa "uLong sourceLen"
+.Fa "int level"
+.Fc
+.
+.Sh DESCRIPTION
+Compresses the source buffer into the destination buffer.
+.Fa sourceLen
+is the byte length of the source buffer.
+Upon entry,
+.Fa destLen
+is the total size of the destination buffer,
+which must be at least the value returned by
+.Fn compressBound sourceLen .
+Upon exit,
+.Fa destLen
+is the actual size of the compressed data.
+.
+.Pp
+.Fn compress
+is equivalent to
+.Fn compress2
+with a
+.Fa level
+parameter of
+.Dv Z_DEFAULT_COMPRESSION .
+.
+.Sh RETURN VALUES
+.Fn compress
+and
+.Fn compress2
+return
+.Dv Z_OK
+on success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_BUF_ERROR
+if there was not enough room in the output buffer,
+.Dv Z_STREAM_ERROR
+if the
+.Fa level
+parameter is invalid.
+.
+.Sh SEE ALSO
+.Xr compressBound 3 ,
+.Xr deflateInit 3 ,
+.Xr uncompress 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/compressBound.3 b/doc/zlib/compressBound.3
new file mode 100644
index 00000000..d61891eb
--- /dev/null
+++ b/doc/zlib/compressBound.3
@@ -0,0 +1,44 @@
+.Dd January 15, 2017
+.Dt COMPRESSBOUND 3
+.Os
+.
+.Sh NAME
+.Nm compressBound
+.Nd compressed size upper bound
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn compressBound "uLong sourceLen"
+.
+.Sh DESCRIPTION
+.Fn compressBound
+returns an upper bound on the compressed size after
+.Xr compress 3
+or
+.Xr compress2 3
+on
+.Fa sourceLen
+bytes.
+It would be used before a
+.Xr compress 3
+or
+.Xr compress2 3
+call to allocate the destination buffer.
+.
+.Sh SEE ALSO
+.Xr compress 3 ,
+.Xr deflateBound 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/crc32.3 b/doc/zlib/crc32.3
new file mode 100644
index 00000000..a42df2af
--- /dev/null
+++ b/doc/zlib/crc32.3
@@ -0,0 +1,66 @@
+.Dd January 15, 2017
+.Dt CRC32 3
+.Os
+.
+.Sh NAME
+.Nm crc32 ,
+.Nm crc32_z
+.Nd update CRC-32 checksum
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn crc32 "uLong crc" "const Bytef *buf" "uInt len"
+.Ft uLong
+.Fn crc32_z "uLong crc" "const Bytef *buf" "z_size_t len"
+.
+.Sh DESCRIPTION
+Update a running CRC-32 with the bytes
+.Fa "buf[0..len-1]"
+and return the updated CRC-32.
+If
+.Fa buf
+is
+.Dv Z_NULL ,
+this function returns
+the required initial value for the CRC.
+Pre- and post-conditioning
+(one's complement)
+is performed within this function
+so it shouldn't be done
+by the application.
+.
+.Pp
+.Fn crc32_z
+is the same as
+.Fn crc32 ,
+but with a
+.Vt size_t
+length.
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+uLong crc = crc32(0L, Z_NULL, 0);
+
+while (read_buffer(buffer, length) != EOF) {
+	crc = crc32(crc, buffer, length);
+}
+if (crc != original_crc) error();
+.Ed
+.
+.Sh SEE ALSO
+.Xr adler32 3 ,
+.Xr crc32_combine 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/crc32_combine.3 b/doc/zlib/crc32_combine.3
new file mode 100644
index 00000000..b25da679
--- /dev/null
+++ b/doc/zlib/crc32_combine.3
@@ -0,0 +1,54 @@
+.Dd January 15, 2017
+.Dt CRC32_COMBINE 3
+.Os
+.
+.Sh NAME
+.Nm crc32_combine
+.Nd combine CRC-32 checksums
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn crc32_combine "uLong crc1" "uLong crc2" "z_off_t len2"
+.
+.Sh DESCRIPTION
+Combine two CRC-32 check values into one.
+For two sequences of bytes,
+.Va seq1
+and
+.Va seq2
+with lengths
+.Va len1
+and
+.Va len2 ,
+CRC-32 check values were calculated for each,
+.Va crc1
+and
+.Va crc2 .
+.Fn crc32_combine
+returns the CRC-32 check value of
+.Va seq1
+and
+.Va seq2
+concatenated,
+requiring only
+.Fa crc1 ,
+.Fa crc2 ,
+and
+.Fa len2 .
+.
+.Sh SEE ALSO
+.Xr crc32 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflate.3 b/doc/zlib/deflate.3
new file mode 100644
index 00000000..be182d96
--- /dev/null
+++ b/doc/zlib/deflate.3
@@ -0,0 +1,370 @@
+.Dd January 15, 2017
+.Dt DEFLATE 3
+.Os
+.
+.Sh NAME
+.Nm deflate
+.Nd deflate compression
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflate "z_streamp strm" "int flush"
+.
+.Sh DESCRIPTION
+.Fn deflate
+compresses as much data as possible,
+and stops when the input buffer becomes empty
+or the output buffer becomes full.
+It may introduce some output latency
+(reading input without producing any output)
+except when forced the flush.
+.
+.Pp
+The detailed semantics are as follows.
+.Fn deflate
+performs one or both of the following actions:
+.
+.Bl -dash
+.It
+Compress more input starting at
+.Fa next_in
+and update
+.Fa next_in
+and
+.Fa avail_in
+accordingly.
+If not all input can be processed
+(because there is not enough room in the output buffer),
+.Fa next_in
+and
+.Fa avail_in
+are updated
+and processing will resume at this point
+for the next call of
+.Fn deflate .
+.
+.It
+Generate more output starting at
+.Fa next_out
+and update
+.Fa next_out
+and
+.Fa avail_out
+accordingly.
+This action is forced if the parameter
+.Fa flush
+is non-zero.
+Forcing flush frequently degrades the compression ratio,
+so this parameter should be set only when necessary.
+Some output may be provided even if
+.Fa flush
+is zero.
+.El
+.
+.Pp
+Before the call of
+.Fn deflate ,
+the application should ensure that
+at least one of the actions is possible,
+by providing more input
+and/or consuming more output,
+and updating
+.Fa avail_in
+or
+.Fa avail_out
+accordingly;
+.Fa avail_out
+should never be zero before the call.
+The application can consume the compressed output
+when it wants,
+for example when the output buffer is full
+.Po
+.Fa avail_out
+== 0
+.Pc ,
+or after each call of
+.Fn deflate .
+If
+.Fn deflate
+returns
+.Dv Z_OK
+and with zero
+.Fa avail_out ,
+it must be called again after making room in the output buffer
+because there might be more output pending.
+See
+.Xr deflatePending 3 ,
+which can be used if desired to determine
+whether or not there is more output in that case.
+.
+.Pp
+Normally the parameter
+.Fa flush
+is set to
+.Dv Z_NO_FLUSH ,
+which allows
+.Fn deflate
+to decide how much data to accumulate before producing output,
+in order to maximize compression.
+.
+.Pp
+If the parameter
+.Fa flush
+is set to
+.Dv Z_SYNC_FLUSH ,
+all pending output is flushed to the output buffer
+and the output is aligned on a byte boundary,
+so that the decompressor can get all input data available so far.
+.Po
+In particular
+.Fa avail_in
+is zero after the call if enough output space
+has been provided before the call.
+.Pc \&
+Flushing may degrade compression for some compression algorithms
+and so it should be used only when necessary.
+This completes the current deflate block
+and follows it with an empty stored block
+that is three bits plus filler bits to the next byte,
+followed by four bytes
+(00 00 ff ff).
+.
+.Pp
+If
+.Fa flush
+is set to
+.Dv Z_PARTIAL_FLUSH ,
+all pending output is flushed to the output buffer,
+but the output is not aligned to a byte boundary.
+All of the input data so far will be available to the decompressor,
+as for
+.Dv Z_SYNC_FLUSH .
+This completes the current deflate block
+and follows it with an empty fixed codes block
+that is 10 bits long.
+This assures that enough bytes are output
+in order for the decompressor to finish the block
+before the empty fixed codes block.
+.
+.Pp
+If
+.Fa flush
+is set to
+.Dv Z_BLOCK ,
+a deflate block is completed and emitted,
+as for
+.Dv Z_SYNC_FLUSH ,
+but the output is not aligned on a byte boundary,
+and up to seven bits of the current block
+are held to be written as the next byte
+after the next deflate block is completed.
+In this case,
+the decompressor may not be provided enough bits
+at this point in order to complete decompression
+of the data provided so far to the compressor.
+It may need to wait for the next block to be emitted.
+This is for advanced applications
+that need to control the emission of deflate blocks.
+.
+.Pp
+If
+.Fa flush
+is set to
+.Dv Z_FULL_FLUSH ,
+all output is flushed as with
+.Dv Z_SYNC_FLUSH ,
+and the compression state is reset
+so that decompression can restart from this point
+if previous compressed data has been damaged
+or if random access is desired.
+Using
+.Dv Z_FULL_FLUSH
+too often can seriously degrade compression.
+.
+.Pp
+If
+.Fn deflate
+returns with
+.Fa avail_out
+== 0,
+this function must be called again
+with the same value of the
+.Fa flush
+parameter
+and more output space
+.Po
+updated
+.Fa avail_out
+.Pc ,
+until the flush is complete
+.Po
+.Fn deflate
+returns with non-zero
+.Fa avail_out
+.Pc .
+In the case of a
+.Dv Z_FULL_FLUSH
+or
+.Dv Z_SYNC_FLUSH ,
+make sure that
+.Fa avail_out
+is greater than six
+to avoid repeated flush markers
+due to
+.Fa avail_out
+== 0
+on return.
+.
+.Pp
+If the parameter
+.Fa flush
+is set to
+.Dv Z_FINISH ,
+pending input is processed,
+pending output is flushed and
+.Fn deflate
+returns with
+.Dv Z_STREAM_END
+if there was enough output space.
+If
+.Fn deflate
+returns with
+.Dv Z_OK
+or
+.Dv Z_BUF_ERROR ,
+this function must be called again with
+.Dv Z_FINISH
+and more output space
+.Pq updated Fa avail_out
+but no more input data,
+until it returns with
+.Dv Z_STREAM_END
+or an error.
+After
+.Fn deflate
+has returned
+.Dv Z_STREAM_END ,
+the only possible operations on the stream are
+.Xr deflateReset 3
+or
+.Xr deflateEnd 3 .
+.
+.Pp
+.Dv Z_FINISH
+can be used in the first
+.Fn deflate
+call after
+.Xr deflateInit 3
+if all the compression is to be done in a single step.
+In order to complete in one call,
+.Fa avail_out
+must be at least the value returned by
+.Xr deflateBound 3 .
+Then
+.Fn deflate
+is guaranteed to return
+.Dv Z_STREAM_END .
+If not enough output space is provided,
+.Fn deflate
+will not return
+.Dv Z_STREAM_END ,
+and it must be called again as described above.
+.
+.Pp
+.Fn deflate
+sets
+.Fa strm->adler
+to the Adler-32 checksum
+of all input read so far
+.Po
+that is,
+.Fa total_in
+bytes
+.Pc .
+If a gzip stream is being generated,
+then
+.Fa strm->adler
+will be the CRC-32 checksum of the input read so far.
+See
+.Xr deflateInit2 3 .
+.
+.Pp
+.Fn deflate
+may update
+.Fa strm->data_type
+if it can make a good guess
+about the input data type
+.Po
+.Dv Z_BINARY
+or
+.Dv Z_TEXT
+.Pc .
+If in doubt,
+the date is considered binary.
+This field is only for information purposes
+and does not affect the compression algorithm in any manner.
+.
+.Sh RETURN VALUES
+.Fn deflate
+returns
+.Dv Z_OK
+if some progress has been made
+(more input processed or more output produced),
+.Dv Z_STREAM_END
+if all input has been consumed
+and all output has been produced
+.Po
+only when
+.Fa flush
+is set to
+.Dv Z_FINISH
+.Pc ,
+.Dv Z_STREAM_ERROR
+if the stream state was inconsistent
+.Po
+for example if
+.Fa next_in
+or
+.Fa next_out
+was
+.Dv Z_NULL
+or the state was inadvertently written over
+by the application
+.Pc ,
+or
+.Dv Z_BUF_ERROR
+if no progress is possible
+.Po
+for example
+.Fa avail_in
+or
+.Fa avail_out
+was zero
+.Pc .
+Note that
+.Dv Z_BUF_ERROR
+is not fatal,
+and
+.Fn deflate
+can be called again with more input and more output space
+to continue compressing.
+.
+.Sh SEE ALSO
+.Xr deflateEnd 3 ,
+.Xr deflateInit 3 ,
+.Xr deflatePending 3 ,
+.Xr inflate 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateBound.3 b/doc/zlib/deflateBound.3
new file mode 100644
index 00000000..be97494c
--- /dev/null
+++ b/doc/zlib/deflateBound.3
@@ -0,0 +1,71 @@
+.Dd January 15, 2017
+.Dt DEFLATEBOUND 3
+.Os
+.
+.Sh NAME
+.Nm deflateBound
+.Nd compressed size upper bound
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn deflateBound "z_streamp strm" "uLong sourceLen"
+.
+.Sh DESCRIPTION
+.Fn deflateBound
+returns an upper bound
+on the compressed size
+after deflation of
+.Fa sourceLen
+bytes.
+It must be called after
+.Xr deflateInit 3
+or
+.Xr deflateInit2 3 ,
+and after
+.Xr deflateSetHeader 3 ,
+if used.
+This would be used
+to allocate an output buffer
+for deflation in a single pass,
+and so would be called before
+.Xr deflate 3 .
+If that first
+.Fn deflate
+call is provided the
+.Fa sourceLen
+input bytes,
+an output buffer allocated
+to the size returned by
+.Fn deflateBound ,
+and the flush value
+.Dv Z_FINISH ,
+then
+.Fn deflate
+is guaranteed to return
+.Dv Z_STREAM_END .
+Note that it is possible
+for the compressed size
+to be larger than the value returned by
+.Fn deflateBound
+if flush options other than
+.Dv Z_FINISH
+or
+.Dv Z_NO_FLUSH
+are used.
+.
+.Sh SEE ALSO
+.Xr compressBound 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateCopy.3 b/doc/zlib/deflateCopy.3
new file mode 100644
index 00000000..f20e0a9e
--- /dev/null
+++ b/doc/zlib/deflateCopy.3
@@ -0,0 +1,66 @@
+.Dd January 15, 2017
+.Dt DEFLATECOPY 3
+.Os
+.
+.Sh NAME
+.Nm deflateCopy
+.Nd copy deflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflateCopy "z_streamp dest" "z_streamp source"
+.
+.Sh DESCRIPTION
+Sets the destination stream
+as a complete copy of the source stream.
+.
+.Pp
+This function can be useful when
+several compression strategies will be tried,
+for example when there are several ways of
+pre-processing the input data with a filter.
+The streams that will be discarded
+should then be freed by calling
+.Xr deflateEnd 3 .
+Note that
+.Fn deflateCopy
+duplicates the internal compression state
+which can be quite large,
+so this strategy is slow
+and can consume lots of memory.
+.
+.Sh RETURN VALUES
+.Fn deflateCopy
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent
+.Po
+such as
+.Fa zalloc
+being
+.Dv Z_NULL
+.Pc .
+.Fa msg
+is left unchanged
+in both source and destination.
+.
+.Sh SEE ALSO
+.Xr deflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateEnd.3 b/doc/zlib/deflateEnd.3
new file mode 100644
index 00000000..0abaabe1
--- /dev/null
+++ b/doc/zlib/deflateEnd.3
@@ -0,0 +1,50 @@
+.Dd January 15, 2017
+.Dt DEFLATEEND 3
+.Os
+.
+.Sh NAME
+.Nm deflateEnd
+.Nd free deflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflateEnd "z_streamp strm"
+.
+.Sh DESCRIPTION
+All dynamically allocated data structures
+for this stream are freed.
+This function discards any unprocessed input
+and does not flush any pending output.
+.
+.Sh RETURN VALUES
+.Fn deflateEnd
+returns
+.Dv Z_OK
+if success,
+.Dv Z_STREAM_ERROR
+if the stream state was inconsistent,
+.Dv Z_DATA_ERROR
+if the stream was freed prematurely
+(some input or output was discarded).
+In the error case,
+.Fa msg
+may be set but then points to a static string
+(which must not be deallocated).
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr deflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateGetDictionary.3 b/doc/zlib/deflateGetDictionary.3
new file mode 100644
index 00000000..b9dabfe2
--- /dev/null
+++ b/doc/zlib/deflateGetDictionary.3
@@ -0,0 +1,79 @@
+.Dd January 15, 2017
+.Dt DEFLATEGETDICTIONARY 3
+.Os
+.
+.Sh NAME
+.Nm deflateGetDictionary
+.Nd deflate sliding dictionary
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo deflateGetDictionary
+.Fa "z_streamp strm"
+.Fa "Bytef *dictionary"
+.Fa "uInt *dictLength"
+.Fc
+.
+.Sh DESCRIPTION
+Returns the sliding dictionary
+being maintained by deflate.
+.Fa dictLength
+is set to the number of bytes in the dictionary,
+and that many bytes are copied to
+.Fa dictionary .
+.Fa dictionary
+must have enough space,
+where 32768 bytes is always enough.
+If
+.Fn deflateGetDictionary
+is called with
+.Fa dictionary
+equal to
+.Dv Z_NULL ,
+then only the dictionary length is returned,
+and nothing is copied.
+Similarly,
+if
+.Fa dictLength
+is
+.Dv Z_NULL ,
+then it is not set.
+.
+.Pp
+.Fn deflateGetDictionary
+may return a length less than the window size,
+even when more than the window size in input
+has been provided.
+It may return up to 258 bytes less in that case,
+due to how zlib's implementation of deflate
+manages the sliding window and lookahead for matches,
+where matches can be up to 258 bytes long.
+If the application needs the last window-size bytes of input,
+then that would need to be saved by the application
+outside of zlib.
+.
+.Sh RETURN VALUES
+.Fn deflateGetDictionary
+returns
+.Dv Z_OK
+on success,
+or
+.Dv Z_STREAM_ERROR
+if the stream state is inconsistent.
+.
+.Sh SEE ALSO
+.Xr deflateSetDictionary 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateInit.3 b/doc/zlib/deflateInit.3
new file mode 100644
index 00000000..52179883
--- /dev/null
+++ b/doc/zlib/deflateInit.3
@@ -0,0 +1,178 @@
+.Dd January 15, 2017
+.Dt DEFLATEINIT 3
+.Os
+.
+.Sh NAME
+.Nm deflateInit
+.Nd initialize deflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.
+.Ft typedef voidpf
+.Fo (*alloc_func)
+.Fa "voidpf opaque"
+.Fa "uInt items"
+.Fa "uInt size"
+.Fc
+.
+.Ft typedef void
+.Fo (*free_func)
+.Fa "voidpf opaque"
+.Fa "voidpf address"
+.Fc
+.
+.Bd -literal
+typedef struct z_stream_s {
+	z_const Bytef *next_in;
+	uInt     avail_in;
+	uLong    total_in;
+
+	Bytef    *next_out;
+	uInt     avail_out;
+	uLong    total_out;
+
+	z_const char *msg;
+	struct internal_state FAR *state;
+
+	alloc_func zalloc;
+	free_func  zfree;
+	voidpf     opaque;
+
+	int     data_type;
+	uLong   adler;
+	uLong   reserved;
+} z_stream;
+.Ed
+.
+.Pp
+.Vt typedef z_stream FAR *z_streamp;
+.
+.Ft int
+.Fn deflateInit "z_streamp strm" "int level"
+.
+.Sh DESCRIPTION
+Initializes the internal stream state for compression.
+The fields
+.Fa zalloc ,
+.Fa zfree
+and
+.Fa opaque
+must be initialized before by the caller.
+If
+.Fa zalloc
+and
+.Fa zfree
+are set to
+.Dv Z_NULL ,
+.Fn deflateInit
+updates them to use default allocation functions.
+.Fn deflateInit
+does not perform any compression:
+this will be done by
+.Xr deflate 3 .
+.
+.Pp
+The compression
+.Fa level
+must be
+.Dv Z_DEFAULT_COMPRESSION ,
+or between 0 and 9:
+1 gives best speed,
+9 gives best compression,
+0 gives no compression at all
+(the input data is simply copied a block at a time).
+.Dv Z_DEFAULT_COMPRESSION
+requests a default compromise between speed and compression
+(currently equivalent to level 6).
+.
+.Pp
+The fields of
+.Vt z_stream
+are as follows:
+.
+.Bl -tag -width "data_type"
+.It Fa next_in
+next input byte
+.It Fa avail_in
+number of bytes available at
+.Fa next_in
+.It Fa total_in
+total number of input bytes read so far
+.It Fa next_out
+next output byte will go here
+.It Fa avail_out
+remaining free space at
+.Fa next_out
+.It Fa total_out
+total number of bytes output so far
+.It Fa msg
+last error message,
+.Dv NULL
+if no error
+.It Fa state
+not visible by applications
+.It Fa zalloc
+used to allocate the internal state
+.It Fa zfree
+used to free the internal state
+.It Fa opaque
+private data object passed to
+.Fa zalloc
+and
+.Fa zfree
+.It data_type
+best guess about the data type:
+binary or text for
+.Xr deflate 3 ,
+or the decoding state for
+.Xr inflate 3
+.It adler
+Adler-32 or CRC-32 value of the uncompressed data
+.It reserved
+reserved for future use
+.El
+.
+.Sh RETURN VALUES
+.Fn deflateInit
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_STREAM_ERROR
+if
+.Fa level
+is not a valid compression level,
+or
+.Dv Z_VERSION_ERROR
+if the zlib library version
+.Pq Xr zlibVersion 3
+is incompatible with the version assumed by the caller
+.Pq Dv ZLIB_VERSION .
+.Fa msg
+is set to null
+if there is no error message.
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr deflateCopy 3 ,
+.Xr deflateEnd 3 ,
+.Xr deflateInit2 3 ,
+.Xr deflatePrime 3 ,
+.Xr deflateReset 3 ,
+.Xr deflateSetDictionary 3 ,
+.Xr deflateTune 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateInit2.3 b/doc/zlib/deflateInit2.3
new file mode 100644
index 00000000..a7d68b99
--- /dev/null
+++ b/doc/zlib/deflateInit2.3
@@ -0,0 +1,227 @@
+.Dd January 15, 2017
+.Dt DEFLATEINIT2 3
+.Os
+.
+.Sh NAME
+.Nm deflateInit2
+.Nd deflate compression options
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo deflateInit2
+.Fa "z_streamp strm"
+.Fa "int level"
+.Fa "int method"
+.Fa "int windowBits"
+.Fa "int memLevel"
+.Fa "int strategy"
+.Fc
+.
+.Sh DESCRIPTION
+This is another version of
+.Xr deflateInit 3
+with more compression options.
+The fields
+.Fa next_in ,
+.Fa zalloc ,
+.Fa zfree
+and
+.Fa opaque
+must be initialized before by the caller.
+.
+.Pp
+The
+.Fa method
+parameter is the compression method.
+It must be
+.Dv Z_DEFLATED
+in this version of the library.
+.
+.Pp
+The
+.Fa windowBits
+parameter is the base two logarithm
+of the window size
+(the size of the history buffer).
+It should be in the range 8..15
+for this version of the library.
+Larger values of this parameter
+result in better compression
+at the expense of memory usage.
+The default value is 15 if
+.Xr deflateInit 3
+is used instead.
+.
+.Pp
+For the current implementation of
+.Xr deflate 3 ,
+a
+.Fa windowBits
+value of 8
+(a window size of 256 bytes)
+is not supported.
+As a result,
+a request for 8
+will result in 9
+(a 512-byte window).
+In that case,
+providing 8 to
+.Xr inflateInit2 3
+will result in an error
+when the zlib header with 9
+is checked against the initialization of
+.Xr inflate 3 .
+The remedy is to not use 8 with
+.Fn deflateInit2
+with this initialization,
+or at least in that case use 9 with
+.Xr inflateInit2 3 .
+.
+.Pp
+.Fa windowBits
+can also be -8..-15 for raw deflate.
+In this case,
+.Fa -windowBits
+determines the window size.
+.Xr deflate 3
+will then generate raw deflate data
+with no zlib header or trailer,
+and will not compute a check value.
+.
+.Pp
+.Fa windowBits
+can also be greater than 15
+for optional gzip encoding.
+Add 16 to
+.Fa windowBits
+to write a simple gzip header and trailer
+around the compressed data
+instead of a zlib wrapper.
+The gzip header will have
+no file name,
+no extra data,
+no comment,
+no modification time (set to zero),
+no header CRC,
+and the operating system will be set
+to the appropriate value,
+if the operating system was determined at compile time.
+If a gzip stream is being written,
+.Fa strm->adler
+is a CRC-32 instead of an Adler-32.
+.
+.Pp
+For raw deflate or gzip encoding,
+a request for a 256-byte window
+is rejected as invalid,
+since only the zlib header provides
+a means of transmitting the window size
+to the decompressor.
+.
+.Pp
+The
+.Fa memLevel
+parameter specifies how much memory should be allocated
+for the internal compression state.
+.Fa memLevel=1
+uses minimum memory
+but is slow and reduces compression ratio;
+.Fa memLevel=9
+uses maximum memory for optimal speed.
+The default value is 8.
+See
+.In zconf.h
+for total memory usage
+as a function of
+.Fa windowBits
+and
+.Fa memLevel .
+.
+.Pp
+The
+.Fa strategy
+parameter is used to tune the compression algorithm.
+Use the value
+.Dv Z_DEFAULT_STRATEGY
+for normal data,
+.Dv Z_FILTERED
+for data produced by a filter
+(or predictor),
+.Dv Z_HUFFMAN_ONLY
+to force Huffman encoding only
+(no string match),
+or
+.Dv Z_RLE
+to limit match distances to one
+(run-length encoding).
+Filtered data consists mostly of small values
+with a somewhat random distribution.
+In this case,
+the compression algorithm
+is tuned to compress them better.
+The effect of
+.Dv Z_FILTERED
+is to force more Huffman coding
+and less string matching;
+it is somewhat intermediate between
+.Dv Z_DEFAULT_STRATEGY
+and
+.Dv Z_HUFFMAN_ONLY .
+.Dv Z_RLE
+is designed to be almost as fast as
+.Dv Z_HUFFMAN_ONLY ,
+but give better compression for PNG image data.
+The
+.Fa strategy
+parameter only affects the compression ratio
+but not the correctness of the compressed output
+even if it is not set appropriately.
+.Dv Z_FIXED
+prevents the use of dynamic Huffman codes,
+allowing for a simpler decoder
+for special applications.
+.
+.Pp
+.Fn deflateInit2
+does not perform any compression:
+this will be done by
+.Xr deflate 3 .
+.
+.Sh RETURN VALUES
+.Fn deflateInit2
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_STREAM_ERROR
+if any parameter is invalid
+(such as invalid method),
+or
+.Dv Z_VERSION_ERROR
+if the zlib library version
+.Pq Xr zlibVersion 3
+is incompatible with the version assumed by the caller
+.Pq Dv ZLIB_VERSION .
+.Fa msg
+is set to null if there is no error message.
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr deflateInit 3 ,
+.Xr deflateParams 3 ,
+.Xr deflateSetHeader 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateParams.3 b/doc/zlib/deflateParams.3
new file mode 100644
index 00000000..9eb5ca16
--- /dev/null
+++ b/doc/zlib/deflateParams.3
@@ -0,0 +1,123 @@
+.Dd January 15, 2017
+.Dt DEFLATEPARAMS 3
+.Os
+.
+.Sh NAME
+.Nm deflateParams
+.Nd update compression level and strategy
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflateParams "z_streamp strm" "int level" "int strategy"
+.
+.Sh DESCRIPTION
+Dynamically update the compression level
+and compression strategy.
+The interpretation of
+.Fa level
+and
+.Fa strategy
+is as in
+.Xr deflateInit2 3 .
+This can be used to switch between compression
+and straight copy of the input data,
+or to switch to a different kind of input data
+requiring a different strategy.
+If the compression approach
+(which is a function of the level)
+or the strategy is changed,
+and if any input has been consumed
+in a previous
+.Xr deflate 3
+call,
+then the input available so far is compressed
+with the old level and strategy using
+.Fn deflate strm Z_BLOCK .
+There are three approaches
+for the compression levels
+0, 1..3, and 4..9 respectively.
+The new level and strategy
+will take effect at the next call of
+.Xr deflate 3 .
+.
+.Pp
+If a
+.Fn deflate strm Z_BLOCK
+is performed by
+.Fn deflateParams ,
+and it does not have enough output space to complete,
+then the parameter change will not take effect.
+In this case,
+.Fn deflateParams
+can be called again
+with the same parameters
+and more output space
+to try again.
+.
+.Pp
+In order to assure a change in the parameters
+on the first try,
+the deflate stream should be flushed using
+.Xr deflate 3
+with
+.Dv Z_BLOCK
+or other flush request until
+.Fa strm.avail_out
+is not zero,
+before calling
+.Fn deflateParams .
+Then no more input data
+should be provided before the
+.Fn deflateParams
+call.
+If this is done,
+the old level and strategy
+will be applied
+to the data compressed before
+.Fn deflateParams ,
+and the new level and strategy
+will be applied
+to the data compressed after
+.Fn deflateParams .
+.
+.Sh RETURN VALUES
+.Fn deflateParams
+returns
+.Dv Z_OK
+on success,
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent
+or if a parameter was invalid,
+or
+.Dv Z_BUF_ERROR
+if there was not enough output space
+to complete the compression
+of the available input data
+before a change in the strategy or approach.
+Note that in the case of a
+.Dv Z_BUF_ERROR ,
+the parameters are not changed.
+A return value of
+.Dv Z_BUF_ERROR
+is not fatal,
+in which case
+.Fn deflateParams
+can be retried
+with more output space.
+.
+.Sh SEE ALSO
+.Xr deflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflatePending.3 b/doc/zlib/deflatePending.3
new file mode 100644
index 00000000..35fa6d38
--- /dev/null
+++ b/doc/zlib/deflatePending.3
@@ -0,0 +1,56 @@
+.Dd January 15, 2017
+.Dt DEFLATEPENDING 3
+.Os
+.
+.Sh NAME
+.Nm deflatePending
+.Nd pending deflate output
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflatePending "z_streamp strm" "unsigned *pending" "int *bits"
+.
+.Sh DESCRIPTION
+.Fn deflatePending
+returns the number of bytes and bits
+of output that have been generated,
+but not yet provided in the available output.
+The bytes not provided would be due to
+the available output space having been consumed.
+The number of bits of output not provided
+are between 0 and 7,
+where they await more bits to join them
+in order to fill out a full byte.
+If
+.Fa pending
+or
+.Fa bits
+are
+.Dv Z_NULL ,
+then those values are not set.
+.
+.Sh RETURN VALUES
+.Fn deflatePending
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr deflate 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflatePrime.3 b/doc/zlib/deflatePrime.3
new file mode 100644
index 00000000..10a2924b
--- /dev/null
+++ b/doc/zlib/deflatePrime.3
@@ -0,0 +1,64 @@
+.Dd January 15, 2017
+.Dt DEFLATEPRIME 3
+.Os
+.
+.Sh NAME
+.Nm deflatePrime
+.Nd insert bits in deflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflatePrime "z_streamp strm" "int bits" "int value"
+.
+.Sh DESCRIPTION
+.Fn deflatePrime
+inserts bits in the deflate output stream.
+The intent is that this function
+is used to start off the deflate output
+with the bits leftover
+from a previous deflate stream
+when appending to it.
+As such,
+this function can only be used for raw deflate,
+and must be used before the first
+.Xr deflate 3
+call
+after a
+.Xr deflateInit2 3
+or
+.Xr deflateReset 3 .
+.Fa bits
+must be less than or equal to 16,
+and that many of the least significant bits of
+.Fa value
+will be inserted in the output.
+.
+.Sh RETURN VALUES
+.Fn deflatePrime
+returns
+.Dv Z_OK
+if success,
+.Dv Z_BUF_ERROR
+if there was not enough room
+in the internal buffer
+to insert the bits,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr deflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateReset.3 b/doc/zlib/deflateReset.3
new file mode 100644
index 00000000..1a18c507
--- /dev/null
+++ b/doc/zlib/deflateReset.3
@@ -0,0 +1,57 @@
+.Dd January 15, 2017
+.Dt DEFLATERESET 3
+.Os
+.
+.Sh NAME
+.Nm deflateReset
+.Nd reset deflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn deflateReset "z_streamp strm"
+.
+.Sh DESCRIPTION
+This function is equivalent to
+.Xr deflateEnd 3
+followed by
+.Xr deflateInit 3 ,
+but does not free and reallocate
+the internal compression state.
+The stream will leave the compression level
+and any other attributes
+that may have been set unchanged.
+.
+.Sh RETURN VALUES
+.Fn deflateReset
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent
+.Po
+such as
+.Fa zalloc
+or
+.Fa state
+being
+.Dv Z_NULL
+.Pc .
+.
+.Sh SEE ALSO
+.Xr deflateEnd 3 ,
+.Xr deflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateSetDictionary.3 b/doc/zlib/deflateSetDictionary.3
new file mode 100644
index 00000000..3e66d3cf
--- /dev/null
+++ b/doc/zlib/deflateSetDictionary.3
@@ -0,0 +1,142 @@
+.Dd January 15, 2017
+.Dt DEFLATESETDICTIONARY 3
+.Os
+.
+.Sh NAME
+.Nm deflateSetDictionary
+.Nd initialize compression dictionary
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo deflateSetDictionary
+.Fa "z_streamp strm"
+.Fa "const Bytef *dictionary"
+.Fa "uInt dictLength"
+.Fc
+.
+.Sh DESCRIPTION
+.Fn deflateSetDictionary
+initializes the compression dictionary
+from the given byte sequence
+without producing any compressed output.
+When using the zlib format,
+this function must be called immediately after
+.Xr deflateInit 3 ,
+.Xr deflateInit2 3 ,
+or
+.Xr deflateReset 3 ,
+and before any call of
+.Xr deflate 3 .
+When doing raw deflate,
+this function must be called
+either before any call of
+.Xr deflate 3 ,
+or immediately after the completion of a deflate block,
+i.e. after all input has been consumed
+and all output has been delivered
+when using any of the flush options
+.Dv Z_BLOCK ,
+.Dv Z_PARTIAL_FLUSH ,
+.Dv Z_SYNC_FLUSH ,
+or
+.Dv Z_FULL_FLUSH .
+The compressor and decompressor
+must use exactly the same dictionary
+.Po
+see
+.Xr inflateSetDictionary 3
+.Pc .
+.
+.Pp
+The dictionary should consist of strings
+(byte sequences)
+that are likely to be encountered later
+in the data to be compressed,
+with the most commonly used strings
+preferably put towards the end of the dictionary.
+Using a dictionary is most useful
+when the data to be compressed is short
+and can be predicted with good accuracy;
+the data can then be compressed better than
+with the default empty dictionary.
+.
+.Pp
+Depending on the size of
+the compression data structures selected by
+.Xr deflateInit 3
+or
+.Xr deflateInit2 3 ,
+a part of the dictionary may in effect be discarded,
+for example if the dictionary is larger
+than the window size provided in
+.Xr deflateInit 3
+or
+.Xr deflateInit2 3 .
+Thus the strings most likely to be useful
+should be put at the end of the dictionary,
+not at the front.
+In addition,
+the current implementation of deflate
+will use at most the window size minus 262 bytes
+of the provided dictionary.
+.
+.Pp
+Upon return of this function,
+.Fa strm->adler
+is set to the Adler-32 value
+of the dictionary;
+the decompressor may later use this value
+to determine which dictionary has been used
+by the compressor.
+(The Adler-32 value applies to the whole dictionary
+even if only a subset of the dictionary
+is actually used by the compressor.)
+If a raw deflate was requested,
+then the Adler-32 value is not computed and
+.Fa strm->adler
+is not set.
+.
+.Pp
+.Fn deflateSetDictionary
+does not perform any compression:
+this will be done by
+.Xr deflate 3 .
+.
+.Sh RETURN VALUES
+.Fn deflateSetDictionary
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if a parameter is invalid
+.Po
+e.g. dictionary being
+.Dv Z_NULL
+.Pc
+or the stream state is inconsistent
+.Po
+for example if
+.Xr deflate 3
+has already been called for this stream
+or if not at a block boundary
+for raw deflate
+.Pc .
+.
+.Sh SEE ALSO
+.Xr deflateGetDictionary 3 ,
+.Xr inflateSetDictionary 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateSetHeader.3 b/doc/zlib/deflateSetHeader.3
new file mode 100644
index 00000000..03d4f4d3
--- /dev/null
+++ b/doc/zlib/deflateSetHeader.3
@@ -0,0 +1,180 @@
+.Dd January 15, 2017
+.Dt DEFLATESETHEADER 3
+.Os
+.
+.Sh NAME
+.Nm deflateSetHeader
+.Nd set gzip header
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.
+.Bd -literal
+typedef struct gz_header_s {
+	int     text;
+	uLong   time;
+	int     xflags;
+	int     os;
+	Bytef   *extra;
+	uInt    extra_len;
+	uInt    extra_max;
+	Bytef   *name;
+	uInt    name_max;
+	Bytef   *comment;
+	uInt    comm_max;
+	int     hcrc;
+	int     done;
+} gz_header;
+.Ed
+.
+.Pp
+.Vt typedef gz_header FAR *gz_headerp;
+.
+.Ft int
+.Fn deflateSetHeader "z_streamp strm" "gz_headerp head"
+.
+.Sh DESCRIPTION
+.Fn deflateSetHeader
+provides gzip header information
+for when a gzip stream
+is requested by
+.Xr deflateInit2 3 .
+.Fn deflateSetHeader
+may be called after
+.Xr deflateInit2 3
+or
+.Xr deflateReset 3
+and before the first call of
+.Xr deflate 3 .
+The
+text,
+time,
+OS,
+extra field,
+name,
+and comment
+information in the provided
+.Vt gz_header
+structure
+are written to the gzip header
+.Po
+.Fa xflag
+is ignored \(em
+the extra flags are set
+according to the compression level
+.Pc .
+The caller must assure that,
+if not
+.Dv Z_NULL ,
+.Fa name
+and
+.Fa comment
+are terminated with a zero byte,
+and that if
+.Fa extra
+is not
+.Dv Z_NULL ,
+that
+.Fa extra_len
+bytes are available there.
+If
+.Fa hcrc
+is true,
+a gzip header CRC is included.
+Note that the current versions
+of the command-line version of
+.Xr gzip 1
+(up through version 1.3.x)
+do not support header CRCs,
+and will report that it is a
+"multi-part gzip file"
+and give up.
+.
+.Pp
+If
+.Fn deflateSetHeader
+is not used,
+the default gzip header has
+text false,
+the time set to zero,
+and OS set to 255,
+with no extra, name, or comment fields.
+The gzip header is returned
+to the default state by
+.Xr deflateReset 3 .
+.
+.Pp
+The fields of
+.Vt gz_header
+are as follows:
+.
+.Bl -tag -width "extra_len"
+.It Fa text
+true if compressed data believed to be text
+.It Fa time
+modification time
+.It Fa xflags
+extra flags
+(not used when writing a gzip file)
+.It Fa os
+operating system
+.It Fa extra
+pointer to extra field or
+.Dv Z_NULL
+if none
+.It Fa extra_len
+extra field length
+.Po
+valid if
+.Fa extra
+!=
+.Dv Z_NULL
+.Pc
+.It Fa extra_max
+space at extra
+(only when reading header)
+.It Fa name
+pointer to zero-terminated file name or
+.Dv Z_NULL
+.It Fa name_max
+space at
+.Fa name
+(only when reading header)
+.It Fa comment
+pointer to zero-terminated comment or
+.Dv Z_NULL
+.It Fa comm_max
+space at comment
+(only when reading header)
+.It Fa hcrc
+true if there was or will be a header CRC
+.It Fa done
+true when done reading gzip header
+(not used when writing a gzip file)
+.El
+.
+.Sh RETURN VALUES
+.Fn deflateSetHeader
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr deflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/deflateTune.3 b/doc/zlib/deflateTune.3
new file mode 100644
index 00000000..ea4dd915
--- /dev/null
+++ b/doc/zlib/deflateTune.3
@@ -0,0 +1,70 @@
+.Dd January 15, 2017
+.Dt DEFLATETUNE 3
+.Os
+.
+.Sh NAME
+.Nm deflateTune
+.Nd fine tune compression parameters
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo deflateTune
+.Fa "z_streamp strm"
+.Fa "int good_length"
+.Fa "int max_lazy"
+.Fa "int nice_length"
+.Fa "int max_chain"
+.Fc
+.
+.Sh DESCRIPTION
+Fine tune deflate's internal compression parameters.
+This should only be used
+by someone who understands the algorithm
+used by zlib's deflate
+for searching for the best matching string,
+and even then only by the most fanatic optimizer
+trying to squeeze out the last compressed bit
+for their specific input data.
+Read the
+.Pa deflate.c
+source code for the meaning of the
+.Fa max_lazy ,
+.Fa good_length ,
+.Fa nice_length ,
+and
+.Fa max_chain
+parameters.
+.
+.Pp
+.Fn deflateTune
+can be called after
+.Xr deflateInit 3
+or
+.Xr deflateInit2 3 .
+.
+.Sh RETURN VALUES
+.Fn deflateTune
+returns
+.Dv Z_OK
+on success,
+or
+.Dv Z_STREAM_ERROR
+for an invalid deflate stream.
+.
+.Sh SEE ALSO
+.Xr deflateInit 3 ,
+.Xr deflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzbuffer.3 b/doc/zlib/gzbuffer.3
new file mode 100644
index 00000000..92438c48
--- /dev/null
+++ b/doc/zlib/gzbuffer.3
@@ -0,0 +1,59 @@
+.Dd January 15, 2017
+.Dt GZBUFFER 3
+.Os
+.
+.Sh NAME
+.Nm gzbuffer
+.Nd set buffer size
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzbuffer "gzFile file" "unsigned size"
+.
+.Sh DESCRIPTION
+Set the internal buffer size
+used by this library's functions.
+The default buffer size is 8192 bytes.
+This function must be called after
+.Xr gzopen 3
+or
+.Xr gzdopen 3 ,
+and before any other calls
+that read or write the file.
+The buffer memory allocation
+is always deferred to the first read or write.
+Three times that size in buffer space is allocated.
+A larger buffer size of,
+for example,
+64K or 128K bytes
+will noticeably increase the speed
+of decompression (reading).
+.
+.Pp
+The new buffer size also affects
+the maximum length for
+.Xr gzprintf 3 .
+.
+.Sh RETURN VALUES
+.Fn gzbuffer
+returns 0 on success,
+or -1 on failure,
+such as being called too late.
+.
+.Sh SEE ALSO
+.Xr gzopen 3 ,
+.Xr gzprintf 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzclose.3 b/doc/zlib/gzclose.3
new file mode 100644
index 00000000..bfcc583e
--- /dev/null
+++ b/doc/zlib/gzclose.3
@@ -0,0 +1,97 @@
+.Dd January 15, 2017
+.Dt GZCLOSE 3
+.Os
+.
+.Sh NAME
+.Nm gzclose ,
+.Nm gzclose_r ,
+.Nm gzclose_w
+.Nd close compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzclose "gzFile file"
+.Ft int
+.Fn gzclose_r "gzFile file"
+.Ft int
+.Fn gzclose_w "gzFile file"
+.
+.Sh DESCRIPTION
+Flushes all pending output if necessary,
+closes the compressed file
+and deallocates the (de)compression state.
+Note that once
+.Fa file
+is closed,
+you cannot call
+.Xr gzerror 3
+with
+.Fa file ,
+since its structures
+have been deallocated.
+.Fn gzclose
+must not be called more than once
+on the same file,
+just as
+.Xr free 3
+must not be called more than once
+on the same allocation.
+.
+.Pp
+.Fn gzclose_r
+and
+.Fn gzclose_w
+are the same as
+.Fn gzclose ,
+but
+.Fn gzclose_r
+is only for use when reading,
+and
+.Fn gzclose_w
+is only for use when writing or appending.
+The advantage to using these instead of
+.Fn gzclose
+is that they avoid linking in
+zlib compression or decompression code
+that is not used when only reading
+or only writing respectively.
+If
+.Fn gzclose
+is used,
+then both compression and decompression code
+will be included in the application
+when linking to a static zlib library.
+.
+.Sh RETURN VALUES
+.Fn gzclose
+will return
+.Dv Z_STREAM_ERROR
+if
+.Fa file
+is not valid,
+.Dv Z_ERRNO
+on a file operator error,
+.Dv Z_MEM_ERROR
+if out of memory,
+.Dv Z_BUF_ERROR
+if the last read ended in the middle of a gzip stream,
+or
+.Dv Z_OK
+on success.
+.
+.Sh SEE ALSO
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzdirect.3 b/doc/zlib/gzdirect.3
new file mode 100644
index 00000000..640fd4c5
--- /dev/null
+++ b/doc/zlib/gzdirect.3
@@ -0,0 +1,85 @@
+.Dd January 15, 2017
+.Dt GZDIRECT 3
+.Os
+.
+.Sh NAME
+.Nm gzdirect
+.Nd check direct copy
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzdirect "gzFile file"
+.
+.Sh DESCRIPTION
+Returns true (1) if
+.Fa file
+is being copied directly while reading,
+or false (0) if
+.Fa file
+is a gzip stream being decompressed.
+.
+.Pp
+If the input file is empty,
+.Fn gzdirect
+will return true,
+since the input does not contain a gzip stream.
+.
+.Pp
+If
+.Fn gzdirect
+is used immediately after
+.Xr gzopen 3
+or
+.Xr gzdopen 3
+it will cause buffers to be allocated
+to allow reading the file
+to determine if it is a gzip file.
+Therefore if
+.Xr gzbuffer 3
+is used,
+it should be called before
+.Fn gzdirect .
+.
+.Pp
+When writing,
+.Fn gzdirect
+returns true (1)
+if transparent writing was requested
+.Po
+.Dq wT
+for the
+.Xr gzopen 3
+mode
+.Pc ,
+or false (0) otherwise.
+.Po
+Note:
+.Fn gzdirect
+is not needed when writing.
+Transparent writing
+must be explicitly requested,
+so the application already knows the answer.
+When linking statically,
+using
+.Fn gzdirect
+will include all of the zlib code
+for gzip file reading and decompression,
+which may not be desired.
+.Pc
+.
+.Sh SEE ALSO
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzeof.3 b/doc/zlib/gzeof.3
new file mode 100644
index 00000000..ba823aa6
--- /dev/null
+++ b/doc/zlib/gzeof.3
@@ -0,0 +1,63 @@
+.Dd January 15, 2017
+.Dt GZEOF 3
+.Os
+.
+.Sh NAME
+.Nm gzeof
+.Nd check end-of-file indicator
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzeof "gzFile file"
+.
+.Sh DESCRIPTION
+Returns true (1)
+if the end-of-file indicator
+has been set while reading,
+false (0) otherwise.
+Note that the end-of-file indicator
+is set only if the read
+tried to go past the end of the input,
+but came up short.
+Therefore,
+just like
+.Xr feof 3 ,
+.Fn gzeof
+may return false
+even if there is no more data to read,
+in the event that the last read request
+was for the exact number of bytes
+remaining in the input file.
+This will happen if the input file size
+is an exact multiple of the buffer size.
+.
+.Pp
+If
+.Fn gzeof
+returns true,
+then the read functions
+will return no more data,
+unless the end-of-file indicator
+is reset by
+.Xr gzclearerr 3
+and the input file
+has grown since the previous
+end of file was detected.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzread 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzerror.3 b/doc/zlib/gzerror.3
new file mode 100644
index 00000000..a9e175fc
--- /dev/null
+++ b/doc/zlib/gzerror.3
@@ -0,0 +1,75 @@
+.Dd January 15, 2017
+.Dt GZERROR 3
+.Os
+.
+.Sh NAME
+.Nm gzerror ,
+.Nm gzclearerr
+.Nd check and reset compressed file error
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft const char *
+.Fn gzerror "gzFile file" "int *errnum"
+.Ft void
+.Fn gzclearerr "gzFile file"
+.
+.Sh DESCRIPTION
+.Fn gzerror
+returns the error message for the last error
+which occured on the given compressed file.
+.Fa errnum
+is set to the zlib error number.
+If an error occurred in the file system
+and not in the compression library,
+.Fa errnum
+is set to
+.Dv Z_ERRNO
+and the application may consult
+.Va errno
+to get the exact error code.
+.
+.Pp
+The application must not modify the returned string.
+Future calls to this function
+may invalidate the previously returned string.
+If
+.Fa file
+is closed,
+then the string previously returned by
+.Fn gzerror
+will no longer be available.
+.
+.Pp
+.Fn gzerror
+should be used to distinguish errors from end-of-file
+for those functions that do not distinguish those cases
+in their return values.
+.
+.Pp
+.Fn gzclearerr
+clears the error and end-of-file for
+.Fa file .
+This is analogous to the
+.Xr clearerr 3
+function in stdio.
+This is useful for continuing to read a gzip file
+that is being written concurrently.
+.
+.Sh SEE ALSO
+.Xr gzeof 3 ,
+.Xr gzread 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzflush.3 b/doc/zlib/gzflush.3
new file mode 100644
index 00000000..476f7c09
--- /dev/null
+++ b/doc/zlib/gzflush.3
@@ -0,0 +1,73 @@
+.Dd January 15, 2017
+.Dt GZFLUSH 3
+.Os
+.
+.Sh NAME
+.Nm gzflush
+.Nd flush output to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzflush "gzFile file" "int flush"
+.
+.Sh DESCRIPTION
+Flushes all pending output
+into the compressed file.
+The parameter
+.Fa flush
+is as in the
+.Xr deflate 3
+function.
+.Fn gzflush
+is only permitted when writing.
+.
+.Pp
+If the
+.Fa flush
+parameter is
+.Dv Z_FINISH ,
+the remaining data is written
+and the gzip stream
+is completed in the output.
+If
+.Xr gzwrite 3
+is called again,
+a new gzip stream
+will be started in the output.
+.Xr gzread 3
+is able to read
+such concatenated gzip streams.
+.
+.Pp
+.Fn gzflush
+should be called only when strictly necessary
+because it will degrade compression
+if called too often.
+.
+.Sh RETURN VALUES
+The return value
+is the zlib error number
+.Po
+see function
+.Xr gzerror 3
+.Pc .
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr gzerror 3 ,
+.Xr gzread 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzfread.3 b/doc/zlib/gzfread.3
new file mode 100644
index 00000000..7bf57fc5
--- /dev/null
+++ b/doc/zlib/gzfread.3
@@ -0,0 +1,107 @@
+.Dd January 15, 2017
+.Dt GZFREAD 3
+.Os
+.
+.Sh NAME
+.Nm gzfread
+.Nd read from compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft z_size_t
+.Fn gzfread "voidp buf" "z_size_t size" "z_size_t nitems" "gzFile file"
+.
+.Sh DESCRIPTION
+Read up to
+.Fa nitems
+of size
+.Fa size
+from
+.Fa file
+to
+.Fa buf ,
+otherwise operating as
+.Xr gzread 3
+does.
+This duplicates the interface of stdio's
+.Xr fread 3 ,
+with
+.Vt size_t
+request and return types.
+If the library defines
+.Vt size_t ,
+then
+.Vt z_size_t
+is identical to
+.Vt size_t .
+If not,
+then
+.Vt z_size_t
+is an unsigned integer type
+that can contain a pointer.
+.
+.Pp
+In the event that the end of file is reached
+and only a partial item is available at the end,
+i.e. the remaining uncompressed data length
+is not a multiple of
+.Fa size ,
+then the file partial item
+is nevertheless read into
+.Fa buf
+and the end-of-file flag is set.
+The length of the partial item read
+is not provided,
+but could be inferred from the result of
+.Xr gztell 3 .
+This behavior is the same as the behavior of
+.Xr fread 3
+implementations in common libraries,
+but it prevents the direct use of
+.Fn gzfread
+to read a concurrently written file,
+reseting and retrying on end-of-file,
+when
+.Fa size
+is not 1.
+.
+.Sh RETURN VALUES
+.Fn gzfread
+returns the number of full items read of size
+.Fa size ,
+or zero if the end of the file was reached
+and a full item could not be read,
+or if there was an error.
+.Xr gzerror 3
+must be consulted if zero is returned
+in order to determine if there was an error.
+If the multiplication of
+.Fa size
+and
+.Fa nitems
+overflows,
+i.e. the product does not fit in
+.Vt z_size_t ,
+then nothing is read,
+zero is returned,
+and the error state is set to
+.Dv Z_STREAM_ERROR .
+.
+.Sh SEE ALSO
+.Xr gzeof 3 ,
+.Xr gzerror 3 ,
+.Xr gzopen 3 ,
+.Xr gzread 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzfwrite.3 b/doc/zlib/gzfwrite.3
new file mode 100644
index 00000000..6835db3a
--- /dev/null
+++ b/doc/zlib/gzfwrite.3
@@ -0,0 +1,75 @@
+.Dd January 15, 2017
+.Dt GZFWRITE 3
+.Os
+.
+.Sh NAME
+.Nm gzfwrite
+.Nd write to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft z_size_t
+.Fn gzfwrite "voidpc buf" "z_size_t size" "z_size_t nitems" "gzFile file"
+.
+.Sh DESCRIPTION
+.Fn gzfwrite
+writes
+.Fa nitems
+items of size
+.Fa size
+from
+.Fa buf
+to
+.Fa file ,
+duplicating the interface of stdio's
+.Xr fwrite 3 ,
+with
+.Vt size_t
+request and return types.
+If the library defines
+.Vt size_t ,
+then
+.Vt z_size_t
+is identical to
+.Vt size_t .
+If not,
+then
+.Vt z_size_t
+is an unsigned integer type
+that can contain a pointer.
+.
+.Sh RETURN VALUES
+.Fn gzfwrite
+returns the number of full items
+written of size
+.Fa size ,
+or zero if there was an error.
+If the multiplication of
+.Fa size
+and
+.Fa nitems
+overflows,
+i.e. the product does not fit in a
+.Vt z_size_t ,
+then nothing is written,
+zero is returned,
+and the error state is set to
+.Dv Z_STREAM_ERROR .
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzopen 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzgetc.3 b/doc/zlib/gzgetc.3
new file mode 100644
index 00000000..db9143ec
--- /dev/null
+++ b/doc/zlib/gzgetc.3
@@ -0,0 +1,55 @@
+.Dd January 15, 2017
+.Dt GZGETC 3
+.Os
+.
+.Sh NAME
+.Nm gzgetc
+.Nd get character from compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzgetc "gzFile file"
+.
+.Sh DESCRIPTION
+Reads one byte from the compressed file.
+This is implemented as a macro for speed.
+As such,
+it does not do all of the checking
+the other functions do.
+I.e.\&
+it does not check to see if
+.Fa file
+is
+.Dv NULL ,
+nor whether the structure
+.Fa file
+points to has been clobbered or not.
+.
+.Sh RETURN VALUES
+.Fn gzgetc
+returns the byte
+or -1 in case of
+end of file
+or error.
+.
+.Sh SEE ALSO
+.Xr gzeof 3 ,
+.Xr gzerror 3 ,
+.Xr gzgets 3 ,
+.Xr gzopen 3 ,
+.Xr gzread 3 ,
+.Xr gzungetc 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzgets.3 b/doc/zlib/gzgets.3
new file mode 100644
index 00000000..c1435b39
--- /dev/null
+++ b/doc/zlib/gzgets.3
@@ -0,0 +1,67 @@
+.Dd January 15, 2017
+.Dt GZGETS 3
+.Os
+.
+.Sh NAME
+.Nm gzgets
+.Nd read line from compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft char *
+.Fn gzgets "gzFile file" "char *buf" "int len"
+.
+.Sh DESCRIPTION
+Reads bytes from the compressed file
+until
+.Fa len-1
+characters are read,
+or a newline character
+is read and transferred to
+.Fa buf ,
+or an end-of-file condition
+is encountered.
+If any characters are read or if
+.Fa len
+== 1,
+the string is terminated
+with a null character.
+If no characters are read
+due to an end-of-file or
+.Fa len
+< 1,
+then the buffer is left untouched.
+.
+.Sh RETURN VALUES
+.Fn gzgets
+returns
+.Fa buf
+which is a null-terminated string,
+or it returns
+.Dv NULL
+for end-of-file
+or in case of error.
+If there was an error,
+the contents at
+.Fa buf
+are indeterminate.
+.
+.Sh SEE ALSO
+.Xr gzeof 3 ,
+.Xr gzerror 3 ,
+.Xr gzgetc 3 ,
+.Xr gzopen 3 ,
+.Xr gzread 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzoffset.3 b/doc/zlib/gzoffset.3
new file mode 100644
index 00000000..b03c557e
--- /dev/null
+++ b/doc/zlib/gzoffset.3
@@ -0,0 +1,51 @@
+.Dd January 15, 2017
+.Dt GZOFFSET 3
+.Os
+.
+.Sh NAME
+.Nm gzoffset
+.Nd offset in compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft z_off_t
+.Fn gzoffset "gzFile file"
+.
+.Sh DESCRIPTION
+Returns the current offset
+in the file being read or written.
+This offset includes
+the count of bytes
+that precede the gzip stream,
+for example when appending
+or when using
+.Xr gzdopen 3
+for reading.
+When reading,
+the offset does not include
+as yet unused buffered input.
+This information can be used
+for a progress indicator.
+.
+.Sh RETURN VALUES
+On error,
+.Fn gzoffset
+returns -1.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzopen 3 ,
+.Xr gzseek 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzopen.3 b/doc/zlib/gzopen.3
new file mode 100644
index 00000000..9da647e1
--- /dev/null
+++ b/doc/zlib/gzopen.3
@@ -0,0 +1,261 @@
+.Dd January 15, 2017
+.Dt GZOPEN 3
+.Os
+.
+.Sh NAME
+.Nm gzopen ,
+.Nm gzdopen
+.Nd open gzip file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft gzFile
+.Fn gzopen "const char *path" "const char *mode"
+.Ft gzFile
+.Fn gzdopen "int fd" "const char *mode"
+.
+.Sh DESCRIPTION
+Opens a gzip (.gz) file
+for reading or writing.
+The
+.Fa mode
+parameter is as in
+.Xr fopen 3
+.Po
+.Dq rb
+or
+.Dq wb
+.Pc
+but can also include a compression level
+.Pq Dq wb9
+or a strategy:
+.Sq f
+for filtered data as in
+.Dq wb6f ,
+.Sq h
+for Huffman-only compression as in
+.Dq wb1h ,
+.Sq R
+for run-length encoding as in
+.Dq wb1R ,
+or
+.Sq F
+for fixed code compression as in
+.Dq wb9F .
+.Po
+See the description of
+.Xr deflateInit2 3
+for more information about the
+.Fa strategy
+parameter.
+.Pc \&
+.Sq T
+will request transparent writing or appending
+with no compression
+and not using the gzip format.
+.
+.Pp
+.Dq a
+can be used instead of
+.Dq w
+to request that the gzip stream
+that will be written
+be appended to the file.
+.Dq +
+will result in an error,
+since reading and writing
+to the same gzip file
+is not supported.
+The addition of
+.Dq x
+when writing will create the file exclusively,
+which fails if the file already exists.
+On systems that support it,
+the addition of
+.Dq e
+when reading or writing
+will set the flag to close the file on an
+.Xr execve 2
+call.
+.
+.Pp
+These functions,
+as well as
+.Xr gzip 1 ,
+will read and decode
+a sequence of gzip streams in a file.
+The append function of
+.Fn gzopen
+can be used to create such a file.
+.Po
+Also see
+.Xr gzflush 3
+for another way to do this.
+.Pc \&
+When appending,
+.Fn gzopen
+does not test whether the file begins with a gzip stream,
+nor does it look for the end of the gzip streams
+to begin appending.
+.Fn gzopen
+will simply append a gzip stream
+to the existing file.
+.
+.Pp
+.Fn gzopen
+can be used to read a file which is not in gzip format;
+in this case
+.Xr gzread 3
+will directly read from the file without decompression.
+When reading,
+this will be detected automatically
+by looking for the magic two-byte gzip header.
+.
+.Pp
+.Fn gzdopen
+associates at
+.Vt gzFile
+with the file descriptor
+.Fa fd .
+File descriptors
+are obtained from calls like
+.Xr open 2 ,
+.Xr dup 2 ,
+.Xr creat 2 ,
+.Xr pipe 2
+or
+.Xr fileno 3
+.Po
+if the file has been previously opened with
+.Xr fopen 3
+.Pc .
+The
+.Fa mode
+parameter is as in
+.Fn gzopen .
+.
+.Pp
+The next call of
+.Xr gzclose 3
+on the returned
+.Vt gzFile
+will also close the file descriptor
+.Fa fd ,
+just like
+.Xr fclose 3 .
+If you want to keep
+.Fa fd
+open,
+use
+.Li "fd = dup(fd_keep); gz = gzdopen(fd, mode)" .
+The duplicated descriptor should be saved
+to avoid a leak,
+since
+.Fn gzdopen
+does not close
+.Fa fd
+if it fails.
+If you are using
+.Xr fileno 3
+to get the file descriptor from a
+.Vt FILE * ,
+then you will have to use
+.Xr dup 2
+to avoid double-closing
+the file descriptor.
+Both
+.Xr gzclose 3
+and
+.Xr flcose 3
+will close the associated file descriptor,
+so they need to have different file descriptors.
+.
+.Sh RETURN VALUES
+.Fn gzopen
+and
+.Fn gzdopen
+return
+.Dv NULL
+if the file could not be opened,
+if there was insufficient memory
+to allocate the
+.Vt gzFile
+state,
+or if an invalid
+.Fa mode
+was specified
+.Po
+an
+.Sq r ,
+.Sq w ,
+or
+.Sq a
+was not provided,
+or
+.Sq +
+was provided
+.Pc ,
+or if
+.Fa fd
+is -1.
+The file descriptor
+is not used until the next
+gz* read, write, seek, or close operation,
+so
+.Fn gzdopen
+will not detect if
+.Fa fd
+is invalid
+.Po
+unless
+.Fa fd
+is -1
+.Pc .
+.Va errno
+can be checked
+to determine if the reason
+.Fn gzopen
+failed was that the file
+could not be opened.
+.
+.Sh ERRORS
+The
+.Fn gzopen
+function may fail and set
+.Va errno
+for any of the errors specified
+for the routine
+.Xr fopen 3 .
+.
+.Sh SEE ALSO
+.Xr deflateInit2 3 ,
+.Xr fopen 3 ,
+.Xr gzbuffer 3 ,
+.Xr gzclose 3 ,
+.Xr gzdirect 3 ,
+.Xr gzeof 3 ,
+.Xr gzerror 3 ,
+.Xr gzflush 3 ,
+.Xr gzgetc 3 ,
+.Xr gzgets 3 ,
+.Xr gzoffset 3 ,
+.Xr gzprintf 3 ,
+.Xr gzputc 3 ,
+.Xr gzputs 3 ,
+.Xr gzread 3 ,
+.Xr gzseek 3 ,
+.Xr gzsetparams 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzprintf.3 b/doc/zlib/gzprintf.3
new file mode 100644
index 00000000..a2a241a2
--- /dev/null
+++ b/doc/zlib/gzprintf.3
@@ -0,0 +1,71 @@
+.Dd January 15, 2017
+.Dt GZPRINTF 3
+.Os
+.
+.Sh NAME
+.Nm gzprintf
+.Nd format output to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzprintf "gzFile file" "const char *format" "..."
+.
+.Sh DESCRIPTION
+Converts, formats, and writes the arguments
+to the compressed file
+under control of the format string,
+as in
+.Xr fprintf 3 .
+.
+.Sh RETURN VALUES
+.Fn gzprintf
+returns the number of
+uncompressed bytes actually written,
+or a negative zlib error code
+in case of error.
+The number of uncompressed bytes written
+is limited to 8191,
+or one less than the buffer size given to
+.Xr gzbuffer 3 .
+The caller should assure that
+this limit is not exceeded.
+If it is exceeded,
+then
+.Fn gzprintf
+will return an error (0)
+with nothing written.
+In this case,
+there may also be a buffer overflow
+with unpredictable consequences,
+which is possibly only if zlib
+was compiled with the insecure functions
+.Xr sprintf 3
+or
+.Xr vsprintf 3
+because the secure
+.Xr snprintf 3
+or
+.Xr vsnprintf 3
+functions
+were not available.
+This can be determined using
+.Xr zlibCompileFlags 3 .
+.
+.Sh SEE ALSO
+.Xr fprintf 3 ,
+.Xr gzerror 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzputc.3 b/doc/zlib/gzputc.3
new file mode 100644
index 00000000..66897b5e
--- /dev/null
+++ b/doc/zlib/gzputc.3
@@ -0,0 +1,43 @@
+.Dd January 15, 2017
+.Dt GZPUTC 3
+.Os
+.
+.Sh NAME
+.Nm gzputc
+.Nd write character to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzputc "gzFile file" "int c"
+.
+.Sh DESCRIPTION
+Writes
+.Fa c ,
+converted to an
+.Vt unsigned char ,
+into the compressed file.
+.
+.Sh RETURN VALUES
+.Fn gzputc
+returns the value that was written,
+or -1 in case of error.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzopen 3 ,
+.Xr gzputs 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzputs.3 b/doc/zlib/gzputs.3
new file mode 100644
index 00000000..71833ab2
--- /dev/null
+++ b/doc/zlib/gzputs.3
@@ -0,0 +1,41 @@
+.Dd January 15, 2017
+.Dt GZPUTS 3
+.Os
+.
+.Sh NAME
+.Nm gzputs
+.Nd write string to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzputs "gzFile file" "const char *s"
+.
+.Sh DESCRIPTION
+Writes the given null-terminated string
+to the compressed file,
+excluding the terminating null character.
+.
+.Sh RETURN VALUES
+.Fn gzputs
+returns the number of characters written,
+or -1 in case of error.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzopen 3 ,
+.Xr gzputc 3 ,
+.Xr gzwrite 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzread.3 b/doc/zlib/gzread.3
new file mode 100644
index 00000000..4118eca7
--- /dev/null
+++ b/doc/zlib/gzread.3
@@ -0,0 +1,115 @@
+.Dd January 15, 2017
+.Dt GZREAD 3
+.Os
+.
+.Sh NAME
+.Nm gzread
+.Nd read from compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzread "gzFile file" "voidp buf" "unsigned len"
+.
+.Sh DESCRIPTION
+Reads the given number of uncompressed bytes
+from the compressed file.
+If the input file
+is not in gzip format,
+.Fn gzread
+copies the given number of bytes
+into the buffer directly from the file.
+.
+.Pp
+After reaching the end of a gzip stream
+in the input,
+.Fn gzread
+will continue to read,
+looking for another gzip stream.
+Any number of gzip streams
+may be concatenated in the input file,
+and will all be decompressed by
+.Fn gzread .
+If something other than a gzip stream
+is encountered after a gzip stream,
+that remaining trailing garbage is ignored
+(and no error is returned).
+.
+.Pp
+.Fn gzread
+can be used to read a gzip file
+that is being concurrently written.
+Upon reaching the end of the input,
+.Fn gzread
+will return with the available data.
+If the error code returned by
+.Xr gzerror 3
+is
+.Dv Z_OK
+or
+.Dv Z_BUF_ERROR ,
+then
+.Xr gzclearerr 3
+can be used
+to clear the end of file indicator
+in order to permit
+.Fn gzread
+to be tried again.
+.Dv Z_OK
+indicates that a gzip stream was completed
+on the last
+.Fn gzread .
+.Dv Z_BUF_ERROR
+indicates that the input file
+ended in the middle of a gzip stream.
+Note that
+.Fn gzread
+does not return -1
+in the event of an incomplete gzip stream.
+This error is deferred until
+.Xr gzclose 3 ,
+which will return
+.Dv Z_BUF_ERROR
+if the last
+.Fn gzread
+ended in the middle of a gzip stream.
+Alternatively,
+.Xr gzerror 3
+can be used before
+.Xr gzclose 3
+to detect this case.
+.
+.Sh RETURN VALUES
+.Fn gzread
+returns the number of uncompressed bytes actually read,
+less than
+.Fa len
+for end of file,
+or -1 for error.
+If
+.Fa len
+is too large to fit in an
+.Vt int ,
+then nothing is read,
+-1 is returned,
+and the error state is set to
+.Dv Z_STREAM_ERROR .
+.
+.Sh SEE ALSO
+.Xr gzeof 3 ,
+.Xr gzerror 3 ,
+.Xr gzfread 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzseek.3 b/doc/zlib/gzseek.3
new file mode 100644
index 00000000..a14b2db6
--- /dev/null
+++ b/doc/zlib/gzseek.3
@@ -0,0 +1,108 @@
+.Dd January 15, 2017
+.Dt GZSEEK 3
+.Os
+.
+.Sh NAME
+.Nm gzseek ,
+.Nm gzrewind ,
+.Nm gztell
+.Nd seek compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft z_off_t
+.Fn gzseek "gzFile file" "z_off_t offset" "int whence"
+.Ft int
+.Fn gzrewind "gzFile file"
+.Ft z_off_t
+.Fn gztell "gzFile file"
+.
+.Sh DESCRIPTION
+Sets the starting position
+for the next
+.Xr gzread 3
+or
+.Xr gzwrite 3
+on the given compressed file.
+The
+.Fa offset
+represents a number of bytes
+in the uncompressed data stream.
+The
+.Fa whence
+parameter
+is defined as in
+.Xr lseek 2 ;
+the value
+.Dv SEEK_END
+is not supported.
+.
+.Pp
+If the file is opened for reading,
+this function is emulated
+but can be extremely slow.
+If the file is opened for writing,
+only forward seeks are supported;
+.Fn gzseek
+then compresses a sequence of zeroes
+up to the new starting position.
+.
+.Pp
+.Fn gzrewind
+rewinds the given file.
+This function is supported
+only for reading.
+.
+.Pp
+.Fn gzrewind file
+is equivalent to
+.Li (int) Ns Fn gzseek file 0L SEEK_SET .
+.
+.Pp
+.Fn gztell
+returns the starting position
+for the next
+.Xr gzread 3
+or
+.Xr gzwrite 3
+on the given compressed file.
+This position represents a number of bytes
+in the uncompressed data stream,
+and is zero when starting,
+even if appending or reading
+a gzip stream from the middle of a file using
+.Xr gzdopen 3 .
+.
+.Pp
+.Fn gztell file
+is equivalent to
+.Fn gzseek file 0L SEEK_CUR .
+.
+.Sh RETURN VALUES
+.Fn gzseek
+returns the resulting offset location
+as measured in bytes
+from the beginning of the uncompressed stream,
+or -1 in case of error,
+in particular if the file
+is opened for writing
+and the new starting position
+would be before the current position.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzoffset 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzsetparams.3 b/doc/zlib/gzsetparams.3
new file mode 100644
index 00000000..f6ff9ed7
--- /dev/null
+++ b/doc/zlib/gzsetparams.3
@@ -0,0 +1,51 @@
+.Dd January 15, 2017
+.Dt GZSETPARAMS 3
+.Os
+.
+.Sh NAME
+.Nm gzsetparams
+.Nd set compression level and strategy
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzsetparams "gzFile file" "int level" "int strategy"
+.
+.Sh DESCRIPTION
+Dynamically update the compression level or strategy.
+See the description of
+.Xr deflateInit2 3
+for the meaning
+of these parameters.
+Previously provided data is flushed
+before the parameter change.
+.
+.Sh RETURN VALUES
+.Fn gzsetparams
+returns
+.Dv Z_OK
+if success,
+.Dv Z_STREAM_ERROR
+if the file was not opened for writing,
+.Dv Z_ERRNO
+if there is an error writing the flushed data,
+or
+.Dv Z_MEM_ERROR
+if there is a memory allocation error.
+.
+.Sh SEE ALSO
+.Xr deflateInit2 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzungetc.3 b/doc/zlib/gzungetc.3
new file mode 100644
index 00000000..fbe9371c
--- /dev/null
+++ b/doc/zlib/gzungetc.3
@@ -0,0 +1,67 @@
+.Dd January 15, 2017
+.Dt GZUNGETC 3
+.Os
+.
+.Sh NAME
+.Nm gzungetc
+.Nd un-get character from compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzungetc "int c" "gzFile file"
+.
+.Sh DESCRIPTION
+Push one character back onto the stream
+to be read as the first character
+on the next read.
+At least one character of push-back
+is allowed.
+.Fn gzungetc
+will fail if
+.Fa c
+is -1,
+and may fail if a character
+has been pushed
+but not read yet.
+If
+.Fn gzungetc
+is used immediately after
+.Xr gzopen 3
+or
+.Xr gzdopen 3 ,
+at least the output buffer size
+of pushed characters is allowed.
+.Po
+See
+.Xr gzbuffer 3 .
+.Pc \&
+The pushed character will be discarded
+if the stream is repositioned with
+.Xr gzseek 3
+or
+.Xr gzrewind 3 .
+.
+.Sh RETURN VALUES
+.Fn gzungetc
+returns the character pushed,
+or -1 on failure.
+.
+.Sh SEE ALSO
+.Xr gzbuffer 3 ,
+.Xr gzerror 3 ,
+.Xr gzgetc 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/gzwrite.3 b/doc/zlib/gzwrite.3
new file mode 100644
index 00000000..73407ef5
--- /dev/null
+++ b/doc/zlib/gzwrite.3
@@ -0,0 +1,39 @@
+.Dd January 15, 2017
+.Dt GZWRITE 3
+.Os
+.
+.Sh NAME
+.Nm gzwrite
+.Nd write to compressed file
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn gzwrite "gzFile file" "voidpc buf" "unsigned len"
+.
+.Sh DESCRIPTION
+Writes the given number of uncompressed bytes
+into the compressed file.
+.
+.Sh RETURN VALUES
+.Fn gzwrite
+returns the number of uncompressed bytes written
+or 0 in case of error.
+.
+.Sh SEE ALSO
+.Xr gzerror 3 ,
+.Xr gzfwrite 3 ,
+.Xr gzopen 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflate.3 b/doc/zlib/inflate.3
new file mode 100644
index 00000000..255e0f84
--- /dev/null
+++ b/doc/zlib/inflate.3
@@ -0,0 +1,398 @@
+.Dd January 15, 2017
+.Dt INFLATE 3
+.Os
+.
+.Sh NAME
+.Nm inflate
+.Nd deflate decompression
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflate "z_streamp strm" "int flush"
+.
+.Sh DESCRIPTION
+.Fn inflate
+decompresses as much data as possible,
+and stops when the input buffer becomes empty
+or the output buffer becomes full.
+It may introduce some output latency
+(reading input without producing any output)
+except when forced to flush.
+.
+.Pp
+The detailed semantics are as follows.
+.Fn inflate
+performs one or both of the following actions:
+.
+.Bl -dash
+.It
+Decompress more input starting at
+.Fa next_in
+and update
+.Fa next_in
+and
+.Fa avail_in
+accordingly.
+If not all input can be processed
+(because there is not enough room in the output buffer),
+then
+.Fa next_in
+and
+.Fa avail_in
+are updated accordingly,
+and processing will resume at this point
+for the next call of
+.Fn inflate .
+.
+.It
+Generate more output starting at
+.Fa next_out
+and update
+.Fa next_out
+and
+.Fa avail_out
+accordingly.
+.Fn inflate
+provides as much output as possible,
+until there is no more input data
+or no more space in the output buffer
+.Po
+see below about the
+.Fa flush
+parameter
+.Pc .
+.El
+.
+.Pp
+Before the call of
+.Fn inflate ,
+the application should ensure that
+at least one of the actions is possible,
+by providing more input
+and/or consuming more output,
+and updating the
+.Fa next_*
+and
+.Fa avail_*
+values accordingly.
+If the caller of
+.Fn inflate
+does not provide both available input
+and available output space,
+it is possible that there will be no progress made.
+The application can consume the uncompressed output
+when it wants,
+for example when the output buffer is full
+.Po
+.Fa avail_out
+== 0
+.Pc ,
+or after each call of
+.Fn inflate .
+If
+.Fn inflate
+returns
+.Dv Z_OK
+and with zero
+.Fa avail)out ,
+it must be called again after making room
+in the output buffer
+because there might be more output pending.
+.
+.Pp
+The
+.Fa flush
+parameter of
+.Fn inflate
+can be
+.Dv Z_NO_FLUSH ,
+.Dv Z_SYNC_FLUSH ,
+.Dv Z_FINISH ,
+.Dv Z_BLOCK ,
+or
+.Dv Z_TREES .
+.Dv Z_SYNC_FLUSH
+requests that
+.Fn inflate
+flush as much output as possible
+to the output buffer.
+.Dv Z_BLOCK
+requests that
+.Fn inflate
+stop if and when it gets to the next deflate block boundary.
+When decoding the zlib or gzip format,
+this will cause
+.Fn inflate
+to return immediately after the header
+and before the first block.
+When doing a raw inflate,
+.Fn inflate
+will go ahead and process the first block,
+and will return when it gets to the end of that block,
+or when it runs out of data.
+.
+.Pp
+The
+.Dv Z_BLOCK
+option assists in appending to
+or combining deflate streams.
+To assist in this,
+on return
+.Fn inflate
+always sets
+.Fa strm->data_type
+to the number of unused bits
+in the last byte taken from
+.Fa strm->next_in ,
+plus 64 if
+.Fn inflate
+is currently decoding the last block in the deflate stream,
+plus 128 if
+.Fn inflate
+returned immediately after decoding an end-of-block code
+or decoding the complete header up to
+just before the first byte of the deflate stream.
+The end-of-block will not be indicated
+until all of the uncompressed data
+from that block has been written to
+.Fa strm->next_out .
+The number of unused bits may in general be greater than seven,
+except when bit 7 of
+.Fa data_type
+is set,
+in which case the number of unused bits
+will be less than eight.
+.Fa data_type
+is set as noted here every time
+.Fn inflate
+returns for all flush options,
+and so can be used to determine
+the amount of currently consumed input in bits.
+.
+.Pp
+The
+.Dv Z_TREES
+option behaves as
+.Dv Z_BLOCK
+does,
+but it also returns
+when the end of each deflate block header is reached,
+before any actual data in that block is decoded.
+This allows the caller to determine
+the length of the deflate block header
+for later use in random access
+within a deflate block.
+256 is added to the value of
+.Fa strm->data_type
+when
+.Fn inflate
+returns immediately after reaching
+the end of the deflate block header.
+.
+.Pp
+.Fn inflate
+should normally be called until it returns
+.Dv Z_STREAM_END
+or an error.
+However if all decompression is to be performed
+in a single step
+.Po
+a single call of
+.Fn inflate
+.Pc ,
+the parameter
+.Fa flush
+should be set to
+.Dv Z_FINISH .
+In this case all pending input is processed
+and all pending output is flushed;
+.Fa avail_out
+must be large enough to hold all of
+the uncompressed data for the operation to complete.
+(The size of the uncompressed data
+may have been saved by the compressor for this purpose.)
+The use of
+.Dv Z_FINISH
+is not required to perform inflation in one step.
+However it may be used to inform
+.Fn inflate
+that a faster approach can be used for the single
+.Fn inflate
+call.
+.Dv Z_FINISH also informs
+.Fn inflate
+to not maintain a sliding window
+if the stream completes,
+which reduces
+.Fn inflate Ap s
+memory footprint.
+If the stream does not complete,
+either because not all of the stream is provided
+or not enough output space is provided,
+then a sliding window will be allocated and
+.Fn inflate
+can be called again to continue the operation as if
+.Dv Z_NO_FLUSH
+had been used.
+.
+.Pp
+In this implementation,
+.Fn inflate
+always flushes as much output as possible
+to the output buffer,
+and always uses the faster approach
+on the first call.
+So the effects of the
+.Fa flush
+parameter in this implementation
+are on the return value of
+.Fn inflate
+as noted below,
+when
+.Fn inflate
+returns early when
+.Dv Z_BLOCK
+or
+.Dv Z_TREES
+is used,
+and when
+.Fn inflate
+avoids the allocation of memory
+for a sliding window when
+.Dv Z_FINISH
+is used.
+.
+.Pp
+If a preset dictionary is needed after this call
+.Po
+see
+.Xr inflateSetDictionary 3
+.Pc ,
+.Fn inflate
+sets
+.Fa strm->adler
+to the Adler-32 checksum of the dictionary
+chosen by the compressor
+and returns
+.Dv Z_NEED_DICT ;
+otherwise it sets
+.Fa strm->adler
+to the Adler-32 checksum
+of all output produced so far
+.Po
+that is,
+.Fa total_out
+bytes
+.Pc
+and returns
+.Dv Z_OK ,
+.Dv Z_STREAM_END
+or an error code
+as described in
+.Sx RETURN VALUES .
+At the end of the stream,
+.Fn inflate
+checks that its computed Adler-32 checksum
+is equal to that saved by the compressor
+and returns
+.Dv Z_STREAM_END
+only if the checksum is correct.
+.
+.Pp
+.Fn inflate
+can decompress and check
+either zlib-wrapped or gzip-wrapped
+deflate data.
+The header type is detected automatically,
+if requested when initializing with
+.Xr inflateInit2 3 .
+Any information contained in the gzip header
+is not retained unless
+.Xr inflateGetHeader 3
+is used.
+When processing gzip-wrapped deflate data,
+.Fa strm->adler32
+is set to the CRC-32
+of the output produced so far.
+The CRC-32 is checked against the gzip trailer,
+as is the uncompressed length,
+modulo 2^32.
+.
+.Sh RETURN VALUES
+.Fn inflate
+returns
+.Dv Z_OK
+if some progress has been made
+(more input processed or more output produced),
+.Dv Z_STREAM_END
+if the end of the compressed data has been reached
+and all uncompressed output has been produced,
+.Dv Z_NEED_DICT
+if a preset dictionary is needed at this point,
+.Dv Z_DATA_ERROR
+if the input data was corrupted
+.Po
+input stream not conforming to the zlib format
+or incorrect check value,
+in which case
+.Fa strm->msg
+points to a string with a more specific error
+.Pc ,
+.Dv Z_STREAM_ERROR
+if the stream structure was inconsistent
+.Po
+for example
+.Fa next_in
+or
+.Fa next_out
+was
+.Dv Z_NULL ,
+or the state was inadvertently written over
+by the application
+.Pc ,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_BUF_ERROR
+if no progress was possible
+or if there was not enough room
+in the output buffer when
+.Dv Z_FINISH
+is used.
+Note that
+.Dv Z_BUF_ERROR
+is not fatal,
+and
+.Fn inflate
+can be called again with more input
+and more output space
+to continue decompressing.
+If
+.Dv Z_DATA_ERROR
+is returned,
+the application may then call
+.Xr inflateSync 3
+to look for a good compression block
+if a partial recovery of the data
+is to be attempted.
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr inflateBack 3 ,
+.Xr inflateEnd 3 ,
+.Xr inflateInit 3 ,
+.Xr inflateMark 3 ,
+.Xr inflateSync 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateBack.3 b/doc/zlib/inflateBack.3
new file mode 100644
index 00000000..fcda7452
--- /dev/null
+++ b/doc/zlib/inflateBack.3
@@ -0,0 +1,285 @@
+.Dd January 15, 2017
+.Dt INFLATEBACK 3
+.Os
+.
+.Sh NAME
+.Nm inflateBack
+.Nd inflate call-back interface
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.
+.Ft typedef unsigned
+.Fo (*in_func)
+.Fa "void FAR *"
+.Fa "z_const unsigned char FAR * FAR *"
+.Fc
+.
+.Ft typedef int
+.Fo (*out_func)
+.Fa "void FAR *"
+.Fa "unsigned char FAR *"
+.Fa "unsigned"
+.Fc
+.
+.Ft int
+.Fo inflateBack
+.Fa "z_streamp strm"
+.Fa "in_func in"
+.Fa "void FAR *in_desc"
+.Fa "out_func out"
+.Fa "void FAR *out_desc"
+.Fc
+.
+.Sh DESCRIPTION
+.Fn inflateBack
+does a raw inflate
+with a single call
+using a call-back interface
+for input and output.
+This is potentially more efficient than
+.Xr inflate 3
+for file I/O applications,
+in that it avoids copying between the output
+and the sliding window
+by simply making the window itself the output buffer.
+.Xr inflate 3
+can be faster on modern CPUs
+when used with large buffers.
+.Fn inflateBack
+trusts the application to not change
+the output buffer passed by the output function,
+at least until
+.Fn inflateBack
+returns.
+.
+.Pp
+.Xr inflateBackInit 3
+must be called first
+to allocate the internal state
+and to initialize the state
+with the user-provided window buffer.
+.Fn inflateBack
+may then be used multiple times
+to inflate a complete,
+raw deflate stream
+with each call.
+.Xr inflateBackEnd 3
+is then called to free the allocated state.
+.
+.Pp
+A raw deflate stream
+is one with no zlib or gzip header or trailer.
+This routine would normally be used
+in a utility that reads zip or gzip files
+and write out uncompressed files.
+The utility would decode the header
+and process the trailer on its own,
+hence this routine expects only
+the raw deflate stream to decompress.
+This is different from the default behaviour of
+.Xr inflate 3 ,
+which expects a zlib header and trailer
+around the deflate stream.
+.
+.Pp
+.Fn inflateBack
+uses two subroutines
+supplied by the caller
+that are then called by
+.Fn inflateBack
+for input and output.
+.Fn inflateBack
+calls those routines
+until it reads a complete deflate stream
+and writes out all of the uncompressed data,
+or until it encounters an error.
+The function's parameters and return types
+are defined above in the
+.Vt in_func
+and
+.Vt out_func
+typedefs.
+.Fn inflateBack
+will call
+.Fn in in_desc &buf
+which should return
+the number of bytes of provided input,
+and a pointer to that input in
+.Fa buf .
+If there is no input available,
+.Fn in
+must return zero \(em
+.Fa buf
+is ignored in that case \(em
+and
+.Fn inflateBack
+will return a buffer error.
+.Fn inflateBack
+will call
+.Fn out out_desc buf len
+to write the uncompressed data
+.Fa buf[0..len-1] .
+.Fn out
+should return zero on success,
+or non-zero on failure.
+If
+.Fn out
+returns non-zero,
+.Fn inflateBack
+will return with an error.
+Neither
+.Fn in
+nor
+.Fn out
+are permitted to change
+the contents of the window provided to
+.Xr inflateBackInit 3 ,
+which is also the buffer that
+.Fn out
+uses to write from.
+The length written by
+.Fn out
+will be at most the window size.
+Any non-zero amount of input
+may be provided by
+.Fn in .
+.
+.Pp
+For convenience,
+.Fn inflateBack
+can be provided input on the first call
+by setting
+.Fa strm->next_in
+and
+.Fa strm->avail_in .
+If that input is exhausted,
+then
+.Fn in
+will be called.
+Therefore
+.Fa strm->next_in
+must be initialized before calling
+.Fn inflateBack .
+If
+.Fa strm->next_in
+is
+.Dv Z_NULL ,
+then
+.Fn in
+will be called immediately for input.
+If
+.Fa strm->next_in
+is not
+.Dv Z_NULL ,
+then
+.Fa strm->avail_in
+must also be initialized,
+and then if
+.Fa strm->avail_in
+is not zero,
+input will initially be taken from
+.Fa "strm->next_in[0 .. strm->avail_in - 1]" .
+.
+.Pp
+The
+.Fa in_desc
+and
+.Fa out_desc
+parameters of
+.Fn inflateBack
+is passed as the first parameter of
+.Fn in
+and
+.Fn out
+respectively when they are called.
+These descriptors can be optionally used
+to pass any information that the caller-supplied
+.Fn in
+and
+.Fn out
+functions need to do their job.
+.
+.Sh RETURN VALUES
+On return,
+.Fn inflateBack
+will set
+.Fa strm->next_in
+and
+.Fa strm->avail_in
+to pass back any unused input
+that was provided by the last
+.Fn in
+call.
+The return values of
+.Fn inflateBack
+can be
+.Dv Z_STREAM_END
+on success,
+.Dv Z_BUF_ERROR
+if
+.Fn in
+or
+.Fn out
+returned an error,
+.Dv Z_DATA_ERROR
+if there was a format error
+in the deflate stream
+.Po
+in which case
+.Fa strm->msg
+is set to indicate the nature of the error
+.Pc ,
+or
+.Dv Z_STREAM_ERROR
+if the stream was not properly initialized.
+In the case of
+.Dv Z_BUF_ERROR ,
+an input or output error can be distinguished using
+.Fa strm->next_in
+which will be
+.Dv Z_NULL
+only if
+.Fn in
+returned an error.
+If
+.Fa strm->next_in
+is not
+.Dv Z_NULL ,
+then the
+.Dv Z_BUF_ERROR
+was due to
+.Fn out
+returning non-zero.
+.Po
+.Fn in
+will always be called before
+.Fn out ,
+so
+.Fa strm->next_in
+is assured to be defined if
+.Fa out
+returns non-zero.
+.Pc \&
+Note that
+.Fn inflateBack
+cannot return
+.Dv Z_OK .
+.
+.Sh SEE ALSO
+.Xr inflate 3 ,
+.Xr inflateBackEnd 3 ,
+.Xr inflateBackInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateBackEnd.3 b/doc/zlib/inflateBackEnd.3
new file mode 100644
index 00000000..39fbea8f
--- /dev/null
+++ b/doc/zlib/inflateBackEnd.3
@@ -0,0 +1,43 @@
+.Dd January 15, 2017
+.Dt INFLATEBACKEND 3
+.Os
+.
+.Sh NAME
+.Nm inflateBackEnd
+.Nd free inflateBack stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateBackEnd "z_streamp strm"
+.
+.Sh DESCRIPTION
+All memory allocated by
+.Xr inflateBackInit 3
+is freed.
+.
+.Sh RETURN VALUES
+.Fn inflateBackEnd
+returns
+.Dv Z_OK
+on success,
+or
+.Dv Z_STREAM_ERROR
+if the stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr inflateBack 3 ,
+.Xr inflateBackInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateBackInit.3 b/doc/zlib/inflateBackInit.3
new file mode 100644
index 00000000..d029542e
--- /dev/null
+++ b/doc/zlib/inflateBackInit.3
@@ -0,0 +1,84 @@
+.Dd January 15, 2017
+.Dt INFLATEBACKINIT 3
+.Os
+.
+.Sh NAME
+.Nm inflateBackInit
+.Nd initialize inflateBack stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo inflateBackInit
+.Fa "z_streamp strm"
+.Fa "int windowBits"
+.Fa "unsigned char FAR *window"
+.Fc
+.
+.Sh DESCRIPTION
+Initialize the internal stream state
+for decompression using
+.Xr inflateBack 3
+calls.
+The fields
+.Fa zalloc ,
+.Fa zfree
+and
+.Fa opaque
+in
+.Fa strm
+must be initialized before the call.
+If
+.Fa zalloc
+and
+.Fa zfree
+are
+.Dv Z_NULL ,
+then the default
+library-derived memory allocation routines are used.
+.Fa windowBits
+is the base two logarithm of the window size,
+in the range 8..15.
+.Fa window
+is a caller supplied buffer of that size.
+Except for special applications
+where it is assured that deflate
+was used with small window sizes,
+.Fa windowBits
+must be 15
+and a 32K byte
+.Fa window
+must be supplied
+to be able to decompress general deflate streams.
+.
+.Sh RETURN VALUES
+.Fn inflateBackInit
+will return
+.Dv Z_OK
+on success,
+.Dv Z_STREAM_ERROR
+if any of the parameters are invalid,
+.Dv Z_MEM_ERROR
+if the internal state could not be allocated,
+or
+.Dv Z_VERSION_ERROR
+if the version of the library
+does not match the version of the header file.
+.
+.Sh SEE ALSO
+.Xr inflateBack 3 ,
+.Xr inflateBackEnd 3 ,
+.Xr inflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateCopy.3 b/doc/zlib/inflateCopy.3
new file mode 100644
index 00000000..167b879b
--- /dev/null
+++ b/doc/zlib/inflateCopy.3
@@ -0,0 +1,59 @@
+.Dd January 15, 2017
+.Dt INFLATECOPY 3
+.Os
+.
+.Sh NAME
+.Nm inflateCopy
+.Nd copy inflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateCopy "z_streamp dest" "z_streamp source"
+.
+.Sh DESCRIPTION
+Sets the destination stream
+as a complete copy of the source stream.
+.
+.Pp
+This function can be useful
+when randomly accessing a large stream.
+The first pass through the stream
+can periodically record the inflate state,
+allowing restarting inflate at those points
+when randomly accessing the stream.
+.
+.Sh RETURN VALUES
+.Fn inflateCopy
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent
+.Po
+such as
+.Fa zalloc
+being
+.Dv Z_NULL
+.Pc .
+.Fa msg
+is left unchanged
+in both source and destination.
+.
+.Sh SEE ALSO
+.Xr inflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateEnd.3 b/doc/zlib/inflateEnd.3
new file mode 100644
index 00000000..54945b50
--- /dev/null
+++ b/doc/zlib/inflateEnd.3
@@ -0,0 +1,44 @@
+.Dd January 15, 2017
+.Dt INFLATEEND 3
+.Os
+.
+.Sh NAME
+.Nm inflateEnd
+.Nd free inflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateEnd "z_streamp strm"
+.
+.Sh DESCRIPTION
+All dynamically allocated data structures
+for this stream are feed.
+This function discards any unprocessed input
+and does not flush any pending output.
+.
+.Sh RETURN VALUES
+.Fn inflateEnd
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr inflate 3 ,
+.Xr inflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateGetDictionary.3 b/doc/zlib/inflateGetDictionary.3
new file mode 100644
index 00000000..9290850c
--- /dev/null
+++ b/doc/zlib/inflateGetDictionary.3
@@ -0,0 +1,69 @@
+.Dd January 15, 2017
+.Dt INFLATEGETDICTIONARY 3
+.Os
+.
+.Sh NAME
+.Nm inflateGetDictionary
+.Nd inflate sliding dictionary
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo inflateGetDictionary
+.Fa "z_streamp strm"
+.Fa "Bytef *dictionary"
+.Fa "uInt *dictLength"
+.Fc
+.
+.Sh DESCRIPTION
+Returns the sliding dictionary
+being maintained by
+.Xr inflate 3 .
+.Fa dictLength
+is set to the number of bytes
+in the dictionary,
+and that many bytes are copied to
+.Fa dictionary .
+.Fa dictionary
+must have enough space,
+where 32768 bytes is always enough.
+If
+.Fn inflateGetDictionary
+is called with
+.Fa dictionary
+equal to
+.Dv Z_NULL ,
+then only the dictionary length is returned,
+and nothing is copied.
+Similarly,
+if
+.Fa dictLength
+is
+.Dv Z_NULL ,
+then it is not set.
+.
+.Sh RETURN VALUES
+.Fn inflateGetDictionary
+returns
+.Dv Z_OK
+on success,
+or
+.Dv Z_STREAM_ERROR
+if the stream state is inconsistent.
+.
+.Sh SEE ALSO
+.Xr deflateSetDictionary 3 ,
+.Xr inflateSetDictionary 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateGetHeader.3 b/doc/zlib/inflateGetHeader.3
new file mode 100644
index 00000000..57f7c443
--- /dev/null
+++ b/doc/zlib/inflateGetHeader.3
@@ -0,0 +1,170 @@
+.Dd January 15, 2017
+.Dt INFLATEGETHEADER 3
+.Os
+.
+.Sh NAME
+.Nm inflateGetHeader
+.Nd get gzip header
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateGetHeader "z_streamp strm" "gz_headerp head"
+.
+.Sh DESCRIPTION
+.Fn inflateGetHeader
+requests that gzip header information
+be stored in the provided
+.Vt gz_header
+structure.
+.Fn inflateGetHeader
+may be called after
+.Xr inflateInit2 3
+or
+.Xr inflateReset 3 ,
+and before the first call of
+.Xr inflate 3 .
+As
+.Xr inflate 3
+processes the gzip stream,
+.Fa head->done
+is zero until the header is completed,
+at which time
+.Fa head->done
+is set to one.
+If a zlib stream is being decoded,
+then
+.Fa head->done
+is set to -1 to indicate that
+there will be no gzip header information forthcoming.
+Note that
+.Dv Z_BLOCK
+or
+.Dv Z_TREES
+can be used to force
+.Xr inflate 3
+to return immediately after
+header processing is complete
+and before any actual data is decompressed.
+.
+.Pp
+The
+.Fa text ,
+.Fa time ,
+.Fa xflags ,
+and
+.Fa os
+fields are filled in with the gzip header contents.
+.Fa hcrc
+is set to true if there is a header CRC.
+.Po
+The header CRC was valid if
+.Fa done
+is set to one.
+.Pc \&
+If
+.Fa extra
+is not
+.Dv Z_NULL ,
+then
+.Fa extra_max
+contains the maximum number of bytes to write to
+.Fa extra .
+Once
+.Fa done
+is true,
+.Fa extra_len
+contains the actual extra field length,
+and
+.Fa extra
+contains the extra field,
+or that field truncated if
+.Fa extra_max
+is less than
+.Fa extra_len .
+If
+.Fa name
+is not
+.Dv Z_NULL ,
+then up to
+.Fa name_max
+characters are written there,
+terminated with a zero
+unless the length is greater than
+.Fa name_max .
+If
+.Fa comment
+is not
+.Dv Z_NULL ,
+then up to
+.Fa comm_max
+characters are written there,
+terminated with a zero
+unless the length is greater than
+.Fa comm_max .
+When any of
+.Fa extra ,
+.Fa name ,
+or
+.Fa comment
+are not
+.Dv Z_NULL
+and the respective field
+is not present in the header,
+then that field is set to
+.Dv Z_NULL
+to signal its absence.
+This allows the use of
+.Xr deflateSetHeader 3
+with the returned structure
+to duplicate the header.
+However if those fields are set to allocated memory,
+then the application will need to
+save those pointers elsewhere
+so that they can be eventually feed.
+.
+.Pp
+If
+.Fn inflateGetHeader
+is not used,
+then the header information is simply discarded.
+The header is always checked for validity,
+including the header CRC if present.
+.Xr inflateReset 3
+will reset the process to discard the header information.
+The application would need to call
+.Fn inflateGetHeader
+again to retrieve the header from the next gzip stream.
+.
+.Pp
+The
+.Vt gz_headerp
+type is documented in
+.Xr deflateSetHeader 3 .
+.
+.Sh RETURN VALUES
+.Fn inflateGetHeader
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr deflateSetHeader 3 ,
+.Xr inflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateInit.3 b/doc/zlib/inflateInit.3
new file mode 100644
index 00000000..66a1d4f7
--- /dev/null
+++ b/doc/zlib/inflateInit.3
@@ -0,0 +1,101 @@
+.Dd January 15, 2017
+.Dt INFLATEINIT 3
+.Os
+.
+.Sh NAME
+.Nm inflateInit
+.Nd initialize inflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateInit "z_streamp strm"
+.
+.Sh DESCRIPTION
+Initializes the internal stream state for decompression.
+The fields
+.Fa next_in ,
+.Fa avail_in ,
+.Fa zalloc ,
+.Fa zfree
+and
+.Fa opaque
+must be initialized before by the caller.
+In the current version of
+.Fn inflateInit ,
+the provided input is not read or consumed.
+The allocation of a sliding window
+will be deferred to the first call of
+.Xr inflate 3
+(if the decompression does not complete on the first call).
+If
+.Fa zalloc
+and
+.Fa zfree
+are set to
+.Dv Z_NULL ,
+.Fn inflateInit
+updates them to use default allocation functions.
+.
+.Pp
+.Fn inflateInit
+does not perform any decompression.
+Actual decompression will be done by
+.Xr inflate 3 .
+So
+.Fa next_in ,
+.Fa avail_in ,
+.Fa next_out
+and
+.Fa avail_out
+are unused and unchanged.
+The current implementation of
+.Fn inflateInit
+does not process any header information \(em
+that is deferred until
+.Xr inflate 3
+is called.
+.
+.Pp
+The
+.Vt z_streamp
+type is documented in
+.Xr deflateInit 3 .
+.
+.Sh RETURN VALUES
+.Fn inflateInit
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_VERSION_ERROR
+if the zlib library version
+is incompatible with the version assumed by the caller,
+or
+.Dv Z_STREAM_ERROR
+if the parameters are invalid,
+such as a null pointer to the structure.
+.Fa msg
+is set to null if there is no error message.
+.
+.Sh SEE ALSO
+.Xr inflate 3 ,
+.Xr inflateBackInit 3 ,
+.Xr inflateCopy 3 ,
+.Xr inflateEnd 3 ,
+.Xr inflateInit2 3 ,
+.Xr inflateSetDictionary 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateInit2.3 b/doc/zlib/inflateInit2.3
new file mode 100644
index 00000000..5b8b49ac
--- /dev/null
+++ b/doc/zlib/inflateInit2.3
@@ -0,0 +1,181 @@
+.Dd January 15, 2017
+.Dt INFLATEINIT2 3
+.Os
+.
+.Sh NAME
+.Nm inflateInit2
+.Nd inflate compression options
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateInit2 "z_streamp strm" "int windowBits"
+.
+.Sh DESCRIPTION
+This is another version of
+.Xr inflateInit 3
+with an extra parameter.
+The fields
+.Fa next_in ,
+.Fa avail_in ,
+.Fa zalloc ,
+.Fa zfree
+and
+.Fa opaque
+must be initialized before by the caller.
+.
+.Pp
+The
+.Fa windowBits
+parameter is the base two logarithm
+of the maximum window size
+(the size of the history buffer).
+It should be in the range 8..15
+for this version of the library.
+The default value is 15 if
+.Xr inflateInit 3
+is used instead.
+.Fa windowBits
+must be greater than or equal to the
+.Fa windowBits
+value provided to
+.Xr deflateInit2 3
+while compressing,
+or it must be equal to 15 if
+.Xr deflateInit2 3
+was not used.
+If a compressed stream with a larger window size
+is given as input,
+.Xr inflate 3
+will return with the error code
+.Dv Z_DATA_ERROR
+instead of trying to allocate a larger window.
+.
+.Pp
+.Fa windowBits
+can also be zero
+to request that
+.Xr inflate 3
+use the window size
+in the zlib header
+of the compressed stream.
+.
+.Pp
+.Fa windowBits
+can also be -8..-15
+for raw inflate.
+In this case,
+.Fa -windowBits
+determines the window size.
+.Xr inflate 3
+will then process raw deflate data,
+not looking for a zlib or gzip header,
+not generating a check value,
+and not looking for any check values
+for comparison at the end of the stream.
+This is for use with other formats
+that use the deflate compressed data format
+such as zip.
+Those formats provide their own check values.
+If a custom format is developed
+using the raw deflate format for compressed data,
+it is recommended that a check value
+such as an Adler-32 or a CRC-32
+be applied to the uncompressed data
+as is done in the zlib, gzip and zip formats.
+For most applications,
+the zlib format should be used as is.
+Note that comments above on the use in
+.Xr deflateInit2 3
+applies to the magnitude of
+.Fa windowBits .
+.
+.Pp
+.Fa windowBits
+can also be greater than 15
+for optional gzip decoding.
+Add 32 to
+.Fa windowBits
+to enable zlib and gzip decoding
+with automatic header detection,
+or add 16 to decode only the gzip format
+.Po
+the zlib format will return a
+.Dv Z_DATA_ERROR
+.Pc .
+If a gzip stream is being decoded,
+.Fa strm->adler
+is a CRC-32 instead of an Adler-32.
+Unlike the
+.Xr gunzip 1
+utility and
+.Xr gzread 3 ,
+.Xr inflate 3
+will not automatically decode
+concatenated gzip streams.
+.Xr inflate 3
+will return
+.Dv Z_STREAM_END
+at the end of the gzip stream.
+The state would need to be reset
+to continue decoding a subsequent gzip stream.
+.
+.Pp
+.Fn inflateInit2
+does not perform any decompression
+apart from possibly reading the zlib header if present:
+actual decompression will be done by
+.Xr inflate 3 .
+.Po
+So
+.Fa next_in
+and
+.Fa avail_in
+may be modified,
+but
+.Fa next_out
+and
+.Fa avail_out
+are unused and unchanged.
+.Pc \&
+The current implementation of
+.Fn inflateInit2
+does not process any header information \(em
+that is deferred until
+.Xr inflate 3
+is called.
+.
+.Sh RETURN VALUES
+.Fn inflateInit2
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_VERSION_ERROR
+if the zlib library version
+is incompatible with the version assumed by the caller,
+or
+.Dv Z_STREAM_ERROR
+if the parameters are invalid,
+such as a null pointer to the structure.
+.Fa msg
+is set to null if there is no error message.
+.
+.Sh SEE ALSO
+.Xr deflateInit2 3 ,
+.Xr inflateInit 3 ,
+.Xr inflatePrime 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateMark.3 b/doc/zlib/inflateMark.3
new file mode 100644
index 00000000..90e2ee0b
--- /dev/null
+++ b/doc/zlib/inflateMark.3
@@ -0,0 +1,88 @@
+.Dd January 15, 2017
+.Dt INFLATEMARK 3
+.Os
+.
+.Sh NAME
+.Nm inflateMark
+.Nd mark location for random access
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft long
+.Fn inflateMark "z_streamp strm"
+.
+.Sh DESCRIPTION
+This function returns two values,
+one in the lower 16 bits of the return value,
+and the other in the remaining upper bits,
+obtained by shifting the return value down 16 bits.
+If the upper value is -1
+and the lower value is zero,
+then
+.Xr inflate 3
+is currently decoding information outside of a block.
+If the upper value is -1
+and the lower value is non-zero,
+then
+.Xr inflate 3
+is in the middle of a stored block,
+with the lower value equaling
+the number of bytes from the input remaining to copy.
+If the upper value is not -1,
+then it is the number of bits
+back from the current bit position
+in the input of the code
+(literal of length/distance pair)
+currently being processed.
+In that case the lower value
+is the number of bytes
+already emitted for that code.
+.
+.Pp
+A code is being processed if
+.Xr inflate 3
+is waiting for more input to complete
+decoding of the code,
+or if it has completed decoding
+but is waiting for more output space
+to write the literal or match data.
+.
+.Pp
+.Fn inflateMark
+is used to mark locations in the input data
+for random access,
+which may be at bit positions,
+and to note those cases where
+the output of a code may span boundaries
+of random access blocks.
+The current location in the input stream
+can be determined from
+.Fa avail_in
+and
+.Fa data_type
+as noted in the description for the
+.Dv Z_BLOCK
+.Fa flush
+parameter for
+.Xr inflate 3 .
+.
+.Sh RETURN VALUES
+.Fn inflateMark
+returns the value noted above,
+or -65536 if the provided source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr inflate 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflatePrime.3 b/doc/zlib/inflatePrime.3
new file mode 100644
index 00000000..66953665
--- /dev/null
+++ b/doc/zlib/inflatePrime.3
@@ -0,0 +1,73 @@
+.Dd January 15, 2017
+.Dt INFLATEPRIME 3
+.Os
+.
+.Sh NAME
+.Nm inflatePrime
+.Nd insert bits in inflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflatePrime "z_streamp strm" "int bits" "int value"
+.
+.Sh DESCRIPTION
+This function inserts bits
+in the inflate input stream.
+The intent is that this function
+is used to start inflating
+at a bit position
+in the middle of a byte.
+The provided bits will be used
+before any bytes are used from
+.Fa next_in .
+This function should only be used with raw inflate,
+and should be used before the first
+.Xr inflate 3
+call after
+.Xr inflateInit2 3
+or
+.Xr inflateReset 3 .
+.Fa bits
+must be less than or equal to 16,
+and that many of the least significant bits of
+.Fa value
+will be inserted in the input.
+.
+.Pp
+If
+.Fa bits
+is negative,
+then the input stream bit buffer is emptied.
+Then
+.Fn inflatePrime
+can be called again
+to put bits in the buffer.
+This is used to clear out bits leftover
+after feeding inflate a block description
+prior to feeding inflate codes.
+.
+.Sh RETURN VALUES
+.Fn inflatePrime
+returns
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent.
+.
+.Sh SEE ALSO
+.Xr inflateInit2 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateReset.3 b/doc/zlib/inflateReset.3
new file mode 100644
index 00000000..53c4ffe2
--- /dev/null
+++ b/doc/zlib/inflateReset.3
@@ -0,0 +1,81 @@
+.Dd January 15, 2017
+.Dt INFLATERESET 3
+.Os
+.
+.Sh NAME
+.Nm inflateReset ,
+.Nm inflateReset2
+.Nd reset inflate stream
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateReset "z_streamp strm"
+.Ft int
+.Fn inflateReset2 "z_streamp strm" "int windowBits"
+.
+.Sh DESCRIPTION
+This function is equivalent to
+.Xr inflateEnd 3
+followed by
+.Xr inflateInit 3 ,
+but does not free and reallocate
+the internal decompression state.
+The stream will keep attributes
+that may have been set by
+.Xr inflateInit2 3 .
+.
+.Pp
+.Fn inflateReset2
+is the same as
+.Fn inflateReset ,
+but it also permits changing
+the wrap and window size requests.
+The
+.Fa windowBits
+parameter is interpreted the same as it is for
+.Xr inflateInit2 3 .
+If the window size is changed,
+then the memory allocated for the window is freed,
+and the window will be reallocated by
+.Xr inflate 3
+if needed.
+.
+.Sh RETURN VALUES
+.Fn inflateReset
+and
+.Fn inflateReset2
+return
+.Dv Z_OK
+if success,
+or
+.Dv Z_STREAM_ERROR
+if the source stream state was inconsistent
+.Po
+such as
+.Fa zalloc
+or
+.Fa state
+being
+.Dv Z_NULL
+.Pc ,
+or if the
+.Fa windowBits
+parameter is invalid.
+.
+.Sh SEE ALSO
+.Xr inflateEnd 3 ,
+.Xr inflateInit 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateSetDictionary.3 b/doc/zlib/inflateSetDictionary.3
new file mode 100644
index 00000000..291c97e8
--- /dev/null
+++ b/doc/zlib/inflateSetDictionary.3
@@ -0,0 +1,85 @@
+.Dd January 15, 2017
+.Dt INFLATESETDICTIONARY 3
+.Os
+.
+.Sh NAME
+.Nm inflateSetDictionary
+.Nd initialize decompression dictionary
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fo inflateSetDictionary
+.Fa "z_streamp strm"
+.Fa "const Bytef *dictionary"
+.Fa "uInt dictLength"
+.Fc
+.
+.Sh DESCRIPTION
+Initializes the decompression dictionary
+from the given uncompressed byte sequence.
+This function must be called
+immediately after a call of
+.Xr inflate 3 ,
+if that call returned
+.Dv Z_NEED_DICT .
+The dictionary chosen by the compressor
+can be determined from the Adler-32 value
+returned by that call of
+.Xr inflate 3 .
+The compressor and decompressor
+must use exactly the same dictionary
+.Po
+see
+.Xr deflateSetDictionary 3
+.Pc .
+For raw inflate,
+this function can be called at any time
+to set the dictionary.
+If the provided dictionary
+is smaller than the window
+and there is already data in the window,
+then the provided dictionary
+will amend what's there.
+The application must insure that the dictionary
+that was used for compression is provided.
+.
+.Pp
+.Fn inflateSetDictionary
+does not perform any decompression:
+this will be done by subsequent calls of
+.Xr inflate 3 .
+.
+.Sh RETURN VALUES
+.Fn inflateSetDictionary
+returns
+.Dv Z_OK
+if success,
+.Dv Z_STREAM_ERROR
+if a parameter is invalid
+.Po
+e.g. dictionary being
+.Dv Z_NULL
+.Pc
+or the stream state is inconsistent,
+.Dv Z_DATA_ERROR
+if the given dictionary
+doesn't match the expected one
+(incorrect Adler-32 value).
+.
+.Sh SEE ALSO
+.Xr deflateGetDictionary 3 ,
+.Xr inflateGetDictionary 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/inflateSync.3 b/doc/zlib/inflateSync.3
new file mode 100644
index 00000000..56d3ca28
--- /dev/null
+++ b/doc/zlib/inflateSync.3
@@ -0,0 +1,72 @@
+.Dd January 15, 2017
+.Dt INFLATESYNC 3
+.Os
+.
+.Sh NAME
+.Nm inflateSync
+.Nd skip invalid data
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft int
+.Fn inflateSync "z_streamp strm"
+.
+.Sh DESCRIPTION
+Skips invalid compressed data
+until a possible full flush point
+.Po
+see
+.Xr deflate 3
+for the description of
+.Dv Z_FULL_FLUSH
+.Pc
+can be found,
+or until all available input is skipped.
+No output is provided.
+.
+.Pp
+.Fn inflateSync
+searches for a 00 00 FF FF pattern
+in the compressed data.
+All full flush points have this pattern,
+but not all occurrences of this pattern
+are full flush points.
+.
+.Sh RETURN VALUES
+.Fn inflateSync
+returns
+.Dv Z_OK
+if a possible full flush point has been found,
+.Dv Z_BUF_ERROR
+if no more input was provided,
+.Dv Z_DATA_ERROR
+if no flush point has been found,
+or
+.Dv Z_STREAM_ERROR
+if the stream structure was inconsistent.
+In the success case,
+the application may save the current value of
+.Fa total_in
+which indicates where valid compressed data was found.
+In the error case,
+the application may repeatedly call
+.Fn inflateSync ,
+providing more input each time,
+until success or the end of the input data.
+.
+.Sh SEE ALSO
+.Xr deflate 3 ,
+.Xr inflate 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/uncompress.3 b/doc/zlib/uncompress.3
new file mode 100644
index 00000000..1047ad91
--- /dev/null
+++ b/doc/zlib/uncompress.3
@@ -0,0 +1,92 @@
+.Dd January 15, 2017
+.Dt UNCOMPRESS 3
+.Os
+.
+.Sh NAME
+.Nm uncompress ,
+.Nm uncompress2
+.Nd decompress source buffer into destination buffer
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.
+.Ft int
+.Fo uncompress
+.Fa "Bytef *dest"
+.Fa "uLongf *destLen"
+.Fa "const Bytef *source"
+.Fa "uLong sourceLen"
+.Fc
+.
+.Ft int
+.Fo uncompress2
+.Fa "Bytef *dest"
+.Fa "uLongf *destLen"
+.Fa "const Bytef *source"
+.Fa "uLong *sourceLen"
+.Fc
+.
+.Sh DESCRIPTION
+Decompresses the source buffer into the destination buffer.
+.Fa sourceLen
+is the byte length of the source buffer.
+Upon entry,
+.Fa destLen
+is the total size of the destination buffer,
+which must be large enough to hold the entire uncompressed data.
+.Po
+The size of the uncompressed data
+must have been saved previously by the compressor
+and transmitted to the decompressor
+by some mechanism outside the scope of this compression library.
+.Pc \&
+Upon exit,
+.Fa destLen
+is the actual size of the uncompressed data.
+.
+.Pp
+.Fn uncompress2
+is the same as
+.Fn uncompress ,
+except that
+.Fa sourceLen
+is a pointer,
+where the length of the source is
+.Fa *sourceLen .
+On return,
+.Fa *sourceLen
+is the number of source bytes consumed.
+.
+.Sh RETURN VALUES
+.Fn uncompress
+returns
+.Dv Z_OK
+if success,
+.Dv Z_MEM_ERROR
+if there was not enough memory,
+.Dv Z_BUF_ERROR
+if there was not enough room in the output buffer,
+or
+.Dv Z_DATA_ERROR
+if the input data was corrupted or incomplete.
+In the case where there is not enough room,
+.Fn uncompress
+will fill the output buffer
+with the uncompressed data up to that point.
+.
+.Sh SEE ALSO
+.Xr compress 3 ,
+.Xr inflate 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/zlibCompileFlags.3 b/doc/zlib/zlibCompileFlags.3
new file mode 100644
index 00000000..59cc24a8
--- /dev/null
+++ b/doc/zlib/zlibCompileFlags.3
@@ -0,0 +1,163 @@
+.Dd January 15, 2017
+.Dt ZLIBCOMPILEFLAGS 3
+.Os
+.
+.Sh NAME
+.Nm zlibCompileFlags
+.Nd zlib compile-time options
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Ft uLong
+.Fn zlibCompileFlags void
+.
+.Sh DESCRIPTION
+Return flags indicating compile-time options.
+.
+.Ss Type sizes
+Two bits each,
+00 = 16 bits,
+01 = 32 bits,
+10 = 64 bits,
+11 = other.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It 1.0
+size of
+.Vt uInt
+.It 3.2
+size of
+.Vt uLong
+.It 5.4
+size of
+.Vt voidpf
+(pointer)
+.It 7.6
+size of
+.Vt z_off_t
+.El
+.
+.Ss Compiler, assembler, and debug options
+.Bl -tag -width Ds -compact
+.It 8
+.Dv ZLIB_DEBUG
+.It 9
+.Dv ASMV
+or
+.Dv ASMINF
+\(em
+use ASM code
+.It 10
+.Dv ZLIB_WINAPI
+\(em
+exported functions use the WINAPI calling convention
+.It 11
+0 (reserved)
+.El
+.
+.Ss One-time table building
+Smaller code,
+but not thread-safe if true.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It 12
+.Dv BUILDFIXED
+\(em
+build static block decoding tables when needed
+.It 13
+.Dv DYNAMIC_CRC_TABLE
+\(em
+build CRC calculation tables when needed
+.It 14,15
+0 (reserved)
+.El
+.
+.Ss Library content
+Indicates missing functionality.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It 16
+.Dv NO_GZCOMPRESS
+\(em
+gz* functions cannot compress
+(to avoid linking deflate code when not needed)
+.It 17
+.Dv NO_GZIP
+\(em
+.Xr deflate 3
+can't write gzip streams,
+and
+.Xr inflate 3
+can't detect and decode gzip streams
+(to avoid linking crc code)
+.It 18-19
+0 (reserved)
+.El
+.
+.Ss Operation variations
+Changes in library functionality.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It 20
+.Dv PKZIP_BUG_WORKAROUND
+\(em
+slightly more permissive
+.Xr inflate 3
+.It 21
+.Dv FASTEST
+\(em
+deflate algorithm with only one,
+lowest compression level
+.It 22,23
+0 (reserved)
+.El
+.
+.Ss sprintf variant used by gzprintf
+Zero is best.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It 24
+0 = vs*,
+1 = s*
+\(em
+1 means limited to 20 arguments after the format
+.It 25
+0 = *nprintf,
+1 = *printf
+\(em
+1 means
+.Xr gzprintf 3
+not secure!
+.It 26
+0 = returns value,
+1 = void
+\(em
+1 means inferred string length returned
+.El
+.
+.Ss Remainder
+.Bl -tag -width Ds -compact
+.It 27-31
+0 (reserved)
+.El
+.
+.Sh SEE ALSO
+.Xr zlibVersion 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/doc/zlib/zlibVersion.3 b/doc/zlib/zlibVersion.3
new file mode 100644
index 00000000..04377527
--- /dev/null
+++ b/doc/zlib/zlibVersion.3
@@ -0,0 +1,45 @@
+.Dd January 15, 2017
+.Dt ZLIBVERSION 3
+.Os
+.
+.Sh NAME
+.Nm zlibVersion
+.Nd check zlib version
+.
+.Sh LIBRARY
+.Lb libz
+.
+.Sh SYNOPSIS
+.In zlib.h
+.Fd #define ZLIB_VERSION \(dq1.2.11\(dq
+.Ft "const char *"
+.Fn zlibVersion void
+.
+.Sh DESCRIPTION
+The application can compare
+.Fn zlibVersion
+and
+.Dv ZLIB_VERSION
+for consistency.
+If the first character differs,
+the library code actually used
+is not compatible with the
+.In zlib.h
+header file used by the application.
+This check is automatically made by
+.Xr deflateInit 3
+and
+.Xr inflateInit 3 .
+.
+.Sh SEE ALSO
+.Xr zlibCompileFlags 3
+.
+.Sh HISTORY
+This manual page was converted from
+.In zlib.h
+to mdoc format by
+.An June McEnroe Aq Mt june@causal.agency .
+.
+.Sh AUTHORS
+.An Jean-loup Gailly Aq Mt jloup@gzip.org
+.An Mark Adler Aq Mt madler@alumni.caltech.edu
diff --git a/etc/CodeQWERTY.bundle/Contents/Info.plist b/etc/CodeQWERTY.bundle/Contents/Info.plist
new file mode 100644
index 00000000..f78351e8
--- /dev/null
+++ b/etc/CodeQWERTY.bundle/Contents/Info.plist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<key>CFBundleIdentifier</key>
+		<string>agency.causal.keyboardlayout.code</string>
+		<key>CFBundleName</key>
+		<string>Code QWERTY</string>
+		<key>CFBundleVersion</key>
+		<string>1.0</string>
+		<key>KLInfo_Code QWERTY</key>
+		<dict>
+			<key>TISInputSourceID</key>
+			<string>agency.causal.keyboardlayout.code.qwerty</string>
+			<key>TISIntendedLanguage</key>
+			<string>en-CA</string>
+		</dict>
+	</dict>
+</plist>
diff --git a/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout b/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout
new file mode 100644
index 00000000..393a86dd
--- /dev/null
+++ b/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout
@@ -0,0 +1,1178 @@
+<?xml version="1.1" encoding="UTF-8"?>
+<!DOCTYPE keyboard PUBLIC "" "file://localhost/System/Library/DTDs/KeyboardLayout.dtd">
+<keyboard group="0" id="5069" maxout="1" name="Code QWERTY">
+    <layouts>
+        <layout first="0" last="17" mapSet="16c" modifiers="f4"/>
+        <layout first="18" last="18" mapSet="994" modifiers="f4"/>
+        <layout first="21" last="23" mapSet="994" modifiers="f4"/>
+        <layout first="30" last="30" mapSet="994" modifiers="f4"/>
+        <layout first="194" last="194" mapSet="994" modifiers="f4"/>
+        <layout first="197" last="197" mapSet="994" modifiers="f4"/>
+        <layout first="200" last="201" mapSet="994" modifiers="f4"/>
+        <layout first="206" last="207" mapSet="994" modifiers="f4"/>
+    </layouts>
+    <modifierMap defaultIndex="7" id="f4">
+        <keyMapSelect mapIndex="8">
+            <modifier keys="command?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="0">
+            <modifier keys="anyShift? caps? command"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="9">
+            <modifier keys="anyShift caps?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="2">
+            <modifier keys="caps"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="3">
+            <modifier keys="anyOption"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="4">
+            <modifier keys="anyShift caps? anyOption command?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="5">
+            <modifier keys="caps anyOption"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="6">
+            <modifier keys="caps? anyOption command"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="7">
+            <modifier keys="anyShift caps? option? command? control"/>
+            <modifier keys="shift? caps? anyOption command? control"/>
+            <modifier keys="caps? anyOption? command? control"/>
+        </keyMapSelect>
+    </modifierMap>
+    <keyMapSet id="16c">
+        <keyMap index="0">
+            <key action="13" code="0"/>
+            <key code="1" output="s"/>
+            <key code="2" output="d"/>
+            <key code="3" output="f"/>
+            <key code="4" output="h"/>
+            <key code="5" output="g"/>
+            <key code="6" output="z"/>
+            <key code="7" output="x"/>
+            <key code="8" output="c"/>
+            <key code="9" output="v"/>
+            <key code="10" output="§"/>
+            <key code="11" output="b"/>
+            <key code="12" output="q"/>
+            <key code="13" output="w"/>
+            <key action="14" code="14"/>
+            <key code="15" output="r"/>
+            <key action="19" code="16"/>
+            <key code="17" output="t"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key action="17" code="31"/>
+            <key action="18" code="32"/>
+            <key code="33" output="["/>
+            <key action="15" code="34"/>
+            <key code="35" output="p"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="l"/>
+            <key code="38" output="j"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="k"/>
+            <key code="41" output=";"/>
+            <key code="42" output="\"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key action="16" code="45"/>
+            <key code="46" output="m"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="1">
+            <key action="6" code="0"/>
+            <key code="1" output="S"/>
+            <key code="2" output="D"/>
+            <key code="3" output="F"/>
+            <key code="4" output="H"/>
+            <key code="5" output="G"/>
+            <key code="6" output="Z"/>
+            <key code="7" output="X"/>
+            <key code="8" output="C"/>
+            <key code="9" output="V"/>
+            <key code="10" output="±"/>
+            <key code="11" output="B"/>
+            <key code="12" output="Q"/>
+            <key code="13" output="W"/>
+            <key action="7" code="14"/>
+            <key code="15" output="R"/>
+            <key action="12" code="16"/>
+            <key code="17" output="T"/>
+            <key code="18" output="!"/>
+            <key code="19" output="@"/>
+            <key code="20" output="#"/>
+            <key code="21" output="$"/>
+            <key code="22" output="^"/>
+            <key code="23" output="%"/>
+            <key code="24" output="+"/>
+            <key code="25" output="("/>
+            <key code="26" output="&#x0026;"/>
+            <key code="27" output="_"/>
+            <key code="28" output="*"/>
+            <key code="29" output=")"/>
+            <key code="30" output="}"/>
+            <key action="10" code="31"/>
+            <key action="11" code="32"/>
+            <key code="33" output="{"/>
+            <key action="8" code="34"/>
+            <key code="35" output="P"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="L"/>
+            <key code="38" output="J"/>
+            <key code="39" output="&#x0022;"/>
+            <key code="40" output="K"/>
+            <key code="41" output=":"/>
+            <key code="42" output="|"/>
+            <key code="43" output="&#x003C;"/>
+            <key code="44" output="?"/>
+            <key action="9" code="45"/>
+            <key code="46" output="M"/>
+            <key code="47" output="&#x003E;"/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="~"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="*"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="+"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="="/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="/"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="2">
+            <key action="6" code="0"/>
+            <key code="1" output="S"/>
+            <key code="2" output="D"/>
+            <key code="3" output="F"/>
+            <key code="4" output="H"/>
+            <key code="5" output="G"/>
+            <key code="6" output="Z"/>
+            <key code="7" output="X"/>
+            <key code="8" output="C"/>
+            <key code="9" output="V"/>
+            <key code="10" output="§"/>
+            <key code="11" output="B"/>
+            <key code="12" output="Q"/>
+            <key code="13" output="W"/>
+            <key action="7" code="14"/>
+            <key code="15" output="R"/>
+            <key action="12" code="16"/>
+            <key code="17" output="T"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key action="10" code="31"/>
+            <key action="11" code="32"/>
+            <key code="33" output="["/>
+            <key action="8" code="34"/>
+            <key code="35" output="P"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="L"/>
+            <key code="38" output="J"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="K"/>
+            <key code="41" output=";"/>
+            <key code="42" output="\"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key action="9" code="45"/>
+            <key code="46" output="M"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="3">
+            <key code="0" output="å"/>
+            <key code="1" output="ß"/>
+            <key code="2" output="∂"/>
+            <key code="3" output="ƒ"/>
+            <key code="4" output="˙"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="∫"/>
+            <key code="12" output="œ"/>
+            <key code="13" output="∑"/>
+            <key action="0" code="14"/>
+            <key code="15" output="®"/>
+            <key code="16" output="¥"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="™"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="ø"/>
+            <key action="3" code="32"/>
+            <key code="33" output="“"/>
+            <key action="2" code="34"/>
+            <key code="35" output="π"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="¬"/>
+            <key code="38" output="∆"/>
+            <key code="39" output="æ"/>
+            <key code="40" output="˚"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key action="4" code="45"/>
+            <key code="46" output="µ"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key action="1" code="50"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="4">
+            <key code="0" output="Å"/>
+            <key code="1" output="Í"/>
+            <key code="2" output="Î"/>
+            <key code="3" output="Ï"/>
+            <key code="4" output="Ó"/>
+            <key code="5" output="˝"/>
+            <key code="6" output="¸"/>
+            <key code="7" output="˛"/>
+            <key code="8" output="Ç"/>
+            <key code="9" output="◊"/>
+            <key code="10" output="±"/>
+            <key code="11" output="ı"/>
+            <key code="12" output="Œ"/>
+            <key code="13" output="„"/>
+            <key code="14" output="´"/>
+            <key code="15" output="‰"/>
+            <key code="16" output="Á"/>
+            <key code="17" output="ˇ"/>
+            <key code="18" output="⁄"/>
+            <key code="19" output="€"/>
+            <key code="20" output="‹"/>
+            <key code="21" output="›"/>
+            <key code="22" output="fl"/>
+            <key code="23" output="fi"/>
+            <key code="24" output="±"/>
+            <key code="25" output="·"/>
+            <key code="26" output="‡"/>
+            <key code="27" output="—"/>
+            <key code="28" output="°"/>
+            <key code="29" output="‚"/>
+            <key code="30" output="’"/>
+            <key code="31" output="Ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="”"/>
+            <key code="34" output="ˆ"/>
+            <key code="35" output="∏"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="Ò"/>
+            <key code="38" output="Ô"/>
+            <key code="39" output="Æ"/>
+            <key code="40" output=""/>
+            <key code="41" output="Ú"/>
+            <key code="42" output="»"/>
+            <key code="43" output="¯"/>
+            <key code="44" output="¿"/>
+            <key code="45" output="˜"/>
+            <key code="46" output="Â"/>
+            <key code="47" output="˘"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="*"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="+"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="="/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="/"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="5">
+            <key code="0" output="Å"/>
+            <key code="1" output="Í"/>
+            <key code="2" output="Î"/>
+            <key code="3" output="Ï"/>
+            <key code="4" output="Ó"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="Ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="ı"/>
+            <key code="12" output="Œ"/>
+            <key code="13" output="∑"/>
+            <key code="14" output="´"/>
+            <key code="15" output="®"/>
+            <key code="16" output="Á"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="™"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="Ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="“"/>
+            <key code="34" output="ˆ"/>
+            <key code="35" output="∏"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="Ò"/>
+            <key code="38" output="Ô"/>
+            <key code="39" output="Æ"/>
+            <key code="40" output="˚"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key code="45" output="˜"/>
+            <key code="46" output="Â"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="6">
+            <key code="0" output="å"/>
+            <key code="1" output="ß"/>
+            <key code="2" output="∂"/>
+            <key code="3" output="ƒ"/>
+            <key code="4" output="˙"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="∫"/>
+            <key code="12" output="œ"/>
+            <key code="13" output="∑"/>
+            <key code="14" output="´"/>
+            <key code="15" output="®"/>
+            <key code="16" output="¥"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="™"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="“"/>
+            <key code="34" output="^"/>
+            <key code="35" output="π"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="¬"/>
+            <key code="38" output="∆"/>
+            <key code="39" output="æ"/>
+            <key code="40" output="˚"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key code="45" output="~"/>
+            <key code="46" output="µ"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="7">
+            <key code="0" output="&#x0001;"/>
+            <key code="1" output="&#x0013;"/>
+            <key code="2" output="&#x0004;"/>
+            <key code="3" output="&#x0006;"/>
+            <key code="4" output="&#x0008;"/>
+            <key code="5" output="&#x0007;"/>
+            <key code="6" output="&#x001A;"/>
+            <key code="7" output="&#x0018;"/>
+            <key code="8" output="&#x0003;"/>
+            <key code="9" output="&#x0016;"/>
+            <key code="10" output="0"/>
+            <key code="11" output="&#x0002;"/>
+            <key code="12" output="&#x0011;"/>
+            <key code="13" output="&#x0017;"/>
+            <key code="14" output="&#x0005;"/>
+            <key code="15" output="&#x0012;"/>
+            <key code="16" output="&#x0019;"/>
+            <key code="17" output="&#x0014;"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="&#x001F;"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="&#x001D;"/>
+            <key code="31" output="&#x000F;"/>
+            <key code="32" output="&#x0015;"/>
+            <key code="33" output="&#x001B;"/>
+            <key code="34" output="&#x0009;"/>
+            <key code="35" output="&#x0010;"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="&#x000C;"/>
+            <key code="38" output="&#x000A;"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="&#x000B;"/>
+            <key code="41" output=";"/>
+            <key code="42" output="&#x001C;"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key code="45" output="&#x000E;"/>
+            <key code="46" output="&#x000D;"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="8" baseMapSet="16c" baseIndex="0">
+            <key code="18" output="!"/>
+            <key code="19" output="@"/>
+            <key code="20" output="#"/>
+            <key code="21" output="$"/>
+            <key code="22" output="^"/>
+            <key code="23" output="%"/>
+            <key code="25" output="("/>
+            <key code="26" output="&#x0026;"/>
+            <key code="27" output="_"/>
+            <key code="28" output="*"/>
+            <key code="29" output=")"/>
+            <key code="30" output="}"/>
+            <key code="33" output="{"/>
+            <key code="42" output="|"/>
+        </keyMap>
+        <keyMap index="9" baseMapSet="16c" baseIndex="1">
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key code="33" output="["/>
+            <key code="42" output="\"/>
+        </keyMap>
+    </keyMapSet>
+    <keyMapSet id="994">
+        <keyMap baseIndex="0" baseMapSet="16c" index="0">
+            <key code="24" output="^"/>
+            <key code="30" output="["/>
+            <key code="33" output="@"/>
+            <key code="39" output=":"/>
+            <key code="42" output="]"/>
+            <key code="93" output="¥"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="1" baseMapSet="16c" index="1">
+            <key code="19" output="&#x0022;"/>
+            <key code="22" output="&#x0026;"/>
+            <key code="24" output="~"/>
+            <key code="25" output=")"/>
+            <key code="26" output="&#x0027;"/>
+            <key code="27" output="="/>
+            <key code="28" output="("/>
+            <key code="29" output="0"/>
+            <key code="30" output="{"/>
+            <key code="33" output="`"/>
+            <key code="39" output="*"/>
+            <key code="41" output="+"/>
+            <key code="42" output="}"/>
+            <key code="93" output="|"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="2" baseMapSet="16c" index="2">
+            <key code="24" output="^"/>
+            <key code="30" output="["/>
+            <key code="33" output="@"/>
+            <key code="39" output=":"/>
+            <key code="42" output="]"/>
+            <key code="93" output="¥"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="3" baseMapSet="16c" index="3">
+            <key code="93" output="\"/>
+            <key action="1" code="94"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="4" baseMapSet="16c" index="4">
+            <key code="93" output="|"/>
+            <key code="94" output="`"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="5" baseMapSet="16c" index="5">
+            <key code="93" output="\"/>
+            <key code="94" output="`"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="6" baseMapSet="16c" index="6">
+            <key code="93" output="\"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="7" baseMapSet="16c" index="7">
+            <key code="93" output="|"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+    </keyMapSet>
+    <actions>
+        <action id="0">
+            <when next="s1" state="none"/>
+        </action>
+        <action id="1">
+            <when next="s2" state="none"/>
+        </action>
+        <action id="10">
+            <when output="O" state="none"/>
+            <when output="Ó" state="s1"/>
+            <when output="Ò" state="s2"/>
+            <when output="Ô" state="s3"/>
+            <when output="Ö" state="s4"/>
+            <when output="Õ" state="s5"/>
+        </action>
+        <action id="11">
+            <when output="U" state="none"/>
+            <when output="Ú" state="s1"/>
+            <when output="Ù" state="s2"/>
+            <when output="Û" state="s3"/>
+            <when output="Ü" state="s4"/>
+        </action>
+        <action id="12">
+            <when output="Y" state="none"/>
+            <when output="Ÿ" state="s4"/>
+        </action>
+        <action id="13">
+            <when output="a" state="none"/>
+            <when output="á" state="s1"/>
+            <when output="à" state="s2"/>
+            <when output="â" state="s3"/>
+            <when output="ä" state="s4"/>
+            <when output="ã" state="s5"/>
+        </action>
+        <action id="14">
+            <when output="e" state="none"/>
+            <when output="é" state="s1"/>
+            <when output="è" state="s2"/>
+            <when output="ê" state="s3"/>
+            <when output="ë" state="s4"/>
+        </action>
+        <action id="15">
+            <when output="i" state="none"/>
+            <when output="í" state="s1"/>
+            <when output="ì" state="s2"/>
+            <when output="î" state="s3"/>
+            <when output="ï" state="s4"/>
+        </action>
+        <action id="16">
+            <when output="n" state="none"/>
+            <when output="ñ" state="s5"/>
+        </action>
+        <action id="17">
+            <when output="o" state="none"/>
+            <when output="ó" state="s1"/>
+            <when output="ò" state="s2"/>
+            <when output="ô" state="s3"/>
+            <when output="ö" state="s4"/>
+            <when output="õ" state="s5"/>
+        </action>
+        <action id="18">
+            <when output="u" state="none"/>
+            <when output="ú" state="s1"/>
+            <when output="ù" state="s2"/>
+            <when output="û" state="s3"/>
+            <when output="ü" state="s4"/>
+        </action>
+        <action id="19">
+            <when output="y" state="none"/>
+            <when output="ÿ" state="s4"/>
+        </action>
+        <action id="2">
+            <when next="s3" state="none"/>
+        </action>
+        <action id="3">
+            <when next="s4" state="none"/>
+        </action>
+        <action id="4">
+            <when next="s5" state="none"/>
+        </action>
+        <action id="5">
+            <when output=" " state="none"/>
+            <when output="´" state="s1"/>
+            <when output="`" state="s2"/>
+            <when output="ˆ" state="s3"/>
+            <when output="¨" state="s4"/>
+            <when output="˜" state="s5"/>
+        </action>
+        <action id="6">
+            <when output="A" state="none"/>
+            <when output="Á" state="s1"/>
+            <when output="À" state="s2"/>
+            <when output="Â" state="s3"/>
+            <when output="Ä" state="s4"/>
+            <when output="Ã" state="s5"/>
+        </action>
+        <action id="7">
+            <when output="E" state="none"/>
+            <when output="É" state="s1"/>
+            <when output="È" state="s2"/>
+            <when output="Ê" state="s3"/>
+            <when output="Ë" state="s4"/>
+        </action>
+        <action id="8">
+            <when output="I" state="none"/>
+            <when output="Í" state="s1"/>
+            <when output="Ì" state="s2"/>
+            <when output="Î" state="s3"/>
+            <when output="Ï" state="s4"/>
+        </action>
+        <action id="9">
+            <when output="N" state="none"/>
+            <when output="Ñ" state="s5"/>
+        </action>
+    </actions>
+    <terminators>
+        <when output="´" state="s1"/>
+        <when output="`" state="s2"/>
+        <when output="ˆ" state="s3"/>
+        <when output="¨" state="s4"/>
+        <when output="˜" state="s5"/>
+    </terminators>
+</keyboard>
diff --git a/etc/Dark.terminal b/etc/Dark.terminal
new file mode 100644
index 00000000..983489e0
--- /dev/null
+++ b/etc/Dark.terminal
@@ -0,0 +1,1654 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ANSIBlackColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECswLjA4NjQxNDgyMTQ1IDAuMDgyNjczMjQ0MTggMC4wNjE3
+	NjQxODgxMSAxTxAqMC4wNjg1NjUyNzkyNSAwLjA2NjU1MTc5NzA5IDAuMDUzMTg5Nzg0
+	MjkAEAGAAoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1u
+	dHJSR0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAA
+	AAAAAAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAA
+	FGJrcHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRt
+	bmQAAAJUAAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkA
+	AAP4AAAAFG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8
+	AAAIDGJUUkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQt
+	UGFja2FyZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAA
+	AAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAA
+	AAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY
+	2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVj
+	LmNoAAAAAAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklF
+	QyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRp
+	b24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29u
+	ZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmll
+	dwAAAAAAE6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABX
+	H+dtZWFzAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBj
+	dXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBe
+	AGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA
+	4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8
+	AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksC
+	VAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNa
+	A2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoE
+	qAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYn
+	BjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH
+	+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7
+	ChEKJwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMM
+	XAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7u
+	DwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR
+	6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUS
+	FTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoY
+	rxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7
+	HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJgg
+	xCDwIRwhSCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4
+	JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIq
+	NSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9a
+	L5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1
+	EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrv
+	Oy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlB
+	akGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgF
+	SEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBP
+	SU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFap
+	VvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxe
+	vV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmbo
+	Zz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv
+	0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjM
+	eSp5iXnnekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCC
+	koL0g1eDuoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/Ixj
+	jMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+X
+	Cpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2
+	oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCt
+	RK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR
+	uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7F
+	S8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+
+	0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLf
+	Kd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG
+	7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf6
+	5/t3/Af8mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xv
+	clNwYWNloiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAAR
+	ABoAJAApADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDVAQIBBAEGAQgBDwEUARoB
+	HAEeASANbA1xDXwNhQ2SDZUNog2rDbANuAAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAA
+	AAAAAA27
+	</data>
+	<key>ANSIBlueColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjIzNzg3NzEzMDUgMC4zODQ0ODYzMTc2IDAuNDAxODE3
+	NTYwMiAxTxAnMC4xODUxNzQyNDE3IDAuMzEyNjkxMDYyNyAwLjMyNzM1NjEwMDEAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIBrightBlackColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjI5ODc1MjYwNTkgMC4yNzU0NDMxMzY3IDAuMjA4Mjg5
+	OTIxMyAxTxAnMC4yMzI1NTgyODAyIDAuMjEzOTg4NTQyNiAwLjE1NzM3MzgwMDkAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIBrightBlueColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjI5OTI5MDg5NTUgMC40ODI3MTYzODE1IDAuNDk2MTAy
+	ODA5OSAxTxAnMC4yMzg5MzgwOTMyIDAuNDA5NTE3ODI0NiAwLjQyMDQyMjc5MjQAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIBrightCyanColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjQxODI5MzY1NDkgMC41OTkyNjI5NTI4IDAuNDIxMTYz
+	NDY5NiAxTxAnMC4zNDk1MDAyOTg1IDAuNTM3MzM2ODg1OSAwLjM0NjU0NjIzMjcAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIBrightGreenColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjU1NjU1MTQ1NjUgMC42MDA4OTg1NjM5IDAuMTE2MjQx
+	OTY5MiAxTxAoMC40ODUyMTU1MTQ5IDAuNTQxMTI5NjQ4NyAwLjA5MjAwNTcxNDc3ABAB
+	gAKABdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdC
+	IFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA
+	9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0
+	AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAAC
+	VAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAA
+	ABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxi
+	VFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2th
+	cmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAA
+	ABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAA
+	AAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVog
+	AAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAA
+	AAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4x
+	IERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5
+	NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGlu
+	IElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlv
+	biBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAA
+	ABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVh
+	cwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAA
+	AAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgA
+	bQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDr
+	APAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsB
+	kgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJn
+	AnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3ID
+	fgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTE
+	BNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgG
+	WQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgf
+	CDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicK
+	PQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyO
+	DKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUP
+	QQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxIm
+	EkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYV
+	eBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6
+	GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc
+	9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEc
+	IUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl
+	xyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqb
+	Ks8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv
+	/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWH
+	NcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7
+	qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHu
+	QjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI
+	10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/d
+	UCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RX
+	klfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19h
+	X7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn
+	6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CG
+	cOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl5
+	53pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INX
+	g7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGN
+	mI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfg
+	mEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopaj
+	BqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4t
+	rqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6
+	O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZG
+	xsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHT
+	RNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A2
+	4L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7Zzu
+	KO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH
+	/Jj9Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFj
+	ZaIjJFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQA
+	KQAyADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0gD9AP8BAQEDAQoBDwEVARcBGQEb
+	DWcNbA13DYANjQ2QDZ0Npg2rDbMAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAAN
+	tg==
+	</data>
+	<key>ANSIBrightMagentaColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjYwMDY0NDIzMDggMC4zNTgyNDcxNjA5IDAuNDE4NDg3
+	MzEwNCAxTxAlMC41MjUyNzg5ODU1IDAuMjc3OTMxMTI0IDAuMzQzOTE0NjI4ABABgAKA
+	BdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZ
+	WiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYA
+	AQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAAC
+	BAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAA
+	AHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRt
+	ZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJD
+	AAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg
+	Q29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJz
+	UkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAA
+	AAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAA
+	AAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERl
+	ZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYt
+	Mi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElF
+	QzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk
+	/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAA
+	AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAA
+	AAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQBy
+	AHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA
+	9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGa
+	AaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnEC
+	egKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOK
+	A5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME
+	4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZq
+	BnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII
+	RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpU
+	CmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcM
+	wAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9e
+	D3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUS
+	ZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWb
+	Fb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZ
+	RRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0e
+	HUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUgh
+	dSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3
+	JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8r
+	Ais2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1
+	MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1
+	/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvo
+	PCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBC
+	ckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kd
+	SWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ
+	cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfg
+	WC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7Ng
+	BWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/
+	aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBx
+	OnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pG
+	eqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qE
+	HYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/
+	jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyY
+	uJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2
+	o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGv
+	Fq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1
+	uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPH
+	Qce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG
+	1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3h
+	ROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO60
+	70DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9
+	Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFjZaIj
+	JFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQAKQAy
+	ADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0gD6APwA/gEAAQcBDAESARQBFgEYDWQN
+	aQ10DX0Nig2NDZoNow2oDbAAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAANsw==
+	</data>
+	<key>ANSIBrightRedColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECkwLjgwMDg5NjQ2NTggMC4xOTc5Nzc1NDI5IDAuMDc4OTg3
+	MTU4ODMgMU8QKDAuNzQ2NjI4MTY1MiAwLjExOTYzNzUxOTEgMC4wNjg5MzE0MzA1OAAQ
+	AYACgAXTGBkRGhscVE5TSURVTlNJQ0MQB4ADgARPEQxIAAAMSExpbm8CEAAAbW50clJH
+	QiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAA
+	APbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtw
+	dAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAA
+	AlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gA
+	AAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgM
+	YlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNr
+	YXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAA
+	AAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAA
+	AAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFla
+	IAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gA
+	AAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIu
+	MSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYx
+	OTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRp
+	b24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAA
+	AAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521l
+	YXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYA
+	AAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBo
+	AG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA
+	6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGL
+	AZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0C
+	ZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNy
+	A34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYE
+	xATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZI
+	BlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsI
+	HwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQon
+	Cj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUM
+	jgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8l
+	D0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcS
+	JhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVW
+	FXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY
+	+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM
+	HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAh
+	HCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWX
+	Jccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1Kmgq
+	myrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/H
+	L/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01
+	hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtr
+	O6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB
+	7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iR
+	SNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP
+	3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dE
+	V5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9f
+	YV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeT
+	Z+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtw
+	hnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJ
+	eed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSD
+	V4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0x
+	jZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX
+	4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKW
+	owajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1Erbiu
+	La6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnC
+	uju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjG
+	RsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB
+	00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/g
+	NuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c
+	7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8
+	B/yY/Sn9uv5L/tz/bf//0h8gISJaJGNsYXNzbmFtZVgkY2xhc3Nlc1xOU0NvbG9yU3Bh
+	Y2WiIyRcTlNDb2xvclNwYWNlWE5TT2JqZWN00h8gJidXTlNDb2xvcqImJAAIABEAGgAk
+	ACkAMgA3AEkATABRAFMAWgBgAGsAeAB+AIsAoACnANMA/gEAAQIBBAELARABFgEYARoB
+	HA1oDW0NeA2BDY4NkQ2eDacNrA20AAAAAAAAAgEAAAAAAAAAKAAAAAAAAAAAAAAAAAAA
+	Dbc=
+	</data>
+	<key>ANSIBrightWhiteColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjgwMTk4NTU2MTggMC43MzU3NzAwNDY3IDAuNTU1MDUz
+	NTMyMSAxTxAlMC43NTY0MTU2MDU1IDAuNjg1MjQ2OTQ0NCAwLjQ4MjY3NTkxABABgAKA
+	BdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZ
+	WiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYA
+	AQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAAC
+	BAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAA
+	AHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRt
+	ZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJD
+	AAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg
+	Q29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJz
+	UkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAA
+	AAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAA
+	AAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERl
+	ZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYt
+	Mi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElF
+	QzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk
+	/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAA
+	AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAA
+	AAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQBy
+	AHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA
+	9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGa
+	AaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnEC
+	egKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOK
+	A5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME
+	4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZq
+	BnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII
+	RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpU
+	CmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcM
+	wAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9e
+	D3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUS
+	ZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWb
+	Fb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZ
+	RRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0e
+	HUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUgh
+	dSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3
+	JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8r
+	Ais2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1
+	MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1
+	/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvo
+	PCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBC
+	ckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kd
+	SWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ
+	cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfg
+	WC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7Ng
+	BWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/
+	aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBx
+	OnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pG
+	eqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qE
+	HYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/
+	jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyY
+	uJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2
+	o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGv
+	Fq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1
+	uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPH
+	Qce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG
+	1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3h
+	ROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO60
+	70DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9
+	Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFjZaIj
+	JFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQAKQAy
+	ADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0gD6APwA/gEAAQcBDAESARQBFgEYDWQN
+	aQ10DX0Nig2NDZoNow2oDbAAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAANsw==
+	</data>
+	<key>ANSIBrightYellowColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjgwMTIwNzQyMzIgMC41ODM2NjYxNDU4IDAuMTU3Mjcw
+	OTk3OCAxTxAnMC43NTIwNzMwNDk1IDAuNTE1MzMzMjk0OSAwLjEyMjE5MzkxNzYAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSICyanColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECcwLjMzMzEyNjMwNjUgMC40NzcwOTI5ODEzIDAuMzMyMDk3
+	ODg4IDFPECcwLjI2ODEwNjM3MTIgMC40MDc4Njg3MTMxIDAuMjYyOTM5OTU5OAAQAYAC
+	gAXTGBkRGhscVE5TSURVTlNJQ0MQB4ADgARPEQxIAAAMSExpbm8CEAAAbW50clJHQiBY
+	WVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbW
+	AAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAA
+	AgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQA
+	AABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAU
+	bWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRS
+	QwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJk
+	IENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAS
+	c1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAA
+	AAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAA
+	AAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAA
+	AAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBE
+	ZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2
+	LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJ
+	RUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24g
+	aW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAAT
+	pP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMA
+	AAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAA
+	AAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0A
+	cgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDw
+	APYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIB
+	mgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJx
+	AnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34D
+	igOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATT
+	BOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkG
+	agZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgy
+	CEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0K
+	VApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgyn
+	DMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EP
+	Xg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJF
+	EmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgV
+	mxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkg
+	GUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUd
+	Hh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFI
+	IXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl
+	9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrP
+	KwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4w
+	NTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXC
+	Nf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o7
+	6DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIw
+	QnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJ
+	HUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAn
+	UHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX
+	4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+z
+	YAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+lo
+	P2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDg
+	cTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6
+	RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6
+	hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN
+	/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhM
+	mLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowaj
+	dqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6h
+	rxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6
+	tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbD
+	x0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TT
+	xtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC9
+	4UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7iju
+	tO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY
+	/Sn9uv5L/tz/bf//0h8gISJaJGNsYXNzbmFtZVgkY2xhc3Nlc1xOU0NvbG9yU3BhY2Wi
+	IyRcTlNDb2xvclNwYWNlWE5TT2JqZWN00h8gJidXTlNDb2xvcqImJAAIABEAGgAkACkA
+	MgA3AEkATABRAFMAWgBgAGsAeAB+AIsAoACnANEA+wD9AP8BAQEIAQ0BEwEVARcBGQ1l
+	DWoNdQ1+DYsNjg2bDaQNqQ2xAAAAAAAAAgEAAAAAAAAAKAAAAAAAAAAAAAAAAAAADbQ=
+	</data>
+	<key>ANSIGreenColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECkwLjQ0NjkyMDM5NDkgMC40NzgzODYyMjMzIDAuMDkzNTM1
+	OTM3MzcgMU8QKDAuMzcyNzA0NDE2NSAwLjQxMDYxMzgzNDkgMC4wNzU3MTMxNzI1NQAQ
+	AYACgAXTGBkRGhscVE5TSURVTlNJQ0MQB4ADgARPEQxIAAAMSExpbm8CEAAAbW50clJH
+	QiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAA
+	APbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtw
+	dAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAA
+	AlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gA
+	AAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgM
+	YlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNr
+	YXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAA
+	AAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAA
+	AAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFla
+	IAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gA
+	AAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIu
+	MSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYx
+	OTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRp
+	b24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAA
+	AAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521l
+	YXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYA
+	AAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBo
+	AG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA
+	6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGL
+	AZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0C
+	ZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNy
+	A34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYE
+	xATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZI
+	BlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsI
+	HwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQon
+	Cj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUM
+	jgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8l
+	D0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcS
+	JhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVW
+	FXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY
+	+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM
+	HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAh
+	HCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWX
+	Jccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1Kmgq
+	myrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/H
+	L/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01
+	hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtr
+	O6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB
+	7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iR
+	SNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP
+	3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dE
+	V5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9f
+	YV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeT
+	Z+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtw
+	hnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJ
+	eed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSD
+	V4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0x
+	jZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX
+	4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKW
+	owajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1Erbiu
+	La6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnC
+	uju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjG
+	RsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB
+	00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/g
+	NuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c
+	7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8
+	B/yY/Sn9uv5L/tz/bf//0h8gISJaJGNsYXNzbmFtZVgkY2xhc3Nlc1xOU0NvbG9yU3Bh
+	Y2WiIyRcTlNDb2xvclNwYWNlWE5TT2JqZWN00h8gJidXTlNDb2xvcqImJAAIABEAGgAk
+	ACkAMgA3AEkATABRAFMAWgBgAGsAeAB+AIsAoACnANMA/gEAAQIBBAELARABFgEYARoB
+	HA1oDW0NeA2BDY4NkQ2eDacNrA20AAAAAAAAAgEAAAAAAAAAKAAAAAAAAAAAAAAAAAAA
+	Dbc=
+	</data>
+	<key>ANSIMagentaColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjQ3ODEyOTQ0NjUgMC4yODgyOTk1NjA1IDAuMzMyMzUx
+	ODMzNiAxTxAnMC4zOTg3NzAwMzQzIDAuMjE3NjUyMjMxNSAwLjI2MzEzNDQxOTkAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIRedColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECkwLjYzODQyMzk3OTMgMC4xNTYyMTk5NTkzIDAuMDYzNTUz
+	ODE3NTcgMU8QKTAuNTYzOTQwODIzMSAwLjA5NTk3Mzk5MDg2IDAuMDU3NzQzNjA4OTUA
+	EAGAAoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJS
+	R0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAA
+	AAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJr
+	cHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQA
+	AAJUAAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4
+	AAAAFG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAI
+	DGJUUkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFj
+	a2FyZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAA
+	AAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAA
+	AAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZ
+	WiAAAAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNo
+	AAAAAAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0y
+	LjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2
+	MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24g
+	aW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0
+	aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAA
+	AAAAE6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dt
+	ZWFzAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2
+	AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMA
+	aABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl
+	AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMB
+	iwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJd
+	AmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YD
+	cgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2
+	BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcG
+	SAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgL
+	CB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEK
+	Jwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1
+	DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkP
+	JQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIH
+	EiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQV
+	VhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjV
+	GPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMc
+	zBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDw
+	IRwhSCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgl
+	lyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpo
+	KpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Ev
+	xy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVN
+	NYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07
+	azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs
+	Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtI
+	kUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+T
+	T91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdX
+	RFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8P
+	X2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1n
+	k2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XAr
+	cIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5
+	iXnnekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0
+	g1eDuoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqN
+	MY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1
+	l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiai
+	lqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24
+	ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5
+	wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XI
+	xkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/S
+	wdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v
+	4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHt
+	nO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3
+	/Af8mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNw
+	YWNloiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoA
+	JAApADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDTAP8BAQEDAQUBDAERARcBGQEb
+	AR0NaQ1uDXkNgg2PDZINnw2oDa0NtQAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAA
+	AA24
+	</data>
+	<key>ANSIWhiteColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjYwMTQyMTM1NjIgMC41NTQ3NzkxNzE5IDAuNDIwMzc2
+	ODM3MyAxTxAnMC41MzAyMTQzMDk3IDAuNDg0NTM0MzgyOCAwLjM0NjA2NzkzNTIAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>ANSIYellowColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjYzOTQxMzA1ODggMC40NjU0MDE4MjgzIDAuMTI1ODc5
+	NzM0OCAxTxAoMC41Njg4NzAzNjU2IDAuMzkyMjM4MDIwOSAwLjA5ODMyNDM0MzU2ABAB
+	gAKABdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdC
+	IFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA
+	9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0
+	AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAAC
+	VAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAA
+	ABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxi
+	VFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2th
+	cmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAA
+	ABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAA
+	AAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVog
+	AAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAA
+	AAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4x
+	IERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5
+	NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGlu
+	IElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlv
+	biBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAA
+	ABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVh
+	cwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAA
+	AAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgA
+	bQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDr
+	APAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsB
+	kgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJn
+	AnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3ID
+	fgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTE
+	BNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgG
+	WQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgf
+	CDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicK
+	PQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyO
+	DKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUP
+	QQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxIm
+	EkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYV
+	eBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6
+	GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc
+	9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEc
+	IUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZcl
+	xyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqb
+	Ks8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv
+	/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWH
+	NcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7
+	qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHu
+	QjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI
+	10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/d
+	UCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RX
+	klfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19h
+	X7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn
+	6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CG
+	cOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl5
+	53pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INX
+	g7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGN
+	mI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfg
+	mEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopaj
+	BqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4t
+	rqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6
+	O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZG
+	xsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHT
+	RNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A2
+	4L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7Zzu
+	KO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH
+	/Jj9Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFj
+	ZaIjJFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQA
+	KQAyADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0gD9AP8BAQEDAQoBDwEVARcBGQEb
+	DWcNbA13DYANjQ2QDZ0Npg2rDbMAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAAN
+	tg==
+	</data>
+	<key>BackgroundColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECswLjA3ODU3MTg5MzI3IDAuMDc0ODI3NTQ0MzkgMC4wNTM5
+	MTM1MTEzNCAxTxAqMC4wNjM1NDA0MzYzMyAwLjA2MTU2MDk3MzUzIDAuMDQ4NDc5NTY0
+	NDkAEAGAAoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1u
+	dHJSR0IgWFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAA
+	AAAAAAD21gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAA
+	FGJrcHQAAAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRt
+	bmQAAAJUAAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkA
+	AAP4AAAAFG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8
+	AAAIDGJUUkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQt
+	UGFja2FyZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAA
+	AAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAA
+	AAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY
+	2lhZWiAAAAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVj
+	LmNoAAAAAAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklF
+	QyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRp
+	b24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29u
+	ZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmll
+	dwAAAAAAE6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABX
+	H+dtZWFzAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBj
+	dXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBe
+	AGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA
+	4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8
+	AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksC
+	VAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNa
+	A2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoE
+	qAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYn
+	BjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH
+	+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7
+	ChEKJwo9ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMM
+	XAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7u
+	DwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR
+	6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUS
+	FTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoY
+	rxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7
+	HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJgg
+	xCDwIRwhSCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4
+	JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIq
+	NSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9a
+	L5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1
+	EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrv
+	Oy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlB
+	akGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgF
+	SEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBP
+	SU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFap
+	VvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxe
+	vV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmbo
+	Zz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv
+	0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjM
+	eSp5iXnnekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCC
+	koL0g1eDuoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/Ixj
+	jMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+X
+	Cpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2
+	oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCt
+	RK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR
+	uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7F
+	S8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+
+	0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLf
+	Kd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG
+	7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf6
+	5/t3/Af8mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xv
+	clNwYWNloiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAAR
+	ABoAJAApADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDVAQIBBAEGAQgBDwEUARoB
+	HAEeASANbA1xDXwNhQ2SDZUNog2rDbANuAAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAA
+	AAAAAA27
+	</data>
+	<key>Bell</key>
+	<false/>
+	<key>BellBounceCritical</key>
+	<false/>
+	<key>CursorColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjQ3OTUyNTg2NDEgMC40NDQ2OTg0ODI4IDAuMzMxMzI2
+	MjQ2MyAxTxAnMC40MDMyNjAxNzE0IDAuMzcxNzE3NjYxNiAwLjI2MjQ2MjY3NTYAEAGA
+	AoAF0xgZERobHFROU0lEVU5TSUNDEAeAA4AETxEMSAAADEhMaW5vAhAAAG1udHJSR0Ig
+	WFlaIAfOAAIACQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD2
+	1gABAAAAANMtSFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAEWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQA
+	AAIEAAAAFHJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJU
+	AAAAcGRtZGQAAALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAA
+	FG1lYXMAAAQMAAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJU
+	UkMAAAQ8AAAIDHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2Fy
+	ZCBDb21wYW55AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAA
+	EnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAA
+	AAAAAAAkoAAAD4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAA
+	AAAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEg
+	RGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2
+	Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABkZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g
+	SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9u
+	IGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAA
+	E6T+ABRfLgAQzxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFz
+	AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAA
+	AAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt
+	AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA
+	8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGS
+	AZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcC
+	cQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+
+	A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE
+	0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZ
+	BmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8I
+	MghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9
+	ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4M
+	pwzADNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9B
+	D14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYS
+	RRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4
+	FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZ
+	IBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1
+	HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwh
+	SCF1IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXH
+	JfcmJyZXJocmtyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsq
+	zysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+
+	MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1
+	wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq
+	O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5C
+	MEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjX
+	SR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91Q
+	J1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeS
+	V+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ff
+	s2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp
+	aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw
+	4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn
+	ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eD
+	uoQdhICE44VHhauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Y
+	jf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CY
+	TJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMG
+	o3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2u
+	oa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7
+	urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbG
+	w8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE
+	08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4Dbg
+	veFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o
+	7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8
+	mP0p/br+S/7c/23//9IfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNcTlNDb2xvclNwYWNl
+	oiMkXE5TQ29sb3JTcGFjZVhOU09iamVjdNIfICYnV05TQ29sb3KiJiQACAARABoAJAAp
+	ADIANwBJAEwAUQBTAFoAYABrAHgAfgCLAKAApwDSAPwA/gEAAQIBCQEOARQBFgEYARoN
+	Zg1rDXYNfw2MDY8NnA2lDaoNsgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAA21
+	</data>
+	<key>DisableANSIColor</key>
+	<false/>
+	<key>Font</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGkCwwVFlUkbnVsbNQNDg8QERIT
+	FFZOU1NpemVYTlNmRmxhZ3NWTlNOYW1lViRjbGFzcyNAKgAAAAAAABAQgAKAA1ZHb01v
+	bm/SFxgZGlokY2xhc3NuYW1lWCRjbGFzc2VzVk5TRm9udKIZG1hOU09iamVjdAgRGiQp
+	MjdJTFFTWF5nbnd+hY6QkpSboKu0u74AAAAAAAABAQAAAAAAAAAcAAAAAAAAAAAAAAAA
+	AAAAxw==
+	</data>
+	<key>FontAntialias</key>
+	<true/>
+	<key>FontHeightSpacing</key>
+	<integer>1</integer>
+	<key>FontWidthSpacing</key>
+	<integer>1</integer>
+	<key>ProfileCurrentVersion</key>
+	<real>2.0699999999999998</real>
+	<key>SelectionColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECkwLjYzNzg1MDc2MTQgMC4yNTM4NjgzNzEyIDAuMDYwOTk5
+	MzExNTEgMU8QKDAuNTY0MDczMzI0MiAwLjE4NDY4Nzc2MzUgMC4wNTY1Mzc3ODQ2NAAQ
+	AYACgAXTGBkRGhscVE5TSURVTlNJQ0MQB4ADgARPEQxIAAAMSExpbm8CEAAAbW50clJH
+	QiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAA
+	APbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtw
+	dAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAA
+	AlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gA
+	AAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgM
+	YlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNr
+	YXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAA
+	AAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAA
+	AAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFla
+	IAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gA
+	AAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIu
+	MSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYx
+	OTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRp
+	b24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAA
+	AAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521l
+	YXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYA
+	AAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBo
+	AG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA
+	6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGL
+	AZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0C
+	ZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNy
+	A34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYE
+	xATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZI
+	BlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsI
+	HwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQon
+	Cj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUM
+	jgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8l
+	D0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcS
+	JhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVW
+	FXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY
+	+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM
+	HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAh
+	HCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWX
+	Jccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1Kmgq
+	myrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/H
+	L/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01
+	hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtr
+	O6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB
+	7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iR
+	SNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP
+	3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dE
+	V5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9f
+	YV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeT
+	Z+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtw
+	hnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJ
+	eed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSD
+	V4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0x
+	jZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX
+	4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKW
+	owajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1Erbiu
+	La6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnC
+	uju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjG
+	RsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB
+	00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/g
+	NuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c
+	7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8
+	B/yY/Sn9uv5L/tz/bf//0h8gISJaJGNsYXNzbmFtZVgkY2xhc3Nlc1xOU0NvbG9yU3Bh
+	Y2WiIyRcTlNDb2xvclNwYWNlWE5TT2JqZWN00h8gJidXTlNDb2xvcqImJAAIABEAGgAk
+	ACkAMgA3AEkATABRAFMAWgBgAGsAeAB+AIsAoACnANMA/gEAAQIBBAELARABFgEYARoB
+	HA1oDW0NeA2BDY4NkQ2eDacNrA20AAAAAAAAAgEAAAAAAAAAKAAAAAAAAAAAAAAAAAAA
+	Dbc=
+	</data>
+	<key>ShowActiveProcessInTabTitle</key>
+	<false/>
+	<key>ShowActiveProcessInTitle</key>
+	<false/>
+	<key>ShowActivityIndicatorInTab</key>
+	<false/>
+	<key>ShowCommandKeyInTitle</key>
+	<false/>
+	<key>ShowComponentsWhenTabHasCustomTitle</key>
+	<true/>
+	<key>ShowDimensionsInTitle</key>
+	<false/>
+	<key>ShowRepresentedURLInTabTitle</key>
+	<false/>
+	<key>ShowRepresentedURLInTitle</key>
+	<false/>
+	<key>ShowRepresentedURLPathInTabTitle</key>
+	<true/>
+	<key>ShowRepresentedURLPathInTitle</key>
+	<false/>
+	<key>ShowShellCommandInTitle</key>
+	<false/>
+	<key>ShowTTYNameInTitle</key>
+	<false/>
+	<key>ShowWindowSettingsNameInTitle</key>
+	<false/>
+	<key>TerminalType</key>
+	<string>xterm</string>
+	<key>TextBoldColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECgwLjgwMTk4NTU2MTggMC43MzU3NzAwNDY3IDAuNTU1MDUz
+	NTMyMSAxTxAlMC43NTY0MTU2MDU1IDAuNjg1MjQ2OTQ0NCAwLjQ4MjY3NTkxABABgAKA
+	BdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZ
+	WiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYA
+	AQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAAC
+	BAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAA
+	AHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRt
+	ZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJD
+	AAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg
+	Q29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJz
+	UkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAA
+	AAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAA
+	AAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERl
+	ZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYt
+	Mi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElF
+	QzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk
+	/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAA
+	AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAA
+	AAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQBy
+	AHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA
+	9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGa
+	AaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnEC
+	egKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOK
+	A5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME
+	4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZq
+	BnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII
+	RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpU
+	CmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcM
+	wAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9e
+	D3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUS
+	ZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWb
+	Fb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZ
+	RRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0e
+	HUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUgh
+	dSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3
+	JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8r
+	Ais2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1
+	MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1
+	/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvo
+	PCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBC
+	ckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kd
+	SWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ
+	cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfg
+	WC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7Ng
+	BWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/
+	aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBx
+	OnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pG
+	eqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qE
+	HYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/
+	jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyY
+	uJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2
+	o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGv
+	Fq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1
+	uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPH
+	Qce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG
+	1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3h
+	ROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO60
+	70DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9
+	Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFjZaIj
+	JFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQAKQAy
+	ADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0gD6APwA/gEAAQcBDAESARQBFgEYDWQN
+	aQ10DX0Nig2NDZoNow2oDbAAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAANsw==
+	</data>
+	<key>TextColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS
+	AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGmCwwXHR4lVSRudWxs1Q0ODxAR
+	EhMUFRZcTlNDb21wb25lbnRzVU5TUkdCXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29s
+	b3JTcGFjZVYkY2xhc3NPECYwLjcxOTQwMjEzNDQgMC42NjA5ODgxNTIgMC41MDEzMjYy
+	NjMgMU8QJzAuNjYwODUzODYyOCAwLjYwMDE2ODE2ODUgMC40MjY4MTQ3MDUxABABgAKA
+	BdMYGREaGxxUTlNJRFVOU0lDQxAHgAOABE8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZ
+	WiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYA
+	AQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAAC
+	BAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAA
+	AHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRt
+	ZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJD
+	AAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg
+	Q29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJz
+	UkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAA
+	AAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAA
+	AAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERl
+	ZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYt
+	Mi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElF
+	QzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp
+	biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk
+	/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAA
+	AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAA
+	AAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQBy
+	AHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA
+	9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGa
+	AaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnEC
+	egKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOK
+	A5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME
+	4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZq
+	BnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDII
+	RghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpU
+	CmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcM
+	wAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9e
+	D3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUS
+	ZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWb
+	Fb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZ
+	RRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0e
+	HUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUgh
+	dSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3
+	JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8r
+	Ais2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1
+	MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1
+	/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvo
+	PCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBC
+	ckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kd
+	SWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQ
+	cVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfg
+	WC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7Ng
+	BWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/
+	aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBx
+	OnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pG
+	eqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qE
+	HYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/
+	jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyY
+	uJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2
+	o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGv
+	Fq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1
+	uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPH
+	Qce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG
+	1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3h
+	ROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO60
+	70DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9
+	Kf26/kv+3P9t///SHyAhIlokY2xhc3NuYW1lWCRjbGFzc2VzXE5TQ29sb3JTcGFjZaIj
+	JFxOU0NvbG9yU3BhY2VYTlNPYmplY3TSHyAmJ1dOU0NvbG9yoiYkAAgAEQAaACQAKQAy
+	ADcASQBMAFEAUwBaAGAAawB4AH4AiwCgAKcA0AD6APwA/gEAAQcBDAESARQBFgEYDWQN
+	aQ10DX0Nig2NDZoNow2oDbAAAAAAAAACAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAANsw==
+	</data>
+	<key>UseBoldFonts</key>
+	<false/>
+	<key>UseBrightBold</key>
+	<true/>
+	<key>VisualBell</key>
+	<false/>
+	<key>VisualBellOnlyWhenMuted</key>
+	<false/>
+	<key>WindowTitle</key>
+	<string>Terminal</string>
+	<key>name</key>
+	<string>Dark</string>
+	<key>noWarnProcesses</key>
+	<array>
+		<dict>
+			<key>ProcessName</key>
+			<string>screen</string>
+		</dict>
+		<dict>
+			<key>ProcessName</key>
+			<string>tmux</string>
+		</dict>
+		<dict>
+			<key>ProcessName</key>
+			<string>atch</string>
+		</dict>
+	</array>
+	<key>rowCount</key>
+	<integer>24</integer>
+	<key>shellExitAction</key>
+	<integer>1</integer>
+	<key>type</key>
+	<string>Window Settings</string>
+	<key>useOptionAsMetaKey</key>
+	<true/>
+</dict>
+</plist>
diff --git a/etc/Go-Mono-Bold-Italic.ttf b/etc/Go-Mono-Bold-Italic.ttf
new file mode 100644
index 00000000..c138a9e1
--- /dev/null
+++ b/etc/Go-Mono-Bold-Italic.ttf
Binary files differdiff --git a/etc/Go-Mono-Bold.ttf b/etc/Go-Mono-Bold.ttf
new file mode 100644
index 00000000..551da07f
--- /dev/null
+++ b/etc/Go-Mono-Bold.ttf
Binary files differdiff --git a/etc/Go-Mono-Italic.ttf b/etc/Go-Mono-Italic.ttf
new file mode 100644
index 00000000..22d4390e
--- /dev/null
+++ b/etc/Go-Mono-Italic.ttf
Binary files differdiff --git a/etc/Go-Mono.ttf b/etc/Go-Mono.ttf
new file mode 100644
index 00000000..71e30123
--- /dev/null
+++ b/etc/Go-Mono.ttf
Binary files differdiff --git a/etc/README.Go-Mono b/etc/README.Go-Mono
new file mode 100644
index 00000000..7043c362
--- /dev/null
+++ b/etc/README.Go-Mono
@@ -0,0 +1,36 @@
+These fonts were created by the Bigelow & Holmes foundry specifically for the
+Go project. See https://blog.golang.org/go-fonts for details.
+
+They are licensed under the same open source license as the rest of the Go
+project's software:
+
+Copyright (c) 2016 Bigelow & Holmes Inc.. All rights reserved.
+
+Distribution of this font is governed by the following license. If you do not
+agree to this license, including the disclaimer, do not distribute or modify
+this font.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	  this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	  this list of conditions and the following disclaimer in the documentation
+	  and/or other materials provided with the distribution.
+
+	* Neither the name of Google Inc. nor the names of its contributors may be
+	  used to endorse or promote products derived from this software without
+	  specific prior written permission.
+
+DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/etc/code.map b/etc/code.map
new file mode 100644
index 00000000..a3749b8d
--- /dev/null
+++ b/etc/code.map
@@ -0,0 +1,20 @@
+include "/usr/share/kbd/keymaps/i386/qwerty/us.map.gz"
+
+keycode 2 = exclam one
+keycode 3 = at two
+keycode 4 = numbersign three
+keycode 5 = dollar four
+keycode 6 = percent five
+keycode 7 = asciicircum six
+keycode 8 = ampersand seven
+keycode 9 = asterisk eight
+keycode 10 = parenleft nine
+keycode 11 = parenright zero
+keycode 12 = underscore minus
+keycode 26 = braceleft bracketleft
+keycode 27 = braceright bracketright
+keycode 43 = bar backslash
+keycode 58 = Escape
+
+keycode 100 = Compose
+keycode 125 = Escape
diff --git a/etc/daticns.c b/etc/daticns.c
new file mode 100644
index 00000000..63f1649d
--- /dev/null
+++ b/etc/daticns.c
@@ -0,0 +1,62 @@
+/* Copyright (C) 2022  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+int main(int argc, char *argv[]) {
+	if (argc < 2) return EX_USAGE;
+
+	FILE *file = fopen(argv[1], "r");
+	if (!file) err(EX_NOINPUT, "%s", argv[1]);
+
+	size_t cap = 0x10000;
+	uint8_t *buf = malloc(cap);
+	if (!buf) err(EX_OSERR, "malloc");
+
+	size_t len = 0;
+	for (size_t n; 0 < (n = fread(&buf[len], 1, cap - len, file)); len += n) {
+		buf = realloc(buf, (cap *= 2));
+		if (!buf) err(EX_OSERR, "realloc");
+	}
+
+	unsigned nr = 0;
+	for (
+		uint8_t *ptr = buf;
+		NULL != (ptr = memmem(ptr, &buf[len] - ptr, "icns", 4));
+		ptr += 4
+	) {
+		if (&ptr[8] > &buf[len]) break;
+		size_t len = ptr[4] << 24 | ptr[5] << 16 | ptr[6] << 8 | ptr[7];
+
+		char path[64];
+		snprintf(path, sizeof(path), "icon%04u.icns", ++nr);
+		printf("%s\n", path);
+
+		FILE *icon = fopen(path, "w");
+		if (!icon) err(EX_CANTCREAT, "%s", path);
+
+		size_t n = fwrite(ptr, 8 + len, 1, icon);
+		if (!n) err(EX_IOERR, "%s", path);
+
+		int error = fclose(icon);
+		if (error) err(EX_IOERR, "%s", path);
+	}
+}
diff --git a/etc/layout.txt b/etc/layout.txt
new file mode 100644
index 00000000..3aa2ed2b
--- /dev/null
+++ b/etc/layout.txt
@@ -0,0 +1,9 @@
+ `  !  @  #  $  %  ^  &  *  (  )  _  =  backspace
+tab  q  w  e  r  t  y  u  i  o  p  {  }  |
+esc   a  s  d  f  g  h  j  k  l  ;  '  enter
+shift  z  x  c  v  b  n  m  ,  .  /  shift
+
+ ~  1  2  3  4  5  6  7  8  9  0  -  +  backspace
+tab  Q  W  E  R  T  Y  U  I  O  P  [  ]  \
+esc   A  S  D  F  G  H  J  K  L  :  "  enter
+shift  Z  X  C  V  B  N  M  <  >  ?  shift
diff --git a/etc/wsconsctl.conf b/etc/wsconsctl.conf
new file mode 100644
index 00000000..05f29b6a
--- /dev/null
+++ b/etc/wsconsctl.conf
@@ -0,0 +1,26 @@
+display.brightness=50%
+keyboard.backlight=0%
+
+mouse1.tp.tapping=1
+mouse1.tp.scaling=0.2
+mouse1.reverse_scrolling=1
+
+keyboard1.repeat.del1=200
+keyboard1.repeat.deln=50
+
+keyboard1.map+='keycode 30 = exclam 1'
+keyboard1.map+='keycode 31 = at 2'
+keyboard1.map+='keycode 32 = numbersign 3'
+keyboard1.map+='keycode 33 = dollar 4'
+keyboard1.map+='keycode 34 = percent 5'
+keyboard1.map+='keycode 35 = asciicircum 6'
+keyboard1.map+='keycode 36 = ampersand 7'
+keyboard1.map+='keycode 37 = asterisk 8'
+keyboard1.map+='keycode 38 = parenleft 9'
+keyboard1.map+='keycode 39 = parenright 0'
+keyboard1.map+='keycode 45 = underscore minus'
+keyboard1.map+='keycode 47 = braceleft bracketleft'
+keyboard1.map+='keycode 48 = braceright bracketright'
+keyboard1.map+='keycode 49 = bar backslash'
+keyboard1.map+='keycode 50 = bar backslash'
+keyboard1.map+='keycode 57 = Escape'
diff --git a/gpl.c b/gpl.c
new file mode 100644
index 00000000..8ff4916d
--- /dev/null
+++ b/gpl.c
@@ -0,0 +1,19 @@
+/* Copyright (C) 2024  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
diff --git a/home/.config/X/modmap b/home/.config/X/modmap
new file mode 100644
index 00000000..b0b1ea79
--- /dev/null
+++ b/home/.config/X/modmap
@@ -0,0 +1,16 @@
+clear Lock
+keysym Caps_Lock = Escape
+keysym 1 = exclam 1
+keysym 2 = at 2
+keysym 3 = numbersign 3
+keysym 4 = dollar 4
+keysym 5 = percent 5
+keysym 6 = asciicircum 6
+keysym 7 = ampersand 7
+keysym 8 = asterisk 8
+keysym 9 = parenleft 9
+keysym 0 = parenright 0
+keysym minus = underscore minus
+keysym bracketleft = braceleft bracketleft
+keysym bracketright = braceright bracketright
+keysym backslash = bar backslash
diff --git a/home/.config/X/resources b/home/.config/X/resources
new file mode 100644
index 00000000..f4603cd9
--- /dev/null
+++ b/home/.config/X/resources
@@ -0,0 +1,52 @@
+Xft.dpi: 144
+Xft.antialias: true
+Xft.hinting: false
+
+Xcursor.size: 64
+Xcursor.theme: dmz-aa
+
+XLock.usefirst: false
+XLock.echokeys: true
+
+*Background: rgb:14/13/0E
+*Foreground: rgb:B7/A9/80
+*BorderColor: rgb:99/8D/6B
+
+XTerm*utf8: true
+XTerm*metaSendsEscape: true
+XTerm*alternateScroll: true
+XTerm*allowMouseOps: false
+XTerm*disallowedMouseOps: X10,Locator,VT200*,Any*,Extended,SGR,URXVT
+XTerm*bellIsUrgent: true
+XTerm*charClass: 33:48,36-47:48,58-59:48,61:48,63-64:48,95:48,126:48
+
+XTerm*VT100*translations: #override \n\
+	Super <Key>C: copy-selection(CLIPBOARD) \n\
+	Super <Key>V: insert-selection(CLIPBOARD) \n\
+	<Btn4Down>: scroll-back(1,line,m) \n\
+	<Btn5Down>: scroll-forw(1,line,m)
+
+XTerm*faceName: Go Mono:size=12
+XTerm*internalBorder: 6
+XTerm*colorBDMode: true
+XTerm*scrollBar: false
+XTerm*pointerMode: 2
+
+XTerm*color0: rgb:16/15/10
+XTerm*color1: rgb:A3/28/10
+XTerm*color2: rgb:72/7A/18
+XTerm*color3: rgb:A3/77/20
+XTerm*color4: rgb:3D/62/66
+XTerm*color5: rgb:7A/49/55
+XTerm*color6: rgb:55/7A/55
+XTerm*color7: rgb:99/8D/6B
+XTerm*color8: rgb:4C/46/35
+XTerm*color9: rgb:CC/32/14
+XTerm*color10: rgb:8E/99/1E
+XTerm*color11: rgb:CC/95/28
+XTerm*color12: rgb:4C/7B/7F
+XTerm*color13: rgb:99/5B/6B
+XTerm*color14: rgb:6B/99/6B
+XTerm*color15: rgb:CC/BC/8E
+XTerm*colorBD: rgb:CC/BC/8E
+XTerm*cursorColor: rgb:7A/71/55
diff --git a/home/.config/cwm/cwmrc b/home/.config/cwm/cwmrc
new file mode 100644
index 00000000..d72ec163
--- /dev/null
+++ b/home/.config/cwm/cwmrc
@@ -0,0 +1,87 @@
+sticky yes
+snapdist 10
+moveamount 10
+
+ignore clock
+autogroup 0 clock,XTerm
+gap 38 0 0 0
+
+unbind-key all
+bind-key 4-n terminal
+bind-key 4-t "firefox -new-tab about:blank"
+bind-key 4-Delete lock
+bind-key 4-Down window-lower
+bind-key 4-Up window-raise
+bind-key 4-slash menu-window
+bind-key 4-Tab group-cycle
+bind-key 4S-Tab group-rcycle
+bind-key 4-grave window-cycle
+bind-key 4S-grave window-rcycle
+bind-key 4-w window-close
+bind-key 4-exclam group-only-1
+bind-key 4-at group-only-2
+bind-key 4-numbersign group-only-3
+bind-key 4-dollar group-only-4
+bind-key 4-percent group-only-5
+bind-key 4-asciicircum group-only-6
+bind-key 4-ampersand group-only-7
+bind-key 4-asterisk group-only-8
+bind-key 4-parenleft group-only-9
+bind-key 4S-exclam window-movetogroup-1
+bind-key 4S-at window-movetogroup-2
+bind-key 4S-numbersign window-movetogroup-3
+bind-key 4S-dollar window-movetogroup-4
+bind-key 4S-percent window-movetogroup-5
+bind-key 4S-asciicircum window-movetogroup-6
+bind-key 4S-ampersand window-movetogroup-7
+bind-key 4S-asterisk window-movetogroup-8
+bind-key 4S-parenleft window-movetogroup-9
+bind-key 4-f window-fullscreen
+bind-key 4-m window-maximize
+bind-key 4-equal window-vmaximize
+bind-key 4S-equal window-hmaximize
+bind-key 4-underscore window-vtile
+bind-key 4S-underscore window-htile
+bind-key 4-h window-move-left-big
+bind-key 4-j window-move-down-big
+bind-key 4-k window-move-up-big
+bind-key 4-l window-move-right-big
+bind-key 4S-h window-move-left
+bind-key 4S-j window-move-down
+bind-key 4S-k window-move-up
+bind-key 4S-l window-move-right
+bind-key 4S-y window-snap-up-left
+bind-key 4S-u window-snap-up-right
+bind-key 4S-b window-snap-down-left
+bind-key 4S-n window-snap-down-right
+bind-key 4M-h window-resize-left
+bind-key 4M-j window-resize-down
+bind-key 4M-k window-resize-up
+bind-key 4M-l window-resize-right
+bind-key 4MS-h window-resize-left-big
+bind-key 4MS-j window-resize-down-big
+bind-key 4MS-k window-resize-up-big
+bind-key 4MS-l window-resize-right-big
+bind-key 4-space menu-exec
+bind-key 4S-r restart
+bind-key 4S-q quit
+
+bind-key F1 "xbacklight -steps 1 -5"
+bind-key F2 "xbacklight -steps 1 +5"
+bind-key F10 "sndioctl output.mute=!"
+bind-key F11 "sndioctl output.level=-0.05"
+bind-key F12 "sndioctl output.level=+0.05"
+
+unbind-mouse all
+bind-mouse 4-1 window-move
+bind-mouse 4S-1 window-resize
+
+fontname "Go Mono:size=11"
+borderwidth 2
+color inactiveborder rgb:4C/46/35
+color activeborder rgb:99/8D/6B
+color urgencyborder rgb:A3/77/20
+color menubg rgb:14/13/0E
+color menufg rgb:B7/A9/80
+color font rgb:B7/A9/80
+color selfont rgb:14/13/0E
diff --git a/home/.config/git/config b/home/.config/git/config
new file mode 100644
index 00000000..c990de2c
--- /dev/null
+++ b/home/.config/git/config
@@ -0,0 +1,34 @@
+[user]
+	name = June McEnroe
+	email = june@causal.agency
+
+[branch]
+	sort = committerdate
+
+[commit]
+	verbose = true
+
+[diff]
+	colorMoved = default
+	colorMovedWS = allow-indentation-change
+
+[merge]
+	conflictStyle = diff3
+
+[push]
+	autoSetupRemote = true
+
+[pull]
+	rebase = true
+
+[rebase]
+	autosquash = true
+
+[pretty]
+	log = %Cred%h %Creset%s%C(yellow)%d %Cgreen(%ar) %Cblue<%aN>
+
+[alias]
+	forgive = blame
+
+[include]
+	path = ./private
diff --git a/home/.config/git/ignore b/home/.config/git/ignore
new file mode 100644
index 00000000..fea54519
--- /dev/null
+++ b/home/.config/git/ignore
@@ -0,0 +1,2 @@
+*.DS_store
+*.dSYM/
diff --git a/home/.config/htop/htoprc b/home/.config/htop/htoprc
new file mode 100644
index 00000000..705323ef
--- /dev/null
+++ b/home/.config/htop/htoprc
@@ -0,0 +1,38 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+fields=0 48 39 2 46 49 1
+sort_key=47
+sort_direction=1
+tree_sort_key=0
+tree_sort_direction=1
+hide_kernel_threads=1
+hide_userland_threads=1
+shadow_other_users=0
+show_thread_names=0
+show_program_path=1
+highlight_base_name=1
+highlight_megabytes=1
+highlight_threads=1
+highlight_changes=0
+highlight_changes_delay_secs=5
+find_comm_in_cmdline=1
+strip_exe_from_cmdline=1
+show_merged_command=0
+tree_view=1
+tree_view_always_by_pid=0
+all_branches_collapsed=0
+header_margin=0
+detailed_cpu_time=0
+cpu_count_from_one=1
+show_cpu_usage=1
+show_cpu_frequency=0
+update_process_names=0
+account_guest_in_cpu_meter=0
+color_scheme=0
+enable_mouse=0
+delay=15
+left_meters=AllCPUs2
+left_meter_modes=1
+right_meters=Memory Swap
+right_meter_modes=1 1
+hide_function_bar=2
diff --git a/home/.editrc b/home/.editrc
new file mode 100644
index 00000000..cf779a7d
--- /dev/null
+++ b/home/.editrc
@@ -0,0 +1 @@
+bind -v
diff --git a/home/.gdbinit b/home/.gdbinit
new file mode 100644
index 00000000..9422460c
--- /dev/null
+++ b/home/.gdbinit
@@ -0,0 +1 @@
+set disassembly-flavor intel
diff --git a/home/.hushlogin b/home/.hushlogin
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/home/.hushlogin
diff --git a/home/.inputrc b/home/.inputrc
new file mode 100644
index 00000000..b2cc9d61
--- /dev/null
+++ b/home/.inputrc
@@ -0,0 +1 @@
+set editing-mode vi
diff --git a/home/.lldbinit b/home/.lldbinit
new file mode 100644
index 00000000..73f3e676
--- /dev/null
+++ b/home/.lldbinit
@@ -0,0 +1 @@
+settings set target.x86-disassembly-flavor intel
diff --git a/home/.local/bin/aes b/home/.local/bin/aes
new file mode 100755
index 00000000..32b52637
--- /dev/null
+++ b/home/.local/bin/aes
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+enwiden() {
+	exec tr ' -~' ' !-~'
+}
+[ $# -gt 0 ] && echo "$*" | enwiden || enwiden
diff --git a/home/.local/bin/clock b/home/.local/bin/clock
new file mode 100755
index 00000000..ef8cd6d8
--- /dev/null
+++ b/home/.local/bin/clock
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -eu
+
+tput civis
+sleep=$(( 60 - $(date +'%S' | sed 's/^0//') ))
+while :; do
+	if [ $(apm -a) -eq 1 ]; then
+		printf '%3s%%' "$(apm -l)"
+	else
+		test $(apm -b) -eq 2 && tput setaf 1 bold
+		printf '%3.3sm' "$(apm -m)"
+		tput sgr0
+	fi
+	printf ' %s\r' "$(date +'%a %H:%M')"
+	sleep $sleep
+	sleep=60
+done
diff --git a/home/.local/bin/def b/home/.local/bin/def
new file mode 100755
index 00000000..6a1681d3
--- /dev/null
+++ b/home/.local/bin/def
@@ -0,0 +1,47 @@
+#!/bin/sh
+set -eu
+
+macro=$1
+headers='
+assert.h
+complex.h
+ctype.h
+errno.h
+fenv.h
+float.h
+inttypes.h
+iso646.h
+limits.h
+locale.h
+math.h
+setjmp.h
+signal.h
+stdalign.h
+stdarg.h
+stdatomic.h
+stdbool.h
+stddef.h
+stdint.h
+stdio.h
+stdlib.h
+stdnoreturn.h
+string.h
+tgmath.h
+threads.h
+time.h
+uchar.h
+wchar.h
+wctype.h
+'
+
+for header in $headers; do
+	defined=$(
+		echo "$macro" \
+		| cc -E -x c -include "$header" - \
+		2> /dev/null \
+		| tail -n 1
+	)
+	[ $? -ne 0 -o "$defined" = "$macro" ] && continue
+	echo "#include <${header}>"
+	echo "$defined"
+done
diff --git a/home/.local/bin/deg b/home/.local/bin/deg
new file mode 100755
index 00000000..216029ed
--- /dev/null
+++ b/home/.local/bin/deg
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -eu
+cat <<EOF
+${1}°F = $(dc -e "1k $(echo "$1" | sed 's/^-/_/') 32-1.8/p")°C
+${1}°C = $(dc -e "1k $(echo "$1" | sed 's/^-/_/') 1.8*32+p")°F
+EOF
diff --git a/home/.local/bin/git-password b/home/.local/bin/git-password
new file mode 100755
index 00000000..41351e38
--- /dev/null
+++ b/home/.local/bin/git-password
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+url=$1
+echo "url=${url}" \
+	| git credential fill \
+	| sed -En 's/^password=(.*)/\1/p'
diff --git a/home/.local/bin/mdate b/home/.local/bin/mdate
new file mode 100755
index 00000000..daff50dc
--- /dev/null
+++ b/home/.local/bin/mdate
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec date +'.Dd %B %e, %Y'
diff --git a/home/.local/bin/mins b/home/.local/bin/mins
new file mode 100755
index 00000000..9cbd5fa8
--- /dev/null
+++ b/home/.local/bin/mins
@@ -0,0 +1,4 @@
+#!/bin/sh
+exec dc <<EOF
+$1 60~rn[h]nn[m]p
+EOF
diff --git a/home/.local/bin/nasd b/home/.local/bin/nasd
new file mode 100755
index 00000000..60241395
--- /dev/null
+++ b/home/.local/bin/nasd
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -eu
+
+dir=$(mktemp -d)
+trap 'rm -r "$dir"' EXIT
+
+echo 'bits 64' >"${dir}/input"
+for ins; do
+	printf '%s\n' "$ins" >>"${dir}/input"
+done
+[ $# -eq 0 ] && cat >>"${dir}/input"
+
+nasm -o "${dir}/output" "${dir}/input"
+ndisasm -b 64 "${dir}/output"
diff --git a/home/.local/bin/notify-send b/home/.local/bin/notify-send
new file mode 100755
index 00000000..5630440d
--- /dev/null
+++ b/home/.local/bin/notify-send
@@ -0,0 +1,9 @@
+#!/usr/bin/osascript
+
+on run argv
+	if count of argv is 2 then
+		display notification (item 2 of argv) with title (item 1 of argv)
+	else
+		display notification (item 1 of argv)
+	end if
+end run
diff --git a/home/.local/bin/np b/home/.local/bin/np
new file mode 100755
index 00000000..b0eb2326
--- /dev/null
+++ b/home/.local/bin/np
@@ -0,0 +1,7 @@
+#!/usr/bin/osascript
+
+tell application "Music"
+	tell current track
+		get "/me is listening to " & artist & " — " & name
+	end tell
+end tell
diff --git a/home/.local/bin/open b/home/.local/bin/open
new file mode 100755
index 00000000..9439f07d
--- /dev/null
+++ b/home/.local/bin/open
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -eu
+
+if [ -n "${SSH_CLIENT:-}" ]; then
+	exec pbd -o "$@"
+fi
+
+case "$1" in
+	(*.gif|*.jpeg|*.jpg|*.png)
+		curl -LSs "$1" | imv -
+		;;
+	(https://youtu.be/*|https://www.youtube.com/watch*|https://twitch.tv/*)
+		ulimit -c 0 # mpv segfaults on exit every time on OpenBSD...
+		exec mpv "$1" >/dev/null 2>&1
+		;;
+	(*)
+		exec firefox -new-tab "$1" >/dev/null 2>&1
+		;;
+esac
diff --git a/home/.local/bin/pbcopy b/home/.local/bin/pbcopy
new file mode 100755
index 00000000..a804f836
--- /dev/null
+++ b/home/.local/bin/pbcopy
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -eu
+
+if [ -n "${SSH_CLIENT:-}" ]; then
+	exec pbd -c
+elif [ -n "${DISPLAY:-}" ]; then
+	exec xsel -bi
+else
+	echo "${0}: don't know what to do" >&2
+	exit 1
+fi
diff --git a/home/.local/bin/pbpaste b/home/.local/bin/pbpaste
new file mode 100755
index 00000000..2924f01e
--- /dev/null
+++ b/home/.local/bin/pbpaste
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -eu
+
+if [ -n "${SSH_CLIENT:-}" ]; then
+	exec pbd -p
+elif [ -n "${DISPLAY:-}" ]; then
+	exec xsel -bo
+else
+	echo "${0}: don't know what to do" >&2
+	exit 1
+fi
diff --git a/home/.local/bin/versions b/home/.local/bin/versions
new file mode 100755
index 00000000..25e5ff72
--- /dev/null
+++ b/home/.local/bin/versions
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -u
+
+for repo in ~/src/git/*; do
+	version=$(git -C "${repo}" describe --dirty 2>/dev/null)
+	if [ $? -eq 0 ]; then
+		echo "${repo##*/}-${version#v}"
+	fi
+done | sort -nr -t '-' -k 3 | column -t -s '-'
diff --git a/home/.local/bin/whinclude b/home/.local/bin/whinclude
new file mode 100755
index 00000000..26445cdc
--- /dev/null
+++ b/home/.local/bin/whinclude
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -eu
+
+echo "#include <${1}>" |
+cc ${CFLAGS:-} -E -x c - |
+sed -En '
+	/^# [0-9]+ "[^<]/{
+		s/.*"([^"]+)".*/\1/p
+		q
+	}
+'
diff --git a/home/.profile b/home/.profile
new file mode 100644
index 00000000..7d4ba822
--- /dev/null
+++ b/home/.profile
@@ -0,0 +1,28 @@
+_PATH=$PATH PATH=
+path() { test -d "$1" && PATH="${PATH}${PATH:+:}${1}"; }
+for prefix in '' /usr/local /opt/local /usr ~/.local ~/.cargo; do
+	path "${prefix}/sbin"
+	path "${prefix}/bin"
+done
+path /usr/X11R6/bin
+path /usr/games
+export MANPATH=:~/.local/share/man
+
+export EDITOR=vi
+command -v nvi >/dev/null && EDITOR=nvi
+export EXINIT='set ai extended iclower sm sw=4 ts=4 para=BlBdPpIt sect=ShSs
+map gg 1G'
+export PAGER=less
+export LESS=FRXix4
+export CLICOLOR=1
+export MANSECT=2:3:1:8:6:5:7:4:9
+export NETHACKOPTIONS='pickup_types:$!?+/=, color, DECgraphics'
+command -v diff-highlight >/dev/null &&
+export GIT_PAGER="diff-highlight | $PAGER"
+
+test -e /usr/share/mk/sys.mk || export CFLAGS=-O
+test -d /usr/home && cd
+
+test -f ~/.profile.local && . ~/.profile.local
+
+export ENV=~/.shrc
diff --git a/home/.shrc b/home/.shrc
new file mode 100644
index 00000000..afa87fe5
--- /dev/null
+++ b/home/.shrc
@@ -0,0 +1,54 @@
+set -o noclobber -o nounset -o vi
+
+CDPATH=:~
+
+alias vi=$EDITOR
+alias ls='LC_COLLATE=C ls -p'
+alias ll='ls -hl'
+alias ff='find . -type f -name'
+alias bc='bc -l'
+alias ag='ag --pager=$PAGER'
+alias gs='git status --short --branch || ls' gd='git diff'
+alias gsh='git show' gl='git log --graph --pretty=log'
+alias gco='git checkout' gb='git branch' gm='git merge' gst='git stash'
+alias ga='git add' gmv='git mv' grm='git rm'
+alias gc='git commit' gca='gc --amend' gt='git tag'
+alias gp='git push' gu='git pull' gf='git fetch'
+alias gr='git rebase' grc='git rebase --continue'
+alias rand='openssl rand -base64 33'
+alias private='eval "$(gpg -d ~/.private)"'
+command -v doas >/dev/null || alias doas=sudo
+
+man() {
+	test $# -ne 1 && { command man "$@"; return $?; }
+	(IFS=:
+	for sect in $MANSECT; do
+		command man -w $sect "$1" >/dev/null 2>&1 && exec man $sect "$1"
+	done
+	exec command man "$1")
+}
+
+cd() {
+	local path
+	if [ $# -eq 0 ]; then
+		command cd
+	elif [ "${1%%:*}" != "$1" ]; then
+		path=${1#*:}
+		[ -n "${path}" ] || path=${PWD#${HOME}/}
+		SSH_CD=$path ssh -o SendEnv=SSH_CD "${1%%:*}"
+	elif [ -e "$1" -a ! -d "$1" ]; then
+		command cd "${1%/*}" && $EDITOR "${1##*/}"
+	else
+		command cd "$@"
+	fi
+}
+if [ -n "${SSH_CD:-}" ]; then
+	cd "${SSH_CD}"
+	unset SSH_CD
+fi
+
+export LESS_TERMCAP_us=$(tput sitm)
+export LESS_TERMCAP_ue=$(tput ritm)
+
+PS1='\[\033]0;${SSH_CLIENT:+\\h:}\W\a\]
+${?#0}$ '
diff --git a/home/.ssh/config b/home/.ssh/config
new file mode 100644
index 00000000..f579ae9f
--- /dev/null
+++ b/home/.ssh/config
@@ -0,0 +1,17 @@
+IgnoreUnknown Include
+Include config_private
+
+AddKeysToAgent yes
+SendEnv LANG LC_*
+
+Host tuesday beastie puffy toaster tux progynova
+	HostName %h.local
+	ForwardAgent yes
+	RemoteForward 7062 127.0.0.1:7062
+
+Host scout soldier pyro demo heavy engi medic sniper spy
+	HostName %h.causal.agency
+	Port 2222
+
+Host git.causal.agency temp.causal.agency
+	Port 2222
diff --git a/home/.xsession b/home/.xsession
new file mode 100644
index 00000000..1e05126c
--- /dev/null
+++ b/home/.xsession
@@ -0,0 +1,14 @@
+. ~/.profile
+export LC_CTYPE=en_US.UTF-8
+
+xset r rate 175 m 5/4 0
+xmodmap ~/.config/X/modmap
+xrdb -load ~/.config/X/resources
+
+fg=998D6B
+command -v scheme && fg=$(scheme -p $(jot -r 1 1 8))
+xsetroot -bitmap /usr/X11R6/include/X11/bitmaps/escherknot \
+	-bg '#14130E' -fg "#${fg}"
+
+xterm -name clock -geometry 14x1-0+0 -sl 0 -e clock &
+exec cwm -c ~/.config/cwm/cwmrc
diff --git a/install.sh b/install.sh
new file mode 100644
index 00000000..11269fb7
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+set -eu
+
+X=
+while getopts 'X' opt; do
+	case "$opt" in
+		(X) X=1;;
+		(?) exit 1;;
+	esac
+done
+
+packages='curl htop sl the_silver_searcher tree'
+
+FreeBSD() {
+	pkg install ddate $packages
+}
+
+OpenBSD() {
+	pkg_add $packages
+	if test $X; then
+		pkg_add firefox go-fonts imv scrot sct w3m-- xcursor-dmz xsel
+	fi
+}
+
+Linux() {
+	packages=$(
+		echo $packages | sed 's/the_silver_searcher/silversearcher-ag/'
+	)
+	apt-get install bc build-essential exuberant-ctags gdb nvi $packages
+}
+
+Darwin() {
+	packages=$(echo $packages | sed 's/the_silver_searcher/ag/')
+	cd git/jorts
+	git pull
+	./Install git mandoc nvi $packages
+}
+
+$(uname)
diff --git a/link.sh b/link.sh
new file mode 100644
index 00000000..6763f2e0
--- /dev/null
+++ b/link.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -eu
+
+die() {
+	echo "$*"
+	exit 1
+}
+
+if [ $# -eq 1 ]; then
+	link=$1
+	file="${PWD}/home/${link#${HOME}/}"
+	[ ! -f "$file" ] || die "${file} exists"
+	mkdir -p "${file%/*}"
+	mv "$link" "$file"
+fi
+
+find home -type f | while read -r find; do
+	file="${PWD}/${find}"
+	link="${HOME}/${find#home/}"
+	mkdir -p "${link%/*}"
+	[ \( -f "$link" -a -L "$link" \) -o ! -f "$link" ] || die "${link} exists"
+	ln -fs "$file" "$link"
+done
diff --git a/port/caesar/.gitignore b/port/caesar/.gitignore
new file mode 100644
index 00000000..e2c3034b
--- /dev/null
+++ b/port/caesar/.gitignore
@@ -0,0 +1,2 @@
+caesar
+rot13
diff --git a/port/caesar/Makefile b/port/caesar/Makefile
new file mode 100644
index 00000000..01205b16
--- /dev/null
+++ b/port/caesar/Makefile
@@ -0,0 +1,19 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+LDLIBS = -lm
+
+all: caesar rot13
+
+clean:
+	rm -f caesar rot13
+
+install: caesar rot13 caesar.6
+	install -d ${PREFIX}/bin ${MANDIR}/man6
+	install caesar rot13 ${PREFIX}/bin
+	install -m 644 caesar.6 ${MANDIR}/man6/caesar.6
+	install -m 644 caesar.6 ${MANDIR}/man6/rot13.6
+
+uninstall:
+	rm -f ${PREFIX}/bin/caesar ${PREFIX}/bin/rot13
+	rm -f ${MANDIR}/man6/caesar.6 ${MANDIR}/man6/rot13.6
diff --git a/port/caesar/caesar.6 b/port/caesar/caesar.6
new file mode 100644
index 00000000..4c4bbfb4
--- /dev/null
+++ b/port/caesar/caesar.6
@@ -0,0 +1,73 @@
+.\" Copyright (c) 1989, 1991, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)caesar.6	8.2 (Berkeley) 11/16/93
+.\" $FreeBSD: releng/11.2/usr.bin/caesar/caesar.6 216239 2010-12-06 19:12:51Z uqs $
+.\"
+.Dd November 16, 1993
+.Dt CAESAR 6
+.Os
+.Sh NAME
+.Nm caesar , rot13
+.Nd decrypt caesar ciphers
+.Sh SYNOPSIS
+.Nm
+.Op Ar rotation
+.Nm rot13
+.Sh DESCRIPTION
+The
+.Nm
+utility attempts to decrypt caesar ciphers using English letter frequency
+statistics.
+.Nm Caesar
+reads from the standard input and writes to the standard output.
+.Pp
+The optional numerical argument
+.Ar rotation
+may be used to specify a specific rotation value.
+If invoked as
+.Nm rot13 ,
+a rotation value of 13 will be used.
+.Pp
+The frequency (from most common to least) of English letters is as follows:
+.Bd -ragged -offset indent
+ETAONRISHDLFCMUGPYWBVKXJQZ
+.Ed
+.Pp
+Their frequencies as a percentage are as follows:
+.Bd -ragged -offset indent
+E(13), T(10.5), A(8.1), O(7.9), N(7.1), R(6.8), I(6.3), S(6.1), H(5.2),
+D(3.8), L(3.4), F(2.9), C(2.7), M(2.5), U(2.4), G(2),
+P(1.9), Y(1.9),
+W(1.5), B(1.4), V(.9), K(.4), X(.15), J(.13), Q(.11), Z(.07).
+.Ed
+.Pp
+Rotated postings to
+.Tn USENET
+and some of the databases used by the
+.Xr fortune 6
+program are rotated by 13 characters.
diff --git a/port/caesar/caesar.c b/port/caesar/caesar.c
new file mode 100644
index 00000000..cd6cd579
--- /dev/null
+++ b/port/caesar/caesar.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Adams.
+ *
+ * Authors:
+ *	Stan King, John Eldridge, based on algorithm suggested by
+ *	Bob Morris
+ * 29-Sep-82
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+	The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char sccsid[] = "@(#)caesar.c    8.1 (Berkeley) 5/31/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: releng/11.2/usr.bin/caesar/caesar.c 241846 2012-10-22 03:06:53Z eadler $");
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#define	LINELENGTH	2048
+#define	ROTATE(ch, perm) \
+     isascii(ch) ? ( \
+	isupper(ch) ? ('A' + (ch - 'A' + perm) % 26) : \
+	    islower(ch) ? ('a' + (ch - 'a' + perm) % 26) : ch) : ch
+
+/*
+ * letter frequencies (taken from some unix(tm) documentation)
+ * (unix is a trademark of Bell Laboratories)
+ */
+static double stdf[26] = {
+	7.97, 1.35, 3.61, 4.78, 12.37, 2.01, 1.46, 4.49, 6.39, 0.04,
+	0.42, 3.81, 2.69, 5.92,  6.96, 2.91, 0.08, 6.63, 8.77, 9.68,
+	2.62, 0.81, 1.88, 0.23,  2.07, 0.06,
+};
+
+static void printit(char *);
+
+int
+main(int argc, char **argv)
+{
+	int ch, dot, i, nread, winnerdot = 0;
+	char *inbuf;
+	int obs[26], try, winner;
+
+	if (argc > 1)
+		printit(argv[1]);
+
+	if (!(inbuf = malloc((size_t)LINELENGTH))) {
+		(void)fprintf(stderr, "caesar: out of memory.\n");
+		exit(1);
+	}
+
+	/* adjust frequency table to weight low probs REAL low */
+	for (i = 0; i < 26; ++i)
+		stdf[i] = log(stdf[i]) + log(26.0 / 100.0);
+
+	/* zero out observation table */
+	bzero(obs, 26 * sizeof(int));
+
+	if ((nread = read(STDIN_FILENO, inbuf, (size_t)LINELENGTH)) < 0) {
+		(void)fprintf(stderr, "caesar: %s\n", strerror(errno));
+		exit(1);
+	}
+	for (i = nread; i--;) {
+		ch = (unsigned char) inbuf[i];
+		if (isascii(ch)) {
+			if (islower(ch))
+				++obs[ch - 'a'];
+			else if (isupper(ch))
+				++obs[ch - 'A'];
+		}
+	}
+
+	/*
+	 * now "dot" the freqs with the observed letter freqs
+	 * and keep track of best fit
+	 */
+	for (try = winner = 0; try < 26; ++try) { /* += 13) { */
+		dot = 0;
+		for (i = 0; i < 26; i++)
+			dot += obs[i] * stdf[(i + try) % 26];
+		/* initialize winning score */
+		if (try == 0)
+			winnerdot = dot;
+		if (dot > winnerdot) {
+			/* got a new winner! */
+			winner = try;
+			winnerdot = dot;
+		}
+	}
+
+	for (;;) {
+		for (i = 0; i < nread; ++i) {
+			ch = (unsigned char) inbuf[i];
+			putchar(ROTATE(ch, winner));
+		}
+		if (nread < LINELENGTH)
+			break;
+		if ((nread = read(STDIN_FILENO, inbuf, (size_t)LINELENGTH)) < 0) {
+			(void)fprintf(stderr, "caesar: %s\n", strerror(errno));
+			exit(1);
+		}
+	}
+	exit(0);
+}
+
+static void
+printit(char *arg)
+{
+	int ch, rot;
+
+	if ((rot = atoi(arg)) < 0) {
+		(void)fprintf(stderr, "caesar: bad rotation value.\n");
+		exit(1);
+	}
+	while ((ch = getchar()) != EOF)
+		putchar(ROTATE(ch, rot));
+	exit(0);
+}
diff --git a/port/caesar/rot13.sh b/port/caesar/rot13.sh
new file mode 100644
index 00000000..8ce4b94e
--- /dev/null
+++ b/port/caesar/rot13.sh
@@ -0,0 +1,33 @@
+#!/bin/sh -
+#
+# Copyright (c) 1992, 1993
+#	The Regents of the University of California.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)rot13.sh	8.1 (Berkeley) 5/31/93
+# $FreeBSD: releng/11.2/usr.bin/caesar/rot13.sh 278616 2015-02-12 05:35:00Z cperciva $
+
+exec caesar 13 "$@"
diff --git a/port/cgram/.gitignore b/port/cgram/.gitignore
new file mode 100644
index 00000000..d4f2ec10
--- /dev/null
+++ b/port/cgram/.gitignore
@@ -0,0 +1 @@
+cgram
diff --git a/port/cgram/Makefile b/port/cgram/Makefile
new file mode 100644
index 00000000..02f11eec
--- /dev/null
+++ b/port/cgram/Makefile
@@ -0,0 +1,17 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+LDLIBS = -lcurses
+
+cgram:
+
+clean:
+	rm -f cgram
+
+install: cgram cgram.6
+	install -d ${PREFIX}/bin ${MANDIR}/man6
+	install cgram ${PREFIX}/bin
+	install -m 644 cgram.6 ${MANDIR}/man6
+
+uninstall:
+	rm -f ${PREFIX}/bin/cgram ${MANDIR}/man6/cgram.6
diff --git a/port/cgram/cgram.6 b/port/cgram/cgram.6
new file mode 100644
index 00000000..9f315804
--- /dev/null
+++ b/port/cgram/cgram.6
@@ -0,0 +1,65 @@
+.\" $NetBSD: cgram.6,v 1.2 2013/08/04 07:55:09 wiz Exp $
+.\"
+.\" Copyright (c) 2004, 2013 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by David A. Holland.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 3, 2013
+.Dt CGRAM 6
+.Os
+.Sh NAME
+.Nm cgram
+.Nd solve Sunday-paper cryptograms
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+is a curses-based widget for solving Sunday-paper-type cryptograms
+based on substitution ciphers.
+A random cleartext is chosen using
+.Xr fortune 6
+and a random substitution key is generated.
+.Pp
+The ciphertext is displayed.
+Typing a letter changes the key so that the letter under the cursor
+maps to the newly typed letter, and updates the display accordingly.
+Use Emacs-type cursor commands to move around.
+Enter a tilde
+.Pq ~
+to quit.
+Press asterisk
+.Pq *
+to enter an easier mode where correct letters are displayed in
+boldface.
+.Sh SEE ALSO
+.Xr caesar 6
+.Sh HISTORY
+.Nm
+was written circa 2004.
+It was imported into
+.Nx
+in 2013 and first appeared in
+.Nx 7.0 .
diff --git a/port/cgram/cgram.c b/port/cgram/cgram.c
new file mode 100644
index 00000000..76ea55fb
--- /dev/null
+++ b/port/cgram/cgram.c
@@ -0,0 +1,344 @@
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <err.h>
+#include <assert.h>
+#include <curses.h>
+#include "pathnames.h"
+
+////////////////////////////////////////////////////////////
+
+static char *xstrdup(const char *s) {
+   char *ret;
+
+   ret = malloc(strlen(s) + 1);
+   if (ret == NULL) {
+      errx(1, "Out of memory");
+   }
+   strcpy(ret, s);
+   return ret;
+}
+
+////////////////////////////////////////////////////////////
+
+struct stringarray {
+   char **v;
+   int num;
+};
+
+static void stringarray_init(struct stringarray *a) {
+   a->v = NULL;
+   a->num = 0;
+}
+
+static void stringarray_cleanup(struct stringarray *a) {
+   free(a->v);
+}
+
+static void stringarray_add(struct stringarray *a, const char *s) {
+   a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
+   if (a->v == NULL) {
+      errx(1, "Out of memory");
+   }
+   a->v[a->num] = xstrdup(s);
+   a->num++;
+}
+
+////////////////////////////////////////////////////////////
+
+static struct stringarray lines;
+static struct stringarray sollines;
+static bool hinting;
+static int scrolldown;
+static unsigned curx;
+static int cury;
+
+static void readquote(void) {
+   FILE *f = popen(_PATH_FORTUNE, "r");
+   if (!f) {
+      err(1, "%s", _PATH_FORTUNE);
+   }
+
+   char buf[128], buf2[8*sizeof(buf)];
+   while (fgets(buf, sizeof(buf), f)) {
+      char *s = strrchr(buf, '\n');
+      assert(s);
+      assert(strlen(s)==1);
+      *s = 0;
+
+      int i,j;
+      for (i=j=0; buf[i]; i++) {
+	 if (buf[i]=='\t') {
+	    buf2[j++] = ' ';
+	    while (j%8) buf2[j++] = ' ';
+	 }
+	 else if (buf[i]=='\b') {
+	    if (j>0) j--;
+	 }
+	 else {
+	    buf2[j++] = buf[i];
+	 }
+      }
+      buf2[j] = 0;
+
+      stringarray_add(&lines, buf2);
+      stringarray_add(&sollines, buf2);
+   }
+
+   pclose(f);
+}
+
+static void encode(void) {
+   int used[26];
+   for (int i=0; i<26; i++) used[i] = 0;
+
+   int key[26];
+   int keypos=0;
+   while (keypos < 26) {
+      int c = random()%26;
+      if (used[c]) continue;
+      key[keypos++] = c;
+      used[c] = 1;
+   }
+
+   for (int y=0; y<lines.num; y++) {
+      for (unsigned x=0; lines.v[y][x]; x++) {
+	 if (islower((unsigned char)lines.v[y][x])) {
+	    int q = lines.v[y][x]-'a';
+	    lines.v[y][x] = 'a'+key[q];
+	 }
+	 if (isupper((unsigned char)lines.v[y][x])) {
+	    int q = lines.v[y][x]-'A';
+	    lines.v[y][x] = 'A'+key[q];
+	 }
+      }
+   }
+}
+
+static int substitute(int ch) {
+   assert(cury>=0 && cury<lines.num);
+   if (curx >= strlen(lines.v[cury])) {
+      beep();
+      return -1;
+   }
+
+   int och = lines.v[cury][curx];
+   if (!isalpha((unsigned char)och)) {
+      beep();
+      return -1;
+   }
+
+   int loch = tolower((unsigned char)och);
+   int uoch = toupper((unsigned char)och);
+   int lch = tolower((unsigned char)ch);
+   int uch = toupper((unsigned char)ch);
+
+   for (int y=0; y<lines.num; y++) {
+      for (unsigned x=0; lines.v[y][x]; x++) {
+	 if (lines.v[y][x]==loch) {
+	    lines.v[y][x] = lch;
+	 }
+	 else if (lines.v[y][x]==uoch) {
+	    lines.v[y][x] = uch;
+	 }
+	 else if (lines.v[y][x]==lch) {
+	    lines.v[y][x] = loch;
+	 }
+	 else if (lines.v[y][x]==uch) {
+	    lines.v[y][x] = uoch;
+	 }
+      }
+   }
+   return 0;
+}
+
+////////////////////////////////////////////////////////////
+
+static void redraw(void) {
+   erase();
+   bool won = true;
+   for (int i=0; i<LINES-1; i++) {
+      move(i, 0);
+      int ln = i+scrolldown;
+      if (ln < lines.num) {
+	 for (unsigned j=0; lines.v[i][j]; j++) {
+	    int ch = lines.v[i][j];
+	    if (ch != sollines.v[i][j] && isalpha((unsigned char)ch)) {
+	       won = false;
+	    }
+	    bool bold=false;
+	    if (hinting && ch==sollines.v[i][j] &&
+		isalpha((unsigned char)ch)) {
+	       bold = true;
+	       attron(A_BOLD);
+	    }
+	    addch(lines.v[i][j]);
+	    if (bold) {
+	       attroff(A_BOLD);
+	    }
+	 }
+      }
+      clrtoeol();
+   }
+
+   move(LINES-1, 0);
+   if (won) {
+      addstr("*solved* ");
+   }
+   addstr("~ to quit, * to cheat, ^pnfb to move");
+
+   move(LINES-1, 0);
+
+   move(cury-scrolldown, curx);
+
+   refresh();
+}
+
+static void opencurses(void) {
+    initscr();
+    cbreak();
+    noecho();
+}
+
+static void closecurses(void) {
+   endwin();
+}
+
+////////////////////////////////////////////////////////////
+
+static void loop(void) {
+   bool done=false;
+   while (!done) {
+      redraw();
+      int ch = getch();
+      switch (ch) {
+       case 1: /* ^A */
+	curx=0;
+	break;
+       case 2: /* ^B */
+	if (curx > 0) {
+	   curx--;
+	}
+	else if (cury > 0) {
+	   cury--;
+	   curx = strlen(lines.v[cury]);
+	}
+	break;
+       case 5: /* ^E */
+	curx = strlen(lines.v[cury]);
+	break;
+       case 6: /* ^F */
+	if (curx < strlen(lines.v[cury])) {
+	   curx++;
+	}
+	else if (cury < lines.num - 1) {
+	   cury++;
+	   curx = 0;
+	}
+	break;
+       case 12: /* ^L */
+	clear();
+	break;
+       case 14: /* ^N */
+	if (cury < lines.num-1) {
+	   cury++;
+	}
+	if (curx > strlen(lines.v[cury])) {
+	   curx =  strlen(lines.v[cury]);
+	}
+	if (scrolldown < cury - (LINES-2)) {
+	   scrolldown = cury - (LINES-2);
+	}
+	break;
+       case 16: /* ^P */
+	if (cury > 0) {
+	   cury--;
+	}
+	if (curx > strlen(lines.v[cury])) {
+	   curx = strlen(lines.v[cury]);
+	}
+	if (scrolldown > cury) {
+	   scrolldown = cury;
+	}
+	break;
+       case '*':
+	hinting = !hinting;
+	break;
+       case '~':
+	done = true;
+	break;
+       default:
+	if (isalpha(ch)) {
+	   if (!substitute(ch)) {
+	      if (curx < strlen(lines.v[cury])) {
+		 curx++;
+	      }
+	      if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
+		 curx=0;
+		 cury++;
+	      }
+	   }
+	}
+	else if (curx < strlen(lines.v[cury]) && ch==lines.v[cury][curx]) {
+	   curx++;
+	   if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
+	      curx=0;
+	      cury++;
+	   }
+	}
+	else {
+	   beep();
+	}
+	break;
+      }
+   }
+}
+
+////////////////////////////////////////////////////////////
+
+int main(void) {
+   stringarray_init(&lines);
+   stringarray_init(&sollines);
+   srandom(time(NULL));
+   readquote();
+   encode();
+   opencurses();
+
+   loop();
+
+   closecurses();
+   stringarray_cleanup(&sollines);
+   stringarray_cleanup(&lines);
+   return 0;
+}
diff --git a/port/cgram/pathnames.h b/port/cgram/pathnames.h
new file mode 100644
index 00000000..40db1eed
--- /dev/null
+++ b/port/cgram/pathnames.h
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _PATH_FORTUNE "fortune"
diff --git a/port/ddate/.gitignore b/port/ddate/.gitignore
new file mode 100644
index 00000000..eaa8a5fd
--- /dev/null
+++ b/port/ddate/.gitignore
@@ -0,0 +1 @@
+ddate
diff --git a/port/ddate/Makefile b/port/ddate/Makefile
new file mode 100644
index 00000000..c1bc4553
--- /dev/null
+++ b/port/ddate/Makefile
@@ -0,0 +1,15 @@
+PREFIX ?= ~/.local
+MANDIR ?= ${PREFIX}/share/man
+
+ddate:
+
+clean:
+	rm -f ddate
+
+install: ddate ddate.1
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+	install ddate ${PREFIX}/bin
+	install -m 644 ddate.1 ${MANDIR}/man1
+
+uninstall:
+	rm -f ${PREFIX}/bin/ddate ${MANDIR}/man1/ddate.1
diff --git a/port/ddate/ddate.1 b/port/ddate/ddate.1
new file mode 100644
index 00000000..c340578f
--- /dev/null
+++ b/port/ddate/ddate.1
@@ -0,0 +1,117 @@
+.\" All Rites Reversed.  This file is in the PUBLIC DOMAIN.
+.\" Kallisti.
+.TH DDATE 1 "Bureaucracy 3161" "ddate" "Emperor Norton User Command"
+.SH NAME
+ddate \- convert Gregorian dates to Discordian dates
+.SH SYNOPSIS
+.B ddate
+.RI [ \fB+\fPformat]
+.RI [ date ]
+.SH DESCRIPTION
+.B ddate
+prints the date in Discordian date format.
+.PP
+If called with no arguments,
+.B ddate
+will get the current system date, convert this to the Discordian
+date format and print this on the standard output. Alternatively, a
+Gregorian date may be specified on the command line, in the form of a numerical
+day, month and year.
+.PP
+If a format string is specified, the Discordian date will be printed in
+a format specified by the string. This mechanism works similarly to the
+format string mechanism of
+.B date(1),
+only almost completely differently. The fields are:
+.IP %A
+Full name of the day of the week (i.e., Sweetmorn)
+.IP %a
+Abbreviated name of the day of the week (i.e., SM)
+.IP %B
+Full name of the season (i.e., Chaos)
+.IP %b
+Abbreviated name of the season (i.e., Chs)
+.IP %d
+Cardinal number of day in season (i.e., 23)
+.IP %e
+Ordinal number of day in season (i.e., 23rd)
+.IP %H
+Name of current Holyday, if any
+.IP %N
+Magic code to prevent rest of format from being printed unless today is
+a Holyday.
+.IP %n
+Newline
+.IP %t
+Tab
+.IP %X
+Number of days remaining until X-Day. (Not valid if the SubGenius options
+are not compiled in.)
+.IP %Y
+The year of our Lady Discord (i.e., 3182)
+.IP %{
+.IP %}
+Used to enclose the part of the string which is to be replaced with the
+words "St. Tib's Day" if the current day is St. Tib's Day.
+.IP %\.
+Try it and see.
+.bp
+.SH EXAMPLES
+.nf
+% ddate
+.br
+Sweetmorn, Bureaucracy 42, 3161 YOLD
+.PP
+% ddate +'Today is %{%A, the %e of %B%}, %Y. %N%nCelebrate %H'
+.br
+Today is Sweetmorn, the 42nd of Bureaucracy, 3161.
+.PP
+% ddate +"It's %{%A, the %e of %B%}, %Y. %N%nCelebrate %H" 26 9 1995
+.br
+It's Prickle-Prickle, the 50th of Bureaucracy, 3161.
+.br
+Celebrate Bureflux
+.PP
+% ddate +"Today's %{%A, the %e of %B%}, %Y. %N%nCelebrate %H" 29 2 1996
+.br
+Today's St. Tib's Day, 3162.
+.br
+
+.SH BUGS
+
+.B ddate(1)
+will produce undefined behavior if asked to produce the date for St. Tib's
+day and its format string does not contain the St. Tib's Day delimiters
+%{ and %}.
+
+.SH NOTE
+
+After `X-Day' passed without incident, the Church of the SubGenius
+declared that it had got the year upside down - X-Day is actually in 8661 AD
+rather than 1998 AD.  Thus, the True X-Day is Cfn 40, 9827.
+
+.SH AUTHOR
+.nh
+Original program by Druel the Chaotic aka Jeremy Johnson (mpython@gnu.ai.mit.edu)
+.br
+Major rewrite by Lee H:. O:. Smith, KYTP, aka Andrew Bulhak (acb@dev.null.org)
+.br
+Gregorian B.C.E. dates fixed by Chaplain Nyan the Wiser, aka Dan Dart (ntw@dandart.co.uk)
+.br
+Five tons of flax.
+
+.SH DISTRIBUTION POLICY
+
+Public domain. All rites reversed.
+
+.SH SEE ALSO
+
+date(1),
+.br
+http://www.subgenius.com/
+.br
+Malaclypse the Younger,
+.I "Principia Discordia, Or How I Found Goddess And What I Did To Her When I Found Her"
+
+.SH AVAILABILITY
+The ddate command is available from https://github.com/bo0ts/ddate.
diff --git a/port/ddate/ddate.c b/port/ddate/ddate.c
new file mode 100644
index 00000000..c0a6bf37
--- /dev/null
+++ b/port/ddate/ddate.c
@@ -0,0 +1,399 @@
+/* $ DVCS ID: $jer|,523/lhos,KYTP!41023161\b"?" <<= DO NOT DELETE! */
+
+/* ddate.c .. converts boring normal dates to fun Discordian Date -><-
+   written  the 65th day of The Aftermath in the Year of Our Lady of 
+   Discord 3157 by Druel the Chaotic aka Jeremy Johnson aka
+   mpython@gnu.ai.mit.edu  
+      28 Sever St Apt #3
+      Worcester MA 01609
+
+   and I'm not responsible if this program messes anything up (except your 
+   mind, I'm responsible for that)
+
+   (k) YOLD 3161 and all time before and after.
+   Reprint, reuse, and recycle what you wish.
+   This program is in the public domain.  Distribute freely.  Or not.
+
+   Majorly hacked, extended and bogotified/debogotified on 
+   Sweetmorn, Bureaucracy 42, 3161 YOLD, by Lee H:. O:. Smith, KYTP, 
+   aka Andrew Bulhak, aka acb@dev.null.org
+
+   Slightly hackled and crackled by a sweet firey stove on
+   Boomtime, the 53rd day of Bureaucracy in the YOLD 3179,
+   by Chaplain Nyan the Wiser, aka Dan Dart, aka ntw@dandart.co.uk
+
+   and I'm not responsible if this program messes anything up (except your 
+   mind, I'm responsible for that) (and that goes for me as well --lhos)
+
+   Version history:
+   Bureflux 3161:      First release of enhanced ddate with format strings
+   59 Bcy, 3161:       PRAISE_BOB and KILL_BOB options split, other minor
+                       changes.
+   53 Bcy, 3179:       Fixed gregorian date conversions less than YOLD 1167
+
+   1999-02-22 Arkadiusz Miskiewicz <misiek@pld.ORG.PL>
+   - added Native Language Support
+
+   2000-03-17 Burt Holzman <holzman+ddate@gmail.com>
+   - added range checks for dates
+
+   2014-06-07 William Woodruff <william@tuffbizz.com>
+   - removed gettext dependent locale code
+
+   15th of Confusion, 3180:
+   - call out adherents of the wrong fruit
+
+   FIVE TONS OF FLAX
+*/
+
+/* configuration options  VVVVV   READ THIS!!! */
+
+/* If you wish ddate(1) to print the date in the same format as Druel's 
+ * original ddate when called in immediate mode, define OLD_IMMEDIATE_FMT 
+ */
+
+#define OLD_IMMEDIATE_FMT
+
+/* If you wish to use the US format for aneristic dates (m-d-y), as opposed to
+ * the Commonwealth format, define US_FORMAT.
+ */
+
+/* #define US_FORMAT */
+
+/* If you are ideologically, theologically or otherwise opposed to the 
+ * Church of the SubGenius and do not wish your copy of ddate(1) to contain
+ * code for counting down to X-Day, undefine KILL_BOB */
+
+#define KILL_BOB 13013
+
+/* If you wish ddate(1) to contain SubGenius slogans, define PRAISE_BOB */
+
+/*#define PRAISE_BOB 13013*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <stdio.h>
+
+
+// work around includes and defines from formerly c.h
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+#endif
+
+/* &a[0] degrades to a pointer: a different type from an array */
+# define __must_be_array(a) \
+	BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(__typeof__(a), __typeof__(&a[0])))
+
+#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
+
+/* work around hacks for standalone package */
+#define PACKAGE "ddate"
+#define PACKAGE_STRING "Stand Alone"
+
+#ifndef __GNUC__
+#define inline /* foo */
+#endif
+
+#ifdef KILL_BOB
+int xday_countdown(int yday, int year);
+#endif
+
+
+/* string constants */
+
+char *day_long[5] = { 
+    "Sweetmorn", "Boomtime", "Pungenday", "Prickle-Prickle", "Setting Orange"
+};
+
+char *day_short[5] = {"SM","BT","PD","PP","SO"};
+
+char *season_long[5] = { 
+    "Chaos", "Discord", "Confusion", "Bureaucracy", "The Aftermath"
+};
+
+char *season_short[5] = {"Chs", "Dsc", "Cfn", "Bcy", "Afm"};
+
+char *holyday[5][2] = { 
+    { "Mungday", "Chaoflux" },
+    { "Mojoday", "Discoflux" },
+    { "Syaday",  "Confuflux" },
+    { "Zaraday", "Bureflux" },
+    { "Maladay", "Afflux" }
+};
+
+struct disc_time {
+    int season; /* 0-4 */
+    int day; /* 0-72 */
+    int yday; /* 0-365 */
+    int year; /* 3066- */
+};
+
+char *excl[] = {
+    "Hail Eris!", "All Hail Discordia!", "Kallisti!", "Fnord.", "Or not.",
+    "Wibble.", "Pzat!", "P'tang!", "Frink!",
+#ifdef PRAISE_BOB
+    "Slack!", "Praise \"Bob\"!", "Or kill me.",
+#endif /* PRAISE_BOB */
+    /* randomness, from the Net and other places. Feel free to add (after
+       checking with the relevant authorities, of course). */
+    "Grudnuk demand sustenance!", "Keep the Lasagna flying!",
+    "You are what you see.",
+    "Or is it?", "This statement is false.",
+    "Lies and slander, sire!", "Hee hee hee!",
+#if defined(linux) || defined (__linux__) || defined (__linux)
+    "Hail Eris, Hack Linux!",
+#elif defined(__APPLE__)
+    "This Fruit is not the True Fruit of Discord.",
+#endif
+    ""
+};
+
+char default_fmt[] = "%{%A, %B %d%}, %Y YOLD";
+char *default_immediate_fmt=
+#ifdef OLD_IMMEDIATE_FMT
+"Today is %{%A, the %e day of %B%} in the YOLD %Y%N%nCelebrate %H"
+#else
+default_fmt
+#endif
+;
+
+#define DY(y) (y+1166)
+
+static inline char *ending(int i) {
+	return i/10==1?"th":(i%10==1?"st":(i%10==2?"nd":(i%10==3?"rd":"th")));
+}
+
+static inline int leapp(int i) {
+	return (!(DY(i)%4))&&((DY(i)%100)||(!(DY(i)%400)));
+}
+
+/* select a random string */
+static inline char *sel(char **strings, int num) {
+	return(strings[random()%num]);
+}
+
+void print(struct disc_time,char **); /* old */
+void format(char *buf, const char* fmt, struct disc_time dt);
+/* read a fortune file */
+int load_fortunes(char *fn, char *delim, char** result);
+
+struct disc_time convert(int,int);
+struct disc_time makeday(int,int,int);
+
+int
+main (int argc, char *argv[]) {
+    time_t t;
+    struct tm *eris;
+    int bob,raw;
+    struct disc_time hastur;
+    char schwa[23*17], *fnord=0;
+    int pi;
+    char *progname, *p;
+
+    progname = argv[0];
+    if ((p = strrchr(progname, '/')) != NULL)
+	progname = p+1;
+
+    srandom(time(NULL));
+    /* do args here */
+    for(pi=1; pi<argc; pi++) {
+	switch(argv[pi][0]) {
+	case '+': fnord=argv[pi]+1; break;
+	case '-': 
+	    switch(argv[pi][1]) {
+	    case 'V':
+		printf(("%s (%s)\n"), progname, PACKAGE_STRING);
+	    default: goto usage;
+	    }
+	default: goto thud;
+	}
+    }
+
+  thud:
+    if (argc-pi==3){ 
+	int moe=atoi(argv[pi]), larry=atoi(argv[pi+1]), curly=atoi(argv[pi+2]);
+	hastur=makeday(
+#ifdef US_FORMAT
+	    moe,larry,
+#else
+	    larry,moe,
+#endif
+	    curly);
+	if (hastur.season == -1) {
+		printf("Invalid date -- out of range\n");
+		return -1;
+	}
+	fnord=fnord?fnord:default_fmt;
+    } else if (argc!=pi) { 
+      usage:
+	fprintf(stderr,("usage: %s [+format] [day month year]\n"), argv[0]);
+	exit(1);
+    } else {
+	t= time(NULL);
+	eris=localtime(&t);
+	bob=eris->tm_yday; /* days since Jan 1. */
+	raw=eris->tm_year; /* years since 1980 */
+	hastur=convert(bob,raw);
+	fnord=fnord?fnord:default_immediate_fmt;
+    }
+    format(schwa, fnord, hastur);
+    printf("%s\n", schwa);
+   
+    return 0;
+}
+
+void format(char *buf, const char* fmt, struct disc_time dt)
+{
+    int tib_start=-1, tib_end=0;
+    int i, fmtlen=strlen(fmt);
+    char *bufptr=buf;
+
+/*    fprintf(stderr, "format(%p, \"%s\", dt)\n", buf, fmt);*/
+
+    /* first, find extents of St. Tib's Day area, if defined */
+    for(i=0; i<fmtlen; i++) {
+	if(fmt[i]=='%') {
+	    switch(fmt[i+1]) {
+	    case 'A':
+	    case 'a':
+	    case 'd':
+	    case 'e':
+		if(tib_start>0)	    tib_end=i+1;
+		else		    tib_start=i;
+		break;
+	    case '{': tib_start=i; break;
+	    case '}': tib_end=i+1; break;
+	    }
+	}
+    }
+
+    /* now do the formatting */
+    buf[0]=0;
+
+    for(i=0; i<fmtlen; i++) {
+	if((i==tib_start) && (dt.day==-1)) {
+	    /* handle St. Tib's Day */
+	    strcpy(bufptr, ("St. Tib's Day"));
+	    bufptr += strlen(bufptr);
+	    i=tib_end;
+	} else {
+	    if(fmt[i]=='%') {
+		char *wibble=0, snarf[23];
+		switch(fmt[++i]) {
+		case 'A': wibble=day_long[dt.yday%5]; break;
+		case 'a': wibble=day_short[dt.yday%5]; break;
+		case 'B': wibble=season_long[dt.season]; break;
+		case 'b': wibble=season_short[dt.season]; break;
+		case 'd': sprintf(snarf, "%d", dt.day+1); wibble=snarf; break;
+		case 'e': sprintf(snarf, "%d%s", dt.day+1, ending(dt.day+1)); 
+		    wibble=snarf; break;
+		case 'H': if(dt.day==4||dt.day==49)
+		    wibble=holyday[dt.season][dt.day==49]; break;
+		case 'N': if(dt.day!=4&&dt.day!=49) goto eschaton; break;
+		case 'n': *(bufptr++)='\n'; break;
+		case 't': *(bufptr++)='\t'; break;
+
+		case 'Y': sprintf(snarf, "%d", dt.year); wibble=snarf; break;
+		case '.': wibble=sel(excl, ARRAY_SIZE(excl));
+		    break;
+#ifdef KILL_BOB
+		case 'X': sprintf(snarf, "%d", 
+				  xday_countdown(dt.yday, dt.year));
+				  wibble = snarf; break;
+#endif /* KILL_BOB */
+		}
+		if(wibble) {
+/*		    fprintf(stderr, "wibble = (%s)\n", wibble);*/
+		    strcpy(bufptr, wibble); bufptr+=strlen(wibble);
+		}
+	    } else {
+		*(bufptr++) = fmt[i];
+	    }
+	}
+    }
+  eschaton:
+    *(bufptr)=0;
+}
+
+struct disc_time makeday(int imonth,int iday,int iyear) /*i for input */
+{ 
+    struct disc_time funkychickens;
+    
+    int cal[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
+    int dayspast=0;
+
+    memset(&funkychickens,0,sizeof(funkychickens));
+    /* basic range checks */
+    if (imonth < 1 || imonth > 12 || iyear == 0) {
+	    funkychickens.season = -1;
+	    return funkychickens;
+    }
+    if (iday < 1 || iday > cal[imonth-1]) {
+	    if (!(imonth == 2 && iday == 29 && iyear%4 == 0 &&
+		  (iyear%100 != 0 || iyear%400 == 0))) {
+		    funkychickens.season = -1;
+		    return funkychickens;
+	    }
+    }
+    
+    imonth--;
+    /*  note: gregorian year 0 doesn't exist so
+     *  add one if user specifies a year less than 0 */
+    funkychickens.year= iyear+1166 + ((0 > iyear)?1:0);
+    while(imonth>0) { dayspast+=cal[--imonth]; }
+    funkychickens.day=dayspast+iday-1;
+    funkychickens.season=0;
+    if((funkychickens.year%4)==2) {
+	if (funkychickens.day==59 && iday==29)  funkychickens.day=-1;
+    }
+    funkychickens.yday=funkychickens.day;
+/*               note: EQUAL SIGN...hopefully that fixes it */
+    while(funkychickens.day>=73) {
+	funkychickens.season++;
+	funkychickens.day-=73;
+    }
+    return funkychickens;
+}
+
+struct disc_time convert(int nday, int nyear)
+{  struct disc_time funkychickens;
+   
+   funkychickens.year = nyear+3066;
+   funkychickens.day=nday;
+   funkychickens.season=0;
+   if ((funkychickens.year%4)==2)
+     {if (funkychickens.day==59)
+	funkychickens.day=-1;
+     else if (funkychickens.day >59)
+       funkychickens.day-=1;
+    }
+   funkychickens.yday=funkychickens.day;
+   while (funkychickens.day>=73)
+     { funkychickens.season++;
+       funkychickens.day-=73;
+     }
+   return funkychickens;
+  
+ }
+
+#ifdef KILL_BOB
+
+/* Code for counting down to X-Day, X-Day being Cfn 40, 3164 
+ *
+ * After `X-Day' passed without incident, the CoSG declared that it had 
+ * got the year upside down --- X-Day is actually in 8661 AD rather than 
+ * 1998 AD.
+ *
+ * Thus, the True X-Day is Cfn 40, 9827.
+ *
+ */
+
+int xday_countdown(int yday, int year) {
+    int r=(185-yday)+(((yday<59)&&(leapp(year)))?1:0);
+    while(year<9827) r+=(leapp(++year)?366:365);
+    while(year>9827) r-=(leapp(year--)?366:365);
+    return r;
+}
+
+#endif
diff --git a/port/file2c/.gitignore b/port/file2c/.gitignore
new file mode 100644
index 00000000..aafb358f
--- /dev/null
+++ b/port/file2c/.gitignore
@@ -0,0 +1 @@
+file2c
diff --git a/port/file2c/Makefile b/port/file2c/Makefile
new file mode 100644
index 00000000..09f6b5d0
--- /dev/null
+++ b/port/file2c/Makefile
@@ -0,0 +1,15 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+file2c:
+
+clean:
+	rm -f file2c
+
+install: file2c file2c.1
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+	install file2c ${PREFIX}/bin
+	install -m 644 file2c.1 ${MANDIR}/man1
+
+uninstall:
+	rm -f ${PREFIX}/bin/file2c ${MANDIR}/man1/file2c.1
diff --git a/port/file2c/file2c.1 b/port/file2c/file2c.1
new file mode 100644
index 00000000..fe1fe5e7
--- /dev/null
+++ b/port/file2c/file2c.1
@@ -0,0 +1,75 @@
+.\"----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <phk@FreeBSD.org> wrote this file.  As long as you retain this notice, you
+.\" can do whatever you want with this file. If we meet some day, and you think
+.\" this stuff is worth it, you can buy me a beer in return.  Poul-Henning Kamp
+.\" ---------------------------------------------------------------------------
+.\"
+.\" $FreeBSD: releng/11.2/usr.bin/file2c/file2c.1 173197 2007-10-30 17:49:00Z ru $
+.\"
+.Dd March 22, 2007
+.Dt FILE2C 1
+.Os
+.Sh NAME
+.Nm file2c
+.Nd convert file to c-source
+.Sh SYNOPSIS
+.Nm
+.Op Fl sx
+.Op Fl n Ar count
+.Op Ar prefix Op Ar suffix
+.Sh DESCRIPTION
+The
+.Nm
+utility reads a file from stdin and writes it to stdout, converting each
+byte to its decimal or hexadecimal representation on the fly.
+The byte values are separated by a comma.
+This also means that the last byte value is not followed by a comma.
+By default the byte values are printed in decimal, but when the
+.Fl x
+option is given, the values will be printed in hexadecimal.
+When
+.Fl s
+option is given, each line is printed with a leading tab and each comma is
+followed by a space except for the last one on the line.
+.Pp
+If more than 70 characters are printed on the same line, that line is
+ended and the output continues on the next line.
+With the
+.Fl n
+option this can be made to happen after the specified number of
+byte values have been printed.
+The length of the line will not be considered anymore.
+To have all the byte values printed on the same line, give the
+.Fl n
+option a negative number.
+.Pp
+A prefix and suffix strings can be printed before and after the byte values
+(resp.)
+If a suffix is to be printed, a prefix must also be specified.
+The first non-option word is the prefix, which may optionally be followed
+by a word that is to be used as the suffix.
+.Pp
+This program is typically used to embed binary files into C source files.
+The prefix is used to define an array type and the suffix is used to end
+the C statement.
+The
+.Fl n , s
+and
+.Fl x
+options are useful when the binary data represents a bitmap and the output
+needs to remain readable and/or editable.
+Fonts, for example, are a good example of this.
+.Sh EXAMPLES
+The command:
+.Bd -literal -offset indent
+date | file2c 'const char date[] = {' ',0};'
+.Ed
+.Pp
+will produce:
+.Bd -literal -offset indent
+const char date[] = {
+83,97,116,32,74,97,110,32,50,56,32,49,54,58,50,56,58,48,53,
+32,80,83,84,32,49,57,57,53,10
+,0};
+.Ed
diff --git a/port/file2c/file2c.c b/port/file2c/file2c.c
new file mode 100644
index 00000000..cff7f602
--- /dev/null
+++ b/port/file2c/file2c.c
@@ -0,0 +1,92 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: releng/11.2/usr.bin/file2c/file2c.c 200462 2009-12-13 03:14:06Z delphij $");
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void
+usage(void)
+{
+
+	fprintf(stderr, "usage: %s [-sx] [-n count] [prefix [suffix]]\n",
+	    "file2c");
+	exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int c, count, linepos, maxcount, pretty, radix;
+
+	maxcount = 0;
+	pretty = 0;
+	radix = 10;
+	while ((c = getopt(argc, argv, "n:sx")) != -1) {
+		switch (c) {
+		case 'n':	/* Max. number of bytes per line. */
+			maxcount = strtol(optarg, NULL, 10);
+			break;
+		case 's':	/* Be more style(9) comliant. */
+			pretty = 1;
+			break;
+		case 'x':	/* Print hexadecimal numbers. */
+			radix = 16;
+			break;
+		case '?':
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 0)
+		printf("%s\n", argv[0]);
+	count = linepos = 0;
+	while((c = getchar()) != EOF) {
+		if (count) {
+			putchar(',');
+			linepos++;
+		}
+		if ((maxcount == 0 && linepos > 70) ||
+		    (maxcount > 0 && count >= maxcount)) {
+			putchar('\n');
+			count = linepos = 0;
+		}
+		if (pretty) {
+			if (count) {
+				putchar(' ');
+				linepos++;
+			} else {
+				putchar('\t');
+				linepos += 8;
+			}
+		}
+		switch (radix) {
+		case 10:
+			linepos += printf("%d", c);
+			break;
+		case 16:
+			linepos += printf("0x%02x", c);
+			break;
+		default:
+			abort();
+		}
+		count++;
+	}
+	putchar('\n');
+	if (argc > 1)
+		printf("%s\n", argv[1]);
+	return (0);
+}
diff --git a/prune.sh b/prune.sh
new file mode 100644
index 00000000..fabe865f
--- /dev/null
+++ b/prune.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+find -L ~/.config ~/.local -type l -lname "${PWD}/*" | while read -r link; do
+	echo "$link"
+	rm "$link"
+done
diff --git a/txt/.notemap b/txt/.notemap
new file mode 100644
index 00000000..acfe44ab
--- /dev/null
+++ b/txt/.notemap
@@ -0,0 +1,3 @@
+5347bbff-b124-432a-a67e-5d90fa34d0d4 books.txt
+9efa92ef-a8c0-4fa6-8a2a-13315992162b music.txt
+2d3c58f2-a193-4ff5-b79f-fd430f80eb64 shows.txt
diff --git a/txt/books.txt b/txt/books.txt
new file mode 100644
index 00000000..7ebae70c
--- /dev/null
+++ b/txt/books.txt
@@ -0,0 +1,171 @@
+[ 2023 ]
+
+  7. ★★☆ Alix E. Harrow, Starling House
+  6. ★★☆ Alix E. Harrow, A Mirror Mended
+  5. ★★★ Alix E. Harrow, A Spindle Splintered
+  4. ★★☆ Alyson Greaves, The Sisters of Dorley (ch. 1-15)
+  3. ★☆☆ Nnedi Okorafor, Noor
+  2. ★★☆ Nnedi Okorafor, Remote Control
+  1. ★★☆ Becky Chambers, A Prayer for the Crown-Shy
+
+[ 2022 ]
+
+ 15. ★★★ Becky Chambers, The Long Way To a Small Angry Planet
+ 14. ★★★ ed. Tristan Taormino, Take Me There
+ 13. ★★★★ Becky Chambers, A Closed and Common Orbit
+ 12. ★★☆ Sybil Lamb, I've Got a Time Bomb
+ 11. ☆☆☆ Ruth Ozeki, The Book of Form and Emptiness
+ 10. ★☆☆ Sally Rooney, Conversations With Friends
+  9. ★☆☆ Sally Rooney, Normal People
+  8. ★★★ Andrea Stewart, The Bone Shard Emperor
+  7. ★★☆ ed. Gwen Benaway, Maiden, Mother, Crone
+  6. ★★★ Andrea Stewart, The Bone Shard Daughter
+  5. ★☆☆ Madeline Miller, Circe
+  4. ★★★ Natalie Zina Walschots, Hench
+  3. ★★★ V. E. Schwab, The Invisible Life of Addie LaRue
+  2. ★★☆ Sayaka Murata, Convenience Store Woman
+  1. ★★★★ Becky Chambers, The Galaxy, and the Ground Within
+
+[ 2021 ]
+
+ 26. ★★☆ Charlie Jane Anders, The City in the Middle of the Night
+ 25. ★★☆ Charlie Jane Anders, Six Months, Three Days, Five Others
+ 24. ★★☆ Rivers Solomon, Sorrowland
+ 23. ★★★ Becky Chambers, A Psalm for the Wild-Built
+ 22. ★★★ Susanna Clarke, Piranesi
+ 21. ★☆☆ Kentaro Miura, Berserk, vols. 1–3
+ 20. ★☆☆ Ursula Le Guin, A Wizard of Earthsea
+ 19. ★★☆ Sayaka Murata, Earthlings
+ 18. ★★☆ J. R. R. Tolkien, The Hobbit
+ 17. ★★☆ Octavia E. Butler, Clay's Ark
+ 16. ★★★ Arkady Martine, A Desolation Called Peace
+ 15. ★★☆ Martha Wells, Fugitive Telemetry
+ 14. ★★☆ ed. Ann & Jeff VanderMeer, Sisters of the Revolution
+ 13. ★☆☆ Margaret Atwood, Oryx and Crake
+ 12. ★★☆ Margaret Atwood, The Year of the Flood
+ 11. ★★☆ Charlie Jane Anders, All the Birds in the Sky
+ 10. ★☆☆ Margaret Atwood, The Penelopiad
+  9. ★★★ Alix E. Harrow, The Once and Future Witches
+  8. ★★☆ Mary Robinette Kowal, The Relentless Moon
+  7. ★☆☆ Connie Willis, Bellwether
+  6. ★★☆ Carmen Maria Machado, Her Body and Other Parties
+  5. ★★☆ N. K. Jemisin, The City We Became
+  4. ★★★ Martha Wells, Network Effect
+  3. ★★☆ Nnedi Okorafor, Binti
+  2. ★★☆ Kai Cheng Thom, I Hope We Choose Love
+  1. ★★☆ Tade Thompson, The Rosewater Redemption
+
+[ 2020 ]
+
+ 19. ★★★ N. K. Jemisin, The Awakened Kingdom
+ 18. ★★☆ N. K. Jemisin, The Kingdom of Gods
+ 17. ★★☆ N. K. Jemisin, The Broken Kingdoms
+ 16. ★★☆ Ann Leckie, Ancillary Justice
+ 15. ★★☆ Madeline Miller, The Song of Achilles
+ 14. ★★☆ N. K. Jemisin, The Hundred Thousand Kingdoms
+ 13. ★★☆ Annalee Newitz, Autonomous
+ 12. ★☆☆ Tade Thompson, The Rosewater Insurrection
+ 11. ★★☆ Alix E. Harrow, The Ten Thousand Doors of January
+ 10. ★★☆ N. K. Jemisin, The Stone Sky
+  9. ★★★ Kai Cheng Thom, Fierce Femmes and Notorious Liars
+  8. ★☆☆ Amal El-Mohtar & Max Gladstone, This Is How You Lose the Time War
+  7. ★★★ N. K. Jemisin, The Obelisk Gate
+  6. ★★★ Becky Chambers, To Be Taught, If Fortunate
+  5. ★★★ Annalee Newitz, The Future of Another Timeline
+  4. ★★☆ Rivers Solomon, The Deep
+  3. ★★☆ ed. Hope Nicholson, Love Beyond Body, Space & Time
+  2. ★★☆ Tade Thompson, Rosewater
+  1. ★★★ Meg Elison, The Book of Flora
+
+[ 2019 ]
+
+ 42. ★★★ Meg Elison, The Book of Etta
+ 41. ★★☆ Martha Wells, Exit Strategy
+ 40. ★★☆ Martha Wells, Artificial Condition
+ 39. ★★☆ N. K. Jemisin, The Fifth Season
+ 38. ★★☆ Martha Wells, Rogue Protocol
+ 37. ★★☆ Yoon Ha Lee, Ninefox Gambit
+ 36. ★★★ Rebecca Makkai, The Great Believers
+ 35. ★★☆ Meg Elison, The Book of the Unnamed Midwife
+ 34. ★★☆ Martha Wells, All Systems Red
+ 33. ★★☆ Nnedi Okorafor, The Book of Phoenix
+ 32. ★★☆ JY Yang, The Red Threads of Fortune
+ 31. ★★☆ JY Yang, The Black Tides of Heaven
+ 30. ★☆☆ Rebecca Roanhorse, Trail of Lightning
+ 29. ★☆☆ Jo Walton, The Just City
+ 28. ★★★ Arkady Martine, A Memory Called Empire
+ 27. ★★☆ Mary Robinette Kowal, The Fated Sky
+ 26. ★★★ Becky Chambers, Record of a Spaceborn Few
+ 25. ★★☆ Mary Robinette Kowal, The Calculating Stars
+ 24. ★★☆ Octavia E. Butler, Imago
+ 23. ★★☆ Octavia E. Butler, Kindred
+ 22. ★★☆ Octavia E. Butler, Adulthood Rites
+ 21. ★★★ Octavia E. Butler, Wild Seed
+ 20. ★★☆ Octavia E. Butler, Parable of the Talents
+ 19. ★★☆ Jeff VanderMeer, Acceptance
+ 18. ★★★ Becky Chambers, A Closed and Common Orbit
+ 17. ★★☆ Octavia E. Butler, Dawn
+ 16. ★★☆ Jeff VanderMeer, Authority
+ 15. ★★☆ Octavia E. Butler, Parable of the Sower
+ 14. ★★☆ C. A. Higgins, Lightless
+ 13. ★☆☆ Alfred Bester, The Demolished Man
+ 12. ★★★ Karin Tidbeck, Amatka
+ 11. ★☆☆ Catherynne M. Valente, Space Opera
+ 10. ★★☆ Rivers Solomon, An Unkindness of Ghosts
+  9. ★★★ Becky Chambers, The Long Way to a Small Angry Planet
+  8. ★★☆ Emily St. John Mandel, Station Eleven
+  7. ★★★ Douglas Adams, Dirk Gently's Holistic Detective Agency
+  6. ★★★ Octavia E. Butler, Fledgling
+  5. ★★★ Jeff VanderMeer, Annihilation
+  4. ★★☆ Alfred Bester, The Stars My Destination
+  3. ★☆☆ Samuel R. Delany, The Einstein Intersection
+  2. ★★★ Octavia E. Butler, Mind of My Mind
+  1. ★☆☆ Robert A. Heinlein, Waldo & Magic, Inc.
+
+[ 2018 ]
+
+ 25. ★★★ Douglas Adams, The Long Dark Tea-Time of the Soul
+ 24. ★★☆ Khaled Hosseini, A Thousand Splendid Suns
+ 23. ★★☆ Nathan Altice, I Am Error
+ 22. ★★★ Ruth Ozeki, A Tale for the Time Being
+ 21. ★☆☆ Arthur C. Clarke, Earthlight
+ 20. ★★★ Terry Pratchett & Neil Gaiman, Good Omens
+ 19. ★☆☆ Robert A. Heinlein, Starship Troopers
+ 18. ★★☆ Liu Cixin, The Three-Body Problem
+ 17. ★★☆ Robert A. Heinlein, Revolt in 2100
+ 16. ★★☆ ed. Robert A. Heinlein, Tomorrow, the Stars
+ 15. ★★☆ Robert A. Heinlein, Assignment in Eternity
+ 14. ★☆☆ Robert A. Heinlein, The Rolling Stones
+ 13. ★★☆ Robert A. Heinlein, Starman Jones
+ 12. ★☆☆ Robert A. Heinlein, Space Cadet
+ 11. ★☆☆ Robert A. Heinlein, Farmer in the Sky
+ 10. ★★☆ Robert A. Heinlein, Between Planets
+  9. ★★☆ Robert A. Heinlein, Red Planet
+  8. ★☆☆ Robert A. Heinlein, Rocket Ship Galileo
+  7. ★☆☆ Robert A. Heinlein, Sixth Column
+  6. ★☆☆ Robert A. Heinlein, Beyond This Horizon
+  5. ★★☆ Robert A. Heinlein, Orphans of the Sky
+  4. ★★☆ Tracy Kidder, The Soul of a New Machine
+  3. ★★★ James Alan Gardner, Ascending
+  2. ★★★ Anne Frank, The Diary of a Young Girl
+  1. ★★★ Harper Lee, Go Set a Watchman
+
+[ 2017 ]
+
+  1. ★★★ James Alan Gardner, Expendable
+
+[ ... ]
+
+ • ★★★ Harper Lee, To Kill a Mockingbird
+ • ★★★ Douglas Adams, The Hitchhiker's Guide to the Galaxy
+ • ★★★ Douglas Adams, The Restaurant at the End of the Universe
+ • ★★☆ Douglas Adams, Life, the Universe and Everything
+ • ★★★ Douglas Adams, So Long, and Thanks for All the Fish
+ • ★★☆ Douglas Adams, Mostly Harmless
+ • ★☆☆ Eoin Colfer, And Another Thing...
+ • ★★★ Margaret Atwood, The Handmaid's Tale
+ • ★★☆ Kazuo Ishiguro, Never Let Me Go
+ • ★★★ Philip Pullman, The Goldan Compass
+ • ★★★ Philip Pullman, The Subtle Knife
+ • ★★★ Philip Pullman, The Amber Spyglass
+ • ★★★ William Goldman, The Princess Bride
diff --git a/txt/diminishing-shine.txt b/txt/diminishing-shine.txt
new file mode 100644
index 00000000..f51faf20
--- /dev/null
+++ b/txt/diminishing-shine.txt
@@ -0,0 +1,32 @@
+you gotta get yourself a cheap plair--
+you gotta get yourself a cheap pair of plastic sunglasses
+or a really cheap pair of plastic wraparounds
+you can get em just down the street
+it's four bucks for the pair
+you should stock up
+you should buy three or four pairs
+really, no one ever fucks with you
+you put em on
+and no one fucks with you
+and they're cheap
+it's better than--
+better than a wig
+or a really stupid haircut
+or a nose ring
+
+you get afraid on the bus?
+when you ever get--
+you ever get scared on the bus?
+or the subway?
+you ever get afraid, and--
+
+we were all really high
+and I was sitting on the couch
+we were all sitting on the couch
+we were all really high
+the TV was on and no one was talking and no one was saying a word
+and no one had said a word for a long, long time
+everyone was just staring at the TV set
+and I got the fear
+I got the fear really bad
+I remember thinking, this is it, I'm going to die
diff --git a/txt/music.txt b/txt/music.txt
new file mode 100644
index 00000000..efcd8fbe
--- /dev/null
+++ b/txt/music.txt
@@ -0,0 +1,294 @@
+The Shaggs — My Pal Foot Foot
+<https://youtu.be/k5T2kaFiFgg>
+
+Philip Glass — Einstein on the Beach Knee Play 1
+<https://youtu.be/jeEobpQMgD4>
+
+osno1 — SAUSAGE (osno1 RIP vine remix)
+<https://osno1.bandcamp.com/track/sausage-osno1-rip-vine-remix>
+
+Feist — I Feel It All
+<https://youtu.be/g-1Gb2rxzlk>
+
+The Blow — "Come On Petunia"
+<https://youtu.be/MO1HSfzK1Ns>
+
+Black Country, New Road — Sunglasses
+<https://youtu.be/UAZhzi9cpkc>
+
+Mitski, Xiu Xiu — Between the Breaths
+<https://youtu.be/HnhTkFl5Imw>
+
+Black Dresses — CREEP U
+<https://youtu.be/w9RSZmltcVI>
+
+BACKXWASH — DONT COME TO THE WOODS
+<https://youtu.be/7ZcJsWyGbOw>
+
+William Bonney — See Ya Later
+<https://youtu.be/-uCUlvUlr9s>
+
+Four Tet — Hands
+<https://youtu.be/ojZoQbRt1tc>
+
+Buffy Sainte-Marie — Darling Don't Cry
+
+LINGUA IGNOTA — DO YOU DOUBT ME TRAITOR
+<https://youtu.be/M1ZweG__q-w>
+
+Lizzo — Truth Hurts
+<https://youtu.be/P00HMxdsVZI>
+
+Hobo Johnson and The Lovemakers — Tiny Desk Concert
+<https://youtu.be/A8a2EosJIbM>
+
+Poppy — Concrete
+<https://youtu.be/WwoGhpYdebQ>
+
+Kim Petras — Do Me
+<https://youtu.be/LShK0Yhd964>
+
+Kim Petras — Heart to Break
+<https://youtu.be/5CPeHQHAQyo>
+
+Anomie — Avorter n'est pas tuer
+<https://youtu.be/W1iGXRDeZf4>
+
+Zhaoze — Birds Contending
+<https://zhaoze.bandcamp.com/album/birds-contending>
+
+Colin Stetson — Reborn
+<https://youtu.be/MVnSFj6XQZY>
+
+Holly Herndon — Frontier
+<https://youtu.be/rvNqNgHAEys>
+
+Bleachers — Tiny Desk Concert
+<https://youtu.be/QCtkkX2f18M>
+
+Girlpool — Tiny Desk Concert
+<https://youtu.be/VNM8Tg9pvDU>
+
+Daughters — You Won't Get What You Want
+<https://daughters.bandcamp.com/album/you-wont-get-what-you-want>
+
+La Dispute — FULTON STREET I
+<https://ladispute.bandcamp.com/track/fulton-street-i>
+
+KASHIWA Daisuke — Stella
+<https://youtu.be/ei7cdynwRMA>
+
+Jeff Wayne — The Eve of the War
+<https://youtu.be/6YwFvmnbj3E>
+
+Julia Holter — I Shall Love 2
+<https://youtu.be/k5uwPaCvbhA>
+
+Chromatics — Running Up That Hill
+<https://youtu.be/Mgv88ZLi6LY>
+
+Low — Double Negative
+<https://lowtheband.bandcamp.com/album/double-negative>
+
+Rival Consoles — Helios
+<https://youtu.be/T8n-XC_2a-k>
+
+Tiffany — I Think We're Alone Now
+<https://youtu.be/w6Q3mHyzn78>
+
+Pulp — Common People
+<https://youtu.be/yuTMWgOduFM>
+
+XTC — Making Plans for Nigel
+<https://youtu.be/gZjZBCZWxpg>
+
+New Order — Temptation
+<https://youtu.be/xxDv_RTdLQo>
+
+Street Sects — In for a World of Hurt
+<https://streetsects.bandcamp.com/track/in-for-a-world-of-hurt>
+
+New Order — Temptation
+<https://youtu.be/xxDv_RTdLQo>
+
+Blondie — Heart of Glass
+<https://youtu.be/fWPhhlKHM80>
+
+Suuns — Watch You, Watch Me
+<https://suuns.bandcamp.com/track/watch-you-watch-me>
+
+Neckbeard Deathcamp — White Nationalism is for Basement Dwelling Losers
+<https://neckbearddeathcamp.bandcamp.com/album/white-nationalism-is-for-basement-dwelling-losers>
+
+rook — shed blood
+<https://rooksfeather.bandcamp.com/album/shed-blood>
+
+SOPHIE — OIL OF EVERY PEARL'S UN-INSIDES
+<http://smarturl.it/SOPHIEALBUM>
+
+Rosetta — Utopioid
+<https://theanaesthete.bandcamp.com/album/utopioid>
+
+Desire — Under Your Spell
+<https://youtu.be/9K7rmxjk5RQ>
+
+Broken Social Scene — Anthems for a Seventeen-Year Old Girl
+<https://youtu.be/DDqNL0js0iU>
+
+Shinsei Kamattechan — Yuugure no tori
+<https://youtu.be/sUW4dDWiz-A>
+
+Petite Meller — The Flute
+<https://youtu.be/BLwgeV7dXOI>
+
+King Gizzard and the Lizard Wizard — Flying Microtonal Banana
+<https://youtu.be/D0BsgJxw208>
+
+rook&nomie — DAYDREAM
+<https://youtu.be/00TdaTffFeY>
+
+Spiritualized — Ladies and Gentlemen We Are Floating in Space
+<https://youtu.be/p47V3w4m1yg>
+
+Street Sects — End Position
+<https://streetsects.bandcamp.com/album/end-position-2>
+
+Converge — The Dusk in Us
+<https://convergecult.bandcamp.com/album/the-dusk-in-us>
+
+St. Vincent — MASSEDUCTION
+
+Godspeed You! Black Emperor — Undoing a Luciferian Towers
+<https://godspeedyoublackemperor.bandcamp.com/track/undoing-a-luciferian-towers>
+
+Florist — Tiny Desk Concert
+<https://youtu.be/WbyyxIZ02Zs>
+
+CHVRCHES — Tiny Desk Concert
+<https://youtu.be/haunJARHPm4>
+
+Aurora — Tiny Desk Concert
+<https://youtu.be/evBgLWQwAFA>
+
+Joy Division — Atmosphere
+<https://youtu.be/1EdUjlawLJM>
+
+Underworld — Born Slippy
+<https://youtu.be/iTFrCbQGyvM>
+
+Converge — I Can Tell You About Pain
+<https://convergecult.bandcamp.com/album/i-can-tell-you-about-pain>
+
+CocoRosie — Lost Girls
+<https://youtu.be/aRa-SlftLQo>
+
+FAUVE — Blizzard
+<https://youtu.be/HMpmedi_pH4>
+
+FAUVE — Nuits Fauves
+<https://youtu.be/cwaAppsy5yo>
+
+Sarin — House of Leaves
+<https://sarin.bandcamp.com/track/house-of-leaves-split-w-guiltfeeder>
+
+Chromatics — Shadow
+<https://youtu.be/IGUboLZx3Tk>
+
+Arcade Fire — Creature Comfort
+<https://youtu.be/xzwicesJQ7E>
+
+Arcade Fire — Everything Now
+<https://youtu.be/zC30BYR3CUk>
+
+Ed Schrader's Music Beat — Sermon
+
+Jessica Moss — Pools of Light
+
+Death Grips — Steroids (Crouching Tiger Hidden Gabber Megamix)
+<https://youtu.be/JUTKTk60aGk>
+
+Saltland — A Common Truth
+
+Colin Stetson — All This I Do for Glory
+<https://colinstetson.bandcamp.com/album/all-this-i-do-for-glory>
+
+Xiu Xiu — Forget
+<https://youtu.be/ywRzfwA75pY>
+
+Arca — Arca
+
+Joni Void — Dissociation (Kyla's Song)
+<http://cstrecords.com/cst125/>
+
+Do Make Say Think — Bound and Boundless
+<http://cstrecords.com/cst120/>
+
+The Body Lovers / The Body Haters
+
+Unwound — Leaves Turn Inside You
+
+Xiu Xiu — Fabulous Muscles
+
+Xiu Xiu — A Promise
+
+Saltland — Light of Mercy
+<http://cstrecords.com/saltland-releases-new-single-light-of-mercy/>
+
+Jessica Moss — Glaciers I (Pt I)
+<http://cstrecords.com/cst124/>
+
+BNNY RBBT — Big World
+<http://www.bnnyrbbt.fans>
+
+Woman is the Earth — Depths
+<https://womanistheearth.bandcamp.com/album/depths>
+
+Saltland — I Only Wish This For You
+<http://cstrecords.com/cst123/>
+
+Sun Kil Moon — Benji
+<https://youtu.be/UtndQzCUEY4>
+
+Neil Cicierega — Mouth Moods
+<http://www.neilcic.com/mouthmoods/>
+
+Those Who Walk Away — "First Degraded Rhythm" + "First Partially Recollected Conversation"
+<http://cstrecords.com/cst122/>
+
+Dan Smith — Some Tunes
+<https://thedancemyth.bandcamp.com/album/some-tunes>
+
+Avec le soleil sortant de sa bouche — Pas pire pop, I Love You So Much
+
+Arcade Fire — I Give You Power
+<https://youtu.be/f6jma9VQEls>
+
+Xiu Xiu — Jenny GoGo
+<https://youtu.be/WMT6MsA3ut8>
+
+Kero Kero Bonito — Fish Bowl
+<https://youtu.be/FY-CjOJCjJE>
+
+Avec le soleil sortant de sa bouche — Alizé et Margaret D. Midi moins le quart. Sur la plage, un palmier ensanglanté
+<http://cstrecords.com/cst121/>
+
+G.L.O.S.S. — Trans Day of Revenge
+<https://girlslivingoutsidesocietysshit.bandcamp.com/releases>
+
+Joy Division — Love Will Tear Us Apart
+<https://youtu.be/zuuObGsB0No>
+
+Xiu Xiu — Honeysuckle
+<https://youtu.be/hYKGR8Er4vM>
+
+Xiu Xiu — Ceremony
+<https://youtu.be/95ms8A2XJY0>
+
+Xiu Xiu — I Luv the Valley Oh
+<https://youtu.be/dztURk0_DOg>
+
+Porter Robinson & Madeon — Shelter
+<https://youtu.be/fzQ6gRAEoy0>
+
+Julien Baker — Tiny Desk Concert
+<https://youtu.be/tADWPTqR_4A>
diff --git a/txt/shows.txt b/txt/shows.txt
new file mode 100644
index 00000000..2abacf5b
--- /dev/null
+++ b/txt/shows.txt
@@ -0,0 +1,34 @@
+2022-12-18 (SAT) LINGUA IGNOTA
+2022-06-04 (MAI) Honeydrip, MAGELLA, BACKXWASH
+2020-01-23 (La Sala Rossa) Secondsight, BIG|BRAVE
+2019-12-10 (Casa del Popolo) meth, Street Sects
+2019-06-22 (Casa del Popolo) Persons Unknown, Palissade, Police des Moeurs, Blu Anxiety
+2019-06-22 (Bar Le Ritz) Cloakroom, Pelican
+2019-06-21 (La Vitrola) Leash Aggression, CPU Rave, Vitex, Pinocchio
+2019-06-20 (Casa del Popolo) Beep Test, Liar//Lier, Wetware
+2019-06-18 (Casa del Popolo) Nomadic War Machine; it foot, it ears; Wendy Eisenberg
+2019-06-17 (La Sala Rossa) SaskPWR, Lubomyr Melnyk, Architek Percussion (Nicole Lizée)
+2019-06-08 (La Sotterenea) Eliza Kavtion, Beep Test, Dri Hiev
+2019-05-03 (Turbo Haüs) Issfenn
+2019-04-21 (Bar Le Ritz PDB) Claud, Hatchie, Girlpool
+2018-11-14 (Turbo Haus) Juss, Street Sects
+2018-07-26 (Theatre Fairmount) Uniform, Deafheaven
+2018-06-14 (Casa del Popolo) Markus Floats, Mark Lowe, Carodiaro, Eliza Kavtion
+2018-04-26 (Bar Le Ritz PDB) Strega, Show of Bedlam, Rosetta
+2018-04-13 (La Vitrola) Eliza Kavtion, Lungbutter, Wrekmeister Harmonies
+2018-03-08 (Brasserie Beaubien) Clayborne, Nightwitches, Aseethe, Vile Creature
+2018-02-08 (Bar Le Ritz PDB) KGD, Efrim Manuel Menuck
+2017-09-30 (La Sala Rossa) Truster, Lungbutter, Big Brave
+2017-09-19 (La Vitrola) Saccharine, Xiu Xiu
+2017-06-01 (Casa del Popolo) Genevieve Heistek, Saltland
+2017-05-27 (l’Escogriffe) Sough, Ikaray, C H R I S T
+2017-05-14 (Bar Le Ritz PDB) Mutter’d, Aim Low, Wrekmeister Harmonies
+2016-09-25 (La Sala Rossa) Cloud Rat, Wolves in the Throne Room
+2016-09-21 (Theatre Paradoxe) Godspeed You! Black Emperor
+2016-04-08 (La Sala Rossa) Caro Diaro, Wreckage With Stick, Ought
+2015-08-13 (La Vitrola) Gutser, Chemical Way, Saul Hittner, In the Name of Havoc
+2015-06-10 (La Sala Rossa) Ryan Sawyer, Colin Stetson & Sarah Neufeld
+2015-06-09 (La Sala Rossa) The Mile End Ladies String Auxiliary, Christof Migone
+2015-06-08 (Casa del Popolo) C H R I S T, Jessica Moss, Big Brave
+2014-01-19 (Metropolis) Godspeed You! Black Emperor
+2014-01-18 (l’Olympia) Elf Power, Neutral Milk Hotel
diff --git a/txt/trouble-at-jinx-hotel.txt b/txt/trouble-at-jinx-hotel.txt
new file mode 100644
index 00000000..4db28dd6
--- /dev/null
+++ b/txt/trouble-at-jinx-hotel.txt
@@ -0,0 +1,236 @@
+mollasses
+
+...à l'hôtel jinx...
+
+thierry amar; flüffy erskine; norsola johnson; kate lawrence;
+jennifer ménard; mike moya; sam shalabi; scott lregrande chernoff...
+chris brokaw; bruce cawdron; david michael curry; lisa gamble; efrim
+menuck; michel meunier; js truchy; thalia zedek... à l'hôtel jinx.
+
+lucky 13: siren's song. la la la, amerika. Saint Christopher's
+blues.  sign of judgment. Lynn Canyon wedding song. coda. You Can't
+Win. trouble in mind. las niñas. Miss Peach's Pawnshop. buffaloed
+at Wounded Knee/the weeping winds. no love lost. songs from the
+basement.
+
+trouble at jinx hotel. these cursed recordings began in late winter
+2003 at the hotel2tango in montréal. it was a bitter sunday in
+march. the world was on the brink of war in old mesopotamia, and
+we were all of us sick with the influenza. headaches. fevers. wrecked
+guts and water on the lungs. coughing like crazy. everyone with a
+base case of the shakes.  the hotel wass cold and its ghosts were
+unhappy. electricity was being weird. lights flickered and all the
+microphones crackled with live current. little blue sparks dancing
+through the studio rooms. amplifiers were picking up strange radio
+frequencies and the piano, which couldn't hold a tune to save its
+own life, was behaving like a cantankerous old man. our cats and
+dogs were nervous and excited, a-jitter the way animals get whenever
+catastrope looms. we started with a 16-track tape machine, three
+spools of 1/4" analog audio tape, and a lucky thirteen little songs
+hanging in our heads. but when the 16-track busted and broke down
+a few days later, we moved to 2" tape on a 24-track machine and
+began anew with our fingers crossed. then the 24-track showed the
+first symptoms of its own terrible illness. so we did what we could
+with digital audio tape while our trusty friend and technician,
+garfield lamb, nursed the equipment back to some semblance of health.
+then we transfered the digital recordings to 2" tape and went to
+work again...  waiting to see what the gods would do next. they
+toyed with us for a while before unleashing their full fury: the
+24-track, seizing mid-song one evening, ate a reel of tape and sent
+it spewing around the studio in little pieces, a scene as cruel as
+confetti at a funeral.  garfield somehow managed to splice the tape
+together again. but our nerves were now in tatters too, scattered
+all over the hotel floor. when we finally finished tracking this
+music in the summer of 2003, the cats and dogs didn't look any less
+worried than when we'd begun. of course, they sensed the disaster
+we would soon see for ourselves: as we started mixing the very next
+night, a faulty lamp at the studio short-circuited and the tape
+machine caught on fire. a freak electrical accident. there was an
+asterisk of light; then a dragon-tail of smoke coiling across the
+room; then there was a darkness; and then the 24-track quietly died.
+the animals, cowering in corners, whined, *why didn't you fucking
+fools jump ship at the first sign of the storm*? that's when we
+drifted away from the jinxed hotel... floating until we found safe
+harbour in a studio we could borrow for a few days - one whose own
+resident dogs, zoro and ciska, greeted us with idiot grins and the
+contented looks of drunken sailors on calm seas. mixing was finished
+in a kind of delirious distress at MixArt in notre-dame-de-grace,
+montréal, autumn 2003. these lucky thirteen. dieu merci. slgc. *new
+year's eve* 2003.
+
+siren's song. instrumental.
+
+la la la, amerika. amerika is crawling with cops tonight as we put
+our fists through the windows of the world i know you're broken and
+you're down at the mouth but you're pouting like a punished little
+girl when darkness calls night falls oh mother where are the children
+you've orphaned all over the Earth we're frightened and sickened
+and poverty-stricken and waiting for you to send word when darkness
+calls the sky falls we're tired mother and we're poor we're wretched
+at your teeming shore we're homeless and tossed by the storm looking
+for the light at your golden door when darkness calls night falls.
+
+flüffy: marching drum, percussion & choir; norsola: cello & voice;
+kate: violin; jennifer: voice; moya: Crumar organ; dmc: viola &
+choir; gamble: choir; efrim: choir; js: upright bass; thalia: voice;
+scott: acoustic guitars & vocals; scott recorded the air-raid siren
+and bombs on the nightly news when baghdad was attacked by the
+united states and great britain in march 2003; words & music by
+scott chernoff, as stolen from the inscription on the statue of
+liberty: *give me your tired your poor your huddled masses yearning
+to breathe the wretched refuse of your teeming shore send these the
+homeless tempest-tossed to me i lift my lamp beside the golden
+door*.
+
+Saint Christopher's blues. the locusts are in the jimson Shit Creek
+is on the rise and you're out wrecking eternity this town looks
+like a prison in September's crimson sky when i should be loving
+you perfectly you're such a pretty little mess with your Saint
+Christopher blues and that doomed little look in your eye the weight
+of the world rests all upon your tiny shoes but June why are you
+so terrified give me the locust and the flood i'm a Fool for Love
+but i'm kneeling in Shit Creek tonight.
+
+thierry: upright & electric bass; flüffy: marching drum; kate:
+violin; jennifer: voice; moya: piano & Crumar organ; bruce: drums;
+gramble: scrap metal; scott: electric guitar & vocals. words & music
+by scott chernoff.
+
+sign of judgment. yes sign of judgment yes sign of judgment yes
+sign of judgment time ain't long i don't like old Satan none of his
+tempting charms cheats you at your Jesus now and roll you in his
+arms yes sign of judgment yes sign of judgment yes sign of judgment
+time ain't long i don't like old Satan nothing he say or do tell
+one lie to hurt us all and two to make it true yes sign of judgment
+yes sign of judgment yes sign of judgment time ain't long.
+
+norsola: cello; jennifer: voice; efrim: electric guitar & harmonium;
+js: upright bass; thalia: voice; scott: acoustic guitar & vocals.
+words & music by kid prince moore (traditional).
+
+Lynn Canyon wedding song. well i never cared very much for the west
+except for you there who i loved the best now please take me back
+east i wrote your name in silver and green where Love and Hate Play
+with Fate and History now please take me back east oh please take
+me back east we came to the Canyon across the calm and the wild
+from Mountain to Mountain and Island to aisle i came for Abel you
+came for Cain and we danced on the tables between the cradle and
+the grave and i never cared very much for the west but i will be
+there in my Sunday best if you will wear your red wedding dress
+then please take me back east oh please take me back east.
+
+flüffy: saw; jennifer: voice; moya: piano & Crumar organ; dmc: viola
+& saw; js: upright bass & voice; scott: acoustic guitar & vocals.
+words & music by scott chernoff.
+
+coda. instrumental.
+
+flüffy: metal heating duct; michel: banjo; dog & voice drone recorded
+by julian evans on video tape at Lynn Canyon 2002; mixed by scott
+& harris newman in montréal 2003.
+
+You Can't Win. you with your railroad bones and you with your
+golden-spiked skin you with the wind in your clothes and your hat
+like a house caving in oh man You Can't Win oh man You Can't Win
+oh man You Can't Win when the Angel of Light is at the window with
+the Sanctimonious Kid when you're down and out from Sacramento on
+the New York City skids oh man You Can't Win oh man You Can't Win
+oh man You Can't Win when the East River is groaning all below the
+Williamsburg Bridge when airplanes are exploding in the skyline's
+falling limbs oh man You Can't Win oh man You Can't Win oh man You
+Can't Win when the Swans are at your throat with their busted burning
+wings when they squeal and scream and moan at every last ship that
+comes in oh man You Can't Win oh man You Can't Win oh man You Can't
+Win.
+
+flüffy: chor; norsola: choir; jennifer: voice; moya: electric guitars
+& Crumar organ; sam: oud; bruce: drums & marimba; dmc: choir;
+gramble: scrap metal & choir; efrim: electric guitar & choir; js:
+choir; thalia: choir; scott: electric & acoustic guitars, vocals.
+words & music by scott chernoff, on the road from sacramento to
+nyc. september 11 2001, while reading jack black's 1926 hobo memoir
+*You Can't Win* and listening to the music of michael gira..
+
+trouble in mind. trouble in mind i'm blue but i won't be blue all
+the way you know that sun is gonna shine on me someday i'm gonna
+lay my head on some lonesome railroad line and let that big
+motherfucking engine nullify my mind trouble in mind i'm blue but
+i won't be blue all the way you know that sun is gonna shine on me
+someday.
+
+jennifer: voice; sam: electric guitars; chris: electric guitar;
+scott: acoustic guitar & vocals. words & music traditional, as
+learned from a 1960s  recording by sam lightnin' hopkins.
+
+las niñas. instrumental.
+
+andrew mayrs recorded his baby daughter illimani crying while banging
+on a piano in kitsilano, vancouver 2003; screaming children recorded
+by scott at rue roy, montréal 2003. mixed by scott & harris newman
+in montréal 2003.
+
+Miss Peach's Pawnshop. i bought this cross on the island of St.
+Thomas because i wanted jesus on my chest i got this tattoo in New
+Orleans and this one in the Orient for my honeymoon and i got this
+scar somewhere far far away i was drunk as hell and i tried to walk
+home and my hands were broken and i found this prayer in the back
+of a magazine i know it's ripped but it came like this it came like
+this i bought this song at Miss Peach's Pawn with this old overcoat
+and a gold-plated wristwatch and i found this prayer in the back
+of a magazine i know it's ripped but it came like this it came like
+this it came like this it came like this.
+
+norsola: cello; kate: violin; jennifer: voice; moya: electric guitar,
+piano & Crumar organ; dmc: viola; js: upright bass; scott: acoustic
+guitar & vocals. words by hilary peach & scott chernoff; music by
+scott chernoff. this is a bastard adaptation of peach's poem *Tattoo*
+from her album, *Poems Only Dogs Can Hear*.
+
+buffaloed at Wounded Knee/the weeping winds. instrumental. (for
+l.p. & a.i.m. and a pox on mean-spirited sons of bitches everywhere).
+
+flüffy: saws; dmc: saws.
+
+no love lost. good-bye old friend it's time to go this is the end
+of all that we know when you worship in the house of burden i worship
+in the house of blame as you learn to let your spirit burn i yearn
+to be a flame to be a flame and there's no love no love lost as
+there is the righteous there is the wrong and there is the night
+which is betrayed by the dawn and there's no love no love lost as
+there is the sinner there is the god and there is the singer profaned
+by the song and there's no love no love lost there's no love no
+love lost.
+
+thierry: electric bass; flüffy: bowed piano & saws; jennifer: voice;
+moya: piano; michel: banjo; thalia: voice; scott: acoustic guitar,
+bowed piano & vocals. words & music by scott chernoff.
+
+songs from the basement. there's a song for hatred and a song for
+love a song for betrayal and a song for trust there's a song for
+what's fated a song for dumb luck a song for what's sacred and one
+for what's fucked there's a song which is prayed a song which is
+cussed these songs from the basement which play unto us.
+
+norsola: cello; kate: violin; jennifer: voice; sam: electric guitars;
+bruce: marimba & cymbals; dmc: viola; efrim: harmonium; garfield
+lamb: knife; scott: acoustic guitar & vocals. words & music by scott
+chernoff.  mixed by howard bilerman at the hotel... ...after the
+storm had all blown by...
+
+recorded by howard bilerman, efrim & scott at mom & pop sounds in
+the hotel2tango, mile end, montréal 2003. except where noted,
+everything was mixed by howard bilerman & molasses at MixArt in
+notre-dame-de-grace, montréal 2003. mastered by harris newman in
+little italy, montréal 2003 & 2004. all songs c&p SOCAN 2004, except
+*sign of judgment* & *trouble in mind* which are in the public
+domain. hilary peach's poem *Tattoo*, which inspired *Miss Peach's
+Pawnshop*, is c&p hilary peach 2003. all music arranged by molasses,
+design and hand-lettering by slgc ink. in tokyo. photography by dmc
+& norsola. layout by slgc ink. with assistance by sean o'hara. all
+love to those who helped: sohara & gary at alien8 recordings; howard
+bilerman; cada del popolo; don & ian at constellation; eric craven;
+julian evans; michael feuerstack; aidan girt; lea grahovac; taras
+grescoe; garfield lamb; tim mahony; andrew mayrs, vally mendoza &
+illimani mendoza-mayrs; boris & nicolas at MixArt; harris newman,
+*who is never wrong*; hilary peach; sugar & smokey; and all the
+cars and dogs... xoxo...
diff --git a/www/causal.agency/.gitignore b/www/causal.agency/.gitignore
new file mode 100644
index 00000000..b00b1c3c
--- /dev/null
+++ b/www/causal.agency/.gitignore
@@ -0,0 +1,4 @@
+index.html
+leveler.html
+scheme.css
+scheme.png
diff --git a/www/causal.agency/Makefile b/www/causal.agency/Makefile
new file mode 100644
index 00000000..8c74f8f1
--- /dev/null
+++ b/www/causal.agency/Makefile
@@ -0,0 +1,23 @@
+WEBROOT = /var/www/causal.agency
+
+GEN = index.html scheme.css scheme.png
+FILES = ${GEN} style.css alpha.html lands.html
+
+all: ${FILES}
+
+.SUFFIXES: .7 .html
+
+.7.html:
+	mandoc -T html -O style=style.css $< > $@
+
+scheme.css:
+	scheme -st > scheme.css
+
+scheme.png:
+	scheme -g > scheme.png
+
+install: ${FILES}
+	install -C -m 644 ${FILES} ${WEBROOT}
+
+clean:
+	rm -f ${GEN}
diff --git a/www/causal.agency/alpha.html b/www/causal.agency/alpha.html
new file mode 100644
index 00000000..0d83f530
--- /dev/null
+++ b/www/causal.agency/alpha.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>all 26 letters of the alphabet RANKED</title>
+<style>
+body, button { font-size: 200%; text-align: center; }
+button { margin: 1em; padding: 1ch; }
+button#shuffle { font-size: 100%; }
+</style>
+
+which letter do you like more?
+<p>
+<button id="a">A</button>
+<button id="b">B</button>
+<p>
+<details>
+<summary>current ranking</summary>
+<p>
+<span id="ranking">ABCDEFGHIJKLMNOPQRSTUVWXYZ</span>
+<p>
+<button id="shuffle">reshuffle</button>
+</details>
+
+<script>
+let buttonA = document.getElementById("a");
+let buttonB = document.getElementById("b");
+let ranking = document.getElementById("ranking");
+
+let alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
+let rand = (bound) => Math.floor(Math.random() * bound);
+function shuffle() {
+	for (let i = alpha.length - 1; i > 0; --i) {
+		let j = rand(i + 1);
+		let x = alpha[i];
+		alpha[i] = alpha[j];
+		alpha[j] = x;
+	}
+}
+if (localStorage.getItem("alpha")) {
+	alpha = localStorage.getItem("alpha").split("");
+} else {
+	shuffle();
+}
+
+let index = 0;
+let even = true;
+function choose(o) {
+	if (o == "b") {
+		let x = alpha[index];
+		alpha[index] = alpha[index + 1];
+		alpha[index + 1] = x;
+	}
+	index += 2;
+	if (index > alpha.length - 2) {
+		even = !even;
+		index = (even ? 0 : 1);
+	}
+	update();
+}
+
+document.onkeydown = function(event) {
+	if (event.key.toUpperCase() == alpha[index]) {
+		choose("a");
+	} else if (event.key.toUpperCase() == alpha[index + 1]) {
+		choose("b");
+	}
+}
+
+function update() {
+	localStorage.setItem("alpha", alpha.join(""));
+	ranking.innerText = alpha.join("");
+	let a = buttonA;
+	let b = buttonB;
+	if (rand(2)) {
+		a = buttonB;
+		b = buttonA;
+	}
+	let lc = (c) => c;
+	if (rand(2)) lc = (c) => c.toLowerCase();
+	a.innerText = lc(alpha[index]);
+	b.innerText = lc(alpha[index + 1]);
+	a.onclick = () => choose("a");
+	b.onclick = () => choose("b");
+}
+update();
+
+document.getElementById("shuffle").onclick = function() {
+	if (confirm("Are you SURE you want to throw away all your hard work?")) {
+		shuffle();
+		update();
+	}
+}
+</script>
diff --git a/www/causal.agency/index.7 b/www/causal.agency/index.7
new file mode 100644
index 00000000..d29c1a6b
--- /dev/null
+++ b/www/causal.agency/index.7
@@ -0,0 +1,74 @@
+.Dd November 28, 2023
+.Dt CAUSAL.AGENCY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm june
+.Nd computer enthusiast (she/her)
+.
+.Sh SYNOPSIS
+.Nm mail
+.Mt june@causal.agency
+.Nm
+in
+.Li #ascii.town
+on tilde.chat
+.
+.Sh DESCRIPTION
+I make mostly IRC software in C.
+I like
+.Ox
+but also the GPL.
+I just want to read books
+and try to learn to be kinder.
+When I can I'd like to talk to strangers
+and experience more magic.
+.
+.Pp
+.Lk https://git.causal.agency code
+\(em
+.Lk https://text.causal.agency words
+\(em
+.Lk /list/ mailist
+.
+.Pp
+These are some things I've done:
+.Bl -tag -width Ds
+.It Lk https://git.causal.agency/pounce/about pounce
+a multi-client-first IRC bouncer
+.It Lk https://git.causal.agency/catgirl/about catgirl
+a cosy IRC client
+.It Lk https://git.causal.agency/litterbox/about litterbox
+a full-text search IRC logger
+.It Lk https://git.causal.agency/scooper/about scooper
+a web interface for litterbox
+.It Lk https://git.causal.agency/kitd/about kitd
+a process supervisor
+.It Lk https://git.causal.agency/imbox/about "imbox & git-fetch-email"
+a tool to pull patches out of IMAP
+.It Lk https://git.causal.agency/bubger/about bubger
+a mailing list archive generator for IMAP
+.It Lk https://git.causal.agency/notemap/about notemap
+a tool to mirror text files to IMAP notes
+.It Lk https://ascii.town/explore.html torus@ascii.town
+a collaborative ASCII art project
+.It Lk ssh://play@ascii.town play@ascii.town
+some games to play over
+.Xr ssh 1
+.It Lk https://git.causal.agency/cards/about cards
+a
+.Pa CARDS.DLL
+loader for SDL
+.It Lk scheme.png scheme
+an earthy terminal colour scheme
+.El
+.
+.Sh SEE ALSO
+.Bl -bullet
+.It
+.Lk /bin/ bin
+.It
+.Lk lands.html "Magic lands quiz"
+.It
+.Lk alpha.html "alphabet ranking game"
+.El
diff --git a/www/causal.agency/lands.html b/www/causal.agency/lands.html
new file mode 100644
index 00000000..7aaadd80
--- /dev/null
+++ b/www/causal.agency/lands.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<title>Lands Quiz</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+html { font: 14pt sans-serif; line-height: 1.5em; }
+body { padding: 1em 1ch; max-width: 78ch; margin: auto; }
+h1 { text-align: center; }
+h2 { margin-top: 0; }
+button { font-size: 100%; padding: 0.5em 1ch; }
+img { max-width: 100%; }
+div.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 2ch; }
+</style>
+
+<h1 id="loading">Loading...</h1>
+<h1 id="error" hidden>Failed to load cards :(</h1>
+
+<div id="game" hidden>
+<h1>Magic Lands Quiz</h1>
+<p>Try to guess the colours of mana each land produces!</p>
+<div class="cols">
+	<div>
+		<img id="back" src="https://backs.scryfall.io/normal/0/a/0aeebaf5-8c7d-4636-9e82-8c27447861f7.jpg">
+		<a id="link" target="_blank">
+			<img id="image1" hidden>
+			<img id="image2" hidden>
+		</a>
+	</div>
+	<div>
+		<h2 id="name"></h2>
+		<input type="checkbox" id="w"> <label for="w">White</label><br>
+		<input type="checkbox" id="u"> <label for="u">Blue</label><br>
+		<input type="checkbox" id="b"> <label for="b">Black</label><br>
+		<input type="checkbox" id="r"> <label for="r">Red</label><br>
+		<input type="checkbox" id="g"> <label for="g">Green</label><br>
+		<p><button id="submit">Submit</button></p>
+		<h3>Score: <span id="score">0</span>/<span id="total">0</span></h3>
+	</div>
+</div>
+</div>
+
+<script>
+function shuffle(arr) {
+	let rand = (bound) => Math.floor(Math.random() * bound);
+	for (let i = arr.length-1; i > 0; --i) {
+		let j = rand(i+1);
+		let x = arr[i];
+		arr[i] = arr[j];
+		arr[j] = x;
+	}
+}
+
+const CardBack =
+"https://backs.scryfall.io/normal/0/a/0aeebaf5-8c7d-4636-9e82-8c27447861f7.jpg";
+
+function hideCard() {
+	document.getElementById("back").hidden = false;
+	document.getElementById("image1").hidden = true;
+	document.getElementById("image2").hidden = true;
+}
+
+function showCard(card) {
+	document.getElementById("back").hidden = true;
+	document.getElementById("link").href = card.scryfall_uri;
+	let image1 = document.getElementById("image1");
+	let image2 = document.getElementById("image2");
+	if (card.card_faces) {
+		image1.src = card.card_faces[0].image_uris.normal;
+		image2.src = card.card_faces[1].image_uris.normal;
+		image1.hidden = false;
+		image2.hidden = false;
+	} else {
+		image1.src = card.image_uris.normal;
+		image1.hidden = false;
+	}
+}
+
+function resetChecks() {
+	for (let c of "wubrg") {
+		let input = document.getElementById(c);
+		input.checked = false;
+		input.disabled = false;
+		input.labels[0].style.fontWeight = "normal";
+	}
+}
+
+function checkChecks(card) {
+	let score = 0;
+	let total = 0;
+	let checked = 0;
+	for (let c of "wubrg") {
+		let input = document.getElementById(c);
+		let produced = card.produced_mana.includes(c.toUpperCase());
+		if (produced) {
+			total++;
+			input.labels[0].style.fontWeight = "bold";
+			if (input.checked) score++;
+		}
+		if (input.checked) checked++;
+		input.disabled = true;
+	}
+	if (checked > total) score -= (checked - total);
+	if (score < 0) score = 0;
+	return { score: score, total: total };
+}
+
+document.onkeydown = function(event) {
+	for (let c of "wubrg") {
+		if (event.key == c) {
+			let input = document.getElementById(c);
+			if (!input.disabled) input.checked ^= true;
+		}
+	}
+	if (event.key == "Enter") {
+		document.getElementById("submit").click();
+	}
+}
+
+let score = 0;
+let total = 0;
+let cards = [];
+let card = null;
+
+function nextCard() {
+	hideCard();
+	resetChecks();
+	card = cards.shift();
+	document.getElementById("name").innerText = card.name;
+}
+
+document.getElementById("submit").onclick = function() {
+	if (card) {
+		let { score: cardScore, total: cardTotal } = checkChecks(card);
+		total += cardTotal;
+		score += cardScore;
+		document.getElementById("score").innerText = score;
+		document.getElementById("total").innerText = total;
+		showCard(card);
+		card = null;
+		if (cards.length) {
+			this.innerText = "Next card";
+		} else {
+			this.disabled = true;
+			this.innerText = "No more cards";
+		}
+	} else {
+		nextCard();
+		this.innerText = "Submit";
+	}
+}
+
+function loadCards(resp) {
+	let loading = document.getElementById("loading");
+	let error = document.getElementById("error");
+	let game = document.getElementById("game");
+	if (resp.status != 200) {
+		loading.hidden = true;
+		error.hidden = false;
+	}
+	resp.json().then((json) => {
+		cards.push(...json.data);
+		if (json.has_more) {
+			setTimeout(() => fetch(json.next_page).then(loadCards), 50);
+		} else {
+			loading.hidden = true;
+			game.hidden = false;
+			shuffle(cards);
+			nextCard();
+		}
+	});
+}
+
+const Search =
+"https://api.scryfall.com/cards/search?q=t:land+id>=2+produces>=2+produces!=wubrg";
+fetch(Search).then(loadCards);
+
+</script>
diff --git a/www/causal.agency/style.css b/www/causal.agency/style.css
new file mode 100644
index 00000000..ee218533
--- /dev/null
+++ b/www/causal.agency/style.css
@@ -0,0 +1,28 @@
+@import url("scheme.css");
+
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
+
+div.head, div.foot { display: flex; justify-content: space-between; }
+.head-ltitle, .foot-left { flex: 1; }
+.head-vol, .foot-date { flex: 0 1 auto; text-align: center; }
+.head-rtitle, .foot-os { flex: 1; text-align: right; }
+
+html { font-family: monospace; line-height: 1.25em; }
+body { max-width: 80ch; margin: 1em auto; padding: 0 1ch; }
+table { border-collapse: collapse; }
+table.Nm code.Nm { padding-right: 1ch; }
+table.foot { margin-top: 1em; }
+
+html { background-color: var(--ansi16); color: var(--ansi17); }
+a { color: var(--ansi4); }
+a:visited { color: var(--ansi5); }
+a.permalink { color: var(--ansi3); text-decoration: none; }
diff --git a/www/git.causal.agency/.gitignore b/www/git.causal.agency/.gitignore
new file mode 100644
index 00000000..eaed8039
--- /dev/null
+++ b/www/git.causal.agency/.gitignore
@@ -0,0 +1,13 @@
+*.html
+about-filter
+compress
+ctags
+email-filter
+filter
+gzip
+hilex
+htagml
+mandoc
+mtags
+owner-filter
+source-filter
diff --git a/www/git.causal.agency/Makefile b/www/git.causal.agency/Makefile
new file mode 100644
index 00000000..86b9f3eb
--- /dev/null
+++ b/www/git.causal.agency/Makefile
@@ -0,0 +1,53 @@
+PREFIX = /var/www
+CONFDIR = ${PREFIX}/conf
+DATADIR = ${PREFIX}/cgit
+BINDIR = ${PREFIX}/bin
+WEBROOT = ${PREIFX}/git.causal.agency
+
+CFLAGS += -Wall -Wextra
+LDFLAGS = -static -pie
+
+BINS += about-filter
+BINS += ctags
+BINS += email-filter
+BINS += gzip
+BINS += hilex
+BINS += htagml
+BINS += mandoc
+BINS += mtags
+BINS += owner-filter
+BINS += source-filter
+
+HTMLS = index.html
+
+all: ${BINS} ${HTMLS}
+
+compress ctags mandoc:
+	${MAKE} -C /usr/src/usr.bin/$@ LDFLAGS='${LDFLAGS}'
+	mv /usr/src/usr.bin/$@/$@ $@
+	${MAKE} -C /usr/src/usr.bin/$@ clean
+
+gzip: compress
+	ln -f compress $@
+
+hilex htagml mtags:
+	rm -f ../../bin/$@
+	${MAKE} -C ../../bin $@ LDFLAGS='${LDFLAGS}'
+	mv ../../bin/$@ $@
+
+about-filter email-filter owner-filter source-filter: filter
+	ln -f filter $@
+
+index.html: index.7
+	mandoc -Thtml -Ostyle=https://causal.agency/style.css index.7 >index.html
+
+install: cgitrc custom.css ${BINS}
+	install -m 644 cgitrc ${CONFDIR}
+	install -m 644 custom.css ${DATADIR}
+	install -d -o www -g daemon ${PREFIX}/cache/cgit
+	install -d -m 1700 -o www -g daemon ${PREFIX}/tmp
+	install -s ${BINS} ${BINDIR}
+	install -m 644 ${HTMLS} ${WEBROOT}
+
+clean:
+	rm -f compress filter ${BINS} ${HTMLS}
diff --git a/www/git.causal.agency/cgitrc b/www/git.causal.agency/cgitrc
new file mode 100644
index 00000000..0666fd28
--- /dev/null
+++ b/www/git.causal.agency/cgitrc
@@ -0,0 +1,30 @@
+root-title=causal agency
+root-desc=“I think some people from the Gentoo project are behind this.”
+logo=
+
+clone-url=https://$HTTP_HOST/$CGIT_REPO_URL
+snapshots=tar.gz zip
+
+enable-blame=1
+enable-commit-graph=1
+enable-subject-links=1
+enable-follow-links=1
+enable-index-owner=0
+repository-sort=age
+branch-sort=age
+
+css=/custom.css
+about-filter=/bin/about-filter
+source-filter=/bin/source-filter
+#owner-filter=/bin/owner-filter
+email-filter=/bin/email-filter
+
+readme=:README.7
+readme=:README
+
+remove-suffix=1
+enable-git-config=1
+scan-path=/git.causal.agency
+
+cache-root=/cache/cgit
+cache-size=1024
diff --git a/www/git.causal.agency/custom.css b/www/git.causal.agency/custom.css
new file mode 100644
index 00000000..b3f4f425
--- /dev/null
+++ b/www/git.causal.agency/custom.css
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+@import url("cgit.css");
+
+* { line-height: 1.25em; }
+
+div#cgit {
+	max-width: 117ch;
+	margin: auto;
+	font-family: monospace;
+	-moz-tab-size: 4;
+	tab-size: 4;
+}
+
+div#cgit table#header td.sub {
+	border-top: none;
+}
+div#cgit table#header td.sub.right {
+	padding-right: 1em;
+}
+div#cgit table.tabs {
+	border-bottom: none;
+}
+div#cgit div.content {
+	border-bottom: none;
+}
+div#cgit table.list th a {
+	color: inherit;
+}
+div#cgit table.list tr:nth-child(even) {
+	background: inherit;
+}
+div#cgit table.list tr:hover {
+	background: inherit;
+}
+div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
+	background: inherit;
+}
+
+div#cgit table.blob td.linenumbers a:target {
+	color: goldenrod;
+	text-decoration: underline;
+	outline: none;
+}
+
+div#cgit div#summary {
+	max-width: 80ch;
+}
+
+/* for hilex(1) */
+div#cgit pre .Ke { color: dimgray; }
+div#cgit pre .Ma { color: green; }
+div#cgit pre .Co { color: navy; }
+div#cgit pre .St { color: teal; }
+div#cgit pre .Fo { color: teal; font-weight: bold; }
+div#cgit pre .Su { color: olive; }
+
+/* for htagml(1) */
+div#cgit pre a.tag { color: inherit; text-decoration: underline; }
+div#cgit pre a.tag:target { color: goldenrod; outline: none; }
+
+/* for mandoc(1) */
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
+
+h1.Sh { font-size: 1.5em; }
+table.Nm td:first-child { padding-right: 1ch; }
+code.Fl { white-space: nowrap; }
+span.RsT { font-style: italic; }
+dl.Bl-tag:not(.Bl-compact) > dt { margin-top: 1em; }
+ul.Bl-bullet:not(.Bl-compact) > li { margin-top: 1em; }
+div.Bd-indent { margin-left: 4ch; }
+table.Bl-column { width: 100%; }
+table.foot { margin-top: 1em; }
+
+div#cgit a.permalink { color: inherit; }
diff --git a/www/git.causal.agency/filter.c b/www/git.causal.agency/filter.c
new file mode 100644
index 00000000..7c7e9320
--- /dev/null
+++ b/www/git.causal.agency/filter.c
@@ -0,0 +1,158 @@
+#include <err.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define Q(...) #__VA_ARGS__
+
+#define MANDOC_OPTIONS "fragment,man=%N.%S,includes=../tree/%I"
+
+static int about(int argc, char *argv[]) {
+	if (argc < 2) return 1;
+	if (!fnmatch("README.[1-9]", argv[1], 0)) {
+		execlp("mandoc", "mandoc", "-T", "html", "-O", MANDOC_OPTIONS, NULL);
+		err(127, "mandoc");
+	} else if (!fnmatch("*.[1-9]", argv[1], 0)) {
+		execlp(
+			"mandoc", "mandoc", "-T", "html", "-O", "toc," MANDOC_OPTIONS, NULL
+		);
+		err(127, "mandoc");
+	} else {
+		execlp("hilex", "hilex", "-l", "text", "-f", "html", "-o", "pre", NULL);
+		err(127, "hilex");
+	}
+}
+
+static int email(void) {
+	size_t cap = 0;
+	char *buf = NULL;
+	if (getline(&buf, &cap, stdin) < 0) err(1, "getline");
+	if (buf[0] == 'C' && !strncmp(&buf[strcspn(buf, " ")], " McEnroe", 8)) {
+		printf("June%s", &buf[strcspn(buf, " ")]);
+	} else {
+		printf("%s", buf);
+	}
+	return 0;
+}
+
+static int owner(void) {
+	printf(Q(<a href="https://liberapay.com/june/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>));
+	return 0;
+}
+
+#define CTAGS_PATTERN "*.[chlmy]"
+#define TEMPLATE "/tmp/filter.XXXXXXXXXX"
+
+static char tmp[PATH_MAX];
+static char tags[] = TEMPLATE;
+static void cleanup(void) {
+	unlink(tmp);
+	unlink(tags);
+}
+
+static int source(int argc, char *argv[]) {
+	if (argc < 2) return 1;
+	if (
+		strcmp("Makefile", argv[1]) &&
+		strcmp(".profile", argv[1]) &&
+		strcmp(".shrc", argv[1]) &&
+		fnmatch(CTAGS_PATTERN, argv[1], 0) &&
+		fnmatch("*.mk", argv[1], 0) &&
+		fnmatch("*.[1-9]", argv[1], 0) &&
+		fnmatch("*.sh", argv[1], 0)
+	) {
+		execlp("hilex", "hilex", "-t", "-n", argv[1], "-f", "html", NULL);
+		err(127, "hilex");
+	}
+
+	const char *ext = strrchr(argv[1], '.');
+	if (!strcmp(argv[1], ".profile") || !strcmp(argv[1], ".shrc")) {
+		ext = ".sh";
+	} else if (!strcmp(argv[1], "Makefile")) {
+		ext = ".mk";
+	} else if (!ext) {
+		ext = "";
+	}
+
+	snprintf(tmp, sizeof(tmp), TEMPLATE "%s", ext);
+	int fd = mkstemps(tmp, strlen(ext));
+	if (fd < 0) err(1, "%s", tmp);
+	atexit(cleanup);
+
+	char buf[4096];
+	for (ssize_t len; 0 < (len = read(STDIN_FILENO, buf, sizeof(buf)));) {
+		if (write(fd, buf, len) < 0) err(1, "%s", tmp);
+	}
+	if (close(fd) < 0) err(1, "%s", tmp);
+
+	fd = mkstemp(tags);
+	if (fd < 0) err(1, "%s", tags);
+	close(fd);
+	pid_t pid = fork();
+	if (pid < 0) err(1, "fork");
+	if (!pid) {
+		if (!fnmatch(CTAGS_PATTERN, argv[1], 0)) {
+			execlp("ctags", "ctags", "-w", "-f", tags, tmp, NULL);
+			warn("ctags");
+		} else {
+			execlp("mtags", "mtags", "-f", tags, tmp, NULL);
+			warn("mtags");
+		}
+		_exit(127);
+	}
+	int status;
+	if (wait(&status) < 0) err(1, "wait");
+
+	int rw[2];
+	if (pipe(rw) < 0) err(1, "pipe");
+	pid = fork();
+	if (pid < 0) err(1, "fork");
+	if (!pid) {
+		dup2(rw[1], STDOUT_FILENO);
+		close(rw[0]);
+		close(rw[1]);
+		execlp("hilex", "hilex", "-f", "html", tmp, NULL);
+		warn("hilex");
+		_exit(127);
+	}
+	pid = fork();
+	if (pid < 0) err(1, "fork");
+	if (!pid) {
+		dup2(rw[0], STDIN_FILENO);
+		close(rw[0]);
+		close(rw[1]);
+		execlp("htagml", "htagml", "-im", "-f", tags, tmp, NULL);
+		warn("htagml");
+		_exit(127);
+	}
+	close(rw[0]);
+	close(rw[1]);
+
+	if (wait(&status) < 0) err(1, "wait");
+	if (wait(&status) < 0) err(1, "wait");
+	return status;
+}
+
+int main(int argc, char *argv[]) {
+#ifdef __OpenBSD__
+	int error;
+	switch (getprogname()[0]) {
+		break; case 'a': error = pledge("stdio exec", NULL);
+		break; case 's': error = pledge("stdio tmppath proc exec", NULL);
+		break; default:  error = pledge("stdio", NULL);
+	}
+	if (error) err(1, "pledge");
+#endif
+	switch (getprogname()[0]) {
+		case 'a': return about(argc, argv);
+		case 'e': return email();
+		case 'o': return owner();
+		case 's': return source(argc, argv);
+		default: return 1;
+	}
+}
diff --git a/www/git.causal.agency/index.7 b/www/git.causal.agency/index.7
new file mode 100644
index 00000000..58a40dfe
--- /dev/null
+++ b/www/git.causal.agency/index.7
@@ -0,0 +1,81 @@
+.Dd January 12, 2024
+.Dt GIT.CAUSAL.AGENCY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm causal agency
+.Nd \(dqI think some people from the Gentoo project are behind this.\(dq
+.
+.Sh DESCRIPTION
+basically cgit (awful software)
+getting hammered by web crawlers
+keeps making my machine crash.
+this static page will be here
+until I can find a better solution.
+clone urls and tarball urls are still functional.
+.
+.Bl -tag
+.It src \(em dontfiles
+.Dl git clone https://git.causal.agency/src
+.It ascii.town
+.Bl -tag
+.It torus \(em collaborative ASCII art
+.Dl git clone https://git.causal.agency/torus
+.It play \(em some games for SSH
+.Dl git clone https://git.causal.agency/play
+.El
+.It email
+.Bl -tag
+.It imbox \(em IMAP to mbox
+.Dl git clone https://git.causal.agency/imbox
+.It bubger \(em IMAP archive generator
+.Dl git clone https://git.causal.agency/bubger
+.It notemap \(em notemap
+.Dl git clone https://git.causal.agency/notemap
+.El
+.It forks
+.Bl -tag
+.It shulker \(em Discord to vanilla Minecraft bridge
+.Dl git clone https://git.causal.agency/shulker
+.It cgit-pink \(em web frontend for git
+.Dl git clone https://git.causal.agency/cgit-pink
+.It dash \(em patched shell with cmake build
+.Dl git clone https://git.causal.agency/dash
+.El
+.It games
+.Bl -tag
+.It wep \(em Windows Entertainment Pack recreations
+.Dl git clone https://git.causal.agency/wep
+.It cards \(em CARDS.DLL loader for SDL
+.Dl git clone https://git.causal.agency/cards
+.El
+.It irc
+.Bl -tag
+.It scooper \(em web interface for litterbox
+.Dl git clone https://git.causal.agency/scooper
+.It litterbox \(em IRC logger
+.Dl git clone https://git.causal.agency/litterbox
+.It pounce \(em IRC bouncer
+.Dl git clone https://git.causal.agency/pounce
+.It catgirl \(em IRC client
+.Dl git clone https://git.causal.agency/catgirl
+.El
+.It ports
+.Bl -tag
+.It jorts \(em my own ports tree for macOS
+.Dl git clone https://git.causal.agency/jorts
+.It exman \(em manuals for other systems
+.Dl git clone https://git.causal.agency/exman
+.It libretls \(em libtls for OpenSSL
+.Dl git clone https://git.causal.agency/libretls
+.It ports \(em Fx and Ox ports for this software
+.Dl git clone https://git.causal.agency/ports
+.El
+.It system
+.Bl -tag
+.It kitd \(em process supervisor for OpenBSD
+.Dl git clone https://git.causal.agency/kitd
+.It catsit \(em (deprecated) process supervisor
+.Dl git clone https://git.causal.agency/catsit
+.El
+.El
diff --git a/www/photo.causal.agency/.gitignore b/www/photo.causal.agency/.gitignore
new file mode 100644
index 00000000..a5f66a9d
--- /dev/null
+++ b/www/photo.causal.agency/.gitignore
@@ -0,0 +1,2 @@
+static/
+*.JPG
diff --git a/2024-04-10/IMG_0832.txt b/www/photo.causal.agency/2024-04-10/IMG_0832.txt
index 65724024..65724024 100644
--- a/2024-04-10/IMG_0832.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0832.txt
diff --git a/2024-04-10/IMG_0850.txt b/www/photo.causal.agency/2024-04-10/IMG_0850.txt
index 4cbb3def..4cbb3def 100644
--- a/2024-04-10/IMG_0850.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0850.txt
diff --git a/2024-04-10/IMG_0852.txt b/www/photo.causal.agency/2024-04-10/IMG_0852.txt
index 707d7cd6..707d7cd6 100644
--- a/2024-04-10/IMG_0852.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0852.txt
diff --git a/2024-04-10/IMG_0858.txt b/www/photo.causal.agency/2024-04-10/IMG_0858.txt
index 42f243e4..42f243e4 100644
--- a/2024-04-10/IMG_0858.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0858.txt
diff --git a/2024-04-10/IMG_0859.txt b/www/photo.causal.agency/2024-04-10/IMG_0859.txt
index ca33d7e0..ca33d7e0 100644
--- a/2024-04-10/IMG_0859.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0859.txt
diff --git a/2024-04-10/IMG_0865.txt b/www/photo.causal.agency/2024-04-10/IMG_0865.txt
index 7a955fc2..7a955fc2 100644
--- a/2024-04-10/IMG_0865.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0865.txt
diff --git a/2024-04-10/IMG_0890.txt b/www/photo.causal.agency/2024-04-10/IMG_0890.txt
index 9d2cdc43..9d2cdc43 100644
--- a/2024-04-10/IMG_0890.txt
+++ b/www/photo.causal.agency/2024-04-10/IMG_0890.txt
diff --git a/2024-04-14/IMG_1054.txt b/www/photo.causal.agency/2024-04-14/IMG_1054.txt
index f4803ee2..f4803ee2 100644
--- a/2024-04-14/IMG_1054.txt
+++ b/www/photo.causal.agency/2024-04-14/IMG_1054.txt
diff --git a/2024-04-14/IMG_1058.txt b/www/photo.causal.agency/2024-04-14/IMG_1058.txt
index 21aeb189..21aeb189 100644
--- a/2024-04-14/IMG_1058.txt
+++ b/www/photo.causal.agency/2024-04-14/IMG_1058.txt
diff --git a/2024-04-14/IMG_1066.txt b/www/photo.causal.agency/2024-04-14/IMG_1066.txt
index 81747287..81747287 100644
--- a/2024-04-14/IMG_1066.txt
+++ b/www/photo.causal.agency/2024-04-14/IMG_1066.txt
diff --git a/generate.sh b/www/photo.causal.agency/generate.sh
index 4b30db92..4b30db92 100644
--- a/generate.sh
+++ b/www/photo.causal.agency/generate.sh
diff --git a/rsync.sh b/www/photo.causal.agency/rsync.sh
index 957911d2..957911d2 100644
--- a/rsync.sh
+++ b/www/photo.causal.agency/rsync.sh
diff --git a/www/temp.causal.agency/.gitignore b/www/temp.causal.agency/.gitignore
new file mode 100644
index 00000000..e31ee94e
--- /dev/null
+++ b/www/temp.causal.agency/.gitignore
@@ -0,0 +1 @@
+up
diff --git a/www/temp.causal.agency/Makefile b/www/temp.causal.agency/Makefile
new file mode 100644
index 00000000..a69a2b48
--- /dev/null
+++ b/www/temp.causal.agency/Makefile
@@ -0,0 +1,15 @@
+CGI_BIN = /var/www/cgi-bin
+
+CFLAGS += -std=c11 -Wall -Wextra -Wpedantic $$(pkg-config --cflags kcgi)
+LDLIBS = -static $$(pkg-config --static --libs kcgi-html)
+
+up:
+
+clean:
+	rm -f up
+
+install: up
+	install up ${CGI_BIN}/up
+
+uninstall:
+	rm -f ${CGI_BIN}/up
diff --git a/www/temp.causal.agency/up.c b/www/temp.causal.agency/up.c
new file mode 100644
index 00000000..561a8901
--- /dev/null
+++ b/www/temp.causal.agency/up.c
@@ -0,0 +1,193 @@
+/* Copyright (C) 2020  June McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+static const char *Page = "up";
+static const struct kvalid Key = { NULL, "file" };
+
+static enum kcgi_err head(struct kreq *req, enum khttp http, enum kmime mime) {
+	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
+		|| khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
+}
+
+static enum kcgi_err fail(struct kreq *req, enum khttp http) {
+	return head(req, http, KMIME_TEXT_PLAIN)
+		|| khttp_body(req)
+		|| khttp_printf(req, "%s\n", khttps[http]);
+}
+
+static int dir = -1;
+static const char *upload(const char *ext, void *ptr, size_t len) {
+	static char name[256];
+	snprintf(
+		name, sizeof(name), "%jx%08x%s%s",
+		(intmax_t)time(NULL), arc4random(),
+		(ext && ext[0] != '.' ? "." : ""), (ext ? ext : "")
+	);
+	int fd = openat(dir, name, O_CREAT | O_EXCL | O_WRONLY, 0644);
+	if (fd < 0) {
+		warn("%s", name);
+		return NULL;
+	}
+	ssize_t n = write(fd, ptr, len);
+	int error = close(fd);
+	if (n < 0 || error) {
+		warn("%s", name);
+		return NULL;
+	}
+	return name;
+}
+
+static enum kcgi_err handle(struct kreq *req) {
+	if (req->page) return fail(req, KHTTP_404);
+
+	if (req->method == KMETHOD_GET) {
+		struct khtmlreq html;
+		struct khtmlreq *h = &html;
+		return head(req, KHTTP_200, KMIME_TEXT_HTML)
+			|| khttp_body(req)
+			|| khtml_open(h, req, 0)
+			|| khtml_elem(h, KELEM_DOCTYPE)
+			|| khtml_elem(h, KELEM_TITLE)
+			|| khtml_puts(h, "Upload")
+			|| khtml_closeelem(h, 1)
+			|| khtml_attr(
+				h, KELEM_FORM,
+				KATTR_METHOD, "post",
+				KATTR_ACTION, "",
+				KATTR_ENCTYPE, "multipart/form-data",
+				KATTR__MAX
+			)
+			|| khtml_attr(
+				h, KELEM_INPUT,
+				KATTR_TYPE, "file",
+				KATTR_NAME, Key.name,
+				KATTR__MAX
+			)
+			|| khtml_attr(
+				h, KELEM_INPUT,
+				KATTR_TYPE, "submit",
+				KATTR_VALUE, "Upload",
+				KATTR__MAX
+			)
+			|| khtml_close(h);
+
+	} else if (req->method == KMETHOD_POST) {
+		struct kpair *field = req->fieldmap[0];
+		if (!field || !field->valsz) return fail(req, KHTTP_400);
+
+		const char *ext = strrchr(field->file, '.');
+		const char *name = upload(ext, field->val, field->valsz);
+		if (!name) return fail(req, KHTTP_507);
+
+		return head(req, KHTTP_303, KMIME_TEXT_PLAIN)
+			|| khttp_head(req, kresps[KRESP_LOCATION], "/%s", name)
+			|| khttp_body(req)
+			|| khttp_puts(req, name);
+
+	} else if (req->method == KMETHOD_PUT) {
+		struct kpair *field = req->fields;
+		if (!field || !field->valsz) return fail(req, KHTTP_400);
+
+		const char *ext = req->suffix;
+		if (!ext[0]) ext = strrchr(field->file, '.');
+		const char *name = upload(ext, field->val, field->valsz);
+		if (!name) return fail(req, KHTTP_507);
+
+		return head(req, KHTTP_200, KMIME_TEXT_PLAIN)
+			|| khttp_body(req)
+			|| khttp_printf(
+				req, "%s://%s/%s\n", kschemes[req->scheme], req->host, name
+			);
+
+	} else {
+		return fail(req, KHTTP_405);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+	const char *path = (argc > 1 ? argv[1] : ".");
+	dir = open(path, O_DIRECTORY);
+	if (dir < 0) err(EX_NOINPUT, "%s", path);
+
+#ifdef __OpenBSD__
+	error = unveil(path, "wc");
+	if (error) err(EX_OSERR, "unveil");
+#endif
+
+	if (!khttp_fcgi_test()) {
+#ifdef __OpenBSD__
+		error = pledge("stdio wpath cpath proc", NULL);
+		if (error) err(EX_OSERR, "pledge");
+#endif
+
+		struct kreq req;
+		error = khttp_parse(&req, &Key, 1, &Page, 1, 0);
+		if (error) errx(EX_PROTOCOL, "khttp_parse: %s", kcgi_strerror(error));
+
+#ifdef __OpenBSD__
+		error = pledge("stdio wpath cpath", NULL);
+		if (error) err(EX_OSERR, "pledge");
+#endif
+
+		error = handle(&req);
+		if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error));
+		khttp_free(&req);
+		return EX_OK;
+	}
+
+#ifdef __OpenBSD__
+	error = pledge("stdio wpath cpath unix sendfd recvfd proc", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	struct kfcgi *fcgi;
+	error = khttp_fcgi_init(&fcgi, &Key, 1, &Page, 1, 0);
+	if (error) errx(EX_CONFIG, "khttp_fcgi_init: %s", kcgi_strerror(error));
+
+#ifdef __OpenBSD__
+	error = pledge("stdio wpath cpath recvfd", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	for (
+		struct kreq req;
+		!(error = khttp_fcgi_parse(fcgi, &req));
+		khttp_free(&req)
+	) {
+		error = handle(&req);
+		if (error && error != KCGI_HUP) break;
+	}
+	if (error != KCGI_EXIT) {
+		errx(EX_PROTOCOL, "khttp_fcgi_parse: %s", kcgi_strerror(error));
+	}
+	khttp_fcgi_free(fcgi);
+}
diff --git a/www/text.causal.agency/.gitignore b/www/text.causal.agency/.gitignore
new file mode 100644
index 00000000..66b3e637
--- /dev/null
+++ b/www/text.causal.agency/.gitignore
@@ -0,0 +1,4 @@
+*.txt
+colb
+feed.atom
+igp
diff --git a/www/text.causal.agency/001-make.7 b/www/text.causal.agency/001-make.7
new file mode 100644
index 00000000..b4805729
--- /dev/null
+++ b/www/text.causal.agency/001-make.7
@@ -0,0 +1,159 @@
+.Dd September 17, 2018
+.Dt MAKE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Using Make
+.Nd writing less Makefile
+.
+.Sh DESCRIPTION
+Let's talk about
+.Xr make 1 .
+I think an important thing to know about
+.Xr make 1
+is that you don't need to write a
+.Pa Makefile
+to use it.
+There are default rules
+for C, C++ and probably Fortran.
+To build
+.Pa foo
+from
+.Pa foo.c ,
+just run:
+.
+.Pp
+.Dl make foo
+.
+.Pp
+The default rule for C files uses the
+.Ev CFLAGS
+variable,
+so you can set that in the environment
+to pass flags to the C compiler:
+.
+.Pp
+.Dl CFLAGS=-Wall make foo
+.
+.Pp
+It also uses
+.Ev LDLIBS
+for linking,
+so you can add libraries with:
+.
+.Pp
+.Dl LDLIBS=-lcurses make foo
+.
+.Pp
+Obviously writing this every time
+would become tedious,
+so it might be time to write a
+.Pa Makefile .
+But it really doesn't need much:
+.
+.Bd -literal -offset indent
+CFLAGS += -Wall -Wextra
+LDLIBS = -lcurses
+
+foo:
+.Ed
+.
+.Pp
+Assigning
+.Ev CFLAGS
+with
+.Ql +=
+preserves the system default
+or anything passed in the environment.
+Declaring
+.Pa foo
+as the first rule
+makes it the default when
+.Ql make
+is run without a target.
+Note that the rule doesn't need a definition;
+the default will still be used.
+.
+.Pp
+If
+.Pa foo
+is built from serveral source files,
+unfortunately a rule definition is required:
+.
+.Bd -literal -offset indent
+OBJS = foo.o bar.o baz.o
+
+foo: $(OBJS)
+	$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
+.Ed
+.
+.Pp
+This rule uses
+.Ev LDFLAGS
+for passing linker flags,
+which is what the default rule does.
+The
+.Ql $@
+variable here expands to
+.Ql foo ,
+so this rule can be copied easily
+for other binary targets.
+.
+.Pp
+If some sources depend on a header file,
+they can be automatically rebuilt
+when the header changes
+by declaring a dependency rule:
+.
+.Pp
+.Dl foo.o bar.o: foo.h
+.
+.Pp
+Note that several files can appear
+either side of the
+.Ql ":" .
+.
+.Pp
+Lastly,
+it's always nice to add a
+.Cm clean
+target:
+.
+.Bd -literal -offset indent
+clean:
+	rm -f $(OBJS) foo
+.Ed
+.
+.Pp
+I hope this helps getting started with
+.Xr make 1
+without writing too much
+.Pa Makefile !
+.
+.Sh EXAMPLES
+The example
+.Pa Makefile
+in its entirety:
+.
+.Bd -literal -offset indent
+CFLAGS += -Wall -Wextra
+LDLIBS = -lcurses
+OBJS = foo.o bar.o baz.o
+
+foo: $(OBJS)
+	$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
+
+foo.o bar.o: foo.h
+
+clean:
+	rm -f $(OBJS) foo
+.Ed
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
diff --git a/www/text.causal.agency/002-writing-mdoc.7 b/www/text.causal.agency/002-writing-mdoc.7
new file mode 100644
index 00000000..b377d364
--- /dev/null
+++ b/www/text.causal.agency/002-writing-mdoc.7
@@ -0,0 +1,138 @@
+.Dd September 27, 2018
+.Dt WRITING-MDOC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Writing mdoc
+.Nd semantic markup
+.
+.Sh DESCRIPTION
+I recently learned how to write man pages
+so that I could document
+a bunch of little programs I've written.
+Modern man pages are written in
+.Xr mdoc 7 ,
+whose documentation is also available from
+.Lk http://mandoc.bsd.lv .
+.
+.Pp
+.Xr mdoc 7
+differs from many other markup languages
+by providing
+.Dq semantic markup
+rather than just
+.Dq physical markup.
+What this means is that
+the markup indicates what something is,
+not how to format it.
+For example,
+the
+.Ql \&Ar
+macro is used to indicate
+command-line arguments
+rather than one of the macros
+for bold, italic or underline.
+This frees each author of having to choose
+and enables consistent presentation
+across different man pages.
+.
+.Pp
+Another advantage of semantic markup
+is that information can be extracted from it.
+For example,
+.Xr makewhatis 8
+can easily extract the name and short description
+from each man page
+thanks to the
+.Ql \&Nm
+and
+.Ql \&Nd
+macros.
+I use the same information
+to generate an Atom feed for these documents,
+though in admittedly a much less robust way than
+.Xr mandoc 1 .
+.
+.Pp
+When it comes to actually writing
+.Xr mdoc 7 ,
+it can take some getting used to.
+The language is of
+.Xr roff 7
+lineage
+so its syntax is very particular.
+Macros cannot appear inline,
+but must start on new lines
+beginning with
+.Ql \&. .
+Sentences should likewise
+always start on a new line.
+Since I'm in the habit of writing with
+semantic line breaks,
+I actually find these requirements
+fit in well.
+.
+.Pp
+The more frustrating syntax limitation to me
+is the rule against empty lines.
+Without them,
+it can be quite difficult to edit a lengthy document.
+Thankfully,
+lines with only a
+.Ql \&.
+on them are allowed,
+but this still causes visual noise.
+To alleviate that,
+I have a
+.Xr vim 1
+syntax file for
+.Xr mdoc 7
+which conceals the lone dots:
+.
+.Bd -literal -offset indent
+if exists("b:current_syntax")
+	finish
+endif
+
+runtime! syntax/nroff.vim
+unlet! b:current_syntax
+
+setlocal sections+=ShSs
+syntax match mdocBlank /^\\.$/ conceal
+setlocal conceallevel=2
+
+let b:current_syntax = "mdoc"
+.Ed
+.
+.Pp
+It also adds the
+.Xr mdoc 7
+section header and subsection header macros to the
+.Cm sections
+option to make
+.Xr vim 1 Ap s
+.Ic {
+and
+.Ic }
+motions
+aware of them.
+.
+.Pp
+With that,
+I've found writing man pages pleasant and rewarding.
+I've started writing other documents with
+.Xr mdoc 7
+as well,
+as you can see here.
+.
+.Sh SEE ALSO
+.Lk http://rhodesmill.org/brandon/2012/one-sentence-per-line/ "Semantic Linefeeds"
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
diff --git a/www/text.causal.agency/003-pleasant-c.7 b/www/text.causal.agency/003-pleasant-c.7
new file mode 100644
index 00000000..16030b7e
--- /dev/null
+++ b/www/text.causal.agency/003-pleasant-c.7
@@ -0,0 +1,120 @@
+.Dd September 30, 2018
+.Dt PLEASANT-C 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Pleasant C
+.Nd it's good, actually
+.
+.Sh DESCRIPTION
+I've been writing a lot of C lately
+and actually find it very pleasant.
+I want to talk about some of its ergonomic features.
+These are C99 features unless otherwise noted.
+.
+.Ss Initializer syntax
+Struct and union initializer syntax
+is well generalized.
+Designators can be chained,
+making initializing nested structs easy,
+and all uninitialized fields are zeroed.
+.
+.Bd -literal -offset indent
+struct {
+	struct pollfd fds[2];
+} loop = {
+	.fds[0].fd = STDIN_FILENO,
+	.fds[1].fd = STDOUT_FILENO,
+	.fds[0].events = POLLIN,
+	.fds[1].events = POLLOUT,
+};
+.Ed
+.
+.Ss Variable-length arrays
+VLAs can be multi-dimensional,
+which can avoid manual stride multiplications
+needed to index a flat
+.Xr malloc 3 Ap d
+array.
+.
+.Bd -literal -offset indent
+uint8_t glyphs[len][height][width];
+fread(glyphs, height * width, len, stdin);
+.Ed
+.
+.Ss Incomplete array types
+The last field of a struct can be an
+.Dq incomplete
+array type,
+which means it doesn't have a length.
+A variable amount of space for the struct can be
+.Xr malloc 3 Ap d ,
+or the struct can be used as
+a sort of pointer with fields.
+.
+.Bd -literal -offset indent
+struct Line {
+	enum Filter type;
+	uint8_t data[];
+} *line = &png.data[1 + lineSize()];
+.Ed
+.
+.Ss Anonymous struct and union fields (C11)
+Members of structs or unions
+which are themselves structs or unions
+can be unnamed.
+In that case,
+each of the inner fields
+is treated as a member of the outer struct or union.
+This makes working with tagged unions nicer.
+.
+.Bd -literal -offset indent
+struct Message {
+	enum { Foo, Bar } type;
+	union {
+		uint8_t foo;
+		uint32_t bar;
+	};
+} msg = { .type = Foo, .foo = 0xFF };
+.Ed
+.
+.Ss Static assert (C11)
+Assertions can be made at compile time.
+Most useful for checking sizes of structs.
+.
+.Bd -literal -offset indent
+static_assert(13 == sizeof(struct PNGHeader), "PNG IHDR size");
+.Ed
+.
+.Ss Leading-break switch
+This one is just an odd style choice
+I came across that C happens to allow.
+To prevent accidental fall-through
+in switch statements,
+you can put breaks before the case labels.
+.
+.Bd -literal -offset indent
+while (0 < (opt = getopt(argc, argv, "h:w:"))) {
+	switch (opt) {
+		break; case 'h': height = optarg;
+		break; case 'w': width = optarg;
+		break; default:  return EX_USAGE;
+	}
+}
+.Ed
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
+.
+.Sh CAVEATS
+This isn't meant to be advice.
+It's just how I like to write C,
+and I don't
+.Dq ship
+software in C.
diff --git a/www/text.causal.agency/004-uloc.7 b/www/text.causal.agency/004-uloc.7
new file mode 100644
index 00000000..edd78d80
--- /dev/null
+++ b/www/text.causal.agency/004-uloc.7
@@ -0,0 +1,64 @@
+.Dd December 14, 2018
+.Dt ULOC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm ULOC
+.Nd unique lines of code
+.
+.Sh DESCRIPTION
+There are many tools available
+which measure SLOC: source lines of code.
+These tools are strangely complex
+for what they intend to do,
+which is to estimate the relative sizes of projects.
+They perform some amount of parsing
+in order to discount comments in various languages,
+and for reasons unknown each format their ouput
+in some oddly encumbered way.
+.
+.Pp
+I propose a much simpler method
+of estimating relative sizes of projects:
+unique lines of code.
+ULOC can be calculated with standard tools as follows:
+.
+.Bd -literal -offset indent
+sort -u *.h *.c | wc -l
+.Ed
+.
+.Pp
+In my opinion,
+the number this produces
+should be a better estimate of
+the complexity of a project.
+Compared to SLOC,
+not only are blank lines discounted,
+but so are close-brace lines
+and other repetitive code
+such as common includes.
+On the other hand,
+ULOC counts comments,
+which require just as much maintenance
+as the code around them does,
+while avoiding inflating the result
+with license headers which appear in every file,
+for example.
+.
+.Pp
+It can also be amusing
+to read all of your code sorted alphabetically.
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
+.
+.Sh CAVEATS
+Estimates such as these
+should not be used for decision making
+as if they were data.
diff --git a/www/text.causal.agency/005-testing-c.7 b/www/text.causal.agency/005-testing-c.7
new file mode 100644
index 00000000..d0c636ff
--- /dev/null
+++ b/www/text.causal.agency/005-testing-c.7
@@ -0,0 +1,73 @@
+.Dd December 21, 2018
+.Dt TESTING-C 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Testing C
+.Nd a simple unit testing setup
+.
+.Sh DESCRIPTION
+This is a simple approach
+to unit testing in C
+that I've used in a couple projects.
+At the bottom of a C file
+with some code I want to test,
+I add:
+.
+.Bd -literal -offset indent
+#ifdef TEST
+#include <assert.h>
+
+int main(void) {
+	assert(...);
+	assert(...);
+}
+
+#endif
+.Ed
+.
+.Pp
+This file normally produces a
+.Pa .o
+to be linked into the main binary.
+For testing,
+I produce separate binaries
+and run them with
+.Xr make 1 :
+.
+.Bd -literal -offset indent
+TESTS = foo.t bar.t
+
+\&.SUFFIXES: .t
+
+\&.c.t:
+	$(CC) $(CFLAGS) -DTEST $(LDFLAGS) $< $(LDLIBS) -o $@
+
+test: $(TESTS)
+	set -e; $(TESTS:%=./%;)
+.Ed
+.
+.Pp
+Note that the test binaries
+aren't linked with the rest of the code,
+so there is potential for simple stubbing or mocking.
+.
+.Pp
+To get the best output
+from C's simple
+.Xr assert 3 ,
+it's best to assert the result
+of a helper function
+which takes the expected output
+and the test input,
+rather than calling
+.Xr assert 3
+inside the helper function.
+This way,
+the message printed by the assert failure
+contains a useful line number
+and the expected output
+rather than just variable names.
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
diff --git a/www/text.causal.agency/006-some-libs.7 b/www/text.causal.agency/006-some-libs.7
new file mode 100644
index 00000000..5af65404
--- /dev/null
+++ b/www/text.causal.agency/006-some-libs.7
@@ -0,0 +1,96 @@
+.Dd December 11, 2019
+.Dt SOME-LIBS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Some Libraries
+.Nd good ones
+.
+.Sh DESCRIPTION
+This is a little list of C libraries
+I've had good experiences using.
+.
+.Bl -tag -width Ds
+.It Fl lcurl
+The library behind the
+.Xr curl 1
+command.
+It downloads or uploads things on the internet
+through a number of protocols,
+not just HTTP.
+It has an easy-to-use library API,
+appropriately named
+.Xr libcurl-easy 3 .
+I've used it to implement a
+.Lk https://causal.agency/bin/title.html "page title fetcher" .
+.
+.It Fl lcurses
+Okay so this one really isn't great.
+Its interfaces can seem archaic
+and its documentation is often poor.
+However, it gets the job done
+and is commonly available pretty much everywhere.
+Interesting to note that
+.Nx
+uses its own implementation of curses
+that is not GNU ncurses,
+unlike
+.Fx .
+.
+.It Fl ledit
+This is a BSD line editing library,
+similar to GNU readline.
+It supports right-aligned prompts,
+which I prefer for variable-length
+information in shells.
+.
+.It Fl lkcgi
+A CGI and FastCGI library
+for web applications in C.
+Don't worry,
+it isolates HTTP parsing and input validation
+from application logic
+in sandboxed processes.
+I think it's an excellent example
+of how to design an API for C.
+I used it to implement the
+.Lk https://ascii.town/explore.html "torus web viewer" .
+.
+.It Fl lsqlite3
+An embedded relational database engine.
+It's amazing what you can do with this,
+and it's super easy to use!
+My one gripe with it is that the library and SQL documentation
+are not available as
+.Xr man 1
+pages.
+I'm currently working on a project using SQLite,
+but it hasn't gotten very far yet.
+.
+.It Fl ltls
+This is a new library in LibreSSL
+which provides a much simpler interface for TLS sockets
+compared to
+.Fl lssl .
+It's much more like what you'd expect
+from other TLS socket wrappers,
+with calls like
+.Xr tls_connect 3 ,
+.Xr tls_read 3
+and
+.Xr tls_write 3 .
+I've used this for IRC clients, bouncers and bots.
+.
+.It Fl lz
+An implementation of the DEFLATE compression algorithm
+and gzip format.
+It's all documented in comments in
+.In zlib.h ,
+which isn't bad,
+but for my own use I copied the docs into
+.Lk https://code.causal.agency/june/zlib-man-pages "manual pages" .
+I've used this for decoding and encoding PNG images.
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/007-cgit-setup.7 b/www/text.causal.agency/007-cgit-setup.7
new file mode 100644
index 00000000..44fb436a
--- /dev/null
+++ b/www/text.causal.agency/007-cgit-setup.7
@@ -0,0 +1,271 @@
+.Dd December 15, 2019
+.Dt CGIT-SETUP 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm cgit setup
+.Nd configuration notes
+.
+.Sh DESCRIPTION
+I just set up cgit on
+.Lk https://git.causal.agency
+to replace an instance of gitea.
+After 30 days of uptime,
+gitea had accumulated over 11 hours of CPU time
+and was using hundreds of megabytes of memory.
+cgit is much more lightweight
+and much more in line with my aesthetic.
+I'm documenting how I set it up here
+mostly to remind myself in the future.
+.
+.Ss slowcgi
+cgit is CGI software,
+but
+.Xr nginx 8
+only supports FastCGI.
+I used
+.Xr slowcgi 8
+as a compatibility layer
+by adding the following to
+.Pa /etc/rc.conf :
+.Bd -literal -offset indent
+slowcgi_enable="YES"
+slowcgi_flags="-p / -s /var/run/slowcgi.sock"
+.Ed
+.
+.Ss nginx
+I added the following in a new
+.Cm server
+block to
+.Pa /usr/local/etc/nginx/nginx.conf :
+.Bd -literal -offset indent
+root /usr/local/www/cgit;
+location / {
+	try_files $uri @cgit;
+}
+location @cgit {
+	fastcgi_pass unix:/var/run/slowcgi.sock;
+	fastcgi_param SCRIPT_FILENAME $document_root/cgit.cgi;
+	fastcgi_param SCRIPT_NAME /;
+	fastcgi_param PATH_INFO $uri;
+	fastcgi_param QUERY_STRING $query_string;
+	fastcgi_param REQUEST_METHOD $request_method;
+	fastcgi_param CONTENT_TYPE $content_type;
+	fastcgi_param CONTENT_LENGTH $content_length;
+	fastcgi_param HTTPS $https if_not_empty;
+	fastcgi_param SERVER_PORT $server_port;
+	fastcgi_param SERVER_NAME $server_name;
+}
+.Ed
+.
+.Pp
+The
+.Cm try_files
+directive causes
+.Xr nginx 8
+to first try to serve static files from
+.Pa /usr/local/www/cgit
+before passing anything else on to FastCGI.
+.
+.Pp
+The
+.Va SCRIPT_FILENAME
+parameter tells
+.Xr slowcgi 8
+the path of the CGI binary to run.
+Setting
+.Va SCRIPT_NAME
+to
+.Pa /
+tells cgit its root URL
+and avoids it using query strings for everything.
+.
+.Ss cgit
+cgit doesn't provide any configuration to start from,
+so you have to just read
+.Xr cgitrc 5 .
+I added the following to
+.Pa /usr/local/etc/cgitrc :
+.Bd -literal -offset indent
+cache-size=1024
+clone-url=https://$HTTP_HOST/$CGIT_REPO_URL
+snapshots=tar.gz zip
+remove-suffix=1
+enable-git-config=1
+scan-path=/home/june/pub
+.Ed
+.
+.Pp
+The
+.Cm cache-size
+option enables caching,
+which by default is stored in
+.Pa /var/cache/cgit ,
+so I made sure that directory exists
+and is writable by the
+.Sy www
+user.
+The
+.Cm clone-url
+option sets the clone URL to advertise.
+cgit will automatically serve git over HTTP.
+The
+.Cm snapshots
+option makes tarballs available for tags and commits.
+.
+.Pp
+The
+.Cm scan-path
+option causes cgit to scan the given path
+for git repositories.
+I'm putting mine in
+.Pa ~/pub .
+The
+.Cm remove-suffix
+option causes cgit to remove the
+.Pa .git
+suffix from the URLs it uses
+for the repositories it finds,
+so that
+.Pa ~/pub/pounce.git
+is served at
+.Pa /pounce .
+The
+.Cm enable-git-config
+option allows controlling some cgit options
+from the
+.Xr git-config 1
+of each repository.
+See
+.Sx git
+below.
+.
+.Pp
+I also set up a filter to render
+.Xr mdoc 7
+files
+and do syntax highlighting
+by adding the following to
+.Pa cgitrc :
+.Bd -literal -offset indent
+readme=:README.7
+readme=:README
+about-filter=/usr/local/libexec/cgit-filter
+source-filter=/usr/local/libexec/cgit-filter
+.Ed
+.
+.Pp
+The
+.Cm readme
+options tell cgit which files to look for
+to render the
+.Dq about
+page.
+The colon prefix causes it to look for them
+in the git tree.
+The
+.Pa /usr/local/libexec/cgit-filter
+script contains the following:
+.Bd -literal -offset indent
+#!/bin/sh
+case "$1" in
+	(*.[1-9])
+		/usr/bin/mandoc -T utf8 | /usr/local/libexec/ttpre
+		;;
+	(*)
+		exec /usr/local/libexec/hi -t -n "$1" -f html -o anchor
+		;;
+esac
+.Ed
+.
+.Pp
+Filter scripts are run with the filename as their first argument
+and the contents of the file on standard input.
+The
+.Xr ttpre 1
+command is my own utility to convert
+.Xr man 1
+output to HTML.
+The
+.Xr hi 1
+command is my own
+.Lk https://causal.agency/bin/hi.html "syntax highlighter" .
+.
+.Ss git
+I create my repositories in
+.Pa ~/pub
+with
+.Ql git init --bare
+and use
+.Pa git.causal.agency:pub/example.git
+locally as the remote.
+Descriptions are set by editing the
+.Pa description
+file in each repository.
+The section and homepage can be set with
+.Xr git-config 1
+through the keys
+.Cm cgit.section
+and
+.Cm cgit.homepage ,
+respectively,
+thanks to the
+.Cm enable-git-config
+option above.
+.
+.Ss Redirects
+I added the following to the
+.Cm server
+block that used to serve gitea in
+.Pa nginx.conf :
+.Bd -literal -offset indent
+location ~* /june/([^.]+)[.]git(.*) {
+	return 301 https://git.causal.agency/$1$2?$query_string;
+}
+location ~* /june/([^/]+) {
+	return 301 https://git.causal.agency/$1;
+}
+location / {
+	return 301 https://git.causal.agency;
+}
+.Ed
+.
+.Pp
+This redirects any links to my gitea repos
+to the corresponding repo in cgit.
+The first
+.Sy location
+block also redirects gitea HTTP clone URLs to cgit
+so that
+.Xr git-pull 1
+continues to work on existing clones.
+.
+.Ss Update: fast HTTPS clones
+Someone pointed out that cloning my repos
+over HTTPS was incredibly slow,
+and this is because cgit only implements the
+.Dq dumb
+HTTP git transport.
+To speed up cloning,
+I send the URLs used by the
+.Dq smart
+HTTP transport to
+.Xr git-http-backend 1
+instead:
+.Bd -literal -offset indent
+location ~ /.+/(info/refs|git-upload-pack) {
+	fastcgi_pass unix:/var/run/slowcgi.sock;
+	fastcgi_param SCRIPT_NAME /usr/local/libexec/git-core/git-http-backend;
+	fastcgi_param GIT_HTTP_EXPORT_ALL 1;
+	fastcgi_param GIT_PROJECT_ROOT /home/june/pub;
+	include fastcgi_params;
+}
+.Ed
+.
+.Pp
+I factored out the FastCGI parameters
+I'm using with cgit
+to be included here as well.
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/008-how-irc.7 b/www/text.causal.agency/008-how-irc.7
new file mode 100644
index 00000000..aba1bbf9
--- /dev/null
+++ b/www/text.causal.agency/008-how-irc.7
@@ -0,0 +1,193 @@
+.Dd March  8, 2020
+.Dt HOW-IRC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm How I Relay Chat
+.Nd in code
+.
+.Sh DESCRIPTION
+I've been writing a lot of IRC software lately
+.Pq Sx SEE ALSO ,
+and developed some nice code patterns
+that I've been reusing.
+Here they are.
+.
+.Ss Parsing
+I use fixed size buffers almost everywhere,
+so it's necessary to know IRC's size limits.
+A traditional IRC message is a maximum of 512 bytes,
+but the IRCv3 message-tags spec adds
+(unreasonably, in my opinion)
+8191 bytes for tags.
+IRC messages also have a maximum of 15 command parameters.
+.Bd -literal -offset indent
+enum { MessageCap = 8191 + 512 };
+enum { ParamCap = 15 };
+.Ed
+.
+.Pp
+If I'm using tags,
+I'll use X macros
+to declare the set I care about.
+X macros are a way of maintaining parallel arrays,
+or in this case an enum and an array.
+.Bd -literal -offset indent
+#define ENUM_TAG \e
+	X("msgid", TagMsgid) \e
+	X("time", TagTime)
+
+enum Tag {
+#define X(name, id) id,
+	ENUM_TAG
+#undef X
+	TagCap,
+};
+
+static const char *TagNames[TagCap] = {
+#define X(name, id) [id] = name,
+	ENUM_TAG
+#undef X
+};
+.Ed
+.
+.Pp
+The TagNames array is used by the parsing function
+to assign tag values into the message structure,
+which looks like this:
+.Bd -literal -offset indent
+struct Message {
+	char *tags[TagCap];
+	char *nick;
+	char *user;
+	char *host;
+	char *cmd;
+	char *params[ParamCap];
+};
+.Ed
+.
+.Pp
+I'm a fan of using
+.Xr strsep 3
+for simple parsing.
+Although it modifies its input
+(replacing delimiters with NUL terminators),
+since the raw message is in a static buffer,
+it is ideal for so-called zero-copy parsing.
+I'm not going to include the whole parsing function here,
+but I will at least include the part that many get wrong,
+which is dealing with the colon-prefixed trailing parameter:
+.Bd -literal -offset indent
+msg.cmd = strsep(&line, " ");
+for (int i = 0; line && i < ParamCap; ++i) {
+	if (line[0] == ':') {
+		msg.params[i] = &line[1];
+		break;
+	}
+	msg.params[i] = strsep(&line, " ");
+}
+.Ed
+.
+.Ss Handling
+To handle IRC commands and replies
+I add handler functions to a big array.
+I usually have some form of helper as well
+to check the number of expected parameters.
+.Bd -literal -offset indent
+typedef void HandlerFn(struct Message *msg);
+
+static const struct Handler {
+	const char *cmd;
+	HandlerFn *fn;
+} Handlers[] = {
+	{ "001", handleReplyWelcome },
+	{ "PING", handlePing },
+	{ "PRIVMSG", handlePrivmsg },
+};
+.Ed
+.
+.Pp
+Since I keep these arrays sorted anyway,
+I started using the standard
+.Xr bsearch 3
+function,
+but a basic for loop probably works just as well.
+I do wish I could compile-time assert
+that the array really is sorted, though.
+.Bd -literal -offset indent
+static int compar(const void *cmd, const void *_handler) {
+	const struct Handler *handler = _handler;
+	return strcmp(cmd, handler->cmd);
+}
+
+void handle(struct Message msg) {
+	if (!msg.cmd) return;
+	const struct Handler *handler = bsearch(
+		msg.cmd,
+		Handlers, ARRAY_LEN(Handlers),
+		sizeof(*handler), compar
+	);
+	if (handler) handler->fn(&msg);
+}
+.Ed
+.
+.Ss Capabilities
+For IRCv3 capabilties
+I use X macros again,
+this time with another handy macro
+for declaring bit flag enums.
+.Bd -literal -offset indent
+#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+
+#define ENUM_CAP \e
+	X("message-tags", CapMessageTags) \e
+	X("sasl", CapSASL) \e
+	X("server-time", CapServerTime)
+
+enum Cap {
+#define X(name, id) BIT(id),
+	ENUM_CAP
+#undef X
+};
+
+static const char *CapNames[] = {
+#define X(name, id) [id##Bit] = name,
+	ENUM_CAP
+#undef X
+};
+.Ed
+.
+.Pp
+The
+.Fn BIT
+macro declares, for example,
+.Dv CapSASL
+as the bit flag and
+.Dv CapSASLBit
+as the corresponding index.
+The
+.Vt "enum Cap"
+is used as a set,
+for example checking if SASL is enabled with
+.Ql caps & CapSASL .
+.
+.Pp
+These patterns are serving my IRC software well,
+and my IRC projects are serving me well.
+It is immensely satisfying
+to be (near) constantly using software
+that I wrote myself and am happy with,
+regardless of how niche it may be.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/pounce/about "IRC bouncer"
+.It
+.Lk https://git.causal.agency/litterbox/about "IRC logger"
+.It
+.Lk https://git.causal.agency/catgirl/about "IRC client"
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/009-casual-update.7 b/www/text.causal.agency/009-casual-update.7
new file mode 100644
index 00000000..0548436a
--- /dev/null
+++ b/www/text.causal.agency/009-casual-update.7
@@ -0,0 +1,127 @@
+.Dd May  6, 2020
+.Dt CASUAL-UPDATE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm casual update
+.Nd software developments
+.
+.Sh DESCRIPTION
+I've been figuring out more of IMAP
+and Internet Messages in general
+while working on a new project
+so I've revisited some older ones.
+I've copied my somewhat more proper
+IMAP parsing code into them,
+so they should be more robust.
+.
+.Pp
+.Xr imbox 1
+is my tool to export messages
+in mboxrd format directly from IMAP.
+It's mostly for applying patches sent by email
+without having any kind of local mail setup.
+For that,
+it includes the
+.Xr git-fetch-email 1
+wrapper which works very similarly to
+.Xr git-send-email 1 .
+I learned by reading the source of
+.Xr git-subtree 1
+that
+.Xr git-rev-parse 1
+can be used by shell scripts
+to parse long options,
+so I added those.
+I also added the
+.Fl Fl apply
+flag to automatically pipe to
+.Xr git-am 1
+with the right flags for mboxrd.
+.
+.Pp
+.Xr notemap 1
+is a tool for mirroring text files
+to an IMAP Notes mailbox,
+which is used by FastMail's web UI
+and the macOS/iOS Notes app.
+Its original parsing code
+was particularly ad-hoc.
+Since I've now learned
+how UTF-8 headers are encoded,
+I updated it to properly encode
+the file name in the Subject line.
+.
+.Pp
+I also got distracted by
+a conversation about UNIX-domain sockets
+where I was comparing the macOS and FreeBSD
+.Xr unix 4
+pages and the Linux
+.Xr unix 7
+page.
+This lead me to make
+.Xr exman 1 ,
+a tool to locally install and read
+manual pages for Linux, POSIX,
+.Fx ,
+.Nx
+and
+.Ox .
+I've already gotten quite a bit of use out of it.
+.
+.Pp
+In yet another IRC distraction,
+I was talking about some further plans for my IRC software,
+and realized it might be time to write
+my future projects list down.
+I opened a
+.Pa .plan
+file,
+immediately wondered how anyone can write plain text,
+then switched to a
+.Pa plan.7
+file.
+There's nothing I won't use
+.Xr mdoc 7
+for.
+After a little setup,
+I can now be fingered,
+and make jokes about this silly little protocol
+from the days of old.
+.Xr finger 1 Ap s
+default output fills me with joy:
+.Bd -unfilled -offset indent
+No Mail.
+No Plan.
+.Ed
+.
+.Pp
+And speaking of IRC and plans,
+I've been meaning to tag
+.Xr catgirl 1
+version 1.0 for a while now.
+I've been using it as my main client
+and my commits to it have really slowed down.
+When I do tag it,
+I'm planning on writing another post
+about my whole
+.Dq suite
+of IRC software
+and how the parts work together.
+Watch this space.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/imbox "imbox"
+.It
+.Lk https://git.causal.agency/notemap "notemap"
+.It
+.Lk https://git.causal.agency/exman "exman"
+.It
+.Lk https://git.causal.agency/catgirl "catgirl"
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/010-irc-suite.7 b/www/text.causal.agency/010-irc-suite.7
new file mode 100644
index 00000000..515a30ab
--- /dev/null
+++ b/www/text.causal.agency/010-irc-suite.7
@@ -0,0 +1,409 @@
+.Dd June 19, 2020
+.Dt IRC-SUITE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm IRC suite
+.Nd my own IRC software
+.
+.Sh DESCRIPTION
+Over the past months
+.Po
+eight of them, according to
+.Xr git-log 1
+.Pc
+I developed a new
+.Dq suite
+of IRC software
+that I now use full-time,
+consisting of a bouncer,
+a new logging and search solution,
+and a terminal client.
+These new programs share some characteristics:
+they are all TLS-only
+and use the libtls API from LibreSSL,
+they can all be entirely configured from the command line
+or with equivalent configuration files,
+they are all designed as
+a one process to one IRC connection mapping,
+and they all take advantage of IRCv3 features.
+.
+.Pp
+For context,
+I was previously running
+the znc IRC bouncer
+and using the Textual IRC client
+with its plain text logs.
+I also continue to use
+the Palaver IRC client for iOS.
+.
+.Ss Background
+A bouncer is a piece of server software
+that stays connected to IRC at all times
+and acts as a relay
+between your client and the IRC server.
+When the client is disconnected,
+the bouncer buffers incoming messages
+to send to the client when it reconnects.
+.
+.Pp
+Aside from this,
+bouncers have another advantage:
+client multiplexing.
+Several clients,
+for instance on different computers
+or a phone,
+should be able to connect to the same bouncer,
+and send and receive messages under the same nick.
+Unfortunately,
+znc does not handle this use-case well at all.
+Out of the box it offers two options:
+either any client connection totally clears the buffer,
+causing other clients to miss chat history;
+or the buffer is never cleared,
+causing every client connection
+to be repeatedly spammed with redundant history.
+There is also a znc wiki page
+that suggests one way to solve this issue
+is to connect znc to itself multiple times over.
+Yikes.
+.
+.Ss pounce
+My dissatisfaction with
+connecting multiple clients to znc
+directly motivated me to start working
+on a new multi-client-focused IRC bouncer.
+The result is
+.Xr pounce 1 ,
+based on a rather straightforward
+single-producer (the IRC server)
+multiple-consumer (each IRC client)
+ring buffer.
+Each client has its own
+independent position in the buffer
+so can properly be brought up to date
+whenever it connects.
+.
+.Pp
+Additionally,
+by assuming support for the IRCv3
+.Sy server-time
+extension,
+all IRC events can be accurately
+relayed to clients at any time,
+and the internals of
+.Xr pounce 1
+can be kept very simple.
+In fact,
+it completely avoids parsing most IRC messages,
+simply pushing them into the buffer
+with an associated timestamp.
+.
+.Pp
+The usernames sent by clients during registration
+are used as opaque identifiers for buffer consumers.
+This was chosen since most clients
+can be configured to send an arbitrary username,
+and those that don't often default
+to the name of the client itself,
+making it an appropriate identifier.
+.
+.Pp
+Later,
+I added a way for clients
+to be informed of their own buffer positions
+using a vendor-specific IRCv3 capability.
+This means a client
+can save the position
+of the last message it actually received,
+and request to set its position
+when it reconnects,
+ensuring no messages are lost
+to network issues
+or software crashes.
+.
+.Ss calico
+Due to the simple design of mapping
+one process to one IRC (server) connection,
+it is necessary to run several instances of
+.Xr pounce 1 .
+Initially I simply used different ports for each,
+but as I connected to more networks
+and even ran some instances for friends,
+it became less feasible.
+.
+.Pp
+The solution I came up with
+was to dispatch incoming connections
+using Server Name Indication, or SNI.
+This way,
+multiple domains pointing to the same host
+could be used with only one port
+to connect to different instances of
+.Xr pounce 1 .
+For example,
+I use a
+.Li *.irc.causal.agency
+wildcard DNS entry
+and a subdomain for each IRC network,
+all on port 6697.
+.
+.Pp
+The
+.Xr calico 1
+daemon included with pounce
+accomplishes this dispatch
+using the
+.Dv MSG_PEEK
+flag of
+.Xr recvmsg(2)
+on incoming connections.
+Since SNI is immediately sent by TLS clients
+as part of the ClientHello message in clear-text,
+it can be processed
+without doing any actual TLS.
+The connection itself is then
+sent to the corresponding
+.Xr pounce 1
+instance
+over UNIX-domain socket,
+which handles TLS as normal.
+This means that
+.Xr calico 1
+and
+.Xr pounce 1
+operate entirely independently of each other.
+.
+.Ss litterbox
+Based on the multiple-consumer ring buffer design,
+I realized it would be easy
+to implement additional functionality
+as independent purpose-built clients
+which connect to
+.Xr pounce 1
+alongside regular clients.
+This could allow dedicated OTR or DCC software
+to operate in parallel with a basic client,
+or for more passive software
+to provide notifications
+or dedicated logging.
+.
+.Pp
+For the latter,
+I wanted to do better than
+plain text log files.
+.Xr grep 1
+over files works fine,
+but search could be faster and smarter,
+and the text format is
+more lossy and less structured
+than I'd like it to be.
+Conveniently,
+SQLite provides an extension
+(actually two)
+for full-text search.
+.
+.Pp
+The litterbox project
+is my dedicated logging solution
+using SQLite FTS5.
+It consists of three tools:
+the
+.Xr litterbox 1
+daemon itself which connects to pounce
+and logs messages to SQLite,
+the
+.Xr scoop 1
+command line query tool,
+and the
+.Xr unscoop 1
+plain text import tool.
+The
+.Xr scoop 1
+tool constructs SQL queries
+and formats the results for viewing,
+with coloured nicks
+and piped to a pager
+by default.
+.
+.Pp
+The
+.Xr litterbox 1
+daemon
+can also provide a simple
+.Dq online
+.Pq over IRC
+search query interface
+to other connected clients.
+The simplest way to allow different
+.Xr pounce 1
+clients to talk to each other
+was to route private messages to self
+internally without sending them to the IRC server.
+So from any client
+I can simply message myself
+a full-text search query
+and
+.Xr litterbox 1
+responds with the results.
+.
+.Pp
+Along with routing self-messages,
+.Xr pounce 1
+also provides a vendor-specific IRCv3 capability
+for passive clients such as
+.Xr litterbox 1
+to indicate that they should not influence
+the automatic away status,
+which is normally only set
+when no clients are connected.
+.
+.Pp
+An advantage of this architecture
+of dedicated clients
+rather than bouncer modules
+is that they need not run
+on the same host.
+I run my bouncers on a VPS,
+but I'd rather not store my private logs there,
+so
+.Xr litterbox 1
+runs instead on a Raspberry Pi
+in my apartment.
+Also,
+since it is essentially
+just a regular IRC bot,
+it could be used independently
+for keeping public logs for a channel.
+.
+.Ss catgirl
+There's not really that much to say
+about the client,
+.Xr catgirl 1 .
+Of the three projects
+it contains the most code
+but is also the least interesting,
+in my opinion.
+It just does what I want a client to do,
+and gets the details right.
+.
+.Pp
+Tab complete is ordered by most recently seen or used,
+and completing several nicks
+inserts commas between them
+as well as the colon following the final nick.
+In the input line,
+the prompt is updated
+to reflect whether the input
+will be interpreted as a command or as a message.
+Messages are automatically scanned for URLs,
+which can be opened or copied with commands
+specifying the nick or a substring of the URL.
+.
+.Pp
+Scrolling in a window creates a split view,
+keeping the latest messages visible.
+Nick colours are based instead on usernames,
+keeping them more stable across renames,
+and mentions in messages are coloured
+to make the conversation easier to follow.
+The visibility of ignored messages
+can be toggled at any time.
+Channels can be muted
+so their activity is hidden
+from the status line
+unless you are pinged.
+.
+.Pp
+.Xr catgirl 1
+is configured entirely on the command line
+or in equivalent simple configuration files.
+There's no dynamic manipulation of state
+using complex
+.Ql /
+commands like in some other clients.
+.
+.Pp
+The major caveat is that
+.Xr catgirl 1
+connects to only one network at a time.
+This keeps the configuration, the interface
+and the code much simpler.
+.Xr tmux 1 ,
+.Xr screen 1
+or a tabbed terminal emulator
+are good options to run several instances.
+.
+.Pp
+If you're interested in giving
+.Xr catgirl 1
+a quick (and necessarily limited) try,
+you can
+.Li ssh chat@ascii.town .
+.
+.Ss Future
+I think I'm done with IRC software for now.
+As mentioned above,
+there are a few more pieces
+that could fit in to this setup,
+but I don't really want or need them right now.
+One thing I definitely want to try
+at some point
+is adding a litterbox component
+to index the contents of URLs
+to make finding previously shared links easier.
+.
+.Pp
+If you try any of this software
+and have feedback,
+let me know in
+.Li #ascii.town
+on tilde.chat
+or by email.
+And of course,
+patches are always welcome.
+.
+.Ss Update: scooper
+Somehow I had the motivation
+to create a web interface for litterbox:
+.Xr scooper 1 .
+It can be used either as CGI
+or as a FastCGI worker,
+and I used the excellent
+.Xr kcgi 3
+library for it.
+.
+.Pp
+The main advantage of this interface
+is that you can click on a search result
+to be brought to its context in the log viewer.
+I also added an option to
+.Xr litterbox 1
+to provide a corresponding scooper link
+in response to its query interface.
+.
+.Pp
+A small demo of scooper is hosted at
+.Aq Lk "https://causal.agency/scooper/" .
+It publicly logs the
+.Li #litterbox
+channel on tilde.chat.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk "https://git.causal.agency/pounce" pounce
+.It
+.Lk "https://git.causal.agency/litterbox" litterbox
+.It
+.Lk "https://git.causal.agency/catgirl" catgirl
+.It
+.Lk "https://www.sqlite.org/fts5.html" "SQLite FTS5 Extension"
+.It
+.Lk "https://git.causal.agency/scooper" scooper
+.It
+.Lk "https://kristaps.bsd.lv/kcgi/" kcgi
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/011-libretls.7 b/www/text.causal.agency/011-libretls.7
new file mode 100644
index 00000000..c29c325e
--- /dev/null
+++ b/www/text.causal.agency/011-libretls.7
@@ -0,0 +1,220 @@
+.Dd August  9, 2020
+.Dt LIBRETLS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm LibreTLS
+.Nd libtls for OpenSSL
+.
+.Sh DESCRIPTION
+This is a sort of announcement post about LibreTLS,
+my port of libtls from LibreSSL to OpenSSL.
+If you've wanted to try any of my software
+but have been unable to because of LibreSSL,
+LibreTLS is an option that will likely work for you.
+I'm including instructions
+for building it and my IRC software
+on Debian as an example,
+since manually installing libraries
+is less straightforward than it could be.
+.
+.Pp
+libtls is
+.Do
+a new TLS library,
+designed to make it easier to write foolproof applications
+.Dc .
+It was developed as part of LibreSSL,
+.Ox Ap s
+fork of OpenSSL,
+and is implemented against their version of libssl.
+It provides a nice high-level API
+for TLS sockets,
+with functions like
+.Xr tls_connect 3 ,
+.Xr tls_read 3
+and
+.Xr tls_write 3 .
+This is a vast improvement over libssl's
+confusing mess of an API!
+Its relative obscurity is a real shame
+for C programmers.
+.
+.Pp
+An obvious cause of its obscurity
+is that it is tied to LibreSSL.
+Although LibreSSL is available
+for platforms other than
+.Ox ,
+it conflicts with OpenSSL
+so is difficult to install alongside it
+and is often not packaged at all.
+Additionally,
+even if a user manually installs LibreSSL,
+libtls is likely not to work on some distros
+due to its hardcoded CA bundle file path.
+.
+.Pp
+Since libtls is implemented against libssl,
+which originates in OpenSSL,
+it should be possible to use libtls with it.
+This is what I set out to do in LibreTLS.
+I started by importing the sources
+from a LibreSSL-portable release,
+then worked on porting the portions
+that were incompatible with OpenSSL.
+.
+.Pp
+The simpler changes just involved
+replacing internal struct field accesses
+with public APIs.
+libtls accesses libssl internals
+using a hack to get the header files
+to declare private struct fields,
+and for basically no reason.
+The bigger changes involved
+reimplementing some functions
+which only exist in LibreSSL,
+but these were still quite small.
+I also imported the necessary compatibility functions
+from LibreSSL's libcrypto
+and adapated the autotools build files
+to produce only a libtls
+which depends on OpenSSL.
+.
+.Pp
+Along the way
+I decided to make one small behavioural change
+in order for LibreTLS to be more likely
+to work for everyone.
+I removed the hardcoded CA file path
+and changed the default configuration
+to use OpenSSL's default CA paths,
+which include a CA directory.
+This seems to be the preferred CA source
+on systems such as Debian,
+where the default CA file path doesn't exist.
+.
+.Pp
+I think the reason LibreSSL
+wants to avoid using a CA directory
+is so that it can fully load the CA file
+once before being sandboxed.
+However,
+using OpenSSL's default configuration,
+the CA file will still be loaded immediately
+if it exists.
+If it doesn't exist,
+sandboxed applications
+will fail when trying to
+load certificates from the directory,
+but unsandboxed applications
+will work just fine.
+Since LibreSSL's libtls
+would fail either way,
+I think the new behaviour
+is an improvement.
+.
+.Pp
+Another advantage of separating libtls from LibreSSL
+is that it is unencumbered by OpenSSL's
+awkward double-license,
+both of which are incompatible with the GPL.
+libtls is all new ISC-licensed code,
+and future versions of OpenSSL (3.0)
+will be released under the Apache 2.0 license,
+which is compatible with GPLv3.
+In the future,
+GPL software will be able to link with
+libtls and OpenSSL without additional permissions.
+.
+.Pp
+It's also worth noting that LibreSSL
+likely will not be able to import any code
+from future versions of OpenSSL,
+since Apache 2.0 is on
+.Ox Ap s
+license shitlist.
+LLVM is also slowly changing their license
+to Apache 2.0,
+so it'll be interesting to see what
+.Ox
+does.
+.
+.Ss Installing Manually
+To install LibreTLS on Debian,
+for example,
+fetch a release tarball from
+.Lk https://causal.agency/libretls/
+and install the build dependencies:
+.Bd -literal -offset indent
+sudo apt-get install build-essential libssl-dev pkgconf
+.Ed
+.
+.Pp
+.Xr pkgconf 1
+isn't a dependency of LibreTLS itself,
+but it's how my software
+configures its build
+for a dependency on libtls.
+The usual build steps
+will install the library:
+.Bd -literal -offset indent
+\&./configure
+make all
+sudo make install
+.Ed
+.
+.Pp
+The library will be installed in
+.Pa /usr/local/lib
+by default,
+and you need to make sure
+the dynamic linker
+will be able to find it there.
+On Debian,
+.Pa /usr/local/lib
+already appears in
+.Pa /etc/ld.so.conf.d/libc.conf ,
+but on other systems
+you'll probably need to add it to either
+.Pa /etc/ld.so.conf
+or a new file such as
+.Pa /etc/ld.so.conf.d/local.conf .
+Once the library is installed
+and the path is configured,
+the linker cache needs to be refreshed:
+.Bd -literal -offset indent
+sudo ldconfig
+.Ed
+.
+.Pp
+You'll probably also need to set
+.Ev PKG_CONFIG_PATH
+for the configure scripts
+of my software:
+.Bd -literal -offset indent
+PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure
+.Ed
+.
+.Pp
+On
+.Fx ,
+LibreTLS and some of my IRC software
+can be installed from my own
+.Lk https://git.causal.agency/ports/ "ports tree"
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/libretls/about LibreTLS
+.It
+.Lk https://man.openbsd.org/tls_init.3 "libtls API documentation"
+.El
+.
+.Pp
+Another alternative libtls implementation,
+.Lk https://sr.ht/~mcf/libtls-bearssl/ "libtls-bearssl"
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/012-inability.7 b/www/text.causal.agency/012-inability.7
new file mode 100644
index 00000000..d352143b
--- /dev/null
+++ b/www/text.causal.agency/012-inability.7
@@ -0,0 +1,39 @@
+.Dd November 26, 2020
+.Dt INABILITY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Inability
+.Nd losing the ability to create
+.
+.Sh DESCRIPTION
+For often weeks, sometimes months at a time,
+I lose the ability to write new code.
+I can still make fixes
+and little cleanups
+in my existing projects,
+but if I try to work on something new,
+nothing happens.
+I can't get anything done.
+.
+.Pp
+I think it's now been
+over 3 months
+since I've created anything.
+I don't know what to do about it.
+In the past I've eventually
+regained the ability to code,
+but it's unclear to me how or why.
+I also don't know what
+I should be doing instead.
+Writing code is the only hobby
+I've ever really developed,
+so without it I basically
+don't do anything.
+.
+.Pp
+Does this happen to anyone else?
+How do you cope?
+.
+.Sh AUTHORS
+.Mt june@causal.agency
diff --git a/www/text.causal.agency/013-hot-tips.7 b/www/text.causal.agency/013-hot-tips.7
new file mode 100644
index 00000000..63b6e353
--- /dev/null
+++ b/www/text.causal.agency/013-hot-tips.7
@@ -0,0 +1,156 @@
+.Dd December  2, 2020
+.Dt HOT-TIPS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm hot tips
+.Nd from my files
+.
+.Sh DESCRIPTION
+This is a short list of tips
+from my configuration files and code
+that might be useful.
+.
+.Ss Shell
+.Bl -tag -width Ds
+.It CDPATH=:~
+This is useful if you sometimes type,
+for example,
+.Ql cd src/bin
+wanting to go to
+.Pa ~/src/bin
+but you aren't in
+.Pa ~ .
+If the path doesn't exist
+in the current directory,
+.Ic cd
+will try it in
+.Pa ~
+as well.
+.
+.It alias ls='LC_COLLATE=C ls'
+This makes it so that
+.Xr ls 1
+lists files in ASCIIbetical order,
+which puts capitalized names like
+.Pa README
+and
+.Pa Makefile
+first.
+.
+.It git config --global commit.verbose true
+Not shell but close enough.
+This makes it so the entire diff is shown
+below the usual summary
+in the editor for a
+.Xr git-commit(1)
+message.
+Useful for doing a quick review
+of what you're committing.
+.El
+.
+.Ss (neo)vim
+.Bl -tag -width Ds
+.It set inccommand=nosplit
+This is the only
+.Xr nvim 1
+feature I really care about
+aside from the improved defaults.
+This provides a live preview of what a
+.Ic :s
+substitution command will do.
+It makes it much easier to
+write complex substitutions.
+.
+.It nmap <leader>s vip:sort<CR>
+This mapping sorts the lines of a paragraph,
+or block of text separated by blank lines.
+I use this a lot to sort
+#include directives.
+.
+.It nmap <leader>S $vi{:sort<CR>
+Similar to the last mapping,
+this one sorts lines inside braces.
+I use this to sort
+switch statement cases
+or array initializers.
+.
+.It nmap <leader>a m':0/^#include <<CR>:nohlsearch<CR>O#include <
+I use this mapping to add new
+#include directives,
+usually followed by
+.Ic <leader>s
+and
+.Ic ''
+to sort them
+and return to where I was.
+.
+.It nmap <leader>d :0delete<CR>:0read !date +'.Dd \e%B \e%e, \e%Y'<CR>
+I use this to replace the first line of
+.Xr mdoc 7
+files with the current date.
+.El
+.
+.Ss C
+.Bl -tag -width Ds
+.It #define Q(...) #__VA_ARGS__
+This is what I've started using
+to quote things like SQL statements
+or HTML fragments in C.
+Anything that happens to be valid C tokens,
+which is most code,
+can be quoted this way.
+Macros are not expanded
+inside the quoted part.
+You can embed (matched) quotes
+without having to escape them.
+Whitespace gets collapsed,
+so you can write nicely formatted multi-line SQL
+that doesn't mess up your debug logging,
+for example.
+.Bd -literal -offset indent
+const char *sql = Q(
+	INSERT OR IGNORE INTO names (nick, user, host)
+	VALUES (:nick, :user, :host);
+);
+.Ed
+.
+.It #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+I use this macro to declare bitflag enums.
+It takes advantage of
+auto-incrementing enum items
+so you don't need to set the values manually.
+You also get constants
+for both the bit index
+and the flag value
+for each item.
+.Bd -literal -offset indent
+enum Attr {
+	BIT(Bold),
+	BIT(Reverse),
+	BIT(Italic),
+	BIT(Underline),
+};
+.Ed
+.Pp
+For example,
+defines
+.Sy ItalicBit = 2
+and
+.Sy Italic = 1 << 2 .
+Ignore the extraneous constants.
+.
+.It typedef int FnType(const char *str, size_t len);
+You can just typedef function types!
+It annoys me more than it probably should
+that everyone writes ugly
+function pointer typedefs.
+Just stick
+.Sy typedef
+on the front of a function declaration
+and use
+.Vt FnType * .
+.El
+.
+.Sh AUTHORS
+.Mt june@causal.agency
diff --git a/www/text.causal.agency/014-using-vi.7 b/www/text.causal.agency/014-using-vi.7
new file mode 100644
index 00000000..e6a6a7a0
--- /dev/null
+++ b/www/text.causal.agency/014-using-vi.7
@@ -0,0 +1,135 @@
+.Dd January 11, 2021
+.Dt USING-VI 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Using vi
+.Nd simpler tools
+.
+.Sh DESCRIPTION
+Happy new year
+and hello from
+.Xr vi 1 !
+I'm in the mood to post something
+but not in the mood for
+.Dq social
+media.
+This one will probably be short.
+.
+.Pp
+Yesterday I was trying to work on sandboxing
+.Xr catgirl 1
+(that's the IRC client I work on)
+with
+.Xr pledge 2
+and
+.Xr unveil 2
+on
+.Ox ,
+as suggested by the maintainer of its port.
+I've done similar things before,
+but only on server software
+rather than user software.
+.
+.Pp
+Anyway I was in
+.Xr ssh 1
+to my
+.Ox
+VM
+.Po
+sadly I don't currently have any hardware to run
+.Ox
+on
+.Pc
+using my usual editor,
+which is
+.Xr nvim 1 .
+I'm honestly not very thrilled
+with what neovim is doing lately,
+but the cleaned up defaults
+make my configuration files happier.
+.
+.Pp
+The real problem with
+.Xr nvim 1 ,
+though,
+is that it's laggy as hell on
+.Ox .
+There is significant delay
+on every single keystroke,
+as if I'm typing remotely to a server
+on the other side of the world,
+but this is on a local VM!
+.
+.Pp
+So I did the only reasonable thing:
+I typed
+.Sy :qa
+followed by
+.Sy vi .
+The difference was astonishing.
+Typing and editing suddenly felt
+.Em physical
+again.
+(I put that in italics even though I know it won't render.)
+Not only was it a vast improvement over
+.Xr nvim 1
+in
+.Xr ssh 1
+in a VM,
+it was a marked improvement over
+.Xr nvim 1
+running locally and natively.
+.
+.Pp
+Now obviously
+.Xr vi 1
+doesn't have all the bells and whistles
+of newer editors,
+but of course the core editing model
+that makes
+.Xr vim 1
+and
+.Xr nvim 1
+so good is there,
+and in purer form,
+I think.
+The
+.Xr vi 1
+manual page
+is feasible to just sit down and read,
+and learn everything there is to know about the editor.
+I set up a basic configuration
+and got coding.
+.Bd -literal -offset indent
+export EXINIT='set ai ic sm sw=4 ts=4'
+.Ed
+.
+.Pp
+After I finished my
+.Xr pledge 2
+and
+.Xr unveil 2
+patch,
+I was so pleased with
+.Xr vi 1
+that I kept on using it
+yesterday and today
+for other work,
+and obviously to write this post.
+Despite the lack of editor amenities,
+its responsiveness and simplicity
+are enough to make using it
+.Em comfortable
+and perhaps
+.Em cosy .
+I'm not sure I'll ever use
+.Xr vi 1
+full-time,
+but for now I am much less likely
+to launch
+.Xr nvim 1 .
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/015-reusing-tags.7 b/www/text.causal.agency/015-reusing-tags.7
new file mode 100644
index 00000000..19546496
--- /dev/null
+++ b/www/text.causal.agency/015-reusing-tags.7
@@ -0,0 +1,155 @@
+.Dd January 17, 2021
+.Dt REUSING-TAGS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm reusing tags
+.Nd beyond ctags
+.
+.Sh DESCRIPTION
+I've tried to start writing this post a couple times now
+and I keep getting bogged down in explanations,
+so I'm just going to tell you
+about some cool things I did
+and hope they make sense.
+.
+.Pp
+When I wrote my first syntax highlighter,
+I decided that function definitions
+should have anchor links,
+because line number anchor links
+are entirely useless
+if you expect the file to change at all.
+Since the syntax highlighter
+was somewhat deliberately just a big pile of regex,
+I hacked in more regex to try
+to identify function and type definitions.
+It wasn't elegant and it didn't always work well.
+It did work though,
+and I found the links very useful.
+.
+.Pp
+Recently I was thinking about
+the lexer generator
+.Xr lex 1
+and decided to
+rewrite the syntax highlighter
+using it.
+Really syntax highlighting
+is no different than lexical analysis.
+I ran into a problem though,
+trying to preserve my anchor link function,
+because really that should involve
+some amount of parsing.
+Trying to port my regex hacks to
+.Xr lex 1
+made the lexers way more complicated
+and less reliable,
+so I gave up on it for a while.
+.
+.Pp
+And then,
+probably in the shower,
+I realized I was approaching it
+completely from the wrong direction.
+There's already a tool that does what I want,
+and I already use it:
+.Xr ctags 1 .
+All I need to do is use its output
+to insert anchor links
+into my syntax highlighter output.
+In an afternoon I wrote
+.Xr htagml 1 ,
+which loads tag definitions for its input file,
+then scans through the input for where they match.
+It can either HTML-escape
+the input as it goes,
+or use already formatted HTML
+being piped into it from a syntax highlighter.
+.
+.Pp
+The result is three simple tools
+working together to accomplish
+what a more complex tool
+couldn't reliably achieve.
+I'm very pleased with it,
+and I've updated my site and cgit
+to use the new
+.Xr lex 1 Ns -based
+highlighter,
+.Xr ctags 1
+and
+.Xr htagml 1 .
+I'm currently missing a lexer for
+.Xr sh 1 ,
+but I plan to write it eventually.
+I also want to write a tool
+to generate tags for
+.Xr make 1 ,
+.Xr mdoc 7
+and perhaps
+.Xr sh 1 .
+The cool thing about generating more kinds of tags
+is that they'll not only improve
+the HTML output,
+they'll also be usable in my editor.
+.
+.Pp
+Speaking of generating different kinds of tags,
+I also wrote some scripts not too long ago
+for reading IETF RFCs offline.
+The plain text files are available to
+.Xr rsync 1 ,
+but they're hard to navigate on their own.
+By scanning the files for headings
+and generating tags,
+it allows jumping to sections using
+.Ic :ta
+or
+.Ic ^]
+in
+.Xr vi 1 .
+For
+.Xr nvim 1
+I also added an
+.Ic :RFC
+command to open an RFC by number
+and set up
+.Ic ^]
+to work optimally for them.
+.
+.Pp
+I'm still using
+.Xr vi 1
+for most of my editing,
+by the way.
+And of course
+.Xr ctags 1
+was made to work with it!
+Simple old tools
+are really doing it for me lately.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://causal.agency/bin/htagml.html htagml
+.It
+.Lk https://causal.agency/bin/hilex.html hilex
+.It
+.Lk https://git.causal.agency/src/tree/doc/rfc rfctags
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Sh ADDENDUM
+.Xr catgirl 1 ,
+.Xr pounce 1 ,
+.Xr litterbox 1
+and
+.Xr scooper 1
+all have new releases,
+if you're using any of them.
+Also, this space is now
+available over gopher,
+if that's your sort of thing.
diff --git a/www/text.causal.agency/016-using-openbsd.7 b/www/text.causal.agency/016-using-openbsd.7
new file mode 100644
index 00000000..b843e3c3
--- /dev/null
+++ b/www/text.causal.agency/016-using-openbsd.7
@@ -0,0 +1,505 @@
+.Dd February 14, 2021
+.Dt USING-OPENBSD 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Using OpenBSD
+.Nd for real
+.
+.Sh DESCRIPTION
+Hello from
+.Ox !
+After wishing one too many times
+that I had a real BSD
+on a physical machine,
+I finally got around to
+just installing one on my
+mid-2014 MacBook Pro.
+I hadn't done it sooner
+because I didn't realize
+how easy it would be.
+It helped that I already had a
+.Dq Boot Camp
+partition with a disused Windows 8 install
+that I could replace.
+.
+.Pp
+I roughly followed an old jcs gist
+along with the
+.Ox
+Disk Setup guide.
+I'm once again happy
+that I bought a printer\(em
+they're very useful for instructions
+to install an operating system
+on your only usable computer.
+I set up encrypted softraid
+and the operating system
+installed smoothly.
+.
+.Pp
+Next I had to install rEFInd,
+since the default Mac boot manager
+is really not keen on booting much.
+Installing it requires using the
+macOS recovery partition these days.
+But there was a problem
+with my new boot menu:
+I was promised a picture of Puffy,
+and instead I just got some abstract coloured circles!
+Turns out a bunch of OS icons
+got removed from rEFInd at some point,
+and I had to rescue Puffy
+from the git history.
+.
+.Pp
+So I could happily boot
+.Ox
+by selecting Puffy,
+but I had no networking.
+I thought the wifi chip might be supported by
+.Xr bwfm 4 ,
+but I got unlucky and it's a BCM4360,
+which everything hates.
+Based on the jcs gist,
+I checked the list of hardware
+supported by the
+.Xr urtwn 4
+driver for a wifi dongle to order.
+Just having a clear list
+in the driver manual is wonderful.
+I went with the Edimax EW-7811Un v2,
+which I could get for around $20.
+It's nice and tiny,
+though it has a piercing blue LED
+(destroy all blue LEDs)
+which I had to cover with electrical tape.
+.
+.Pp
+I had to do one other thing
+before I could get it all working, though.
+When I had checked the
+.Xr urtwn 4
+hardware list,
+I had been looking at
+.Ox Ns -current ,
+but I had installed
+.Ox 6.8 ,
+and support for the v2 hardware
+I had bought was added after that release.
+So I downloaded a snapshot
+.Pa bsd.rd
+along with the
+.Xr urtwn 4
+firmware file
+to a USB drive
+and upgraded the system.
+.
+.Pp
+Connecting to wifi with
+.Xr ifconfig 8
+is a breeze, by the way,
+and then you just write the same thing to a
+.Xr hostname.if 5
+file to make it automatic.
+I wanted to use
+.Ox
+for exactly this reason:
+simple, consistent, cohesive, well-documented tools.
+.
+.Pp
+Finally, I got to configuring.
+The console is configured with
+.Xr wsconsctl 8 ,
+and similarly you can put the commands in
+.Xr wsconsctl.conf 5
+to have them run at boot.
+I added
+.Li display.brightness=50%
+to tone down the brightness,
+which is initially 100%,
+and
+.Li keyboard.backlight=0%
+to turn off those annoying lights.
+.Xr wsconsctl.conf 5
+is also where you can set
+trackpad settings if you're not using
+.Xr synaptics 4 .
+I ended up using:
+.Bd -literal -offset indent
+mouse1.tp.tapping=1
+mouse1.tp.scaling=0.2
+mouse1.reverse_scrolling=1
+.Ed
+.Pp
+This enables tapping with several fingers
+to simulate different mouse buttons,
+makes the cursor move at a reasonable speed
+and scrolling move in the right direction.
+I also set up my usual modified QWERTY layout.
+.
+.Pp
+For
+.Xr X 7
+I had enabled
+.Xr xenodm 1 ,
+which seems quite nice.
+It automatically prompts you to add your
+.Xr ssh 1
+keys to
+.Xr ssh-agent 1
+when you log in.
+One of the reasons I had not wanted
+to set up another graphical system
+is that I thought
+I would have to make too many choices,
+and that I would have to choose least bad options
+rather than actually good options,
+but
+.Ox
+already includes reasonable choices.
+I wanted to use
+.Xr cwm 1 ,
+so I started a basic
+.Pa .xsession
+file:
+.Bd -literal -offset indent
+\&. ~/.profile
+export LC_CTYPE=en_US.UTF-8
+xset r rate 175 m 5/4 0
+xmodmap ~/.config/X/modmap
+xrdb -load ~/.config/X/resources
+exec cwm -c ~/.config/cwm/cwmrc
+.Ed
+.
+.Pp
+The
+.Xr xset 1
+command sets keyboard repeat rate
+and mouse acceleration.
+I spent some time going through
+.Xr cwm 1 Ap s
+functions and writing up bindings
+that would get me something close enough
+to what I'm used to in macOS.
+Most importantly,
+putting everything on the 4 modifier (command key).
+.
+.Pp
+I also added key bindings on F1 and F2
+to adjust the brightness with
+.Xr xbacklight 1 ,
+and on F10, F11 and F12
+to adjust volume with
+.Xr sndioctl 1 .
+I'm not sure why the F keys
+just send regular F1, F2, etc.\&
+regardless of the Fn key.
+I don't use F keys for anything else though,
+so I'm not too concerned.
+Once again,
+.Xr sndioctl 1
+is such an easy straightforward tool:
+.Bd -literal -offset indent
+bind-key F10 "sndioctl output.mute=!"
+bind-key F11 "sndioctl output.level=-0.05"
+bind-key F12 "sndioctl output.level=+0.05"
+.Ed
+.
+.Pp
+For aesthetic configuration,
+I added a new output to my
+.Xr scheme 1
+colour scheme tool for
+.Xr X 7 Ns -style
+RGB and
+.Xr xterm 1
+resources.
+Normally I use the
+.Em Go Mono
+font,
+but since
+.Ox
+already includes
+.Em Luxi Mono ,
+which
+.Em Go Mono
+is based on,
+I used that.
+The most important configuration
+to make anything readable on a high-DPI display is:
+.Bd -literal -offset indent
+Xft.dpi: 144
+Xft.antialias: true
+Xft.hinting: false
+.Ed
+.
+.Pp
+I'm annoyed that I haven't found
+where these resources are actually documented.
+I would hope they'd be in
+.Xr Xft 3
+or something,
+but they're not.
+Anyway,
+turning off hinting
+seems absolutely necessary
+to prevent text from looking like garbage.
+.
+.Pp
+It seems that to get a reasonably sized cursor
+I need to install
+.Sy xcursor-dmz .
+I'd prefer if there wasn't this one
+extra package that I needed
+for a reasonable setup.
+Tangentially,
+I've never understood why
+the black versions of dmz cursors
+are called
+.Dq aa
+when it seems like that
+would stand for antialiasing
+or something.
+.Bd -literal -offset indent
+Xcursor.size: 64
+Xcursor.theme: dmz-aa
+.Ed
+.
+.Pp
+For a desktop background,
+I found a cute bitmap (little picture)
+of snowflakes already in the system
+and used colours from my usual scheme:
+.Bd -literal -offset indent
+xsetroot -bitmap /usr/X11R6/include/X11/bitmaps/xsnow \e
+	-bg rgb:14/13/0E -fg rgb:7A/49/55
+.Ed
+.
+.Pp
+Since I'd rather not install anything
+I don't have to,
+I went with the default
+.Xr xterm 1 .
+It seems more than adequate, honestly.
+I read through its RESOURCES
+section to configure it how I like.
+The important settings are
+.Sy XTerm*utf8
+and
+.Sy XTerm*metaSendsEscape .
+Since I'm used to copying and pasting on macOS,
+I added equivalent
+.Dq translations :
+.Bd -literal -offset indent
+XTerm*VT100*translations: #override \en\e
+	Super <Key>C: copy-selection(CLIPBOARD) \en\e
+	Super <Key>V: insert-selection(CLIPBOARD)
+.Ed
+.
+.Pp
+The next thing I needed
+was a clock and battery indicator.
+I actually had my battery die on me
+while I was doing all this,
+which reminded me.
+.Xr xclock 1
+would be perfect,
+but then I'd need something else
+for battery.
+There are a couple basic battery indicators
+for X in ports,
+but they're terribly ugly.
+I wanted something as simple as
+.Xr xclock 1 ,
+but that I could add some other text to.
+Then I realized I could just use
+.Xr xterm 1
+for that.
+To my
+.Pa xsession
+I added:
+.Bd -literal -offset indent
+xterm -name clock -geometry 14x1-0+0 -sl 0 -e clock &
+.Ed
+.Pp
+This places a little terminal
+in the top-right corner of the screen
+with no scrollback lines,
+running a script called
+.Pa clock .
+To have
+.Xr cwm 1
+treat it like a
+.Dq panel
+and show it on every desktop,
+I added this to my
+.Pa cwmrc :
+.Bd -literal -offset indent
+ignore clock
+autogroup 0 clock,XTerm
+.Ed
+.Pp
+The
+.Pa clock
+script simply uses
+.Xr date 1
+and
+.Xr apm 8
+to print the time and battery charge
+every minute:
+.Bd -literal -offset indent
+tput civis
+sleep=$(( 60 - $(date +'%S' | sed 's/^0//') ))
+while :; do
+	if [ $(apm -a) -eq 1 ]; then
+		printf '%3s%%' "$(apm -l)"
+	else
+		test $(apm -b) -eq 2 && tput setaf 1 bold
+		printf '%3.3sm' "$(apm -m)"
+		tput sgr0
+	fi
+	printf ' %s\r' "$(date +'%a %H:%M')"
+	sleep $sleep
+	sleep=60
+done
+.Ed
+.Pp
+The initial setting of
+.Va sleep
+is to align the updates
+with the minute ticking over.
+I made the battery output
+a bit fancier by showing
+percentage while charging,
+minutes left while discharging,
+and highlighting in red
+when the battery is
+.Dq critical .
+.
+.Pp
+Now is a good time to mention adding
+.Ql apmd_flags=-A
+to
+.Pa /etc/rc.conf.local
+to enable
+.Dq automatic performance adjustment ,
+or not running your battery flat
+as fast as possible mode.
+It seems like I can get up to 3 hours
+of battery life depending on the screen brightness,
+but this is quite an old battery by now.
+.
+.Pp
+The other thing I needed
+was something to tone down
+that awful, evil blue light from the screen.
+I asked around and someone told me about
+.Xr sct 1 ,
+originally written by tedu.
+The package also includes a little
+.Xr sctd 1
+script that you can add to your
+.Pa .xsession
+to have it automatically adjust
+the colour temperature throughout the day.
+My eyes are no longer being assaulted.
+.
+.Pp
+While I was doing all this,
+I of course needed to talk about it on IRC,
+and it was very nice to be able to
+install my own IRC client with:
+.Bd -literal -offset indent
+doas pkg_add catgirl
+.Ed
+.Pp
+I don't plan to do
+general Web Browsing on
+.Ox ,
+and there is definitely
+no good choice for browser,
+so I just installed
+.Xr imv 1 ,
+.Xr mpv 1 ,
+.Xr youtube-dl 1
+and
+.Xr w3m 1 .
+I wrote a script
+to open images by piping
+.Xr curl 1
+into
+.Xr imv 1 ,
+videos with
+.Xr mpv 1 ,
+and everything else with
+.Xr w3m 1
+in a new
+.Xr xterm 1 .
+Annoyingly,
+.Xr mpv 1
+seems incapable of exiting
+without segfaulting.
+That's quality.
+.
+.Pp
+One thing I am still missing
+is automatic brightness adjustment
+based on ambient light
+like macOS can do.
+I can read the sensor with
+.Xr sysctl 8
+.Cm hw.sensors.asmc0.illuminance0 ,
+which is measured in lux.
+I tried doing something with it in a script,
+but it seems tricky to map its value
+to brightness adjustments
+and to play nice with manual brightness changes,
+so I'll just keep doing it manually for now.
+.
+.Pp
+Update:
+prx sent mail to let me know about
+.Aq Lk https://github.com/jcs/xdimmer .
+I should've guessed jcs had written something.
+.
+.Pp
+And that's my current
+.Ox
+setup after a week of using it.
+I'm quite enjoying it,
+and still being pleasantly surprised
+by the quality-of-life from
+.Ox
+tools and documentation.
+For a small example,
+I can jump to sections
+or flag definitions in
+.Xr man 1
+using
+.Ic :t .
+Systems without basic usability like that
+should be ashamed.
+.
+.Pp
+I would post a screenshot,
+but this is
+.Li text.causal.agency
+;)
+.
+.Sh SEE ALSO
+.Lk https://gist.github.com/jcs/5573685
+.Pp
+My full configurations are in
+.Aq Lk https://git.causal.agency/src .
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Sh BUGS
+There's a red LED
+inside the headphone jack
+that is always on
+and I have no idea how to turn off.
+If anyone knows
+please send me an email.
diff --git a/www/text.causal.agency/017-unpasswords.7 b/www/text.causal.agency/017-unpasswords.7
new file mode 100644
index 00000000..f9643f2f
--- /dev/null
+++ b/www/text.causal.agency/017-unpasswords.7
@@ -0,0 +1,153 @@
+.Dd February 20, 2021
+.Dt UNPASSWORDS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Unpasswords
+.Nd password anti-management
+.
+.Sh DESCRIPTION
+Right away I want to say
+that I'm not trying to tell anyone
+how to manage their online authentication.
+This is just how I do it,
+and I haven't seen anyone else write about it.
+.
+.Pp
+I don't use a password manager.
+It's not a type of software
+I want to deal with.
+For the small handful of sites
+that I use regularly
+and that actually matter,
+I use strong passwords
+(stored in my noggin)
+and TOTP.
+For everything else,
+I simply do not know the password,
+and neither does any software.
+.
+.Pp
+I think I started doing this one time
+when I had legitimately forgotten
+the password to some old account.
+I clicked on
+.Dq forgot my password
+and opened the email,
+but I didn't want to
+come up with a new password
+I would just forget again.
+Instead I set a random one
+.Po
+I usually use
+.Ql openssl rand -base64 33
+for this
+.Pc
+and immediately used that to log in
+while it was still in my clipboard.
+Next time I wanted to log in,
+I could use
+.Dq forgot my password
+again.
+.
+.Pp
+Thinking about it,
+I realized that any web authentication
+with an email password reset flow
+is only ever as strong as
+the authentication for your email account.
+So what is the point of having
+all these passwords set on different sites?
+They all answer to your email account,
+and storing them in a password manager
+seems to add another potential point of failure.
+May as well have no other passwords at all,
+or as close as possible.
+.Po
+Shout out to sites like Liberapay
+and asciienema
+which let me not set a password at all.
+.Pc
+.
+.Pp
+So I started doing that for any site
+that I don't regularly log in to.
+Going through the password reset flow
+can be a bit slow,
+but it doesn't need to be done often.
+And I can do it from anywhere
+I have access to my email,
+which I feel is more easily reliable
+than syncing password management databases.
+It's quite stress-free.
+.
+.Pp
+After doing this manually for years,
+this week I finally got around to
+writing some automation for it.
+A while ago I had written
+.Xr imbox 1 ,
+a tool to directly export mail
+in mboxrd format from IMAP,
+along with
+.Xr git-fetch-email 1 ,
+a wrapper which offloads configuration to
+.Xr git-config 1 .
+It can match emails by
+Subject, From, To and Cc.
+This week I added a flag
+to use IMAP IDLE
+to wait for a matching message
+if there isn't one already,
+and a flag to move matching messages
+(for example to Trash)
+after exporting them.
+.
+.Pp
+With those two new flags,
+I started writing some shell scripts
+to automate the password reset flow
+using
+.Xr curl 1
+to submit forms and
+.Xr git-fetch-email 1
+with
+.Xr sed 1
+to pull the reset tokens
+from my inbox.
+At the end of the script,
+the random password it set
+is copied to the clipboard
+and the login page for the site is opened.
+So now logging in is as simple
+as running a command,
+waiting for the login page to open,
+and pasting.
+.
+.Pp
+The script isn't sophisticated,
+but I don't think it needs to be.
+I've written functions
+for a couple different sites already,
+and they all work in mostly the same way.
+Writing a new one is just a matter
+of identifying the form URLs and fields
+along with where the token is in the email.
+I'm not going to turn this automation
+into any kind of generally usable project,
+because I don't want to have to
+maintain functions for tonnes of different services.
+If you're interested in this idea,
+I encourage you to use my script as a template
+and implement the functions for services you use.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/imbox
+.It
+.Lk https://causal.agency/bin/sup.html
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/018-operating-systems.7 b/www/text.causal.agency/018-operating-systems.7
new file mode 100644
index 00000000..691102e2
--- /dev/null
+++ b/www/text.causal.agency/018-operating-systems.7
@@ -0,0 +1,86 @@
+.Dd February 22, 2021
+.Dt OPERATING-SYSTEMS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Operating systems
+.Nd criteria
+.
+.Sh DESCRIPTION
+Sometimes in conversation
+I use the term
+.Dq real operating system
+which people,
+perhaps rightfully,
+take as inflammatory.
+But I have actually thought about
+what I mean when I say
+.Dq real operating system
+and come up with
+this list of criteria.
+.
+.Pp
+An operating system should be...
+.Bl -bullet
+.It
+Consistent and cohesive:
+all parts of the system should have similar
+usage, configuration, documentation and so on.
+Parts of the system should naturally work together,
+because they were designed to do so.
+.
+.It
+Documented:
+the system should include its own documentation.
+A user should not have to
+search some external wiki
+to learn how the system works.
+It should be obvious
+where to find documentation
+on a particular topic.
+.
+.It
+Programmable:
+the system should provide
+a way to program the computer.
+A computer which cannot be programmed
+is not a computer at all.
+Usually this takes the form
+of a C compiler
+and the tools that go with it.
+In earlier times,
+it might have been
+a BASIC interpreter.
+.
+.It
+Examinable and modifiable:
+the full source tree
+for the system should be included,
+or easily obtainable
+through official means.
+A user should have no trouble
+finding the corresponding source
+for a part of the system.
+Together with the previous point,
+the source tree should be
+compiled by the included toolchain,
+allowing local modification.
+.El
+.
+.Pp
+Some things that may be parts
+of real operating systems,
+but are not themselves operating systems:
+a kernel,
+a package manager,
+a collection of packages.
+.
+.Pp
+I will leave it as an
+.Dq exercise for the reader
+to guess which operating systems
+meet these criteria
+and which don't.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/019-mailing-list.7 b/www/text.causal.agency/019-mailing-list.7
new file mode 100644
index 00000000..b3490a94
--- /dev/null
+++ b/www/text.causal.agency/019-mailing-list.7
@@ -0,0 +1,286 @@
+.Dd March  4, 2021
+.Dt MAILING-LIST 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Mailing List
+.Nd a small-scale approach
+.
+.Sh DESCRIPTION
+When I initially published
+some software I expected
+other people to use,
+I just asked that patches
+be mailed directly to me,
+but I figured that
+if more people were interested,
+it would be better
+to have a mailing list.
+Unfortunately
+email software,
+mailing list options in particular,
+are quite daunting.
+I wanted a light-weight option
+that would require me to host
+as little software as possible.
+.
+.Pp
+My regular email is hosted by Fastmail,
+and I poked around its settings
+to see what I could do.
+It turns out Fastmail lets you
+configure address aliases to
+.Dq also send to all contacts in
+a contacts group.
+That's a mailing list!
+I created a group called
+.Dq List
+and an alias called
+.Mt list@causal.agency
+configured to deliver to that group.
+So it's really just an alias
+for my regular address
+that happens to also
+deliver to another group of people.
+.
+.Pp
+It's easier to just configure
+and manage one mailing list,
+so what I do is ask patches and feedback
+to be sent to
+.Mt list+catgirl@causal.agency ,
+for example.
+Fastmail treats any
+.Ar +suffix
+the same as the base address,
+but the full address can be used
+by subscribers to filter mail by topic
+if they wish.
+.
+.Pp
+To subscribe someone to the list,
+I add their contact to the group.
+For a long time I was planning
+to write some software
+to manage these subscriptions.
+It should be possible
+to process subscription requests from IMAP
+and manipulate the contact group with CardDAV.
+When I went to start implementing this,
+however,
+I found CardDAV (and WebDAV in general)
+completely inscrutable.
+It's the kind of protocol
+that is split across like 20
+different RFCs
+and you can't understand anything
+by just reading
+the one you actually care about.
+So I've given up on that
+and will keep manually subscribing people
+on request.
+.
+.Pp
+The only thing missing, then,
+is a way for people to read
+mail sent to the list
+while they aren't subscribed.
+All the existing
+mailing list archive software
+I know of
+expects to have the mail locally,
+but I'd rather keep all my mail in IMAP.
+First,
+in order to make sure
+I keep a complete archive
+of the mailing list in IMAP,
+I added a small amount
+of Sieve code
+to my Fastmail filters configuration:
+.Bd -literal -offset indent
+if address :matches ["To", "Cc"] "list*@causal.agency" {
+	fileinto :copy :flags "\e\eSeen" "INBOX.List";
+}
+.Ed
+.
+.Pp
+Sieve is a small standard language
+specifically for filtering mail.
+This bit of code matches
+anything sent to the list
+and adds a copy of it
+(the original is going into my inbox)
+to the
+.Dq List
+folder
+and marks the copy as read.
+.
+.Pp
+With a pristine IMAP mailbox
+to export from,
+I wrote a new archive generator.
+It's called
+.Xr bubger 1
+kirg (have it in a way).
+My goal was to render directly from IMAP
+and produce only static files as output,
+making it not only easy to serve,
+but also to run in one place
+and copy the files elsewhere.
+That's important to me
+because it has access to my email,
+so I'd rather run it
+on my local network and
+.Xr rsync 1
+its output into The Cloud.
+The static files are in
+HTML, Atom and mboxrd formats.
+.
+.Pp
+The architecture of
+.Xr bubger 1
+is that for each piece of mail,
+identified by its UID in the mailbox,
+HTML and Atom fragments
+are exported along with the mboxrd.
+Those fragments are then stitched together
+using the IMAP SORT and THREAD extensions
+to make full pages and feeds
+for each thread.
+The fragments act as a cache
+for subsequent runs.
+.
+.Pp
+I admit I did some
+pretty questionable things
+to achieve this.
+Namely,
+I wrote a small string templating engine in C.
+I use it to produce the HTML
+and XML for Atom,
+as well as to generate URLs
+and paths.
+I'm really happy with how it works, actually.
+This is also where
+I really started using
+one of my favourite C hacks:
+.Bd -literal -offset indent
+#define Q(...) #__VA_ARGS__
+.Ed
+.
+.Pp
+I quote all my HTML/XML templates
+with this and it's lovely.
+.
+.Pp
+I've been working on
+.Xr bubger 1
+on and off for almost a year now,
+and it's been interesting.
+I learned a lot about how email
+works from having to deal with
+all the ways a message can be.
+Thankfully a lot of that dealing
+is done by the IMAP server.
+.
+.Pp
+As for running it,
+I initially just ran it with
+.Xr cron 8 ,
+and that's still a good way to go.
+To hook it up to
+.Xr rsync 1 ,
+pipe it like so:
+.Bd -literal -offset indent
+bubger -C list [...] | rsync -a --files-from=- list remote:list
+.Ed
+.
+.Pp
+Later,
+I got a little annoyed
+with having to wait
+for the next run
+if I wanted to link
+to some mail I just received.
+I added an option
+to use IMAP IDLE
+to wait for new mail continuously
+and I started running it
+under my process supervisor,
+.Xr catsitd 8 .
+.
+.Pp
+The setup is a little more complex
+to feed the list of updated files to
+.Xr rsync 1 .
+I added the
+.Xr catsit-watch 1
+utility to run a command
+when a file changes,
+and in my
+.Xr catsit.conf 5
+I have the following:
+.Bd -literal -offset indent
+bubger	~/.local/libexec/bubger
+rsync	catsit-watch -i -f ~/list/UIDNEXT ~/.local/libexec/rsync
+.Ed
+.
+.Pp
+The
+.Pa ~/.local/libexec/bubger
+script runs
+.Xr bubger 1 ,
+writing the list of updated paths to
+.Pa ~/list/FILES :
+.Bd -literal -offset indent
+exec bubger -i -C ~/list [...] >~/list/FILES
+.Ed
+.
+.Pp
+And the
+.Pa ~/.local/libexec/rsync
+script gets run each time a
+.Xr bubger 1
+update completes
+.Po
+.Pa UIDNEXT
+is always the last file written
+.Pc
+and copies the listed files
+to the remote host:
+.Bd -literal -offset indent
+exec rsync -a --files-from=$HOME/list/FILES ~/list remote:list
+.Ed
+.
+.Pp
+I haven't tagged any
+.Xr bubger 1
+releases yet
+because it hasn't gotten
+a huge amount of testing,
+and I'm not sure anyone but me
+would even want to use it.
+But I'm happy
+with how it's working right now,
+so I might tag 1.0 soon
+just for fun.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://causal.agency/list/
+.It
+.Lk https://git.causal.agency/bubger/about
+.It
+.Lk https://git.causal.agency/catsit/about
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Sh BUGS
+Almost every time
+I try to type
+.Dq mailing list
+I instead type
+.Dq mailist list .
diff --git a/www/text.causal.agency/020-c-style.7 b/www/text.causal.agency/020-c-style.7
new file mode 100644
index 00000000..9816dbc3
--- /dev/null
+++ b/www/text.causal.agency/020-c-style.7
@@ -0,0 +1,172 @@
+.Dd March 16, 2021
+.Dt C-STYLE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm C Style
+.Nd a rough description
+.
+.Sh DESCRIPTION
+This is a rough description
+of the style in which I write C,
+since it's uncommon
+but some people seem to like it.
+I don't have any hard rules,
+it just needs to look right.
+.
+.Ss Superficialities
+I use tabs
+and they're set to 4 characters wide
+in my editor.
+I keep my lines shorter than 80 columns,
+which I enforce by
+not resizing my terminal's width.
+I use block indentation only,
+meaning I write long function calls
+like this:
+.Bd -literal -offset indent
+fprintf(
+    imap.w, "%s UID THREAD %s UTF-8 %s\er\en",
+    Atoms[thread], algo, search
+);
+.Ed
+.Pp
+Anything that can be sorted
+should be sorted,
+with trailing commas where possible.
+This and block indentation
+make for simpler diffs.
+.Pp
+I either write single-line ifs
+or always use braces.
+I put parentheses
+around ternary expressions.
+I use camelCase
+for functions and variables,
+and PascalCase for types and constants.
+When an acronym appears
+in an identifier,
+it's in either all lower case
+or all upper case.
+The despicable SCREAMING_SNAKE_CASE
+is reserved for macros.
+I don't set globals or statics to zero
+since that is already the default.
+I don't compare against zero or NULL
+unnecessarily.
+.
+.Ss \&No side-effects in control flow
+I never write a function call
+with side-effects
+inside the condition of an if statement.
+I find this makes following the
+.Dq happy path
+through functions
+much easier.
+I write things like this:
+.Bd -literal -offset indent
+pidFile = open(pidPath, O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
+if (pidFile < 0) err(EX_CANTCREAT, "%s", pidPath);
+
+error = flock(pidFile, LOCK_EX | LOCK_NB);
+if (error && errno != EWOULDBLOCK) err(EX_IOERR, "%s", pidPath);
+if (error) errx(EX_CANTCREAT, "%s: file is locked", pidPath);
+.Ed
+.Pp
+I do write side-effects
+inside for and while statement heads,
+since that's generally expected.
+For some reason
+I like to write the constant first
+if I'm comparing the result of an assignment
+with a side-effect.
+.Bd -literal -offset indent
+for (ssize_t len; 0 <= (len = getline(&buf, &cap, file)); ++line)
+.Ed
+.
+.Ss Paragraphs
+I leave blank lines
+between logical chunks of
+.Dq things happening .
+This is usually between side-effects
+with their related error handling,
+or between groups of closely related side-effects.
+I try to keep variable declarations
+glued to the top of the bit of code
+they're used in.
+.
+.Ss Leading break
+I've mentioned this previously.
+I write my switch statement breaks
+before each case label.
+Doing this aligns nicely,
+and being in the habit
+means I always avoid
+accidental fallthrough.
+.Bd -literal -offset indent
+switch (opt) {
+    break; case 'a': append = 1;
+    break; case 'd': delay = strtol(optarg, NULL, 10);
+    break; case 'f': watch(kq, optarg);
+    break; case 'i': init = 1;
+    break; default: return EX_USAGE;
+}
+.Ed
+.
+.Ss Function type definitions
+Function types are always typedef'd,
+and it's the function type itself
+that is defined,
+not a function pointer type!
+I put the typedef above any functions
+that are supposed to be of that type
+so it's clear what the pattern is.
+.Bd -literal -offset indent
+typedef void Action(struct Service *service);
+Action *fn = NULL;
+.Ed
+.
+.Ss Constants
+I prefer enums over #defines
+for integer constants,
+and static const strings over #defines
+unless I want to do concatenation.
+.Bd -literal -offset indent
+enum { Cap = 1024 };
+.Ed
+.Pp
+I avoid the preprocessor
+wherever possible,
+with the notable exception of X macros,
+which I've talked about previously.
+Doing things in the actual language
+makes for easier debugging.
+.
+.Ss Organization
+I usually use only one header file
+in each project.
+The dependency is easy to declare
+and the complete rebuild
+when the header changes
+isn't a problem for small projects.
+Unless it's a single-file program,
+I name the file which contains main
+something generic,
+since the name of the project
+isn't relevant to its function.
+I name functions like
+.Ar nounVerb ,
+and all the functions for a 
+.Ar noun
+are defined in
+.Pa noun.c .
+Not really to do with C,
+but I always put a FILES section
+in my README pages
+to briefly describe
+the layout of the code
+for anyone looking to
+read or make changes to it.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/021-time-machine.7 b/www/text.causal.agency/021-time-machine.7
new file mode 100644
index 00000000..93d35c1e
--- /dev/null
+++ b/www/text.causal.agency/021-time-machine.7
@@ -0,0 +1,144 @@
+.Dd April 25, 2021
+.Dt TIME-MACHINE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Time Machine
+.Nd an awful one
+.
+.Sh DESCRIPTION
+If, like me,
+you have a Raspberry Pi 3 at home
+that you've just upgraded to
+.Fx 13.0
+which has a hard drive
+from an old laptop
+attached to it by USB adapter
+with ZFS on it
+and you want to
+use that as a Time Machine
+backup destination
+over SMB using
+.Xr samba 8 ,
+despite
+.Xr samba 8
+being awful software
+and using ZFS on a system
+with only 1 GB of RAM
+being a terrible idea,
+this is how to do it.
+.
+.Pp
+In
+.Pa /usr/local/etc/smb4.conf :
+.Bd -literal -offset indent
+[global]
+vfs objects = zfsacl catia fruit streams_xattr
+fruit:metadata = stream
+fruit:model = Macmini
+
+[TimeMachine]
+read only = no
+path = /media/zhdd/backup/TimeMachine
+fruit:time machine = yes
+fruit:time machine max size = 250G
+.Ed
+.
+.Pp
+The important thing here is
+.Sy zfsacl
+in the vfs objects list.
+Most pages will tell you about the others,
+but without
+.Sy zfsacl
+Time Machine will just fail to
+create the backup
+and not provide any useful error.
+I'm not actually sure if the
+.Sy fruit:metadata
+setting is required,
+but a bunch of pages recommend it.
+The
+.Sy fruit:model
+just makes it look nice in Finder.
+The rest creates an SMB share called
+.Dq TimeMachine
+that macOS will be willing to use.
+You can limit the size of the share that
+.Xr samba 8
+reports so that Time Machine
+doesn't fill up the whole drive.
+.
+.Pp
+The other important thing to do
+is to create some swap space.
+When I first tried backing up
+to this share,
+it stopped after a while
+because
+.Xr smbd 8
+got killed
+when there was nowhere to swap pages to.
+A wiki page told me to
+create swap on ZFS like this:
+.Bd -literal -offset indent
+zfs create -V 2G \e
+	-o org.freebsd:swap=on \e
+	-o checksum=off \e
+	-o compression=off \e
+	-o dedup=off \e
+	-o sync=disabled \e
+	-o primarycache=none \e
+	zhdd/swap
+swapon /dev/zvol/zhdd/swap
+.Ed
+.
+.Pp
+To be fair to
+.Xr samba 8 ,
+most of the memory
+is being used by the ZFS ARC
+.Po
+which you can see in
+.Xr top 1
+.Pc ,
+but
+.Xr smbd 8
+still seems to be using
+far more memory than is reasonable.
+It's interesting seeing processes
+with 0 RES in
+.Xr htop 1
+because they're all being swapped out
+while the ARC takes half the available RAM.
+And having to wait for my shell
+to be paged back in when I quit
+.Xr htop 1 .
+.
+.Pp
+Anyway,
+as expected this whole thing
+is terribly slow.
+On my initial backup,
+I'm currently at 26.49 GB
+of 104.22 GB
+with an estimate of 8 hours remaining.
+Normally transfer time estimates
+are wildly inaccurate,
+but I think in this case it's right.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Sh BUGS
+.Fx
+doesn't seem to want to mount
+the ZFS volumes on the hard-drive-over-USB
+automatically at boot.
+I have to
+.Xr zpool-import 8
+the drive manually each time.
+I don't know if there's a workaround for this,
+but I don't have anything essential
+to the system on the drive,
+and it doesn't need to reboot often.
diff --git a/www/text.causal.agency/022-swans-are-dead.7 b/www/text.causal.agency/022-swans-are-dead.7
new file mode 100644
index 00000000..8664e886
--- /dev/null
+++ b/www/text.causal.agency/022-swans-are-dead.7
@@ -0,0 +1,164 @@
+.Dd May  5, 2021
+.Dt SWANS-ARE-DEAD 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Swans Are Dead
+.Nd album by Swans
+.
+.Sh DESCRIPTION
+Swans Are Dead
+is the best Swans album.
+Among my favourites are
+Soundtracks for the Blind,
+To Be Kind
+and Love of Life,
+but Swans Are Dead
+is the one I come back to
+most consistently.
+I'm always in the mood
+to listen to these tunes.
+.
+.Pp
+It's interesting to me
+that I enjoy it so much,
+I think because I had the expectation
+that live albums
+are not of the same quality
+as studio albums,
+but that's just completely untrue
+in the case of Swans.
+The performances are excellent
+and the recording is
+for the most part perfect.
+The album feels live,
+without any distracting deficiencies
+of live recording
+that would take you out
+of just enjoying the music.
+.
+.Bl -ohang
+.It Dq Feel Happiness
+This track feels kind of special
+since it's the only song on the album
+that was never released
+as part of another project.
+I absolutely love this format of song.
+It's like 10 minutes of build
+before any lyrics happen,
+which you only get after
+the wave of the first part
+of the song collapses.
+It bookends the first disc nicely with
+.Dq Blood Promise,
+I think,
+which is sort of the reverse.
+.
+.It Dq Blood On Yr Hands
+This is such a great start
+to the Jarboe-focused
+section of the black disc.
+A cappella apart from the hum
+of the equipment on stage,
+I love this vocal performance.
+I sing this song,
+terribly,
+in the shower.
+The lack of instrumental
+seems to make it stick in my mind even more.
+.
+.It Dq I Crawled
+This is another great vocal performance
+by Jarboe.
+It's so much more dynamic and intense
+than the version of this song
+released much earlier on Young God
+with Gira's vocals.
+I remember seeing a bad comment
+somewhere online
+from someone who couldn't stand
+any Swans song Jarboe sang on.
+They must have never heard
+this version of
+.Dq I Crawled.
+Incredible.
+.
+.It Dq Blood Promise
+My favourite track on
+Swans Are Dead,
+by far.
+I had actually never heard of
+.Dq The Whiffenpoof Song
+until I looked up
+the recording they use
+to introduce this song
+and indicate it's the last of the show.
+Anyway,
+this track highlights
+what makes Swans live albums
+so interesting.
+This performance of the song
+has evolved so much
+from the studio recording on
+The Great Annihilator.
+They share the same lyrics,
+but the earlier version is only 4:15,
+to the live version's fifteen and a half minutes!
+And it sucks me in the whole time.
+As the song winds down
+you can hear an audience member yell,
+.Dq Don't stop!
+and I agree.
+.
+.It Dq The Sound
+One of my all-time favourite songs.
+It's the one that got me to listen to
+Soundtracks for the Blind,
+and might've gotten me into Swans altogether.
+I don't quite remember
+what order I started listening to things in.
+This version of it is great.
+I don't think I could choose
+between this and the studio recording.
+There are just
+two ways to enjoy it.
+I love how frantic the guitars get
+at the height of this track.
+.
+.It Dq I See Them All Lined Up
+This version of the song
+is way more harsh
+than the version on Soundtracks.
+It loses some contrast
+between the verses
+and the explosions of sound
+punctuating them,
+it just hits hard
+the whole time.
+.
+.It Dq Yum Yab
+An absolute banger.
+The drums sound so good on this
+and they really get me moving.
+The whole thing is delightfully nasty and fun.
+.El
+.
+.Pp
+Everything else on the album
+is good too,
+of course,
+I just don't have as much to say.
+There's almost two and a half hours of music
+on this thing!
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+I want to try writing
+about different kinds of things here,
+and this is my first attempt
+at doing so.
+There's more music
+I want to write about,
+and maybe some other
+non-computer topics.
diff --git a/www/text.causal.agency/023-sparse-checkout.7 b/www/text.causal.agency/023-sparse-checkout.7
new file mode 100644
index 00000000..925bc043
--- /dev/null
+++ b/www/text.causal.agency/023-sparse-checkout.7
@@ -0,0 +1,144 @@
+.Dd June  9, 2021
+.Dt SPARSE-CHECKOUT 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Sparse Checkout
+.Nd a cool git feature
+.
+.Sh DESCRIPTION
+I was going to write a post about
+.Xr git-subtree 1
+(and I still plan to!)
+but while talking about it
+with a friend
+I came across another command:
+.Xr git-sparse-checkout 1 .
+I got pretty excited because
+I already had a use case for it.
+.
+.Pp
+.Xr git-sparse-checkout 1
+does pretty much what it sounds like.
+It lets you only have
+a subset of files in the repository actually
+.Dq checked out .
+This is really useful
+for huge respositories
+where you are only interested in
+some part of it.
+Any operation touching the working tree
+is much faster because
+it can skip all the files you don't care about.
+.
+.Pp
+My use case is with the
+.Fx
+.Xr ports 7
+tree,
+which recently moved to git
+and contains almost 14 thousand files.
+Working with the whole repository
+was super painful.
+.Xr git-status 1 ,
+which I run as a habit
+when my shell is idle,
+would take dozens of seconds
+to check the whole working tree
+and report back.
+(I didn't get any real time measurements
+before enabling
+.Xr git-sparse-checkout 1 ,
+and I'm not about to disable it now,
+since it'd have to check out
+all those files again.)
+I'm only actually working on
+a small handful of ports,
+so all that work is wasted.
+Time to turn on sparse checkout:
+.Bd -literal -offset indent
+git sparse-checkout init --cone
+.Ed
+.
+.Pp
+The
+.Fl \-cone
+option here
+(which I keep reading as
+.Dq clone
+because it's git)
+restricts the kinds of patterns
+you can use to select files to check out,
+but makes the calculation more efficient.
+Basically it means you can only select
+paths along with everything below them,
+which I think is pretty much
+always what you want anyway.
+Enabling sparse checkout
+can take quite a while
+because it has to do a lot of un-checking-out.
+I should mention
+that you can pass
+.Fl \-sparse
+to
+.Xr git-clone 1
+to avoid ever checking out
+the whole tree.
+.
+.Pp
+The default selection when you run
+.Cm init
+is to check out all the files
+at the root of the repository,
+but none of the subdirectories.
+For
+.Xr ports 7 ,
+I also want to check out
+the shared scripts and Makefiles:
+.Bd -literal -offset indent
+git sparse-checkout add Keywords Mk Templates Tools
+.Ed
+.
+.Pp
+And then I can selectively check out
+just the ports I'm working on:
+.Bd -literal -offset indent
+git sparse-checkout add irc/catgirl irc/pounce
+.Ed
+.
+.Pp
+After enabling sparse checkout,
+.Xr git-status 1
+takes what I'd call
+a normal amount of time.
+I also did this on
+a couple-weeks-out-of-date copy of the
+.Xr ports 7
+tree,
+and when I ran
+.Xr git-pull 1
+it was also really quick,
+because it didn't have to bother
+updating all those files
+I'm not interested in.
+It still downloads all the git objects,
+of course,
+and you can just add any new paths you need
+to the sparse checkout list.
+My disk usage also went down
+by about a gigabyte.
+.
+.Pp
+I'm super pleased to discover this part of git,
+because it makes working with huge
+and/or monorepo-style repositories
+so much more feasible!
+You can see how I came across it,
+since
+.Xr git-subtree 1
+is also a useful tool for monorepos.
+Stay tuned for that post,
+I guess :)
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/024-seprintf.7 b/www/text.causal.agency/024-seprintf.7
new file mode 100644
index 00000000..d1af2e1a
--- /dev/null
+++ b/www/text.causal.agency/024-seprintf.7
@@ -0,0 +1,137 @@
+.Dd June 12, 2021
+.Dt SEPRINTF 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm seprintf
+.Nd an snprintf alternative
+.
+.Sh SYNOPSIS
+.Ft "char *"
+.Fn seprintf "char *ptr" "char *end" "const char *fmt" "..."
+.
+.Sh DESCRIPTION
+While discussing string building in C recently,
+mcf pointed out
+.Xr seprint 2
+from Plan 9,
+and it kind of blew my mind.
+I had implemented my own function in
+.Xr catgirl 1
+for building up strings using
+.Xr snprintf 3
+and a struct containing
+pointer, length and capacity,
+but it felt out of place.
+.Fn seprintf
+(I add the
+.Dq f ,
+Plan 9 doesn't)
+is a much simpler
+and more
+.Dq C-like
+interface with really nice usage patterns.
+.
+.Pp
+The obvious difference from
+.Xr snprintf 3
+is that
+.Fn seprintf
+takes an
+.Fa end
+pointer
+rather than a size.
+This means you need only calculate it
+once for each buffer,
+rather than subtracting
+the running length from the buffer size.
+.Fn seprintf Ap s
+return value is a pointer
+to the terminating null
+of the string it wrote,
+so you can pass that back in
+to continue appending
+to the same buffer.
+.
+.Pp
+I'm not sure of the exact behaviour on Plan 9,
+but my implementation indicates truncation occurred
+by returning the
+.Fa end
+pointer.
+That makes it both easy to check,
+and perfectly fine to keep calling
+.Fn seprintf
+anyway.
+It just won't write anything if
+.Fa ptr
+==
+.Fa end .
+.
+.Pp
+In the case of formatting failure
+(which should be prevented by
+compile-time format string checking,
+but should still be considered),
+.Fn seprintf
+returns
+.Dv NULL .
+I'm again not sure if this matches Plan 9.
+I like this a lot better than
+.Xr snprintf 3
+returning -1,
+because an unchecked
+.Dv NULL
+is likely to quickly cause a crash,
+while blindly adding
+.Xr snprintf 3 Ap s
+return value
+to your running length
+is a non-obvious logic error.
+.
+.Sh EXAMPLES
+Here's an example of what some code using
+.Fn seprintf
+might look like:
+.Bd -literal -offset indent
+char buf[4096];
+char *ptr = buf, *end = &buf[sizeof(buf)];
+ptr = seprintf(ptr, end, "argv: ");
+for (int i = 1; i < argc; ++i) {
+	ptr = seprintf(
+		ptr, end, "%s%s",
+		(i > 1 ? ", " : ""), argv[i]
+	);
+}
+if (ptr == end) errx(1, "truncation occurred :(");
+.Ed
+.
+.Pp
+And here is the very short implementation of it against
+.Xr vsnprintf 3
+which I copy into my project header files:
+.Bd -literal -offset indent
+#include <stdarg.h>
+#include <stdio.h>
+static inline char *
+seprintf(char *ptr, char *end, const char *fmt, ...)
+	__attribute__((format(printf, 3, 4)));
+static inline char *
+seprintf(char *ptr, char *end, const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	int n = vsnprintf(ptr, end - ptr, fmt, ap);
+	va_end(ap);
+	if (n < 0) return NULL;
+	if (n > end - ptr) return end;
+	return ptr + n;
+}
+.Ed
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Another short one before
+.Xr git-subtree 1 .
+I just think this function
+is really neat.
diff --git a/www/text.causal.agency/025-v6-pwd.7 b/www/text.causal.agency/025-v6-pwd.7
new file mode 100644
index 00000000..90bfd6ac
--- /dev/null
+++ b/www/text.causal.agency/025-v6-pwd.7
@@ -0,0 +1,330 @@
+.Dd September  1, 2021
+.Dt V6-PWD 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm V6 pwd
+.Nd deciphering old code
+.
+.Sh DESCRIPTION
+We were talking about
+.Xr wall 1
+on IRC
+and how long it had been annoying users.
+My manual page says
+.Xr wall 1
+appeared in
+.At v6 ,
+which means that
+.Xr wall 1
+has been annoying users for 46 years!
+.
+.Pp
+The Wikipedia page links to the source for
+.At v6 ,
+so I was curious to see how the very first
+.Xr wall 1
+was implemented.
+It's not that surprising,
+except that it is hardcoded
+to handle only 50 logins,
+and it forks to write to each tty,
+waiting one second between each.
+I think the forking must be to avoid
+any of the terminals being opened
+from becoming the controlling terminal
+of the original
+.Xr wall 1
+process.
+.
+.Pp
+Then I started looking
+at some of the other source files
+and found the implementation of
+.Xr pwd 1 ,
+which was surprising.
+There's no
+.Xr getcwd 3
+function
+(the earlier form of which,
+.Xr getwd 3 ,
+appeared in
+.Bx 4.0 ) ,
+so
+.Xr pwd 1
+has to figure out
+the path to the working directory itself.
+It took me a while to figure out how it works.
+.
+.Pp
+To make it easy to talk about,
+I'm just going to include the whole thing here:
+.Bd -literal
+char dot[] ".";
+char dotdot[] "..";
+char root[] "/";
+char name[512];
+int file, off -1;
+struct statb {int devn, inum, i[18];}x;
+struct entry { int jnum; char name[16];}y;
+
+main() {
+	int n;
+
+loop0:
+	stat(dot, &x);
+	if((file = open(dotdot,0)) < 0) prname();
+loop1:
+	if((n = read(file,&y,16)) < 16) prname();
+	if(y.jnum != x.inum)goto loop1;
+	close(file);
+	if(y.jnum == 1) ckroot();
+	cat();
+	chdir(dotdot);
+	goto loop0;
+}
+ckroot() {
+	int i, n;
+
+	if((n = stat(y.name,&x)) < 0) prname();
+	i = x.devn;
+	if((n = chdir(root)) < 0) prname();
+	if((file = open(root,0)) < 0) prname();
+loop:
+	if((n = read(file,&y,16)) < 16) prname();
+	if(y.jnum == 0) goto loop;
+	if((n = stat(y.name,&x)) < 0) prname();
+	if(x.devn != i) goto loop;
+	x.i[0] =& 060000;
+	if(x.i[0] != 040000) goto loop;
+	if(y.name[0]=='.')if(((y.name[1]=='.') && (y.name[2]==0)) ||
+				(y.name[1] == 0)) goto pr;
+	cat();
+pr:
+	write(1,root,1);
+	prname();
+}
+prname() {
+	if(off<0)off=0;
+	name[off] = '\en';
+	write(1,name,off+1);
+	exit();
+}
+cat() {
+	int i, j;
+
+	i = -1;
+	while(y.name[++i] != 0);
+	if((off+i+2) > 511) prname();
+	for(j=off+1; j>=0; --j) name[j+i+1] = name[j];
+	off=i+off+1;
+	name[i] = root[0];
+	for(--i; i>=0; --i) name[i] = y.name[i];
+}
+.Ed
+.
+.Pp
+First, some syntax trivia:
+it seems you don't need
+.Sy =
+to give globals values.
+I guess that makes sense.
+I also noticed that
+it avoids giving
+.Va inum
+and
+.Va jnum
+the same name.
+I think that's because in old C,
+struct field names all shared the same namespace.
+The last difference I noticed
+is the operator
+.Sy =&
+rather than
+.Sy &= .
+Honestly I think the former makes more sense,
+but I can see that the one we have now
+is less ambiguous.
+.
+.Pp
+To get
+.Fn prname
+and
+.Fn cat
+out of the way,
+it's building up a path from the bottom.
+At first I thought it must be
+starting at the end of its buffer
+and moving back as it adds components,
+but no,
+it moves the entire path-so-far over
+every time it adds a new component
+onto the front.
+.Fn cat
+is just a bunch of manual string copying.
+It also gives up
+if the new component
+would make the path longer than 511 characters.
+Fair enough.
+.
+.Pp
+So how does it build up the path?
+The loop in
+.Fn main
+first calls
+.Xr stat 2
+on the current directory
+.Pa \&.
+in order to get its inode number.
+I love that
+.Vt struct statb
+is just declared at the top of this file.
+Clearly this code predates the C preprocessor.
+.
+.Pp
+It then opens the parent directory
+.Pa ..
+and reads directory entries from it.
+The inner loop is looking for
+a directory entry with the same inode number
+as the current directory,
+to figure out what the current directory is called.
+Curiously,
+it reads 16-byte directory entries,
+despite declaring a larger struct.
+The preprocessor can't be invented soon enough.
+.
+.Pp
+Once it finds the matching directory entry,
+it adds the name of the entry
+onto the front of the path,
+changes directory to
+.Pa ..
+and starts over.
+It stops when the current directory
+has an inode number of 1,
+which must be the root of a file system,
+but then it does something else.
+It took me a while to decipher what
+.Fn ckroot
+is doing.
+.
+.Pp
+The loop in
+.Fn main
+stops when it gets to the root
+of a file system,
+but that's not necessarily
+.Pa / .
+I think what
+.Fn ckroot
+is doing is trying to figure out
+where that file system is mounted.
+It starts by checking the device number
+that the current directory is on.
+Or really it calls
+.Xr stat 2
+on the name of the directory entry that
+.Fn main
+just found,
+which I think must be
+.Pa \&.
+at this point anyway since it's at a root.
+.
+.Pp
+Anyway,
+it then changes directory to and opens
+.Pa /
+and starts reading directory entries from that,
+calling
+.Xr stat 2
+on each of them
+and checking for a matching device number.
+I think this implies that file systems
+can only be mounted in
+.Pa /
+and not at any lower level,
+at least not if you want
+.Xr pwd 1
+to understand it.
+I'm not sure what the check for
+an inode number of 0 is skipping over
+in this loop.
+Some kind of special entry in
+.Pa /
+perhaps.
+.
+.Pp
+Once it finds an entry
+with a matching device number,
+it checks the flags
+to make sure the entry is a directory.
+It does so with hardcoded constants,
+but it seems they haven't changed
+in all these years.
+According to
+.Xr stat 2 ,
+040000 is
+.Dv S_IFDIR .
+The number of file types
+clearly has grown since then though,
+since
+.Dv S_IFMT
+is now 0170000 rather than 060000.
+.
+.Pp
+I think the reason it checks
+that the entry is a directory
+is because if it actually is
+on the root file system already,
+then any regular file
+would have a matching device number.
+If the entry is indeed a directory,
+it then checks if the entry is
+.Pa \&.
+or
+.Pa \&.. ,
+which indicates that it really is already at
+.Pa / .
+If it's not,
+it adds the mount point that it found
+to the front of the path.
+.
+.Pp
+Finally,
+it prints
+.Pa /
+followed by the path it built up.
+If it failed at any point before that,
+it would print the path it had built so far
+with no leading
+.Pa / .
+Better than nothing!
+.
+.Pp
+So that's how I think
+.Xr pwd 1
+works in
+.At v6 .
+It was a fun puzzle to work through,
+and it was interesting to see
+the assumptions it makes.
+How simple things were back then...
+Actually I find it really cool
+that code from 1975
+can still be read and understood
+using knowledge of modern C and UNIX-likes.
+.
+.Sh SEE ALSO
+.Lk https://minnie.tuhs.org/cgi-bin/utree.pl?file=V6
+.Pp
+.Pa pwd.c
+appears in
+.Pa V6/usr/source/s2 .
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+I regret saying in two previous posts
+what I planned to write next,
+because this is still not that.
diff --git a/www/text.causal.agency/026-git-comment.7 b/www/text.causal.agency/026-git-comment.7
new file mode 100644
index 00000000..fefb497e
--- /dev/null
+++ b/www/text.causal.agency/026-git-comment.7
@@ -0,0 +1,190 @@
+.Dd September 10, 2021
+.Dt GIT-COMMENT 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm git-comment
+.Nd add comments from commit messages
+.
+.Sh SYNOPSIS
+.Nm git comment
+.Op Fl \-all
+.Op Fl \-comment-start Ar string
+.Op Fl \-comment-lead Ar string
+.Op Fl \-comment-end Ar string
+.Op Fl \-min-group Ar lines
+.Op Fl \-min-repeat Ar lines
+.Op Fl \-no-repeat
+.Op Fl \-pretty Ar format
+.Op Ar options ...
+.Op Fl \-
+.Ar file
+.
+.Sh DESCRIPTION
+The
+.Nm
+command
+adds comments to a file
+showing the commit messages
+which last modified
+each group of lines.
+By default only commit messages with bodies
+and which modified groups of at least 2 lines
+are added.
+Each comment contains
+the abbreviated commit hash
+and the commit summary,
+followed by the commit body.
+.
+.Pp
+.Nm
+accepts all the options of
+.Xr git-blame 1
+in addition to the following:
+.Bl -tag -width Ds
+.It Fl \-all
+Include all commit messages.
+The default is to include
+only commit messages with bodies
+(lines after the summary).
+.
+.It Fl \-comment-start Ar string
+Start comments with
+.Ar string .
+The default is the value of
+.Cm comment.start
+or
+.Ql /* .
+.
+.It Fl \-comment-lead Ar string
+Continue comments with the leading
+.Ar string .
+The default is the value of
+.Cm comment.lead
+or
+.Ql " *" .
+.
+.It Fl \-comment-end Ar string
+End comments with
+.Ar string .
+The default is the value of
+.Cm comment.end
+or
+.Ql " */" .
+.
+.It Fl \-min-group Ar lines
+Add comments only for groups of at least
+.Ar lines .
+The default is 2 lines.
+.
+.It Fl \-min-repeat Ar lines
+Avoid repeating a comment
+if it occurred in the last
+.Ar lines .
+The default is 30 lines.
+.
+.It Fl \-no-repeat
+Avoid repeating comments entirely.
+.
+.It Fl \-pretty Ar format
+Set the pretty-print format
+to use for commit messages.
+The default is the value of
+.Cm comment.pretty
+or
+.Ql format:%h\ %s%n%n%-b .
+See
+.Xr git-show 1 .
+.El
+.
+.Sh EXAMPLES
+For files with
+.Ql #
+comments:
+.Bd -literal -offset indent
+git config comment.start '#'
+git config comment.lead '#'
+git config comment.end ''
+.Ed
+.
+.Pp
+Add as many comments as possible:
+.Bd -literal -offset indent
+git comment --all --min-group 1 --min-repeat 1
+.Ed
+.
+.Pp
+Some examples of output from
+.Xr catgirl 1 :
+.Bd -literal
+/* 347e2b4 Don't apply uiThreshold to Network and Debug
+ *
+ * Messages don't really need to be hidden from <network> and I think
+ * it could be confusing. Debug messages are all Cold so everything
+ * would be hidden, and I want to keep them that way so that <debug>
+ * doesn't clutter the status line needlessly.
+ */
+if (id == Network || id == Debug) {
+	window->thresh = Cold;
+} else {
+	window->thresh = uiThreshold;
+}
+
+/* b4c26a2 Measure timestamp width using ncurses
+ *
+ * This allows for non-ASCII characters in timestamps, and simplifies
+ * things by including the trailing space in the width.
+ */
+int y;
+char buf[TimeCap];
+struct tm *time = localtime(&(time_t) { -22100400 });
+size_t len = strftime(buf, sizeof(buf), uiTime.format, time);
+if (!len) errx(EX_CONFIG, "invalid timestamp format: %s", uiTime.format);
+waddstr(main, buf);
+waddch(main, ' ');
+getyx(main, y, uiTime.width);
+(void)y;
+
+/* 43b1dba Restore toggling ignore with M--
+ *
+ * So that pressing M-- repeatedly maintains the previous behavior.
+ */
+if (n < 0 && window->thresh == Ice) {
+	window->thresh = Cold;
+} else {
+	window->thresh += n;
+}
+
+/* 1891c77 Preserve colon from previous tab-complete
+ *
+ * This fixes the case when pinging multiple nicks and one of them needs to
+ * be cycled through.
+ */
+bool colon = (tab.len >= 2 && buf[tab.pos + tab.len - 2] == L':');
+.Ed
+.
+.Sh SEE ALSO
+.Lk https://git.causal.agency/src/tree/bin/git-comment.pl
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Pp
+In case it's unclear,
+this is a
+.Xr git 1
+subcommand I wrote.
+Did you know you can add new
+.Xr git 1
+subcommands just by
+adding executables named
+.Pa git-*
+to somewhere in
+.Ev PATH ?
+.
+.Pp
+This is also,
+I think,
+my third Perl script ever.
+It's an interestingly shaped language.
+Quite neat.
diff --git a/www/text.causal.agency/027-openbsd-linode.7 b/www/text.causal.agency/027-openbsd-linode.7
new file mode 100644
index 00000000..9f40de42
--- /dev/null
+++ b/www/text.causal.agency/027-openbsd-linode.7
@@ -0,0 +1,202 @@
+.Dd September 26, 2021
+.Dt OPENBSD-LINODE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Installing OpenBSD on Linode
+.Nd a guide
+.
+.Sh DESCRIPTION
+I've been thinking for a while
+about moving my servers to Linode,
+and also about moving them to
+.Ox .
+I actually originally got into
+.Fx
+(and from there,
+.Ox )
+only because DigitalOcean
+started offering it as a
+.Dq droplet
+image.
+I've been running those servers fine for years,
+but now I prefer to run
+.Ox ,
+and some recent DigitalOcean outages
+had me thinking about it more,
+so I'm giving it a shot.
+.
+.Pp
+As an aside,
+running
+.Ox
+on DigitalOcean
+is not really a good option.
+It seems more awkward to install your own OS there,
+and if you do,
+I've heard that IPv6 won't work
+because they don't know how to run SLAAC.
+Also,
+now that I've used
+the Linode control panel and LISH a bit,
+DigitalOcean kind of feels like a toy
+in comparison.
+.
+.Pp
+Here's what I did to install
+.Ox
+on Linode:
+.Bl -enum
+.It
+Create a Linode with the
+.Dq Choose a Distribution
+box blank.
+.
+.It
+Under the Storage tab,
+create a disk called
+.Dq miniroot
+of type raw
+with size 8 MB.
+This will hold the install image.
+.
+.It
+Create another disk called
+.Dq root
+of type raw
+using the remaining available storage.
+.
+.It
+Boot the Linode in rescue mode
+from the option in the three-dots menu
+next to
+.Dq Power On .
+Attach
+.Dq miniroot
+to
+.Pa /dev/sda .
+.
+.It
+Log into the LISH console
+and obtain the install image:
+.Bd -literal
+curl -O https://cdn.openbsd.org/pub/OpenBSD/6.9/amd64/miniroot69.img
+dd if=miniroot69.img of=/dev/sda
+.Ed
+.Pp
+Power off the Linode.
+.
+.It
+Under the Configurations tab,
+create a configuration called
+.Dq install
+in full virtualization mode.
+Paravirtualization works fine once installed,
+but for some reason the installer
+can't see the root disk
+without full virtualization.
+Under boot settings,
+select direct disk.
+Attach
+.Dq root
+to
+.Pa /dev/sda ,
+.Dq miniroot
+to
+.Pa /dev/sdb
+and set the root device to
+.Pa /dev/sdb .
+.
+.It
+Create a similar configuration called
+.Dq boot
+but using paravirtualiztion
+and without
+.Dq miniroot
+attached.
+Set the root device to
+.Pa /dev/sda .
+.
+.It
+Boot the
+.Dq install
+configuration,
+launch the LISH console
+and switch to Glish.
+It's possible
+to have the installer use serial console,
+but it requires entering commands
+at the boot prompt
+before the timeout,
+and I never managed it.
+If you do manage it,
+run:
+.Bd -literal
+stty com0 9600
+set tty com0
+boot
+.Ed
+.
+.It
+Proceed through the
+.Ox
+installer.
+When asked to
+change the default console to com0,
+answer yes
+so that regular LISH will work.
+Power off the Linode.
+.
+.It
+Boot the
+.Dq boot
+configuration
+and log in to LISH.
+Since the installer configured networking
+in full virtualization,
+rename the file to the paravirtualized interface:
+.Bd -literal
+mv /etc/hostname.em0 /etc/hostname.vio0
+.Ed
+.Pp
+In order to get the right public IPv6 address,
+disable privacy extensions
+by changing the inet6 line of
+.Pa hostname.vio0
+to:
+.Bd -literal
+inet6 autoconf -temporary -soii
+.Ed
+.
+.It
+Bring networking up
+and run
+.Xr syspatch 8
+since
+.Pa rc.firsttime
+couldn't do it:
+.Bd -literal
+sh /etc/netstart
+syspatch
+.Ed
+.
+.It
+Reboot.
+.El
+.
+.Pp
+I guess I'll be slowly moving things over
+to the new servers
+for the next little while.
+With any luck the next post here
+will not say
+.Fx
+in its header!
+.
+.Sh SEE ALSO
+I learned the basic idea
+of how to do this from
+.Lk https://www.subgeniuskitty.com/notes/openbsd_on_linode .
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/028-names.7 b/www/text.causal.agency/028-names.7
new file mode 100644
index 00000000..de47c074
--- /dev/null
+++ b/www/text.causal.agency/028-names.7
@@ -0,0 +1,81 @@
+.Dd October 30, 2021
+.Dt NAMES 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Names
+.Nd three types
+.
+.Sh DESCRIPTION
+There are (at least) three
+different types of names
+a person has.
+.
+.Pp
+First, there are normie names.
+These are names usually made up
+of several words
+each of which is capitalized.
+Most people have one of these,
+but it's possible to have more.
+They're names that might appear on
+various types of Documents.
+A
+.Dq legal name
+(dubious)
+is a normie name,
+but normie names need not be
+.Dq legal
+(dubious).
+I list this category first
+not because it's more important,
+but because it is by far the most boring.
+.
+.Pp
+Next, there are Real Names.
+Most people have at least a few
+and will probably go through
+different ones over time.
+Your Real Names are anything people
+use to refer to you.
+On the internet these are often not capitalized.
+Sometimes that is the only distinction
+between a Real Name
+and a normie name.
+.
+.Pp
+There was a period of time
+when I was playing a lot of TF2
+and not really leaving my apartment.
+I had set my steam name to
+.Dq gluten product
+(yeah, from that dril tweet)
+and I talked in the game's voice chat
+quite a bit.
+Naturally other Gamers in voice chat
+called me
+.Dq gluten
+and at some point I realized
+that over the span of months
+I had been refered to as
+.Dq gluten
+more often than any other name.
+So that was a Real Name of mine.
+People used it and I responded to it.
+.
+.Pp
+Last, there are the True Names.
+The kind of name that knowledge of
+gives one power over a person.
+I don't think any humans
+know their own True Names,
+but I do believe they exist.
+It's possible that other animals
+know theirs.
+It's probably best not to know though, right?
+I think if I knew mine
+I would always worry
+about accidentally revealing it.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/029-topics.7 b/www/text.causal.agency/029-topics.7
new file mode 100644
index 00000000..d071eb67
--- /dev/null
+++ b/www/text.causal.agency/029-topics.7
@@ -0,0 +1,116 @@
+.Dd January  8, 2022
+.Dt TOPICS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Topics
+.Nd a bit of a mess
+.
+.Sh DESCRIPTION
+Shortly after my last post
+I started writing another one
+but I never finished it.
+I don't think I had enough to say,
+or if I did it meant going into
+a whole extra thing.
+I also had a list in my mind
+of other things to write about,
+but inspiration hasn't really struck
+for any of them.
+I'm currently in the mood
+to write something anyway,
+so I'm just going to write a bit
+about the topics I have in mind.
+I may or may not ever
+write more about any of them.
+.
+.Pp
+The post I had started writing
+(twice, actually)
+was about voices.
+I like them a lot
+and I'm fascinated by them.
+The problem is
+I don't actually have much
+to say about it
+without getting into Gender,
+which as I say is a whole extra thing,
+and not something I've written about here before.
+.
+.Pp
+When I started writing here,
+I didn't want to blog about
+personal topics or LGBTQ stuff.
+But more recently
+I want to move away from
+only writing about computers.
+Or maybe away from
+writing about computers entirely.
+There are more interesting things,
+but I don't have experience
+writing about them.
+Yet,
+I should say.
+.
+.Pp
+I'm honestly still not sure
+if writing about gender here
+is at all a good idea.
+But it turns out to feel like
+a bit of a prerequisite
+for other things.
+I find gender perception
+in particular
+to be fascinating.
+It's interesting.
+It's neat.
+And I don't know if I can
+write anything coherent about it.
+.
+.Pp
+Related to that,
+I've been thinking of writing
+about how the pandemic
+has had a strangely positive effect
+on my life.
+Or at least,
+I've made a lot of positive changes
+during it.
+I'm in a better place emotionally now
+than ever before,
+and that obviously runs counter
+to most people's experiences.
+Additionally with that positive outlook
+I want to write about the meaning
+of my domain name.
+I'm proud of it.
+.
+.Pp
+This week the topic of fetish
+has been on my mind.
+That actually feels
+a bit less risky
+to write about than gender.
+And it may honestly be more interesting.
+I don't know.
+There's not enough sex
+on computer blogs,
+or whatever this is.
+Although my main ideas
+are not about sex at all.
+.
+.Pp
+Just this turned out to be
+harder to write than I thought it would be.
+I think I want to populate this space
+with more short posts like the previous one.
+I wrote that while very sleepy
+after 3 AM though,
+and I don't exactly
+want to repeat that regularly.
+We'll see.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Listening to Kate Bush \(em Hounds of Love.
diff --git a/www/text.causal.agency/030-discs.7 b/www/text.causal.agency/030-discs.7
new file mode 100644
index 00000000..df73a750
--- /dev/null
+++ b/www/text.causal.agency/030-discs.7
@@ -0,0 +1,114 @@
+.Dd January  8, 2022
+.Dt DISCS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Desert Island Discs
+.Nd we're doing three in this one
+.
+.Sh DESCRIPTION
+In typical fashion
+I'm going to write about something
+completely different instead.
+Something short and simple.
+I got thinking about this
+after reading a little interview thing
+this week.
+The question is
+which three albums would you want to have
+if you were stranded on a desert island.
+What could you listen to
+for the rest of time?
+It's surprisingly easy
+to take this question very seriously.
+.
+.Pp
+My immediate thought was
+.Em Music for 18 Musicians.
+I've literally said this about it
+in conversation before.
+That's an album
+I'd want to have on a desert island.
+I find it incredibly soothing,
+almost hypnotic.
+I really do feel like
+I could listen to it forever.
+And then maybe I could finally determine
+which of its eleven sections
+is the best.
+.
+.Pp
+My next thought was
+.Em Soundtracks for the Blind .
+We already know I'm a huge SWANS fan.
+Despite what I've written about
+.Em Swans Are Dead ,
+I instead jumped to SFTB.
+I still think that
+.Em Dead
+has better tunes,
+but
+.Em Soundtracks
+is definitely the better cohesive album.
+It has such atmosphere and mood on it.
+Like
+.Em 18 ,
+it's an album that sucks me in.
+Also,
+either SWANS album
+is an economical choice
+in this hypothetical
+since they're each 2 hours and 20 minutes long.
+.
+.Pp
+Choosing a third album is a lot harder.
+There's so much other music I like
+and only one slot left.
+There's no other single album
+that stands out above the rest
+like the previous two,
+for me.
+.Em Wildlife ,
+maybe?
+Or 
+.Em Jane Doe ?
+Perhaps a classic like
+.Em Aeroplane ,
+or a boomer classic like
+.Em The Wall .
+But would I really want to
+listen to any of those
+to the exclusion of everything else?
+They're too mood-dependent.
+.
+.Pp
+Then I realized the perfect choice
+for third album.
+.Em Mouth Moods .
+A mashup album is the perfect wildcard,
+and
+.Em Moods
+is just fun as hell to listen to.
+I get songs from it stuck in my head
+instead of the originals.
+The final track,
+.Em Shit ,
+always gets me moving.
+It's a masterpiece.
+.
+.Bl -enum
+.It
+Steve Reich Ensemble \(em
+.Em Music for 18 Musicians
+.It
+SWANS \(em
+.Em Soundtracks for the Blind
+.It
+Neil Cicierega \(em
+.Em Mouth Moods
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Listening to Steve Reich Ensemble \(em Music for 18 Musicians.
diff --git a/www/text.causal.agency/031-books-2021.7 b/www/text.causal.agency/031-books-2021.7
new file mode 100644
index 00000000..d7b46f17
--- /dev/null
+++ b/www/text.causal.agency/031-books-2021.7
@@ -0,0 +1,127 @@
+.Dd January 12, 2022
+.Dt BOOKS-2021 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Books 2021
+.Nd a review, I guess
+.
+.Sh DESCRIPTION
+In 2021 I read 26 books.
+Finished the 26th right on December 31st.
+It's not a lot but it's more than last year.
+Here are the ones I loved
+(in the order I read them).
+I will avoid spoilers,
+of course.
+.
+.Ss Network Effect by Martha Wells
+I've been reading the
+.Em Murderbot Diaries
+series for a while.
+They're fun stories.
+I liked this full-length novel entry a lot.
+I guess it felt like it had more room
+for the characters to develop.
+This is probably when I started
+asking my friends if they'd read it
+because I wanted to talk about
+Murderbot gender vibes.
+.Pp
+You may like if: you're trans.
+.
+.Ss The Once and Future Witches by Alix E. Harrow
+Um,
+it's about witches!
+One of them has the same name as me.
+Kind of has some similar vibes to
+.%T The Future of Another Timeline ,
+which was my favourite book I read in 2020.
+.Pp
+You may like if: you like women.
+.
+.Ss A Desolation Called Peace by Arkady Martine
+I was so excited for this sequel to
+.%T A Memory Called Empire ,
+another previous favourite
+and something I've been wanting more of.
+I kinda wish there was more fucking in it though honestly.
+.Pp
+You may like if: you like women.
+.
+.Ss Piranesi by Susanna Clarke
+Really something different.
+It turned out to be a different story
+than I expected
+from reading the first few pages.
+.Pp
+You may like if: you like statues, I guess?
+.
+.Ss A Psalm for the Wild-Built by Becky Chambers
+Ok yes I do give 3/3 stars
+to every Becky Chambers book.
+They're so fucking good.
+I'm looking forward to
+more entries in this novella series.
+(Also I'm currently reading
+the fourth
+.Em Wayfarers
+book
+and loving it too!)
+.Pp
+You may like if: your pronouns are they/them <3
+.
+.Sh HONOURABLE MENTIONS
+.Ss Her Body and Other Parties by Carmen Maria Machado
+I really enjoyed the short story
+.Dq Especially Heinous: 272 Views of Law & Order SVU
+in this collection.
+It goes on a bit too long
+but the format is unique.
+You can read that one online,
+actually.
+.
+.Ss The Hobbit by J. R. R. Tolkien
+Yeah I hadn't read this until last year.
+I borrowed it after marathoning
+the extended editions of the
+.%T Lord of the Rings
+trilogy during a heat wave.
+As I said at the time,
+pretty good for something
+written by a man
+like a hundred years ago.
+Kind of hilarious that women
+just don't exist
+in the world of
+.%T The Hobbit .
+.
+.Ss Earthlings by Sayaka Murata
+Pretty fucking wild.
+I'd recommend it,
+but I have to say it
+.Em extremely
+needs a child sexual abuse content warning on it.
+.
+.Ss Six Months, Three Days, Five Others by Charlie Jane Anders
+A surprising number of these short stories
+are actual stories!
+They have beginnings,
+middles
+and ends!
+.
+.Ss The City in the Middle of the Night by Charlie Jane Anders
+It's got some
+.Em Xenogenesis
+series vibes.
+Sophie is a goddamn lesbian idiot though
+and she never even realizes it.
+.
+.Sh SEE ALSO
+.Lk https://git.causal.agency/src/tree/txt/books.txt
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Listening to
+.Em Ear Massage with Latex Gloves 100% Sensitivity 40 minute (No Talking) .
diff --git a/www/text.causal.agency/032-albums-2021.7 b/www/text.causal.agency/032-albums-2021.7
new file mode 100644
index 00000000..72c1d0d2
--- /dev/null
+++ b/www/text.causal.agency/032-albums-2021.7
@@ -0,0 +1,173 @@
+.Dd January 13, 2022
+.Dt ALBUMS-2021 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Albums 2021
+.Nd a review
+.
+.Sh DESCRIPTION
+Every year I create a new playlist
+in iTunes
+(Music dot app, whatever)
+for the albums I listen to that year.
+It's usually embarrassingly short.
+I don't listen to new music
+as much as I'd like,
+and usually only one or two
+are actually from the current year.
+Not that the playlist
+is limited to new (to me) music.
+If I get really into an album
+I've heard before,
+more than before,
+I also add it to the list.
+Anyway,
+this is a review
+of my 2021 albums playlist.
+.
+.Ss Black Country, New Road \(em For the First Time (2021)
+I first heard the single
+.Em Sunglasses
+from someone sharing it on IRC,
+and I loved it,
+so I was looking forward to this album.
+What a let down though.
+The version of
+.Em Sunglasses
+on the album is just plain worse
+than the single version.
+I still got some decent listening
+out of the album,
+but that just sours it for me.
+.Pp
+Favourite track:
+.Em Track X .
+.
+.Ss Black Dresses \(em Forever \&In Your Heart (2021)
+I fucking love Black Dresses.
+.Em Peaceful as Hell
+is one of my all-time favourite albums.
+I'm glad they put out another one
+after it looked like they wouldn't.
+The sounds are just so good.
+Exactly what my ears crave.
+The texture of it
+tickles my brain clit.
+.Pp
+Favourite tracks:
+.Em Waiting42moro ,
+.Em Mistake .
+.
+.Ss Low \(em Drums and Guns (2007)
+I've long loved the song
+.Em Breaker
+and its music video,
+but I only listened to the album
+it's on last year.
+Something I didn't realize,
+I guess because I usually pulled up
+the music video
+without headphones on,
+is how aggressively this album
+uses stereo panning.
+Vocals are generally
+panned hard right throughout,
+with much of the instrumentation
+panned centre or hard left.
+It's bold
+and it really works for me.
+I especially love the vocal harmony on
+.Em Breaker
+all the way on the opposite channel.
+Bring back stereo separation!
+.Pp
+Favourite tracks:
+.Em Breaker ,
+.Em Murderer ,
+.Em Violent Past .
+.
+.Ss The Armed \(em Ultrapop (2021)
+I have to admit
+I didn't actually listen to this one much.
+I listened to the previous album,
+.Em Only Love ,
+a lot in 2020.
+I think this album is good,
+but I'll probably only really get into it
+in some future year.
+.
+.Ss Lingua Ignota \(em Caligula (2019)
+Dear lord,
+why did I wait so long
+to listen to this one.
+I had heard
+.Em "Do You Doubt Me Traitor"
+back when it came out,
+but somehow I didn't realize
+just how much this album
+would be my shit.
+Fucking incredible vocals.
+Lovely sometimes minimal,
+sometimes extreme
+instrumentals
+and exquisite percussion.
+The sound of,
+I believe,
+a lightbulb rolling around on the floor on
+.Em Fragrant
+is such an interesting addition.
+.Pp
+Favourite tracks:
+.Em "Do You Doubt Me Traitor" ,
+.Em "Fragrant Is My Many Flower'd Crown" ,
+.Em "If the Poison Won't Take You My Dogs Will" .
+.
+.Ss Black Dresses \(em LOVE AND AFFECTION FOR STUPID LITTLE BITCHES (2019)
+I wanted even more Black Dresses
+and fortunately there was still more
+I hadn't yet listened to!
+I've already gushed about Black Dresses
+so I'll spare you.
+They're so good though.
+.Pp
+Favourite tracks:
+.Em STATIC ,
+.Em HERTZ ,
+.Em MY HEART BEATS OUT OF TIME .
+.
+.Ss Barenaked Ladies \(em All Their Greatest Hits: Disc One 1991-2001
+What?
+Yeah,
+late last year I decided to revisit BNL.
+My parents listened to them a lot
+when I was growing up,
+and I liked them too.
+The first show I ever went to was the
+.Dq Barenaked for the Holidays
+tour with my parents.
+It turns out
+I still think their '90s stuff
+is pretty darn good!
+Steven Page is really a great singer.
+This is also the first time
+I'm listening to these tunes
+with fancy headphones
+and it sounds great.
+Honestly
+.Em The Old Apartment
+can totally compete
+with the favourites
+I've accumulated more recently.
+\&'90s alt rock was good actually?
+.Pp
+Favourite tracks:
+.Em The Old Apartment ,
+.Em Brian Wilson ,
+.Em What a Good Boy ,
+.Em Too Little Too Late .
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Listening to all my favourite tracks :)
diff --git a/www/text.causal.agency/033-jorts.7 b/www/text.causal.agency/033-jorts.7
new file mode 100644
index 00000000..001f877c
--- /dev/null
+++ b/www/text.causal.agency/033-jorts.7
@@ -0,0 +1,485 @@
+.Dd February  2, 2022
+.Dt JORTS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Introducing Jorts
+.Nd june's ports
+.
+.Sh DESCRIPTION
+Alright so I've gone off the deep end,
+maybe.
+After continual frustration with MacPorts
+culminating in not being able to install
+.Xr nvi 1
+on my work MacBook,
+I have just gone ahead
+and started my own personal ports tree
+for macOS.
+After a couple of weeks,
+I have 32 ports in my tree
+and only two remaining requested ports
+installed from MacPorts.
+.
+.Pp
+I set out with a couple ideas in mind:
+.Bl -bullet
+.It
+This will be my own personal ports tree.
+It only has to work for me.
+Since I'm using it on both
+my personal Intel MacBook Pro
+still running Catalina
+and my work M1 MacBook Pro
+running Monterey,
+it is at least that portable.
+.
+.It
+It's ok to rely on
+system libraries and tools
+provided by macOS.
+I'm not creating a distro,
+so it doesn't need to be totally isolated.
+This lets me skip really annoying things
+like compiler toolchains.
+.
+.It
+Sources get vendored,
+either from release tarballs
+or with
+.Xr git-subtree 1 .
+This allows totally pain-free
+local patching,
+and boy has this paid off.
+I can just do what I need to do
+to get the thing to build how I want
+and commit it in git like anything else.
+.Pp
+It also means that the tree itself
+is entirely self-contained
+and doesn't rely on any external sources
+or network access.
+Honestly with some old and obscure software
+it feels like upstream could disappear at any moment,
+so this gives me peace of mind too.
+.Pp
+Another advantage of vendoring upstream sources
+is that all of the code installed on my system
+(in
+.Pa /usr/local
+anyway)
+is easily inspected,
+much like
+.Pa /usr/src
+on a BSD.
+This can be super useful for debugging
+or just for reference.
+.
+.It
+Produce simple package tarballs.
+They're just the contents of
+.Ev DESTDIR
+after a staged install.
+They get installed for real
+by untarring them in
+.Pa / .
+They can then be uninstalled
+(or upgraded)
+by removing the paths contained
+in the tarball from the system.
+.
+.It
+Track installed packages with symbolic links
+to specific package tarballs.
+Keep old tarballs around for rollbacks.
+This means I can see what's installed
+with plain old
+.Xr ls 1 !
+.Bd -literal
+$ ls */Installed
+\&...
+libretls/Installed          toilet/Installed
+mandoc/Installed            tree/Installed
+
+$ ls -l toilet/Installed
+lrwxr-xr-x  1 root  staff  19 17 Jan 21:45 toilet/Installed -> toilet-0.3~1.tar.gz
+.Ed
+.
+.It
+Use
+.Xr bmake 1 .
+It's scrutable.
+It also knows how to bootstrap itself
+pretty well.
+Since
+.Xr bmake 1
+is itself a port in my tree
+that would require
+.Xr bmake 1
+to build and install,
+I wrote a small
+.Pa Bootstrap
+shell script
+to install
+.Xr bmake 1
+.Dq manually
+then use that
+.Xr bmake 1
+to build and install its own port.
+It also requires a bit of care
+when upgrading the
+.Xr bmake 1
+port since macOS
+rather doesn't like a binary
+deleting itself while it's running.
+.
+.It
+No GNU software.
+I simply refuse to do it.
+To that end,
+prefer configuring/building with
+.Xr cmake 1
+where at all possible.
+I fell into this early on
+since I originally just wanted to install
+.Xr nvi 1
+and
+.Sy lichray/nvi2
+is a better upstream source these days
+that uses
+.Xr cmake 1 .
+.Pp
+With a port and support for
+.Xr cmake 1
+in
+.Pa Port.mk ,
+I can make changes to
+.Pa CMakeLists.txt
+files without issue.
+I can also vendor upstreams
+directly from git
+rather than having to find
+release tarballs with generated
+.Pa configure
+scripts and so on.
+When I need to make changes
+to the build systems of projects using autotools,
+I either have to have autotools installed
+(from outside my tree)
+or painstakingly reflect my edits by hand
+in the generated files,
+both of which suck hard.
+.El
+.
+.Pp
+Ok so that's actually quite a number of ideas.
+But they have come together
+into something surprisingly usable
+surprisingly quickly!
+Like I said,
+this is only intended to be
+my own personal ports tree,
+but I hope that some of these ideas
+are interesting
+and maybe inspire others
+to explore similar approaches.
+.
+.Pp
+But wait,
+I'm not done yet!
+There are some other interesting things
+that I came up with along the way,
+and also some complaints
+about some upstreams,
+but I'll try to keep those to a minimum.
+.
+.Pp
+So it turns out that dependencies are hard.
+Who knew?
+It's easy enough to enforce
+direct dependencies
+at build time
+by just checking for the required
+.Pa Installed
+symlinks.
+It's less straightforward
+to do this recursively,
+which you need if
+you want to be able to say,
+.Do
+Install
+.Xr nvi
+for me!
+.Dc
+and get
+.Xr ncurses 3 ,
+.Xr cmake 1
+and
+.Xr pkgconf 1
+installed first
+if they aren't already.
+.
+.Pp
+Rather than trying to do all that in
+.Xr bmake 1 ,
+I wrote a shell script called
+.Pa Plan ,
+which itself produces a shell script.
+Given a list of ports
+to install or upgrade,
+it recursively gathers their dependencies
+and feeds them to
+.Xr tsort 1 ,
+which is a neat utility
+which topologically sorts a graph.
+In other words,
+it determines the order
+in which the graph of dependencies
+should be installed.
+The
+.Pa Plan
+script produces a list of
+.Xr bmake 1
+commands to make that happen
+on standard output,
+which can be piped to
+.Xr sh 1 .
+So,
+the way to say the above is:
+.Bd -literal -offset ident
+$ ./Plan -j4 nvi | sh -e
+.Ed
+.
+.Pp
+Now,
+what's missing from this approach
+is the ability to automatically
+uninstall no-longer-needed dependencies.
+It's something I've criticized Homebrew for lacking
+and one of the reasons I started using MacPorts,
+so it's somewhat ironic that
+my own system lacks it as well.
+However,
+I don't think it's much of a problem,
+since I'm only packaging
+what I actually want installed
+in the first place.
+On my personal computer,
+I have all 32 of my ports installed,
+and I expect that to continue.
+I can always keep using MacPorts
+to install things I only intend
+to use temporarily.
+.
+.Pp
+Another thing I was slightly concerned about
+from the beginning was disk usage.
+I think the benefits of vendoring sources
+far outweigh the cost in storage,
+but it would be nice to at least minimize that cost.
+Previously,
+I wrote about
+.Xr git-sparse-checkout 1 ,
+which allows you to only have certain paths
+checked out in your git working tree.
+Since port sources aren't always interesting
+and only
+.Em required
+while actually building the port,
+it makes sense to not have them always checked out.
+.
+.Pp
+Rather than manipulate
+.Xr git-sparse-checkout 1
+myself,
+I added support for it
+directly into
+.Pa Port.mk .
+If sparse checkout is enabled,
+building a port will automatically
+add its source tree to the checkout list,
+and cleaning that port will
+remove it from the list.
+At rest,
+only the port system itself
+and the package tarballs
+need to be present on the file system.
+.
+.Pp
+It turns out that upstream
+build system behaviour
+is super inconsistent,
+even among projects using
+the same tools.
+I started collecting a list of checks
+to perform on the output of my port builds
+to make sure they didn't do anything weird.
+They live in
+.Pa Check.sh ,
+which gets run
+when a package tarball is created.
+The current list of checks is:
+.Bl -bullet
+.It
+Check for directories not included by
+.Ev PACKAGE_DIRS .
+In other words,
+make sure the port isn't
+trying to install anything
+outside of
+.Pa /usr/local .
+Sometimes this makes sense,
+though,
+which is what
+.Ev PACKAGE_DIRS
+is for.
+.It
+Check for references to PWD,
+i.e. the build directory.
+This can mean the build
+didn't understand
+.Ev PREFIX
+and
+.Ev DESTDIR
+correctly,
+or that it built with debug info.
+.It
+Check for binaries without manuals.
+If your software installs an executable in
+.Pa bin
+but not a manual page,
+your software is incomplete!
+Sometimes this just means
+I missed an extra documentation install target.
+.It
+Check for dynamic linking to outside objects.
+In other words,
+if something ended up linking to
+a library installed by MacPorts
+rather than the one from
+.Nm jorts
+or macOS.
+.It
+Check for dynamic linking
+to system libraries
+.Nm jorts
+provides instead.
+Similar to the last one,
+if both macOS and
+.Nm jorts
+provide a library,
+check that ports link with the latter.
+.It
+Check for scripts with outside interpreters.
+This is analogous to the linking checks
+but for scripts,
+checking that their shebang lines
+refer to interpreters installed
+by macOS or
+.Nm jorts .
+.El
+.
+.Pp
+A number of my ports
+still fail some of these checks,
+but I have fixed a lot of problems
+the script called out.
+.
+.Pp
+Speaking of problem ports...
+git's build system is truly awful.
+I'm sorry,
+it's just really disappointing.
+On the upside though,
+I did manage to patch it
+to use
+.Xr asciidoctor 1
+directly to generate manual pages
+from asciidoc source,
+rather than generating docbook or whatever
+then converting that.
+One less build dependency!
+I also fixed up curl's
+.Pa CMakeLists.txt
+(which I guess are normally only used on Windows)
+to build and install documentation properly.
+And I got libcaca's Cocoa driver working again!
+Very important to be able to run
+.Xr cacafire 1
+in a Cocoa window.
+.
+.Pp
+Shout out to SDL2,
+which didn't require any patching
+or extra options beyond
+.Ev USE_CMAKE=yes .
+Model upstream.
+.
+.Pp
+Some other odds and ends:
+I like being able to name ports how I want
+(for example,
+.Sy ag )
+and use my own port version convention,
+using
+.Ql +
+to append VCS revisions
+and
+.Ql ~
+to append port revisions.
+I don't think those are likely
+to ever clash with upstream versioning schemes.
+Not that I even need to follow upstream versioning.
+There is no reason the version number of
+.Xr dash 1
+should start with a zero.
+.
+.Pp
+Speaking of versions,
+a big downside of maintaining your own ports tree
+is that you actually need to update it.
+Thankfully,
+once I packaged
+.Xr curl 1
+and
+.Xr jq 1
+(which needs a new release dammit,
+it's been 4 years and the build is broken
+on macOS),
+I could use the Repology API
+to check if I'm behind everyone else.
+Far more reliable than
+trying to automate checking upstreams
+for new versions.
+That lives in the
+.Pa Outdated
+shell script.
+.
+.Pp
+Phew!
+I wrote a lot about this.
+It feels a little self-indulgent,
+but I've had fun working on this
+and want to share.
+If anyone else tries anything similar,
+or is weird enough to give
+.Nm jorts
+a try themselves,
+I'd love to hear about it!
+.
+.Sh SEE ALSO
+.Lk https://git.causal.agency/jorts/
+.Pp
+.Lk https://youtu.be/Sx3ORAO1Y6s
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+Listening to
+.Em Arcade Fire \(em Arcade Fire (EP) ,
+.Em Arcade Fire \(em The Suburbs .
+.Pp
+Typed on a brand new
+Leopold FC660M
+with Cherry MX Red switches.
+Lovely keyboard.
diff --git a/www/text.causal.agency/034-voices.7 b/www/text.causal.agency/034-voices.7
new file mode 100644
index 00000000..4990295d
--- /dev/null
+++ b/www/text.causal.agency/034-voices.7
@@ -0,0 +1,56 @@
+.Dd March  5, 2022
+.Dt VOICES 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Voices
+.Nd more kinds of them
+.
+.Sh DESCRIPTION
+Welcome to the third time
+I started writing this post!
+I think the first time
+was after watching a jan Misali video
+that had clips of audio interviews in it.
+It got me thinking about
+how interesting it was
+to hear someone's voice
+without knowing anything else about them.
+.
+.Pp
+That's pretty much all I managed to write
+the first two times I started this.
+If I get past this next sentence,
+then I can probably finish the post.
+What stopped me was that
+all my thoughts and feelings about voices
+are influenced by being trans
+(and being a fan of other trans people),
+and I thought,
+.Dq I don't write about that here,
+but why don't I?
+I don't have to come out to my blog.
+.
+.Pp
+So really what I have been wanting to say is this:
+every trans woman's voice that I have heard
+has sounded genuinely wonderful to me.
+Especially if you're reading this
+and we've been on a voice call before.
+I know,
+voices are the object of so much self-consciousness,
+but I really wish they didn't have to be.
+Most of us do not sound like cis women
+and to me that is fine.
+Good, actually.
+Trans women sound like trans women.
+As a voice appreciator,
+I am so happy to hear more kinds.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+I've been watching some Vektroid streams lately,
+and I love her voice.
+It was another thing
+reminding me to write this.
diff --git a/www/text.causal.agency/035-addendum-2021.7 b/www/text.causal.agency/035-addendum-2021.7
new file mode 100644
index 00000000..262f2178
--- /dev/null
+++ b/www/text.causal.agency/035-addendum-2021.7
@@ -0,0 +1,111 @@
+.Dd March 18, 2022
+.Dt ADDENDUM-2021 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Addendum 2021
+.Nd missed music
+.
+.Sh DESCRIPTION
+I just realized that I totally forgot
+some important music for me from last year
+in my Albums 2021 post,
+because it wasn't in my playlist.
+Last year I watched
+the Berserk anime from 1997,
+and its soundtrack is incredible.
+.
+.Pp
+Actually the only reason
+I started watching it at all
+was because of the music.
+I was watching the wayneradiotv stream,
+.Do
+Mon repas durant un temps de tristesse;
+un pizza je n'oublierai jamais
+.Dc
+and I was mesmerized by the Guts theme.
+I had to find out what it was from.
+This was also around the time
+that Kentaro Miura died
+so people were really talking about it.
+Anyway just hearing
+that part of the soundtrack
+got me to start watching the anime,
+since you can find it all on youtube.
+.
+.Pp
+The anime in general did not disappoint.
+Actually it's really fucking good,
+and so is the rest of the soundtrack.
+The title sequence and credits tracks
+are so good that I let them play
+every episode even though
+I watched the series over only like 2 days.
+.
+.Pp
+I absolutely love whatever genre this stuff is.
+Is '90s anime intros its own genre?
+Something about combining
+acoustic and electric guitars,
+maybe.
+I'm also fond of
+the poorly written english lyrics.
+They're poetic in a distinctive way.
+I feel the same about
+that Shinsei Kamattechan
+song that was used for the credits of
+Attack on Titan season 2.
+Honestly awesome to write lyrics
+in a second language you haven't mastered.
+.
+.Pp
+So,
+the intro track,
+.Em Tell Me Why .
+First off,
+that sword sound effect
+near the beginning rules.
+Put that in more songs.
+What I really can't get enough of
+on this track are
+the quiet shouty vocals
+a bit off to the left
+during the chorus.
+It's such a cool idea
+to have clean lead vocals
+and shouting in the background.
+.
+.Pp
+And the credits track,
+.Em "Waiting So Long" .
+That first low note is so good.
+This is really a perfect credits song
+for the atmosphere of the show.
+It's creeping.
+The dual vocals
+the whole way through
+are such an interesting texture.
+Both of these tracks
+have really cool vocal sounds.
+And that dirty final guitar chord
+is a great sound to end on.
+.
+.Sh SEE ALSO
+These aren't great quality uploads
+but this stuff is sadly hard to find.
+.Bl -tag -width Ds
+.It "Guts"
+.Lk https://youtu.be/vZa0Yh6e7dw
+.It "Earth"
+.Lk https://youtu.be/5iAViNf9Z4Y
+.It "Penpals \(em Tell Me Why"
+.Lk https://youtu.be/I2rV8oKWSdM
+.It "Silver Fins \(em Waiting So Long"
+.Lk https://youtu.be/70GD2SBCq64
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+.Dq I like swinging my sword in battle.
diff --git a/www/text.causal.agency/036-compassion.7 b/www/text.causal.agency/036-compassion.7
new file mode 100644
index 00000000..9d0d887d
--- /dev/null
+++ b/www/text.causal.agency/036-compassion.7
@@ -0,0 +1,105 @@
+.Dd March 31, 2022
+.Dt COMPASSION 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Compassion
+.Nd better world fiction
+.
+.Sh DESCRIPTION
+Recently I watched the film
+.Em Margarita With a Straw .
+I'm not sure how to feel
+about some aspects of it,
+but it tries to do a lot,
+and I was still thinking about it
+a couple days later.
+.
+.Pp
+What really sticks out about it,
+to me,
+is that it is
+better world fiction,
+for lack of a better term.
+It's a film about two characters
+with disabilities,
+but it doesn't play into tropes.
+There's no big dramatic scene
+where a character gets treated unfairly.
+It doesn't really happen.
+In the world of the movie,
+most people are accepting,
+patient
+and compassionate.
+That's not to say
+there is no conflict.
+The film is just telling a different story.
+.
+.Pp
+The story takes place
+in a better world.
+Or maybe it takes place
+in a world that exists
+within our own,
+hidden between the worse parts.
+It's wonderfully subversive.
+Because I went into the film
+expecting at least one deeply upsetting
+scene of discrimination.
+What else would you expect
+of a story like this one,
+right?
+But instead of being upset,
+I was warmed.
+It was so nice to see
+the characters work through
+their own problems
+surrounded by simple kindness.
+And when it was over,
+I was left wanting
+to move our world
+closer to that one.
+.
+.Pp
+That's what I love about this kind of fiction.
+It's why I love the books of Becky Chambers so much.
+They give me hope,
+and guidance.
+I count the
+.Em Murderbot Diaries
+series in this as well,
+which shows a sort of bad world,
+and an alternative.
+I think it's so important
+to see the good that exists
+and the good that could exist.
+Rather than something to fight against,
+these stories show something to fight for.
+A more compassionate world.
+.
+.Pp
+I know,
+one person can't change the world.
+But they can change their own world,
+and the worlds of those around them.
+And slowly,
+good things can spread.
+I'll strive to be
+more patient,
+more understanding,
+more compassionate,
+and I hope you will too.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Pp
+I can't help but worry,
+when I write something like this,
+that someone I know will read it
+and think that I'm lying
+because I've hurt them.
+If that's the case,
+I am sorry,
+and I promise
+I am trying to do better.
diff --git a/www/text.causal.agency/037-care.7 b/www/text.causal.agency/037-care.7
new file mode 100644
index 00000000..052a4727
--- /dev/null
+++ b/www/text.causal.agency/037-care.7
@@ -0,0 +1,167 @@
+.Dd April  3, 2022
+.Dt CARE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Care
+.Nd trans stuff in Montreal
+.
+.Sh DESCRIPTION
+This kind of info
+is frustratingly hard to find
+even from support orgs
+and the like.
+I think it's unlikely
+that anyone in my blog's audience
+is also someone who needs this info,
+and my blog isn't easy to find either,
+but I want to at least
+make it available somewhere.
+Really this is just like
+the posts where I figure out
+how to do something with a computer
+then I write it down.
+.
+.Pp
+Prices obviously change,
+by which I mean they inevitably go up,
+but I'm gonna give the amounts I paid
+in 2021\(en2022.
+Also if you want more details
+about any of this
+please email me.
+I will be happy to tell you all about it.
+.
+.Ss Medication
+I get HRT through
+Dr. Gabrielle Landry
+at La Clinique A,
+which is a private clinic.
+I've done everything over the phone.
+After the first consultation,
+I signed an informed consent form
+and had a prescription the next day,
+which I could start
+after I got an initial blood test.
+The information I found
+said to contact a specific person
+at the clinic with a direct phone number,
+which is what I did.
+Email me if you want that number.
+.
+.Pp
+I paid $300 for the first consult,
+$195 for the first followup,
+and $75 for further followups.
+I think annual appointments
+are more expensive
+than the followups.
+I've been getting blood tests done at a CLSC,
+which is free.
+On the public drug insurance plan,
+I paid $30-$35
+for my prescriptions
+as my dosage increased.
+I have private insurance now
+that entirely covers prescriptions,
+so I'm not sure what I'd be paying
+for my current prescription
+on the public plan.
+.
+.Ss Hair removal
+I tried laser hair removal,
+for longer than I should have.
+It was a waste of time and money.
+Do not believe any arguments about
+its convenience over electrolysis.
+.
+.Pp
+I've started getting electrolysis done
+with Dimi.
+Again,
+feel free to email me for contact info.
+He is very good and can do long sessions.
+I really don't find it very painful,
+which I think is partly my own pain tolerance
+and partly good equipment and skill.
+I've also found that taking acetaminophen beforehand
+and dressing warmly to keep my body relaxed help.
+I've paid $85 for hour-long sessions
+and $160 for two-hour sessions.
+I'm still early in treatment,
+but I'm really happy with the results so far!
+.
+.Ss Sex & name change
+The form for this is
+.Do
+Application to Change the Sex Designation
+of a Person 18 Years of Age and Over
+.Dc
+from the
+.Em Directeur de l'\('etat civil .
+It's self-ID,
+but you have to get it signed by
+someone you know
+and a commissioner for oaths.
+Julien at P10 is qualified for that
+and was super nice.
+We did it over Zoom.
+It's a free service,
+so I made a donation to P10.
+.
+.Pp
+I paid $144 to file mine
+but it's now FREE
+the first time you do it.
+Also $17 to mail it.
+Surprisingly,
+I got an acknowledgment letter
+.Po
+just saying they got it
+and would start looking at it
+.Dq shortly
+.Pc
+like a week and a half
+after I mailed the application.
+My cheque was cashed
+39 days after the date
+on the acknowledgment,
+and I got a
+.Dq favourable decision
+a week later.
+It takes another 30 days
+to get the certificate of change,
+after which you can
+order a new birth certificate
+and RAMQ will (slowly) send you a form
+to get a new card.
+In all it took about 4 months
+from when I mailed the application
+to having ID with my name on it.
+.
+.Ss Therapy
+I'm not seeking therapy
+for gender specifically,
+but I would like to find a good therapist
+that's aware of it.
+I'll update this
+if I find one.
+.
+.Ss Piercings
+Ok I know this isn't trans-specific
+but at least for me getting piercings
+was gender-affirming.
+Cuz I got nipple piercings lol.
+Anyway,
+I went to Mauve.
+They're super nice,
+really know what they're doing,
+and their website has lots of info.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Pp
+If somehow you did find this useful,
+I'd love for you to email me
+and let me know how things went for you.
diff --git a/www/text.causal.agency/038-agency.7 b/www/text.causal.agency/038-agency.7
new file mode 100644
index 00000000..f99a070b
--- /dev/null
+++ b/www/text.causal.agency/038-agency.7
@@ -0,0 +1,85 @@
+.Dd April 14, 2022
+.Dt AGENCY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Agency
+.Nd origin of a name
+.
+.Sh DESCRIPTION
+When I registered this domain name,
+it was aspirational.
+Intentionally so.
+I wanted a new domain
+for a new identity,
+and I was thinking about personhood.
+That's what causal agency means.
+.
+.Pp
+It really was aspirational
+for me at the time.
+I spent a lot of time
+wishing I could be a person,
+because I didn't feel like one.
+I didn't feel real,
+like everyone else was.
+I didn't have any power
+over my own life.
+Things just happened to me,
+and I watched.
+There wasn't really a
+.Dq me
+there.
+The world was something that happened
+but that I couldn't interact with.
+I felt like that
+for most of my life.
+.
+.Pp
+But at some point
+I decided that,
+even if I wasn't now,
+one day I hoped to be an actual real life person.
+Like most programmers
+I am dreadful at naming things,
+so I didn't come up
+with this clever domain name
+myself.
+I typed
+.Dq person
+into some thesaurus,
+and it gave back
+.Dq causal agent ,
+and I realized
+agency is a TLD now.
+.
+.Pp
+Maybe it's a little dramatic
+to label myself with the thing
+I didn't think I had.
+But who knows,
+maybe it helped.
+Because it took a few years,
+but I did become a person.
+I feel real now.
+I can change my own life
+and the world around me.
+I have causal agency.
+.
+.Pp
+I am really proud of this domain name.
+I'm proud to put it on everything I make.
+Every instance of it
+is a reminder
+that I did what I set out to do,
+and that I'm still doing it.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.
+.Pp
+If anything in this post resonates with you,
+I want you to know that,
+whatever you think you can't do,
+it is possible,
+and you'll get there one day.
diff --git a/www/text.causal.agency/039-apologies.7 b/www/text.causal.agency/039-apologies.7
new file mode 100644
index 00000000..1b15076a
--- /dev/null
+++ b/www/text.causal.agency/039-apologies.7
@@ -0,0 +1,81 @@
+.Dd September 19, 2022
+.Dt APOLOGIES 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Apologies
+.Nd making them
+.
+.Sh DESCRIPTION
+Apologies are very important to me.
+Unfortunately
+I've only recently realized
+how valuable they are.
+I've tried to think about
+what makes a good apology,
+since it's not something
+I was ever taught.
+This is the advice
+I came up with for myself,
+on how to apologize.
+.
+.Bl -enum
+.It
+Make the apology.
+This is the most important part.
+If you feel guilty
+for something you've done,
+or think you might have hurt someone,
+apologize.
+Even if they don't need an apology,
+saying sorry won't hurt.
+And start with that.
+Literally say
+.Dq I'm sorry .
+Sometimes people forget that.
+.Pp
+On the other side,
+if you've been hurt by someone,
+and you trust them,
+let them know.
+Give them a chance to apologize.
+People don't always realize
+they've made a mistake.
+.
+.It
+Explain what you did wrong.
+I think it's important
+for the other person
+to know you understand
+how you've messed up.
+Really think about this!
+It's what will help you learn.
+If you know you've hurt someone
+but you're not sure why,
+you can try asking them.
+Take their answer seriously.
+.
+.It
+Don't make excuses.
+Do not talk about yourself.
+Don't even mention
+how you were feeling stressed that day,
+or whatever.
+It's not relevant.
+We all make mistakes,
+we all have bad days.
+.
+.It
+Commit to doing better.
+Try to learn from your mistakes.
+Say it won't happen again.
+Literally say
+.Dq I won't do that again .
+And then try your hardest to make that true.
+An apology is a commitment,
+not something you're done with
+once you've said it.
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/040-sound-memory.7 b/www/text.causal.agency/040-sound-memory.7
new file mode 100644
index 00000000..c995de08
--- /dev/null
+++ b/www/text.causal.agency/040-sound-memory.7
@@ -0,0 +1,165 @@
+.Dd November 14, 2022
+.Dt SOUND-MEMORY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Sound Memory
+.Nd associations
+.
+.Sh DESCRIPTION
+.Ss Talking Heads \(em "Remain In Light"
+The first time I gave this album a serious listen
+was when I was going for several-hour walks
+at 4 in the morning in,
+I think,
+fall 2020.
+I would stay up all night,
+go out walking at 4am
+for a couple hours,
+come home,
+eat
+.Dq breakfast
+and go to sleep.
+I listened to this album
+walking on completely empty
+big city streets
+in the dark.
+.
+.Ss Buffy Sainte-Marie \(em Up Where We Belong
+I started listening to this album
+after hearing it many mornings
+walking into the cafe on my block
+back in 2019.
+I could tell Vincent was working
+if I heard this when I opened the door.
+.
+.Ss Molasses \(em Trilogie: Toil & Peaceful Life
+I listened to this when I had 8am classes
+in CEGEP.
+In particular my first semester philosophy course,
+which was in the forum.
+I usually got there even earlier
+because of how the bus schedules worked out.
+There was another girl in my class,
+who I always sat next to,
+who also got there early,
+but we never spoke outside of class.
+.
+.Ss Arcade Fire \(em Funeral
+This album just feels like walking outside
+in fresh snow in early winter,
+you know?
+.
+.Ss Molasses \(em Trouble at Jinx Hotel
+I listened to this when I was looking for an apartment.
+I specifically remember listening to it
+walking down Clark toward my new place
+to pick up my keys.
+.
+.Ss Arcade Fire \(em Neon Bible
+The song
+.Dq "No Cars Go"
+is strongly associated for me
+with my earliest gender feelings.
+It's how I date when I first
+started to feel like something was wrong.
+The Suburbs was released in 2010,
+so I was probably listening to Neon Bible
+in 2011.
+Ten years between that
+and coming out.
+.
+.Ss "Do Make Say Think" \(em "You You're a History In Rust"
+I remember hearing
+.Dq "A Tender History In Rust"
+for the first time
+at the office of my first job.
+Me and my coworkers stayed late,
+probably on a Friday night,
+drinking free tech startup booze.
+.
+.Ss mewithoutYou \(em It's All Crazy! It's All False! It's All a Dream! It's Alright
+I exclusively listened to this album
+on a high school trip to Europe.
+Every morning when we got on the bus,
+I heard
+.Dq Every Thought a Thought of You
+and every night before bed
+I listened to
+.Dq The King Beetle on a Coconut Estate .
+.
+.Ss Arcade Fire \(em The Suburbs
+I listened to this album a tonne
+when I was playing
+Minecraft and Urban Terror
+with my online friends
+while I was in high school.
+In particular I remember
+a backyard shed World of Padman map
+and the apartments Minecraft world.
+.
+.Ss Arcade Fire \(em Reflektor
+I associate
+.Dq Afterlife
+with the walk between Laurier metro
+and my first job,
+in the winter.
+Must've just been how the timing worked out
+with my commute at the time.
+.
+.Ss Swans \(em To Be Kind
+I listened to this on one of my playthroughs
+of Half-Life 2.
+In particular I associate
+.Dq Bring the Sun / Toussaint L'Ouverture
+with the Water Hazard chapter.
+.
+.Ss Wrekmeister Harmonies \(em Light Falls
+For a while I put this on whenever I
+left my apartment to go somewhere
+and it was already dark,
+so probably winter.
+.
+.Ss St. Vincent \(em MASSEDUCTION
+This,
+along with the next one,
+I think were all I listened to
+on a family vacation
+to Quebec City and New Brunswick
+some years ago.
+.
+.Ss SOPHIE \(em Oil of Every Pearl's Un-Insides
+Many hours on the road
+on that family vacation.
+Two albums on repeat.
+.
+.Ss Julia Holter \(em Aviary
+This is another album
+I listened to when I was taking
+walks at 4am.
+I wasn't in a good place.
+Yet.
+.
+.Ss Beep Test \(em Laugh Track
+A tape from the first act
+at one of my favourite shows
+I've ever been to,
+at La Sotterenea
+in Suoni 2019.
+I wish I had been out already.
+.
+.Ss The Armed \(em Only Love
+The third of the albums I listened to
+on those dark walks.
+I listened to it loud,
+this album's mixing needs it.
+.
+.Ss Eliza Kavtion \(em The Rez That Summer
+A favourite local artist.
+I remember vividly the first time
+I heard her play,
+opening for Wrekmeister Harmonies
+at La Vitrola in 2018.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/041-albums-2022.7 b/www/text.causal.agency/041-albums-2022.7
new file mode 100644
index 00000000..48bd3c3d
--- /dev/null
+++ b/www/text.causal.agency/041-albums-2022.7
@@ -0,0 +1,185 @@
+.Dd December 21, 2022
+.Dt ALBUMS-2022 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm albums 2022
+.Nd review
+.
+.Sh DESCRIPTION
+it's the year-end review
+of albums I listened to.
+same as last year,
+I added any albums I got into
+this year to a playlist.
+I've actually done that
+every year since 2018.
+maybe I'll review
+those old playlists some time.
+.
+.Ss ZHAOZE \(em SUMMER INSECTS TALK ABOUT ICE (2021)
+it's a five-and-a-half-minute album!
+you can loop it however long you want.
+it's really lovely.
+.Pp
+favourite track:
+ON HORSEBACK, TO FARAWAY
+.
+.Ss KATE BUSH \(em HOUNDS OF LOVE (1985)
+first of all I do not watch that one show.
+I've known that track for a while actually.
+I mean I probably first heard the CHROMATICS cover.
+but anyway,
+I think someone mentioned this album
+on IRC at just the right time
+and I put it on.
+the second half really shines tbh.
+love a concept album.
+.Pp
+favourite tracks:
+RUNNING UP THAT HILL,
+HOUNDS OF LOVE,
+AND DREAM OF SHEEP,
+WATCHING YOU WITHOUT ME,
+THE MORNING FOG.
+.
+.Ss GODSPEED YOU! BLACK EMPEROR \(em ALL LIGHTS FUCKED ON THE HAIRY AMP DROOLING (1994)
+didn't expect to hear this probably ever.
+still wild that it finally got uploaded.
+and to be honest I'm a little mad
+that it's actually good.
+like yeah it's not a godspeed album
+but it holds up as a tape on its own.
+it's the kind of shit I listen to.
+also can't believe some people
+still thought it was fake.
+like have you not heard
+any other efrim menuck projects?
+.Pp
+favourite tracks:
+$13.13,
+DIMINISHING SHINE,
+DADMOMDADDY,
+333 FRAMES PER SECOND,
+ALL ANGELS GONE.
+.
+.Ss BLACK DRESSES \(em FORGET YOUR OWN FACE (2022)
+woops I think I only listened to this like twice.
+will need to revisit it later for sure.
+I'll like it.
+.
+.Ss BACKXWASH \(em I LIE HERE BURIED WITH MY RINGS AND MY DRESSES (2021)
+only got into this album
+after hearing it live this summer.
+was the first show I went to in years
+and it was really fucking good.
+gotta listen to this shit loud.
+sampling godspeed for a beat fucks.
+honestly back to back bangers.
+.Pp
+favourite tracks:
+I LIE HERE BURIED WITH MY RINGS AND MY DRESSES,
+TERROR PACKETS,
+SONG OF SINNERS,
+BURN TO ASHES.
+.
+.Ss PHILIP GLASS ENSEMBLE \(em EINSTEIN ON THE BEACH (1979)
+actually just the knee plays
+because I can't be bothered
+listening to all of it.
+and I'm embarrassed by how much
+I enjoy this avant-garde bullshit.
+like ok just sing repeating numbers at me
+and my brain is happy.
+what is this?
+my kink?
+anyway I also have kind of an obsession
+with the
+.Dq story of love
+in knee 5.
+I fucking hate it.
+but it's delivered so well.
+and that violin though!
+.Pp
+favourite tracks:
+KNEE 1,
+KNEE 5.
+.
+.Ss KANYE WEST \(em YEEZUS (2013)
+ok look I listened to this
+before recent events.
+what the fuck.
+it's a really good album though?
+pretty sure I listened to it
+because bound 2 kept getting in my head,
+because of that minecraft parody parody
+wayne did ages ago.
+.Pp
+favourite tracks:
+BLACK SKINHEAD,
+HOLD MY LIQUOR,
+BLOOD ON THE LEAVES,
+BOUND 2.
+.
+.Ss FLYING RACCOON SUIT \(em AFTERGLOW (2021)
+I've listened to the whole album
+a few times
+but I'm mostly just here
+for the title track.
+this also happened to be
+dropped in IRC at just the right time.
+good ska-punk-type shit.
+and I like lisps ok.
+.Pp
+favourite track:
+AFTERGLOW.
+.
+.Ss RAMSHACKLE GLORY \(em LIVE THE DREAM (2011)
+one of those albums
+I don't know why I took so long
+to get to.
+I've been listening to johnny hobo
+since I was like in high school.
+ramshackle is a little more hopeful
+and I love that.
+your heart is a muscle the size of your fist.
+keep on loving.
+keep on fighting.
+.Pp
+favourite tracks:
+WE ARE ALL COMPOST IN TRAINING,
+NEVER COMING HOME,
+YOUR HEART IS A MUSCLE THE SIZE OF YOUR FIST.
+.
+.Ss LES RALLIZES D\('ENUD\('ES \(em THE OZ TAPES (2022)
+a pleasant surprise in someone's playlist.
+lately I've been listening to this
+in the metro to or from electrolysis.
+it's good listening for that.
+bold to have two versions
+of the same 24-minute song
+on the same release.
+.Pp
+favourite tracks:
+A SHADOW ON OUR JOY,
+THE LAST ONE_1970 (ver.2).
+.
+.Ss LINGUA IGNOTA \(em SINNER GET READY (2021)
+another I'm only getting into
+after hearing it live.
+just last sunday actually.
+was a good show.
+people will go wild
+to hear a cover live for real.
+.Pp
+favourite tracks:
+I WHO BEND THE TALL GRASSES,
+PENNSYLVANIA FURNACE,
+PERPETUAL FLAME OF CENTRALIA.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+I started writing this
+before I saw LINGUA IGNOTA.
+good thing I waited.
diff --git a/www/text.causal.agency/042-comfort-music.7 b/www/text.causal.agency/042-comfort-music.7
new file mode 100644
index 00000000..445e04c3
--- /dev/null
+++ b/www/text.causal.agency/042-comfort-music.7
@@ -0,0 +1,62 @@
+.Dd February 23, 2024
+.Dt COMFORT-MUSIC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm comfort music
+.Nd feel better
+.
+.Sh DESCRIPTION
+it's been a while.
+and I'm on almost no sleep
+and haven't eaten a real meal
+since noon.
+which is a state I've written
+at least a couple posts in before,
+so what better time
+to return to what has apparently
+become this blog's format:
+lists of some music I like.
+.
+.Pp
+this is a list of music that comforts me.
+.
+.Bl -bullet
+.It
+knee play 5, from einstein on the beach.
+I like the organ and the counting and the cadence of the story.
+.It
+low \(em words.
+and I'm tired.
+.It
+godspeed you! black emperor \(em storm.
+this is like my original comfort music.
+been listening to it since I was teenage.
+the grooves are worn deep in my mind.
+.It
+set fire to flames \(em love song for 15 ontario (w/ singing police car).
+I like how it ends.
+.It
+va, from the beginner's guide.
+I think that's the whole point.
+though maybe it's too sad
+to be truly comforting.
+.It
+undertale, from undertale.
+what can I say?
+.It
+wrekmeister harmonies \(em covered in blood from invisible wounds.
+I find quite a bit of the album comforting really.
+I'm picking this one because I like the cadence
+of the lyrics.
+.It
+lingua ignota \(em pennsylvania furnace and perpetual flame of centralia.
+these are really my go to in recent times.
+I like waiting for the next line.
+.El
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
+.Pp
+I don't think I've said anything
+very interesting here.
diff --git a/www/text.causal.agency/043-little-blessings.7 b/www/text.causal.agency/043-little-blessings.7
new file mode 100644
index 00000000..957c6289
--- /dev/null
+++ b/www/text.causal.agency/043-little-blessings.7
@@ -0,0 +1,78 @@
+.Dd March 24, 2024
+.Dt LITTLE-BLESSINGS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm little blessings
+.Nd life's
+.
+.Sh DESCRIPTION
+today I went out to go around.
+run some errands and do some shopping.
+along the way I was given
+several of life's little blessings.
+.
+.Pp
+while walking on ste-cath
+between berri and complexe desjardins,
+there was a somewhat disheveled man
+walking in the same direction and singing.
+he had a beautiful voice.
+he was singing a sad song in french,
+and he sung it well and enunciated every word.
+.
+.Pp
+in the mcdonald's at complexe desjardins,
+while waiting for my order,
+there were what appeared to be
+a teenager and her younger brother,
+who must have been
+looking at the display of
+current happy meal toys.
+the teenager was playing smash or pass,
+to the amusement of the younger one.
+they got ice cream
+and ate it across the room from me downstairs.
+.
+.Pp
+later,
+taking the 24 home from atwater
+carrying my new vacuum cleaner,
+the bus got lost.
+I think the driver missed the stop
+and tried to compensate
+by turning north onto peel
+and stopping there.
+but then he had to keep going up peel.
+he turned right onto docteur-penfield,
+which just brings you further up the mountain.
+when it met des pins,
+he turned left and pulled over,
+asking for guidance over the radio.
+we got moving again,
+back towards peel.
+that's how I ended up
+on a 24
+.Dq sherbrooke
+east,
+facing west on des pins.
+it was actually quite scenic.
+and amusing.
+I was in no rush.
+.
+.Pp
+after getting back onto sherbrooke,
+the bus had to take another detour,
+this one planned.
+so my ride on the 24,
+which normally only drives on sherbrooke,
+ended up going on peel,
+docteur-penfield,
+des pins,
+de bleury,
+ren\('e-l\('evesque
+and saint-laurent.
+it was a very exciting bus trip.
+.
+.Sh AUTHORS
+.An june Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/Makefile b/www/text.causal.agency/Makefile
new file mode 100644
index 00000000..a8683a20
--- /dev/null
+++ b/www/text.causal.agency/Makefile
@@ -0,0 +1,66 @@
+WEBROOT = /var/www/text.causal.agency
+
+TXTS += 001-make.txt
+TXTS += 002-writing-mdoc.txt
+TXTS += 003-pleasant-c.txt
+TXTS += 004-uloc.txt
+TXTS += 005-testing-c.txt
+TXTS += 006-some-libs.txt
+TXTS += 007-cgit-setup.txt
+TXTS += 008-how-irc.txt
+TXTS += 009-casual-update.txt
+TXTS += 010-irc-suite.txt
+TXTS += 011-libretls.txt
+TXTS += 012-inability.txt
+TXTS += 013-hot-tips.txt
+TXTS += 014-using-vi.txt
+TXTS += 015-reusing-tags.txt
+TXTS += 016-using-openbsd.txt
+TXTS += 017-unpasswords.txt
+TXTS += 018-operating-systems.txt
+TXTS += 019-mailing-list.txt
+TXTS += 020-c-style.txt
+TXTS += 021-time-machine.txt
+TXTS += 022-swans-are-dead.txt
+TXTS += 023-sparse-checkout.txt
+TXTS += 024-seprintf.txt
+TXTS += 025-v6-pwd.txt
+TXTS += 026-git-comment.txt
+TXTS += 027-openbsd-linode.txt
+TXTS += 028-names.txt
+TXTS += 029-topics.txt
+TXTS += 030-discs.txt
+TXTS += 031-books-2021.txt
+TXTS += 032-albums-2021.txt
+TXTS += 033-jorts.txt
+TXTS += 034-voices.txt
+TXTS += 035-addendum-2021.txt
+TXTS += 036-compassion.txt
+TXTS += 037-care.txt
+TXTS += 038-agency.txt
+TXTS += 039-apologies.txt
+TXTS += 040-sound-memory.txt
+TXTS += 041-albums-2022.txt
+TXTS += 042-comfort-music.txt
+TXTS += 043-little-blessings.txt
+
+all: colb ${TXTS}
+
+.SUFFIXES: .7 .fmt .txt
+
+.7.txt:
+	mandoc -T utf8 $< | ./colb > $@
+	touch -m -r $< $@
+
+.fmt.txt:
+	fmt $< | sed '1,/^$$/d' > $@
+	touch -m -r $< $@
+
+feed.atom: feed.sh colb ${TXTS}
+	sh feed.sh > feed.atom
+
+clean:
+	rm -f colb ${TXTS} feed.atom
+
+install: colb ${TXTS} feed.atom
+	install -p -m 644 ${TXTS} feed.atom ${WEBROOT}
diff --git a/www/text.causal.agency/colb.c b/www/text.causal.agency/colb.c
new file mode 100644
index 00000000..5faabc3a
--- /dev/null
+++ b/www/text.causal.agency/colb.c
@@ -0,0 +1,16 @@
+#include <locale.h>
+#include <stdio.h>
+#include <wchar.h>
+int main(void) {
+	setlocale(LC_CTYPE, "en_US.UTF-8");
+	wint_t next, prev = WEOF;
+	while (WEOF != (next = getwchar())) {
+		if (next == L'\b') {
+			prev = WEOF;
+		} else {
+			if (prev != WEOF) putwchar(prev);
+			prev = next;
+		}
+	}
+	if (prev != WEOF) putwchar(prev);
+}
diff --git a/www/text.causal.agency/feed.sh b/www/text.causal.agency/feed.sh
new file mode 100644
index 00000000..71bbf662
--- /dev/null
+++ b/www/text.causal.agency/feed.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+set -eu
+
+readonly Root='https://text.causal.agency'
+
+updated=$(date -u '+%FT%TZ')
+cat <<-EOF
+	<?xml version="1.0" encoding="utf-8"?>
+	<feed xmlns="http://www.w3.org/2005/Atom">
+	<title>Causal Agency</title>
+	<author><name>June</name><email>june@causal.agency</email></author>
+	<link href="${Root}"/>
+	<link rel="self" href="${Root}/feed.atom"/>
+	<id>${Root}/</id>
+	<updated>${updated}</updated>
+EOF
+
+encode() {
+	sed '
+		s/&/\&amp;/g
+		s/</\&lt;/g
+		s/"/\&quot;/g
+	' "$@"
+}
+
+set -- *.txt
+shift $(( $# - 20 ))
+for txt; do
+	entry="${txt%.txt}.7"
+	test -f "$entry" || entry="${txt%.txt}.fmt"
+	date=$(grep '^[.]Dd' "$entry" | cut -c 5-)
+	title=$(grep -m 1 '^[.]Nm' "$entry" | cut -c 5- | encode)
+	summary=$(grep '^[.]Nd' "$entry" | cut -c 5- | encode)
+	published=$(date -ju -f '%B %d, %Y %T' "${date} 00:00:00" '+%FT%TZ')
+	mtime=$(stat -f '%m' "$entry")
+	updated=$(date -ju -f '%s' "$mtime" '+%FT%TZ')
+	cat <<-EOF
+		<entry>
+		<title>${title}</title>
+		<summary>${summary}</summary>
+		<link href="${Root}/${txt}"/>
+		<id>${Root}/${txt}</id>
+		<published>${published}</published>
+		<updated>${updated}</updated>
+		<content type="xhtml">
+		<div xmlns="http://www.w3.org/1999/xhtml">
+	EOF
+	printf '<pre>'
+	encode "$txt"
+	cat <<-EOF
+		</pre>
+		</div>
+		</content>
+		</entry>
+	EOF
+done
+
+echo '</feed>'