aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--LICENSE661
-rw-r--r--Makefile64
-rw-r--r--README.md1
-rw-r--r--assets/images/logo.pngbin0 -> 182233 bytes
-rw-r--r--config.json261
-rw-r--r--config.toml195
-rw-r--r--config.yaml170
-rw-r--r--content/canory/_index.md4
-rw-r--r--content/canory/feeds/_index.md4
-rw-r--r--content/canory/likes/_index.md4
-rw-r--r--content/canory/media/_index.md4
-rw-r--r--content/canory/media/barn-owl.jpgbin0 -> 162670 bytes
-rw-r--r--content/canory/media/hugo-website.pngbin0 -> 54981 bytes
-rw-r--r--content/canory/media/internet-is-serious.jpgbin0 -> 104697 bytes
-rw-r--r--content/canory/media/profile/header.jpgbin0 -> 247768 bytes
-rw-r--r--content/canory/media/profile/picture.pngbin0 -> 182233 bytes
-rw-r--r--content/canory/messages/code.md22
-rw-r--r--content/canory/messages/definitions.md18
-rw-r--r--content/canory/messages/diagrams.md29
-rw-r--r--content/canory/messages/drafts.md11
-rw-r--r--content/canory/messages/highlighting.md11
-rw-r--r--content/canory/messages/hugo.md13
-rw-r--r--content/canory/messages/imgur-video.md11
-rw-r--r--content/canory/messages/lists.md16
-rw-r--r--content/canory/messages/markdown.md15
-rw-r--r--content/canory/messages/nsfw.md16
-rw-r--r--content/canory/messages/odysee.md11
-rw-r--r--content/canory/messages/pdf.md12
-rw-r--r--content/canory/messages/pinned.md13
-rw-r--r--content/canory/messages/plumbing.md14
-rw-r--r--content/canory/messages/self.md14
-rw-r--r--content/canory/messages/selfdestruct.md30
-rw-r--r--content/canory/messages/styles.md10
-rw-r--r--content/canory/messages/tables.md15
-rw-r--r--content/canory/messages/tasks.md14
-rw-r--r--content/canory/messages/twitter.md11
-rw-r--r--content/canory/messages/unlisted.md11
-rw-r--r--content/canory/messages/videos.md11
-rw-r--r--content/canory/messages/wikipedia.md17
-rw-r--r--content/canory/messages/xkcd.md14
-rw-r--r--content/canory/messages/youtube.md12
-rw-r--r--content/default/_index.md4
-rw-r--r--content/default/feeds/_index.md4
-rw-r--r--content/default/likes/_index.md4
-rw-r--r--content/default/media/_index.md4
-rw-r--r--content/default/media/profile/header.jpgbin0 -> 542296 bytes
-rw-r--r--content/default/media/profile/picture.pngbin0 -> 31774 bytes
-rw-r--r--content/default/messages/authors.md12
-rw-r--r--content/default/messages/quickstart.md33
-rw-r--r--data/authors/canory.yaml21
-rw-r--r--data/authors/default.yaml18
-rw-r--r--shell.nix35
-rw-r--r--static/css/default.css1629
-rw-r--r--static/css/noscript.css3
-rw-r--r--static/css/syntax-highlight-default.css202
-rw-r--r--static/images/404.pngbin0 -> 6718 bytes
-rw-r--r--static/js/code-copy.ts22
-rw-r--r--static/js/fixedsearch.ts194
-rw-r--r--static/js/fuzzysort.js636
-rw-r--r--static/js/index.js424
-rw-r--r--static/js/index.ts6
-rw-r--r--static/js/instantpage.ts292
-rw-r--r--static/js/pager.ts46
-rw-r--r--static/js/plumber.ts59
-rw-r--r--themes/default/layouts/_default/_markup/render-codeblock-goat.html19
-rw-r--r--themes/default/layouts/_default/_markup/render-codeblock.html19
-rw-r--r--themes/default/layouts/_default/_markup/render-heading.html6
-rw-r--r--themes/default/layouts/_default/_markup/render-image.html45
-rw-r--r--themes/default/layouts/_default/_markup/render-link.html21
-rw-r--r--themes/default/layouts/_default/baseof.html32
-rw-r--r--themes/default/layouts/_default/feeds.html151
-rw-r--r--themes/default/layouts/_default/home.archive-messages.html50
-rw-r--r--themes/default/layouts/_default/home.archive-tags.html47
-rw-r--r--themes/default/layouts/_default/home.authors.html40
-rw-r--r--themes/default/layouts/_default/home.browserconfig.xml13
-rw-r--r--themes/default/layouts/_default/home.sources.html120
-rw-r--r--themes/default/layouts/_default/index.json40
-rw-r--r--themes/default/layouts/_default/index.webmanifest24
-rw-r--r--themes/default/layouts/_default/likes.html58
-rw-r--r--themes/default/layouts/_default/list.html29
-rw-r--r--themes/default/layouts/_default/media.html40
-rw-r--r--themes/default/layouts/_default/rss.xml32
-rw-r--r--themes/default/layouts/_default/section.html45
-rw-r--r--themes/default/layouts/_default/single.html22
-rw-r--r--themes/default/layouts/_default/sitemap.xml22
-rw-r--r--themes/default/layouts/_default/summary.html1
-rw-r--r--themes/default/layouts/_default/taxonomy.html62
-rw-r--r--themes/default/layouts/_default/term.html33
-rw-r--r--themes/default/layouts/partials/archive-link.html2
-rw-r--r--themes/default/layouts/partials/author-card.html23
-rw-r--r--themes/default/layouts/partials/author-domain-host.html2
-rw-r--r--themes/default/layouts/partials/author-list.html17
-rw-r--r--themes/default/layouts/partials/author-media-path.html2
-rw-r--r--themes/default/layouts/partials/author-name.html2
-rw-r--r--themes/default/layouts/partials/author-user.html2
-rw-r--r--themes/default/layouts/partials/card-feed.html82
-rw-r--r--themes/default/layouts/partials/card-id.html7
-rw-r--r--themes/default/layouts/partials/card.html35
-rw-r--r--themes/default/layouts/partials/context-menu.html4
-rw-r--r--themes/default/layouts/partials/context-profile.html22
-rw-r--r--themes/default/layouts/partials/count-authors.html6
-rw-r--r--themes/default/layouts/partials/count-likes.html10
-rw-r--r--themes/default/layouts/partials/count-media.html17
-rw-r--r--themes/default/layouts/partials/count-tags.html1
-rw-r--r--themes/default/layouts/partials/count-total-messages.html2
-rw-r--r--themes/default/layouts/partials/csp.html40
-rw-r--r--themes/default/layouts/partials/draft.html1
-rw-r--r--themes/default/layouts/partials/expiry-date.html11
-rw-r--r--themes/default/layouts/partials/footer.html12
-rw-r--r--themes/default/layouts/partials/gallery-images.html8
-rw-r--r--themes/default/layouts/partials/gallery-walk.html41
-rw-r--r--themes/default/layouts/partials/handle.html5
-rw-r--r--themes/default/layouts/partials/head.html50
-rw-r--r--themes/default/layouts/partials/js-back.html2
-rw-r--r--themes/default/layouts/partials/manifest.html16
-rw-r--r--themes/default/layouts/partials/media-search.html27
-rw-r--r--themes/default/layouts/partials/meta-thumbnail-path.html1
-rw-r--r--themes/default/layouts/partials/meta.html64
-rw-r--r--themes/default/layouts/partials/navigator-left.html17
-rw-r--r--themes/default/layouts/partials/navigator-middle-walk.html15
-rw-r--r--themes/default/layouts/partials/navigator-middle.html58
-rw-r--r--themes/default/layouts/partials/navigator-right-default.html4
-rw-r--r--themes/default/layouts/partials/navigator-right.html3
-rw-r--r--themes/default/layouts/partials/pagination.html83
-rw-r--r--themes/default/layouts/partials/profile-picture.html9
-rw-r--r--themes/default/layouts/partials/profile-tabs.html8
-rw-r--r--themes/default/layouts/partials/profile.html52
-rw-r--r--themes/default/layouts/partials/read-time.html6
-rw-r--r--themes/default/layouts/partials/site-description.html8
-rw-r--r--themes/default/layouts/partials/site-title.html15
-rw-r--r--themes/default/layouts/partials/styles-navigator.html14
-rw-r--r--themes/default/layouts/partials/tags.html5
-rw-r--r--themes/default/layouts/partials/title.html1
-rw-r--r--themes/default/layouts/partials/web-ring.html138
-rw-r--r--themes/default/layouts/partials/word-count.html14
-rw-r--r--themes/default/layouts/shortcodes/abbr.html4
-rw-r--r--themes/default/layouts/shortcodes/asciicast.html17
-rw-r--r--themes/default/layouts/shortcodes/audio.html18
-rw-r--r--themes/default/layouts/shortcodes/gist.html9
-rw-r--r--themes/default/layouts/shortcodes/iframe.html13
-rw-r--r--themes/default/layouts/shortcodes/imgur-video.html20
-rw-r--r--themes/default/layouts/shortcodes/imgur.html13
-rw-r--r--themes/default/layouts/shortcodes/ins.html3
-rw-r--r--themes/default/layouts/shortcodes/kbd.html3
-rw-r--r--themes/default/layouts/shortcodes/mark.html5
-rw-r--r--themes/default/layouts/shortcodes/odysee.html14
-rw-r--r--themes/default/layouts/shortcodes/pdf.html11
-rw-r--r--themes/default/layouts/shortcodes/quote.html13
-rw-r--r--themes/default/layouts/shortcodes/reddit.html29
-rw-r--r--themes/default/layouts/shortcodes/self.html13
-rw-r--r--themes/default/layouts/shortcodes/smallcaps.html6
-rw-r--r--themes/default/layouts/shortcodes/spoiler.html9
-rw-r--r--themes/default/layouts/shortcodes/spoiler.rss.xml4
-rw-r--r--themes/default/layouts/shortcodes/tweet.html18
-rw-r--r--themes/default/layouts/shortcodes/video.html36
-rw-r--r--themes/default/layouts/shortcodes/vimeo.html12
-rw-r--r--themes/default/layouts/shortcodes/wikipedia.html16
-rw-r--r--themes/default/layouts/shortcodes/youtube.html29
-rw-r--r--tsconfig.json23
160 files changed, 7663 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4077f0f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+icons
+public
+resources
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..be3f7b2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
+<https://www.gnu.org/licenses/>.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..99aef3d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,64 @@
+all:
+ make config
+ make js
+ make hugo
+
+config:
+ yj -yj -i < config.yaml > config.json
+ yj -yt -i < config.yaml > config.toml
+
+watch:
+ while true; do find . | entr -d -s 'make all'; done
+
+js:
+ deno bundle --no-check --config tsconfig.json static/js/index.ts static/js/index.js
+
+hugo:
+ rm -rf public
+ hugo
+ rm -f .hugo_build.lock
+
+server:
+ php -S 127.0.0.1:9294 -t public
+
+author:
+ifndef user
+ @printf 'Error: No author user name given!\nUsage: make author user=default\n'
+else
+ @cp -rT content/default content/$(user) || printf "Error: Author user '$(user)' data may already exist.\n"
+ @cp -rT data/authors/default.yaml data/authors/$(user).yaml || printf "Error: Author user '$(user)' configuration may already exist.\n"
+ _user=$(user) && \
+ _name=$${_user^} && \
+ grep -rl "default" content/$(user) | xargs sed --in-place 's/default/$(user)/g' && \
+ grep -rl "default" data/authors/$(user).yaml | xargs sed --in-place 's/default/$(user)/g' && \
+ grep -rl "Default" data/authors/$(user).yaml | xargs sed --in-place "s/Default/$$_name/g"
+endif
+
+icons:
+ rm -rf static/icons
+ mkdir -p static/icons/feather static/icons/tabler
+ svn export --force https://github.com/feathericons/feather/trunk/LICENSE static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/alert-circle.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/archive.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/arrow-left.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/arrow-right.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/arrow-up.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/circle.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/clock.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/copy.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/eye-off.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/git-commit.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/globe.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/tag.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/heart.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/home.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/map-pin.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/rss.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/search.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/trash-2.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/user.svg static/icons/feather
+ svn export --force https://github.com/feathericons/feather/trunk/icons/users.svg static/icons/feather
+ svn export --force https://github.com/tabler/tabler-icons/trunk/LICENSE static/icons/tabler
+ svn export --force https://github.com/tabler/tabler-icons/trunk/icons/pinned.svg static/icons/tabler
+ svn export --force https://github.com/tabler/tabler-icons/trunk/icons/book-2.svg static/icons/tabler
+ svn export --force https://github.com/tabler/tabler-icons/trunk/icons/clock.svg static/icons/tabler
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f021ee8
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# A Micro Blog
diff --git a/assets/images/logo.png b/assets/images/logo.png
new file mode 100644
index 0000000..20bf810
--- /dev/null
+++ b/assets/images/logo.png
Binary files differ
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..acd2e1f
--- /dev/null
+++ b/config.json
@@ -0,0 +1,261 @@
+{
+ "baseURL": null,
+ "theme": "default",
+ "title": "A Micro Blog",
+ "paginate": 10,
+ "summaryLength": 1,
+ "enableEmoji": true,
+ "buildDrafts": true,
+ "languageCode": "en-us",
+ "author": {
+ "default": {
+ "user": "default",
+ "webring": "canory"
+ },
+ "footer": "A Micro Blog\n"
+ },
+ "taxonomies": {
+ "tag": "tags"
+ },
+ "menu": {
+ "main": [
+ {
+ "name": "Home",
+ "url": "/",
+ "identifier": "feather/home",
+ "weight": 1
+ },
+ {
+ "name": "Tags",
+ "url": "/tags/",
+ "identifier": "feather/tag",
+ "weight": 2,
+ "icon": "home"
+ },
+ {
+ "name": "History",
+ "url": "/archives/messages/",
+ "identifier": "tabler/clock",
+ "weight": 3
+ },
+ {
+ "name": "Records",
+ "url": "/archives/tags/",
+ "identifier": "tabler/book-2",
+ "weight": 4
+ },
+ {
+ "name": "Authors",
+ "url": "/authors/",
+ "identifier": "feather/users",
+ "weight": 5
+ },
+ {
+ "name": "RSS Feed",
+ "url": "/rss.xml",
+ "identifier": "feather/rss",
+ "weight": 6
+ },
+ {
+ "name": "JSON Feed",
+ "url": "/index.json",
+ "identifier": "feather/circle",
+ "weight": 7
+ },
+ {
+ "name": "Sources",
+ "url": "/sources/",
+ "identifier": "feather/git-commit",
+ "weight": 8
+ }
+ ]
+ },
+ "markup": {
+ "highlight": {
+ "anchorLineNos": true,
+ "codeFences": true,
+ "guessSyntax": true,
+ "lineNos": false,
+ "lineNumbersInTable": false,
+ "noClasses": false,
+ "noHl": false
+ }
+ },
+ "params": {
+ "production": false,
+ "webmanifest": {
+ "name": "Micro Blog",
+ "shortName": "Micro",
+ "themeColor": "#ffffff",
+ "backgroundColor": "#ffffff",
+ "display": "standalone",
+ "logo": "images/logo.png"
+ },
+ "csp": {
+ "referrer": "origin",
+ "childsrc": [
+ "'self'"
+ ],
+ "fontsrc": [
+ "'self'"
+ ],
+ "formaction": [
+ "'self'",
+ "lite.duckduckgo.com"
+ ],
+ "framesrc": [
+ "'self'",
+ "imgur.com",
+ "www.youtube-nocookie.com",
+ "platform.twitter.com",
+ "en.m.wikipedia.org",
+ "odysee.com",
+ "docs.google.com"
+ ],
+ "imgsrc": [
+ "'self'",
+ "http://preview.test",
+ "imgs.xkcd.com"
+ ],
+ "mediasrc": [
+ "'self'",
+ "raw.githubusercontent.com"
+ ],
+ "objectsrc": [
+ "'none'"
+ ],
+ "prefetchsrc": [
+ "'self'"
+ ],
+ "scriptsrc": [
+ "'self'",
+ "s.imgur.com",
+ "platform.twitter.com"
+ ],
+ "scriptsrcelem": [
+ "'self'",
+ "s.imgur.com",
+ "platform.twitter.com"
+ ],
+ "stylesrc": [
+ "'self'",
+ "'unsafe-inline'",
+ "http://preview.test"
+ ]
+ }
+ },
+ "outputFormats": {
+ "html": {
+ "baseName": "index",
+ "mediaType": "text/html"
+ },
+ "json": {
+ "baseName": "index",
+ "mediaType": "application/json"
+ },
+ "rss": {
+ "baseName": "rss",
+ "mediaType": "application/xml"
+ },
+ "authors": {
+ "path": "authors",
+ "baseName": "index",
+ "mediaType": "text/html"
+ },
+ "archive-messages": {
+ "path": "archives/messages",
+ "baseName": "index",
+ "mediaType": "text/html"
+ },
+ "archive-tags": {
+ "path": "archives/tags",
+ "baseName": "index",
+ "mediaType": "text/html"
+ },
+ "sources": {
+ "path": "sources",
+ "baseName": "index",
+ "mediaType": "text/html"
+ },
+ "browserconfig": {
+ "baseName": "browserconfig",
+ "mediaType": "application/xml"
+ },
+ "webmanifest": {
+ "baseName": "site",
+ "mediaType": "application/manifest+json"
+ }
+ },
+ "outputs": {
+ "home": [
+ "html",
+ "rss",
+ "json",
+ "authors",
+ "sources",
+ "browserconfig",
+ "archive-messages",
+ "archive-tags",
+ "webmanifest"
+ ],
+ "section": [
+ "html",
+ "rss"
+ ],
+ "taxonomy": [
+ "html",
+ "rss"
+ ],
+ "term": [
+ "html",
+ "rss"
+ ],
+ "page": [
+ "html"
+ ]
+ },
+ "module": {
+ "mounts": [
+ {
+ "source": "content",
+ "target": "content"
+ },
+ {
+ "source": "static",
+ "target": "static"
+ },
+ {
+ "source": "layouts",
+ "target": "layouts"
+ },
+ {
+ "source": "data",
+ "target": "data"
+ },
+ {
+ "source": "assets",
+ "target": "assets"
+ },
+ {
+ "source": "i18n",
+ "target": "i18n"
+ },
+ {
+ "source": "archetypes",
+ "target": "archetypes"
+ },
+ {
+ "source": "content",
+ "target": "static/raw"
+ },
+ {
+ "source": "themes/default/layouts",
+ "target": "static/source/files"
+ },
+ {
+ "source": "data",
+ "target": "static/data"
+ }
+ ]
+ }
+}
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..f54c945
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,195 @@
+theme = "default"
+title = "A Micro Blog"
+paginate = 10
+summaryLength = 1
+enableEmoji = true
+buildDrafts = true
+languageCode = "en-us"
+
+[author]
+ footer = "A Micro Blog\n"
+
+ [author.default]
+ user = "default"
+ webring = "canory"
+
+[taxonomies]
+ tag = "tags"
+
+[menu]
+
+ [[menu.main]]
+ name = "Home"
+ url = "/"
+ identifier = "feather/home"
+ weight = 1
+
+ [[menu.main]]
+ name = "Tags"
+ url = "/tags/"
+ identifier = "feather/tag"
+ weight = 2
+ icon = "home"
+
+ [[menu.main]]
+ name = "History"
+ url = "/archives/messages/"
+ identifier = "tabler/clock"
+ weight = 3
+
+ [[menu.main]]
+ name = "Records"
+ url = "/archives/tags/"
+ identifier = "tabler/book-2"
+ weight = 4
+
+ [[menu.main]]
+ name = "Authors"
+ url = "/authors/"
+ identifier = "feather/users"
+ weight = 5
+
+ [[menu.main]]
+ name = "RSS Feed"
+ url = "/rss.xml"
+ identifier = "feather/rss"
+ weight = 6
+
+ [[menu.main]]
+ name = "JSON Feed"
+ url = "/index.json"
+ identifier = "feather/circle"
+ weight = 7
+
+ [[menu.main]]
+ name = "Sources"
+ url = "/sources/"
+ identifier = "feather/git-commit"
+ weight = 8
+
+[markup]
+
+ [markup.highlight]
+ anchorLineNos = true
+ codeFences = true
+ guessSyntax = true
+ lineNos = false
+ lineNumbersInTable = false
+ noClasses = false
+ noHl = false
+
+[params]
+ production = false
+
+ [params.webmanifest]
+ name = "Micro Blog"
+ shortName = "Micro"
+ themeColor = "#ffffff"
+ backgroundColor = "#ffffff"
+ display = "standalone"
+ logo = "images/logo.png"
+
+ [params.csp]
+ referrer = "origin"
+ childsrc = ["'self'"]
+ fontsrc = ["'self'"]
+ formaction = ["'self'", "lite.duckduckgo.com"]
+ framesrc = ["'self'", "imgur.com", "www.youtube-nocookie.com", "platform.twitter.com", "en.m.wikipedia.org", "odysee.com", "docs.google.com"]
+ imgsrc = ["'self'", "http://preview.test", "imgs.xkcd.com"]
+ mediasrc = ["'self'", "raw.githubusercontent.com"]
+ objectsrc = ["'none'"]
+ prefetchsrc = ["'self'"]
+ scriptsrc = ["'self'", "s.imgur.com", "platform.twitter.com"]
+ scriptsrcelem = ["'self'", "s.imgur.com", "platform.twitter.com"]
+ stylesrc = ["'self'", "'unsafe-inline'", "http://preview.test"]
+
+[outputFormats]
+
+ [outputFormats.html]
+ baseName = "index"
+ mediaType = "text/html"
+
+ [outputFormats.json]
+ baseName = "index"
+ mediaType = "application/json"
+
+ [outputFormats.rss]
+ baseName = "rss"
+ mediaType = "application/xml"
+
+ [outputFormats.authors]
+ path = "authors"
+ baseName = "index"
+ mediaType = "text/html"
+
+ [outputFormats.archive-messages]
+ path = "archives/messages"
+ baseName = "index"
+ mediaType = "text/html"
+
+ [outputFormats.archive-tags]
+ path = "archives/tags"
+ baseName = "index"
+ mediaType = "text/html"
+
+ [outputFormats.sources]
+ path = "sources"
+ baseName = "index"
+ mediaType = "text/html"
+
+ [outputFormats.browserconfig]
+ baseName = "browserconfig"
+ mediaType = "application/xml"
+
+ [outputFormats.webmanifest]
+ baseName = "site"
+ mediaType = "application/manifest+json"
+
+[outputs]
+ home = ["html", "rss", "json", "authors", "sources", "browserconfig", "archive-messages", "archive-tags", "webmanifest"]
+ section = ["html", "rss"]
+ taxonomy = ["html", "rss"]
+ term = ["html", "rss"]
+ page = ["html"]
+
+[module]
+
+ [[module.mounts]]
+ source = "content"
+ target = "content"
+
+ [[module.mounts]]
+ source = "static"
+ target = "static"
+
+ [[module.mounts]]
+ source = "layouts"
+ target = "layouts"
+
+ [[module.mounts]]
+ source = "data"
+ target = "data"
+
+ [[module.mounts]]
+ source = "assets"
+ target = "assets"
+
+ [[module.mounts]]
+ source = "i18n"
+ target = "i18n"
+
+ [[module.mounts]]
+ source = "archetypes"
+ target = "archetypes"
+
+ [[module.mounts]]
+ source = "content"
+ target = "static/raw"
+
+ [[module.mounts]]
+ source = "themes/default/layouts"
+ target = "static/source/files"
+
+ [[module.mounts]]
+ source = "data"
+ target = "static/data"
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..56888f5
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,170 @@
+---
+baseURL:
+theme: default
+title: A Micro Blog
+
+paginate: 10
+summaryLength: 1
+enableEmoji: true
+buildDrafts: true
+languageCode: en-us
+
+author:
+ default:
+ user: default
+ webring: canory
+ footer: >
+ A Micro Blog
+
+taxonomies:
+ tag: tags
+
+menu:
+ main:
+ - name: Home
+ url: /
+ identifier: feather/home
+ weight: 1
+ - name: Tags
+ url: /tags/
+ identifier: feather/tag
+ weight: 2
+ icon: home
+ - name: History
+ url: /archives/messages/
+ identifier: tabler/clock
+ weight: 3
+ - name: Records
+ url: /archives/tags/
+ identifier: tabler/book-2
+ weight: 4
+ - name: Authors
+ url: /authors/
+ identifier: feather/users
+ weight: 5
+ - name: RSS Feed
+ url: /rss.xml
+ identifier: feather/rss
+ weight: 6
+ - name: JSON Feed
+ url: /index.json
+ identifier: feather/circle
+ weight: 7
+ - name: Sources
+ url: /sources/
+ identifier: feather/git-commit
+ weight: 8
+
+markup:
+ highlight:
+ anchorLineNos: true
+ codeFences: true
+ guessSyntax: true
+ lineNos: false
+ lineNumbersInTable: false
+ noClasses: false
+ noHl: false
+
+params:
+ production: false
+ webmanifest:
+ name: Micro Blog
+ shortName: Micro
+ themeColor: '#ffffff'
+ backgroundColor: '#ffffff'
+ display: standalone
+ logo: images/logo.png
+ csp:
+ referrer: origin
+ childsrc: ["'self'"]
+ fontsrc: ["'self'"]
+ formaction: ["'self'", lite.duckduckgo.com]
+ framesrc: ["'self'", imgur.com, www.youtube-nocookie.com, platform.twitter.com,
+ en.m.wikipedia.org, odysee.com, docs.google.com]
+ imgsrc: ["'self'", http://preview.test, imgs.xkcd.com]
+ mediasrc: ["'self'", raw.githubusercontent.com]
+ objectsrc: ["'none'"]
+ prefetchsrc: ["'self'"]
+ scriptsrc: ["'self'", s.imgur.com, platform.twitter.com]
+ scriptsrcelem: ["'self'", s.imgur.com, platform.twitter.com]
+ stylesrc: ["'self'", "'unsafe-inline'", http://preview.test]
+
+outputFormats:
+ html:
+ baseName: index
+ mediaType: text/html
+ json:
+ baseName: index
+ mediaType: application/json
+ rss:
+ baseName: rss
+ mediaType: application/xml
+ authors:
+ path: authors
+ baseName: index
+ mediaType: text/html
+ archive-messages:
+ path: archives/messages
+ baseName: index
+ mediaType: text/html
+ archive-tags:
+ path: archives/tags
+ baseName: index
+ mediaType: text/html
+ sources:
+ path: sources
+ baseName: index
+ mediaType: text/html
+ browserconfig:
+ baseName: browserconfig
+ mediaType: application/xml
+ webmanifest:
+ baseName: site
+ mediaType: application/manifest+json
+
+outputs:
+ home:
+ - html
+ - rss
+ - json
+ - authors
+ - sources
+ - browserconfig
+ - archive-messages
+ - archive-tags
+ - webmanifest
+ section:
+ - html
+ - rss
+ taxonomy:
+ - html
+ - rss
+ term:
+ - html
+ - rss
+ page:
+ - html
+
+module:
+ mounts:
+ - source: content
+ target: content
+ - source: static
+ target: static
+ - source: layouts
+ target: layouts
+ - source: data
+ target: data
+ - source: assets
+ target: assets
+ - source: i18n
+ target: i18n
+ - source: archetypes
+ target: archetypes
+
+ - source: content
+ target: static/raw
+ - source: themes/default/layouts
+ target: static/source/files
+ - source: data
+ target: static/data
diff --git a/content/canory/_index.md b/content/canory/_index.md
new file mode 100644
index 0000000..9941ff9
--- /dev/null
+++ b/content/canory/_index.md
@@ -0,0 +1,4 @@
+---
+author: canory
+layout: section
+---
diff --git a/content/canory/feeds/_index.md b/content/canory/feeds/_index.md
new file mode 100644
index 0000000..6fcfc2b
--- /dev/null
+++ b/content/canory/feeds/_index.md
@@ -0,0 +1,4 @@
+---
+author: canory
+layout: feeds
+---
diff --git a/content/canory/likes/_index.md b/content/canory/likes/_index.md
new file mode 100644
index 0000000..18f37aa
--- /dev/null
+++ b/content/canory/likes/_index.md
@@ -0,0 +1,4 @@
+---
+author: canory
+layout: likes
+---
diff --git a/content/canory/media/_index.md b/content/canory/media/_index.md
new file mode 100644
index 0000000..8c6b976
--- /dev/null
+++ b/content/canory/media/_index.md
@@ -0,0 +1,4 @@
+---
+author: canory
+layout: media
+---
diff --git a/content/canory/media/barn-owl.jpg b/content/canory/media/barn-owl.jpg
new file mode 100644
index 0000000..bf3f80b
--- /dev/null
+++ b/content/canory/media/barn-owl.jpg
Binary files differ
diff --git a/content/canory/media/hugo-website.png b/content/canory/media/hugo-website.png
new file mode 100644
index 0000000..656d8aa
--- /dev/null
+++ b/content/canory/media/hugo-website.png
Binary files differ
diff --git a/content/canory/media/internet-is-serious.jpg b/content/canory/media/internet-is-serious.jpg
new file mode 100644
index 0000000..678cb0d
--- /dev/null
+++ b/content/canory/media/internet-is-serious.jpg
Binary files differ
diff --git a/content/canory/media/profile/header.jpg b/content/canory/media/profile/header.jpg
new file mode 100644
index 0000000..846ed6a
--- /dev/null
+++ b/content/canory/media/profile/header.jpg
Binary files differ
diff --git a/content/canory/media/profile/picture.png b/content/canory/media/profile/picture.png
new file mode 100644
index 0000000..20bf810
--- /dev/null
+++ b/content/canory/media/profile/picture.png
Binary files differ
diff --git a/content/canory/messages/code.md b/content/canory/messages/code.md
new file mode 100644
index 0000000..d2bf106
--- /dev/null
+++ b/content/canory/messages/code.md
@@ -0,0 +1,22 @@
++++
+date = "2022-03-22T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "code" ]
+author = "canory"
++++
+
+Demonstrate your brilliance by embedding code with impeccable taste and
+beautiful style.
+
+```lisp {options="hl_lines=1,linenos=1",caption="Not too sure what any of this means."}
+;;; Numbers
+
+9999999999999999999999 ; integers
+#b111 ; binary => 7
+#o111 ; octal => 73
+#x111 ; hexadecimal => 273
+3.14159s0 ; single
+3.14159d0 ; double
+1/2 ; ratios
+#C(1 2) ; complex numbers
+```
diff --git a/content/canory/messages/definitions.md b/content/canory/messages/definitions.md
new file mode 100644
index 0000000..f01fb58
--- /dev/null
+++ b/content/canory/messages/definitions.md
@@ -0,0 +1,18 @@
++++
+date = "2022-03-17T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "definitions" ]
+author = "canory"
++++
+
+There's no infinite scrolling here --- it enfeebles the psyche. Here are a list
+of definitions. You have received enlightenment, heed my words.
+
+**Infinite**
+: Subject to no limitation or external determination.
+
+**Enfeeble**
+: To make feeble : deprive of strength.
+
+**Psyche**
+: The totality of elements forming the mind.
diff --git a/content/canory/messages/diagrams.md b/content/canory/messages/diagrams.md
new file mode 100644
index 0000000..f32b056
--- /dev/null
+++ b/content/canory/messages/diagrams.md
@@ -0,0 +1,29 @@
++++
+date = "2022-03-25T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "diagrams" ]
+author = "canory"
++++
+
+{{< abbr >}} I :heart: `hugo` but hate emojis. Here's an
+[emoji cheat sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) because you
+probably like them :sweat_smile:. I'm over the character limit but look mommy
+--- `ASCII` diagrams!
+
+[**GoAT Diagram**](https://github.com/blampe/goat#goat-go-ascii-tool)
+
+> My original implementation was certainly buggy and not on par with markdeep. I'm
+> grateful for the folks who've helped smooth out the rough edges, and I've
+> updated this project to reflect the good changes made in the Hugo fork,
+> including a long-overdue go.mod.
+
+```goat
+ . . . .--- 1
+ / \ | | .---+
+ / \ .---+---. .--+--. | '--- 2
+ + + | | | | ---+
+ / \ / \ .-+-. .-+-. .+. .+. | .--- 3
+ / \ / \ | | | | | | | | '---+
+ 1 2 3 4 1 2 3 4 1 2 3 4 '--- 4
+
+```
diff --git a/content/canory/messages/drafts.md b/content/canory/messages/drafts.md
new file mode 100644
index 0000000..9deb852
--- /dev/null
+++ b/content/canory/messages/drafts.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-04T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "drafts" ]
+draft = true
+author = "canory"
++++
+
+Draft those messages, you message drafter. Get a feel of how it delivers before
+committing the deed. Only reveal your drafts in a local environment --- don't
+write drafts live on the `www` unless you like living on the edge.
diff --git a/content/canory/messages/highlighting.md b/content/canory/messages/highlighting.md
new file mode 100644
index 0000000..75a112f
--- /dev/null
+++ b/content/canory/messages/highlighting.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-24T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "highlighting" ]
+author = "canory"
++++
+
+Sometimes I'll {{< mark "highlight stuff" >}} to draw attention to my fine
+eloquence. If I don't think things through, I'll ~~hide the what I
+wrote~~. Whoops, let's just {{< ins "correct the record." >}}
+{{< spoiler "Move along --- nothing to see here." >}}
diff --git a/content/canory/messages/hugo.md b/content/canory/messages/hugo.md
new file mode 100644
index 0000000..d8c1710
--- /dev/null
+++ b/content/canory/messages/hugo.md
@@ -0,0 +1,13 @@
++++
+date = "2022-03-26T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "hugo" ]
+author = "canory"
++++
+
+This micro blog is powered by Hugo, a static site generator that has lots of
+features.
+
+![Hugo's landing page](/canory/media/hugo-website.png "
+ Hugo's [homepage](https://gohugo.io/)."
+)
diff --git a/content/canory/messages/imgur-video.md b/content/canory/messages/imgur-video.md
new file mode 100644
index 0000000..376fbe3
--- /dev/null
+++ b/content/canory/messages/imgur-video.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-19T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "imgur" ]
+author = "canory"
++++
+
+Embedding a `gif` hosted from an external site is easy. Heard it might be
+illegal someday. Yak shaving and legal jangling and are the worst, truly.
+
+{{< imgur-video rQIb4Vw >}}
diff --git a/content/canory/messages/lists.md b/content/canory/messages/lists.md
new file mode 100644
index 0000000..4fd78dc
--- /dev/null
+++ b/content/canory/messages/lists.md
@@ -0,0 +1,16 @@
++++
+date = "2022-03-12T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "lists" ]
+author = "canory"
++++
+
+Speaking of lists. Everyone likes a good list right? Spur ridicule and
+controversy by ranking things, it's that easy.
+
+ 1. C
+ 2. Bash
+ 3. Perl
+ 4. JavaScript
+ 5. PHP
+ 6. Java
diff --git a/content/canory/messages/markdown.md b/content/canory/messages/markdown.md
new file mode 100644
index 0000000..a2c658f
--- /dev/null
+++ b/content/canory/messages/markdown.md
@@ -0,0 +1,15 @@
++++
+date = "2022-03-14T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "markdown" ]
+author = "canory"
++++
+
+[//]: # "That's a comment alright, hiding in plain sight."
+
+The `metadata` :arrow_up: has a [`raw`](/raw/canory/messages/markdown.md) link
+to the [`markdown`](https://www.markdownguide.org/cheat-sheet/) that generates
+this message --- might as well show my internals. Casual users will frown, power users
+will delight.
+
+{{< iframe "/raw/canory/messages/markdown.md" >}}
diff --git a/content/canory/messages/nsfw.md b/content/canory/messages/nsfw.md
new file mode 100644
index 0000000..b3bdd5f
--- /dev/null
+++ b/content/canory/messages/nsfw.md
@@ -0,0 +1,16 @@
++++
+date = "2022-03-09T02:43:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "nsfw" ]
+nsfw = "1"
+author = "canory"
++++
+
+Is it safe? Well this message ain't.
+{{< quote
+ "Evil communications corrupt good manners"
+ "https://www.kingjamesbibleonline.org/1-Corinthians-15-33/"
+>}}. Grace your hearers with a warning before things get unsavory.
+Shoo.
+
+{{< youtube "gk8XGnKLhfU" "Monopoly Mermaid Monday" >}}
diff --git a/content/canory/messages/odysee.md b/content/canory/messages/odysee.md
new file mode 100644
index 0000000..eaa8e8c
--- /dev/null
+++ b/content/canory/messages/odysee.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-03T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "odysee" ]
+author = "canory"
++++
+
+The cool kids are moving to a new video site called Odysee. How long do you
+reckon this party lasts?
+
+{{< odysee >}}
diff --git a/content/canory/messages/pdf.md b/content/canory/messages/pdf.md
new file mode 100644
index 0000000..f0aecba
--- /dev/null
+++ b/content/canory/messages/pdf.md
@@ -0,0 +1,12 @@
++++
+date = "2022-03-07T02:20:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "pdf" ]
+author = "canory"
++++
+
+Heard you like {{< abbr `PDFs` "Portable Document Format" >}} but hate accidently
+downloading them. Charles Dickens is a word wizard.
+
+{{< pdf >}}
+
diff --git a/content/canory/messages/pinned.md b/content/canory/messages/pinned.md
new file mode 100644
index 0000000..6acab00
--- /dev/null
+++ b/content/canory/messages/pinned.md
@@ -0,0 +1,13 @@
++++
+date = "2022-02-05T02:20:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "pinned", "hugo" ]
+weight = 1
+liked = 1
+author = "canory"
++++
+
+This message is pinned for reasons unknown. For a quick start guide,
+[@default](/tags/hugo/#default-messages-quickstart.md) might be able to help
+you. {{< mark Warning >}}: This is _remarkably_ experimental, don't hurt
+yourself.
diff --git a/content/canory/messages/plumbing.md b/content/canory/messages/plumbing.md
new file mode 100644
index 0000000..c6ca6c2
--- /dev/null
+++ b/content/canory/messages/plumbing.md
@@ -0,0 +1,14 @@
++++
+date = "2022-03-10T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "plumbing" ]
+author = "canory"
++++
+
+Is your `JavaScript` functional? Double[^1] click a word and hold the
+{{< kbd D >}} key to look it up in a dictionary. Select a phrase, and hold the
+{{< kbd S >}} key to look it up in a search engine. Smartphone users can ignore
+this and keep scrolling --- that's all they're good for.
+
+[^1]: First saw this idea [here](https://arachnoid.com/administration/index.html)
+ and promptly boosted it.
diff --git a/content/canory/messages/self.md b/content/canory/messages/self.md
new file mode 100644
index 0000000..f58c2ca
--- /dev/null
+++ b/content/canory/messages/self.md
@@ -0,0 +1,14 @@
++++
+date = "2022-03-02T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "self" ]
+author = "canory"
++++
+
+Check out this party trick; this thing can quote other messages. {{< spoiler >}}
+Technically... the
+instructions from the manufacturer was not to do this --- but rules are made to be
+broken... right?
+{{< /spoiler >}}
+
+{{< self "/canory/messages/self" >}}
diff --git a/content/canory/messages/selfdestruct.md b/content/canory/messages/selfdestruct.md
new file mode 100644
index 0000000..60f6b61
--- /dev/null
+++ b/content/canory/messages/selfdestruct.md
@@ -0,0 +1,30 @@
++++
+date = "2022-03-08T02:20:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+expirydate = "2122-04-29T22:55:17+00:00"
+tags = [ "docs", "selfdestruct" ]
+author = "canory"
+slug = "cf1a1feb1"
++++
+
+Extraterrestrials don't care about speech in the slightest. According to my
+lackey [@tdro](https://thedroneely.com), the Internet is
+{{< abbr srs "serious">}} business for humans.
+
+Professor Wiio says;
+{{< quote
+ "*If a message can be interpreted in several ways, it will be interpreted in a manner that maximizes the damage.*"
+ "https://en.wikipedia.org/wiki/Wiio%27s_laws"
+>}}
+
+Limit or boost virality and damage by self destructing the source of an imagined
+controversy. This is not to be indexed --- search engine bots
+{{< smallcaps "shall not pass" >}} (hopefully), neither will this message propogate through
+`JSON` and `RSS` feeds.
+
+This
+[hidden message](/raw/canory/messages/selfdestruct.md "hidden in plain sight")
+will only disappear in my timeline because I'm an intergalactic time traveler.
+
+
+![Isaac Asimov](/canory/media/internet-is-serious.jpg)
diff --git a/content/canory/messages/styles.md b/content/canory/messages/styles.md
new file mode 100644
index 0000000..b3c839a
--- /dev/null
+++ b/content/canory/messages/styles.md
@@ -0,0 +1,10 @@
++++
+date = "2022-03-23T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "styles" ]
+author = "canory"
++++
+
+**Subtlety** *wins* arguments, and other times an **_impassioned
+plea_** gets the job done. Highlight `bits` of `monospaced` text to look
+sophisticated.
diff --git a/content/canory/messages/tables.md b/content/canory/messages/tables.md
new file mode 100644
index 0000000..2840c23
--- /dev/null
+++ b/content/canory/messages/tables.md
@@ -0,0 +1,15 @@
++++
+date = "2022-03-04T02:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "tables" ]
+author = "canory"
++++
+
+Tables are the most universally understood data presentation format but...
+
+| Tables | Are | Boring |
+|:---------|:-------------:|--------:|
+| col 1 is | left--aligned | 123 |
+| col 2 is | centered | 12 |
+| col 3 is | right--aligned | 1 |
+
diff --git a/content/canory/messages/tasks.md b/content/canory/messages/tasks.md
new file mode 100644
index 0000000..7da9178
--- /dev/null
+++ b/content/canory/messages/tasks.md
@@ -0,0 +1,14 @@
++++
+date = "2022-03-13T03:44:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "tasks" ]
+author = "canory"
++++
+
+We get things done around here! The first step is to write down our goals for
+today. You can schedule posts by setting a date into the future.
+
+- [x] Write Task List
+- [x] Check Task List
+- [ ] Forget About Task List &trade;
+
diff --git a/content/canory/messages/twitter.md b/content/canory/messages/twitter.md
new file mode 100644
index 0000000..e15c1c3
--- /dev/null
+++ b/content/canory/messages/twitter.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-15T03:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "twitter" ]
+author = "canory"
++++
+
+This individual is old enough to remember that the Internet is
+a vast place.
+
+{{< tweet tveastman 1069674780826071040 >}}
diff --git a/content/canory/messages/unlisted.md b/content/canory/messages/unlisted.md
new file mode 100644
index 0000000..60f8074
--- /dev/null
+++ b/content/canory/messages/unlisted.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-08T02:20:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "unlisted" ]
+unlisted = true
+author = "canory"
++++
+
+So you're a bit bold and have the cooking skills needed to stay in the kitchen.
+This message is not propagated and hopefully not indexed. The communication
+stays mostly off the record and will not spontaneously blow up.
diff --git a/content/canory/messages/videos.md b/content/canory/messages/videos.md
new file mode 100644
index 0000000..c71233c
--- /dev/null
+++ b/content/canory/messages/videos.md
@@ -0,0 +1,11 @@
++++
+date = "2022-03-20T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "videos" ]
+author = "canory"
++++
+
+Here's a video that warms my dear heart. Showing videos to others makes you
+appear friendly.
+
+{{< video caption="Big Buck Bunny" start=40 end=55 >}}
diff --git a/content/canory/messages/wikipedia.md b/content/canory/messages/wikipedia.md
new file mode 100644
index 0000000..7ea8b20
--- /dev/null
+++ b/content/canory/messages/wikipedia.md
@@ -0,0 +1,17 @@
++++
+date = "2022-03-06T02:20:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "wikipedia" ]
+author = "canory"
++++
+
+Reason, meaning, and knowledge are the ground truths of society but only when
+all other methods have failed. Invoke the world's most used encyclopedia...
+
+{{< mark Warning >}}:
+Article text may *change* as it appears --- the pedia's not guaranteed to be
+consistent across discrete spacetimes. If the below does not say
+"knowledge" you've been had.
+
+{{< wikipedia >}}
+
diff --git a/content/canory/messages/xkcd.md b/content/canory/messages/xkcd.md
new file mode 100644
index 0000000..41ed4a6
--- /dev/null
+++ b/content/canory/messages/xkcd.md
@@ -0,0 +1,14 @@
++++
+date = "2022-03-18T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "xkcd" ]
+author = "canory"
++++
+
+Hosting and linking content is copyright radioactive. Be a good netizen and
+avoid hotlinking,
+[but do it when it's acceptable](https://xkcd.com/license.html).
+
+![Debugging](https://imgs.xkcd.com/comics/debugging.png "
+ xkcd: [Debugging](https://xkcd.com/1722)"
+)
diff --git a/content/canory/messages/youtube.md b/content/canory/messages/youtube.md
new file mode 100644
index 0000000..bad32be
--- /dev/null
+++ b/content/canory/messages/youtube.md
@@ -0,0 +1,12 @@
++++
+date = "2022-03-16T04:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "YouTube" ]
+author = "canory"
++++
+
+Netizens think that there are only 5 websites. Here's a video from one of them.
+Pat yourself on the back, you have reached [the end of the
+Internet](https://hmpg.net/).
+
+{{< youtube id=9LoVUbYSv5M muted=1 >}}
diff --git a/content/default/_index.md b/content/default/_index.md
new file mode 100644
index 0000000..b80df9c
--- /dev/null
+++ b/content/default/_index.md
@@ -0,0 +1,4 @@
+---
+author: default
+layout: section
+---
diff --git a/content/default/feeds/_index.md b/content/default/feeds/_index.md
new file mode 100644
index 0000000..37996dc
--- /dev/null
+++ b/content/default/feeds/_index.md
@@ -0,0 +1,4 @@
+---
+author: default
+layout: feeds
+---
diff --git a/content/default/likes/_index.md b/content/default/likes/_index.md
new file mode 100644
index 0000000..370aa96
--- /dev/null
+++ b/content/default/likes/_index.md
@@ -0,0 +1,4 @@
+---
+author: default
+layout: likes
+---
diff --git a/content/default/media/_index.md b/content/default/media/_index.md
new file mode 100644
index 0000000..b0b8a46
--- /dev/null
+++ b/content/default/media/_index.md
@@ -0,0 +1,4 @@
+---
+author: default
+layout: media
+---
diff --git a/content/default/media/profile/header.jpg b/content/default/media/profile/header.jpg
new file mode 100644
index 0000000..e2578d5
--- /dev/null
+++ b/content/default/media/profile/header.jpg
Binary files differ
diff --git a/content/default/media/profile/picture.png b/content/default/media/profile/picture.png
new file mode 100644
index 0000000..6e039c0
--- /dev/null
+++ b/content/default/media/profile/picture.png
Binary files differ
diff --git a/content/default/messages/authors.md b/content/default/messages/authors.md
new file mode 100644
index 0000000..3db9714
--- /dev/null
+++ b/content/default/messages/authors.md
@@ -0,0 +1,12 @@
++++
+date = "2022-03-14T04:35:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "authors" ]
++++
+
+Beep beep beep. Another user? Multiple users are available by setting a specific
+author. [@canory](/canory) thinks this is cool but doesn't know any better --
+it's hacks all the way down.
+
+Any soul that sends a message without a name becomes a _default_ because
+{{< smallcaps "I AM" >}} the default.
diff --git a/content/default/messages/quickstart.md b/content/default/messages/quickstart.md
new file mode 100644
index 0000000..ccd51bd
--- /dev/null
+++ b/content/default/messages/quickstart.md
@@ -0,0 +1,33 @@
++++
+date = "2022-03-05T03:45:51+00:00"
+lastmod = "2022-03-29T22:55:17+00:00"
+tags = [ "docs", "hugo", "quickstart" ]
+author = "default"
++++
+
+Somehow you've ended up here, funny how that works. I'm responsible for
+explaining how to get started.
+
+GIT CLONE. EXECUTE HUGO.
+
+```shell
+git clone
+cd canory
+hugo
+```
+
+SERVE HTML. USE ANY HTTP SERVER.
+
+```shell
+cd public
+php -S 127.0.0.1:8124
+python -m http.server 8125
+```
+
+MAKE USER. STUDY CONTENT FOLDER.
+
+```shell
+make auther user=robot
+```
+
+That concludes our community announcement.
diff --git a/data/authors/canory.yaml b/data/authors/canory.yaml
new file mode 100644
index 0000000..00ec9cd
--- /dev/null
+++ b/data/authors/canory.yaml
@@ -0,0 +1,21 @@
+---
+name: Canory
+user: canory
+blogtitle: Canory's Micro Blog
+wordlimit: 50
+place: www
+domain:
+ host: canory.example
+ url: https://example.com
+epoch: Arrived on the second fortnight of April, `2022`
+description: >
+ Descended from an inner star system of the Sunflower galaxy. Not an owl.
+ Theme Canory --- a micro blog theme for [Hugo](https://gohugo.io/), one of many
+ [static site generators](https://jamstack.org/generators/).
+webring:
+ rss:
+ - https://deno.com/feed
+ - https://hacks.mozilla.org/feed/
+ - https://portable.fyi/feed.xml
+ - https://tomcritchlow.com/feed.xml
+ json:
diff --git a/data/authors/default.yaml b/data/authors/default.yaml
new file mode 100644
index 0000000..3ab47b6
--- /dev/null
+++ b/data/authors/default.yaml
@@ -0,0 +1,18 @@
+---
+name: Default
+user: default
+blogtitle: Default's Micro Blog
+wordlimit: 50
+place: In your head
+domain:
+ host: default.example
+ url: https://example.com
+epoch: Appeared sometime around April, `2022`
+description: >
+ Dignissimos quis animi velit. Not a bot. If you believe everything you read on
+ the Internet, you'll think that I'm human.
+webring:
+ rss:
+ - https://hacks.mozilla.org/feed/
+ - https://webkit.org/blog/feed/
+ json:
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..6faf9b8
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,35 @@
+let
+
+ name = "nix-shell.canory";
+
+ pkgs = import (builtins.fetchTarball {
+ url = "https://releases.nixos.org/nixos/21.11/nixos-21.11.336020.2128d0aa28e/nixexprs.tar.xz";
+ sha256 = "0w8plbxms0di6gnh0k2yhj0pgxzxas7g5x0m01zjzixf16i2bapj";
+ }) { };
+
+ hugo = pkgs.callPackage (builtins.fetchurl {
+ url = "https://raw.githubusercontent.com/tdro/dotfiles/73b1c675285e67c623f2c4514602500c6f2cfab7/.config/nixpkgs/packages/hugo/default.nix";
+ sha256 = "07jk987zh7kxynjj21038j0qnd996bl949x1wm3g1iggjz0shgb4";
+ }) { };
+
+ mkShellPure = pkgs.callPackage (builtins.fetchurl {
+ url = "https://raw.githubusercontent.com/tdro/dotfiles/b710281b132056105709c03dda1899a6afc68a93/.config/nixpkgs/helpers/mkShellPure.nix";
+ sha256 = "1ciwifsx2hrp0ymm077zfb5q8ravrk545bda1q249y2spw9np4ms";
+ }) { };
+
+in mkShellPure {
+ packages = [
+ hugo
+ pkgs.busybox
+ pkgs.deno
+ pkgs.entr
+ pkgs.git
+ pkgs.gnumake
+ pkgs.php
+ pkgs.subversion
+ pkgs.yj
+ ];
+ shellHook = ''
+ export PS1='\h (${name}) \W \$ '
+ '';
+}
diff --git a/static/css/default.css b/static/css/default.css
new file mode 100644
index 0000000..5feedfa
--- /dev/null
+++ b/static/css/default.css
@@ -0,0 +1,1629 @@
+/* ###### Semantic Pact CSS ###### */
+
+/* ----- Selection ----- */
+
+::selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+::-webkit-selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+::-moz-selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+/* ----- Colors ----- */
+
+:root {
+ color-scheme: light dark;
+ --active-alternate: #cce1ff;
+ --background-bolder: #fff;
+ --background-transparent: rgba(255, 255, 255, 0.9);
+ --background: #fefefe;
+ --border-darker: #ccc;
+ --border-lighter: #eee;
+ --button-background: #fff;
+ --danger-background: #fcd2cf;
+ --danger: #8f0000;
+ --disabled: #999;
+ --fade: #555;
+ --foreground: #111;
+ --hover-alternate: #ebf3ff;
+ --hover: #eee;
+ --input-background: #eff3f4;
+ --link: #0149bc;
+ --shadow: #999;
+ --spoiler: #333;
+ --success-background: #d5f3c4;
+ --success: #1d3c0c;
+ --target-background: #fffdeb;
+ --warning-background: #ffe699;
+ --warning: #332600;
+ --widget-background: #f9f9f9;
+ --table-row-odd-background: #f3f3f3;
+ --table-row-even-background: #f9f9f9;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --active-alternate: var(--hover-alternate);
+ --background-bolder: #111;
+ --background-transparent: rgba(28, 28, 28, 0.9);
+ --background: #1c1c1c;
+ --border-darker: #555;
+ --border-lighter: #262626;
+ --button-background: #333;
+ --danger-background: #220402;
+ --danger: #ff9494;
+ --disabled: #999;
+ --fade: #aaa;
+ --foreground: #ddd;
+ --hover-alternate: #1c263a;
+ --hover: #303030;
+ --input-background: #333;
+ --link: #8cb4ff;
+ --shadow: #000;
+ --spoiler: #333;
+ --success-background: #2d3e0f;
+ --success: #d5f3c4;
+ --target-background: #2e2c00;
+ --warning-background: #3d3d00;
+ --warning: #ffe699;
+ --widget-background: #262626;
+ --table-row-odd-background: #2e3838;
+ --table-row-even-background: #273030;
+ }
+ img {
+ filter: brightness(75%);
+ }
+ code-block {
+ filter: invert(90%);
+ display: block;
+ }
+}
+
+/* ----- Psuedo ----- */
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+}
+
+::before,
+::after {
+ text-decoration: inherit;
+ vertical-align: inherit;
+}
+
+:target::before {
+ content: "";
+ display: block;
+ height: 4rem;
+ margin-top: -4rem;
+ visibility: hidden;
+}
+
+:not(#main):target micro-summary,
+:not(#main):target micro-summary:hover {
+ background-color: #fffdeb;
+ background-color: var(--target-background);
+ outline: 1px dashed #ccc;
+ outline: 1px dashed var(--border-darker);
+ outline-offset: -1px;
+}
+
+:focus:not(html):not(body):not(svg) {
+ outline: 0.15rem solid;
+ outline-offset: -0.15rem;
+ text-decoration: none;
+}
+
+:focus:not(html):not(body):not(pre):not(svg) {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+}
+
+/* ----- Skip link ----- */
+
+skip-link a {
+ position: absolute;
+ left: -9999em;
+}
+
+skip-link a:focus {
+ z-index: 200;
+ position: fixed;
+ left: 50%;
+ transform: translate(-50%, 0);
+ top: 0.5rem;
+ padding: 0.75rem;
+ border-radius: 0.25rem;
+}
+
+/* ----- Elements ----- */
+
+html {
+ font-size: 95%;
+ line-height: 1.5;
+ word-break: break-word;
+ text-rendering: optimizeSpeed;
+ margin-left: calc(100vw - 100%);
+ margin-right: 0;
+}
+
+html,
+samp {
+ font-family: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
+ "Segoe UI Symbol";
+}
+
+body,
+dl dl,
+dl ol,
+dl ul,
+ol dl,
+ul dl,
+ol ol,
+ol ul,
+ul ol,
+ul ul,
+button,
+input,
+select,
+textarea {
+ margin: 0;
+}
+
+ol {
+ counter-reset: ol;
+ padding-left: 2.5rem;
+}
+
+ol li {
+ overflow-wrap: break-word;
+ word-break: break-word;
+ list-style: none;
+}
+
+ol li:before {
+ border-radius: 100%;
+ border: 1px solid;
+ content: counter(ol);
+ counter-increment: ol;
+ margin-left: -2rem;
+ font-size: 85%;
+ padding: 0 0.35rem;
+ position: absolute;
+}
+
+ol li + li {
+ margin-top: 0.5rem;
+}
+
+main,
+details,
+code-block,
+diagram-container {
+ display: block;
+}
+
+summary {
+ display: list-item;
+}
+
+cite {
+ font-style: normal;
+}
+
+ul {
+ list-style: none;
+ padding-left: 1rem;
+}
+
+nav ol,
+nav ul {
+ list-style: none;
+ display: inline-block;
+ padding: 0;
+}
+
+footer {
+ text-align: center;
+ color: #444;
+ color: var(--fade);
+ margin: 1rem 0;
+ font-size: 85%;
+}
+
+figcaption a {
+ color: inherit;
+}
+
+q a {
+ color: inherit;
+}
+
+q:before {
+ content: "\201C";
+}
+
+q:after {
+ content: "\201D";
+}
+
+object {
+ width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-lighter);
+ min-height: 16rem;
+ border-radius: 0.5rem;
+}
+
+details {
+ cursor: pointer;
+}
+
+details:hover summary span {
+ text-decoration: underline;
+}
+
+a {
+ color: #0149bc;
+ color: var(--link);
+ text-decoration: underline;
+ text-underline-offset: 0.05rem;
+}
+
+a:hover {
+ color: #000;
+ color: var(--foreground);
+ text-decoration-thickness: 0.15rem !important;
+}
+
+a:hover img {
+ box-shadow: 0 8px 32px #999;
+ box-shadow: 0 8px 32px var(--shadow);
+ filter: unset;
+}
+
+a:active,
+button:active {
+ position: relative;
+ top: 1px;
+}
+
+column-left nav {
+ position: sticky;
+ position: -webkit-sticky;
+ align-self: flex-start;
+ top: 0.5rem;
+ text-align: right;
+}
+
+column-left nav a {
+ align-items: center;
+ color: inherit;
+ color: var(--foreground);
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ padding: 0.5rem;
+ text-decoration: none;
+ border-radius: 0.5rem;
+}
+
+column-left nav li {
+ margin-bottom: 0.25rem;
+}
+
+column-left nav li:hover a {
+ background-color: #eee;
+ background-color: var(--hover);
+ border-radius: 0.5rem;
+}
+
+column-left nav li:hover svg {
+ fill: #ffe699;
+ fill: var(--warning-background);
+}
+
+column-left nav a span {
+ padding: 0 1rem;
+}
+
+pre,
+kbd,
+code,
+svg text,
+item-list a span:last-child,
+item-list a span:first-child {
+ font-family: "Fira Code", "Lucida Console", "Andale Mono", "Roboto Mono",
+ "Ubuntu Monospace", "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dashed;
+ text-decoration: none;
+}
+
+abbr[title]:hover {
+ border-bottom: 2px dashed;
+}
+
+samp {
+ font-variant: all-small-caps;
+ letter-spacing: 0.05rem;
+}
+
+b,
+strong {
+ font-weight: 700;
+}
+
+small {
+ font-size: 80%;
+ opacity: 0.85;
+}
+
+audio,
+canvas,
+iframe,
+img,
+svg,
+video {
+ vertical-align: middle;
+}
+
+video,
+audio {
+ width: 100%;
+}
+
+audio {
+ margin: 0.75rem 0;
+}
+
+figure {
+ text-align: center;
+}
+
+iframe {
+ width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ background-color: #fefefe;
+ border-radius: 0.75rem !important;
+ min-height: 16rem;
+}
+
+self-embed iframe {
+ min-height: 16rem;
+ max-height: 16rem;
+}
+
+img {
+ height: auto;
+ max-width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ border-radius: 0.75rem;
+ object-fit: cover;
+ display: block;
+}
+
+img,
+iframe {
+ user-select: none;
+}
+
+p {
+ line-height: 1.35;
+}
+
+svg {
+ stroke-width: 0.08rem;
+}
+
+svg:not([fill]) {
+ fill: currentColor;
+}
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+button,
+input {
+ overflow: visible;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+button,
+[type="reset"],
+[type="button"],
+[type="submit"],
+paginator-navigation a,
+micro-author micro-summary > a {
+ align-items: center;
+ appearance: button;
+ background-color: #fff;
+ background-color: var(--button-background);
+ border-radius: 0.5rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-lighter);
+ box-shadow: 0 1px 1px #999;
+ color: inherit;
+ cursor: pointer;
+ display: flex;
+ font-size: 100%;
+ height: 1.95rem;
+ padding: 0.25rem 0.5rem;
+ text-decoration: none;
+}
+
+button svg,
+micro-author micro-summary > a svg {
+ height: 1.25rem;
+ margin: 0 0.25rem;
+}
+
+hr {
+ border-top: 1px solid;
+}
+
+/* ----- Tables ----- */
+
+table {
+ border-radius: 0.5rem;
+ border-spacing: 0;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ padding: 0.5rem 0 0 0;
+ width: 100%;
+ background-color: #fefefe;
+ background-color: var(--background);
+}
+
+th {
+ border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid var(--border-darker);
+ padding: 0 0.5rem 0.5rem 0.5rem;
+ text-align: left;
+}
+
+td {
+ padding: 0.5rem 0 0.5rem 0.5rem;
+ border-right: 1px solid #ccc;
+ border-right: 1px solid var(--border-darker);
+ border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid var(--border-darker);
+}
+
+td:last-child {
+ padding-right: 0.5rem;
+ border-right: none;
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+tbody tr:nth-child(odd) td {
+ background-color: #f3f3f3;
+ background-color: var(--table-row-odd-background);
+}
+
+tbody tr:nth-child(even) td {
+ background-color: #f9f9f9;
+ background-color: var(--table-row-even-background);
+}
+
+tbody tr:hover td {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+tr:last-child td:first-child {
+ border-bottom-left-radius: 0.5rem;
+}
+
+tr:last-child td:last-child {
+ border-bottom-right-radius: 0.5rem;
+}
+
+/* ----- Print ----- */
+
+@media print {
+ a[href^="http"]::after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]::after {
+ content: " (" attr(title) ")";
+ }
+}
+
+/* ----- Base ----- */
+
+body {
+ background-color: #fefefe;
+ background-color: var(--background);
+ color: #000;
+ color: var(--foreground);
+ display: flex;
+ margin: 0 auto;
+ max-width: 1250px;
+ min-height: 100%;
+ overflow-x: hidden;
+ -webkit-text-size-adjust: none;
+}
+
+column-left {
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
+}
+
+column-right {
+ flex: 2;
+ max-width: 400px;
+}
+
+column-left,
+column-right {
+ margin: 0.5rem 2rem;
+}
+
+column-middle {
+ flex: 6;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ max-width: 600px;
+ min-height: 100vh;
+ width: 100%;
+}
+
+micro-card,
+micro-tags,
+micro-expires,
+micro-article {
+ display: block;
+}
+
+message-archive h1 {
+ text-align: center;
+ font-weight: 400;
+ font-size: 150%;
+}
+
+micro-tags {
+ margin-bottom: -0.5rem;
+ margin-top: 0.5rem;
+}
+
+micro-tags a {
+ border-radius: 0.5rem;
+ padding-top: 0.125rem;
+ padding-bottom: 0.125rem;
+ display: inline-block;
+ padding-left: 0.5rem;
+ text-decoration: none;
+ padding-right: 0.5rem;
+ box-shadow: 0 1px 1px #999;
+ margin: 0 0.25rem 0.5rem 0;
+}
+
+micro-summary,
+micro-article {
+ border-bottom: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ display: flex;
+ padding: 1rem;
+ position: relative;
+}
+
+micro-summary:hover,
+micro-summary:focus-within {
+ background-color: #f5f9ff;
+ background-color: var(--hover-alternate);
+}
+
+micro-author micro-summary {
+ justify-content: start;
+ align-items: center;
+ border: none;
+}
+
+micro-author micro-thumbnail {
+ flex: none;
+}
+
+micro-author micro-header {
+ line-height: 1;
+ margin: 0 1.25rem 0 0.5rem;
+ flex: 1;
+ height: 3rem;
+ padding: 0 0.5rem;
+}
+
+micro-thumbnail a {
+ display: block;
+}
+
+micro-thumbnail figure {
+ margin: 0 0.75rem 0 0;
+}
+
+micro-thumbnail figure img {
+ border-radius: 100%;
+ min-width: 3.5rem;
+ min-height: 3.5rem;
+ max-width: 3.5rem;
+ max-height: 3.5rem;
+}
+
+micro-content {
+ flex: 8;
+ width: 0;
+}
+
+micro-metadata {
+ color: #444;
+ color: var(--fade);
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+}
+
+micro-metadata h2 {
+ margin-right: 0.5rem;
+}
+
+micro-metadata a {
+ color: inherit;
+}
+
+micro-metadata expiry-date,
+micro-metadata word-limit em,
+micro-metadata draft-label em {
+ color: #8f0000;
+ color: var(--danger);
+ font-style: normal;
+}
+
+micro-metadata draft-label em {
+ font-variant: all-small-caps;
+}
+
+micro-header footer svg,
+micro-metadata expiry-date svg,
+micro-metadata anchored-entry svg,
+micro-metadata unlisted-entry svg {
+ height: 1rem;
+ width: 1rem;
+ stroke-width: 0.1rem;
+}
+
+micro-metadata unlisted-entry {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+ color: #332600;
+ color: var(--warning);
+ border-radius: 0.25rem;
+ padding: 0 0.25rem;
+}
+
+micro-metadata read-time,
+micro-metadata word-limit,
+micro-metadata expiry-date,
+micro-metadata unlisted-entry {
+ cursor: help;
+}
+
+anchored-entry {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.25rem;
+ margin-top: -0.5rem;
+}
+
+anchored-entry svg {
+ margin-right: 0.25rem;
+}
+
+anchored-entry span {
+ margin-right: 0.25rem;
+}
+
+anchored-entry span:not(:last-child):after {
+ content: "\00B7";
+ margin-left: 0.25rem;
+}
+
+anchored-pinned svg {
+ fill: #9ed1fa;
+}
+
+anchored-liked svg {
+ fill: #cd5c5c;
+}
+
+micro-header micro-header-image img {
+ height: 12rem;
+ width: 100%;
+ border-radius: 0;
+}
+
+micro-header micro-header-picture img {
+ height: 8rem;
+ width: 8rem;
+ border-radius: 100%;
+ margin: -4rem 2rem 0 2rem;
+ background-color: #fff;
+ padding: 0.35rem;
+ position: relative;
+}
+
+micro-header section {
+ margin: 1rem 2rem 0 2rem;
+ line-height: 1;
+ display: flex;
+ align-items: baseline;
+}
+
+micro-header-title {
+ margin-right: 1rem;
+ flex: 1;
+}
+
+micro-header-description {
+ flex: 6;
+}
+
+micro-header h1 {
+ margin-bottom: 0.25rem;
+}
+
+micro-header h2 {
+ font-weight: 400;
+ color: #444;
+ color: var(--fade);
+ margin: 0;
+}
+
+micro-header footer,
+micro-header footer code {
+ color: #444;
+ color: var(--fade);
+}
+
+micro-header footer {
+ margin: 1.25rem 2rem 0.25rem 2rem;
+ text-align: center;
+ font-size: 95%;
+}
+
+micro-header footer p {
+ margin: 0.75rem 0.5rem 0.25em 0;
+ display: inline;
+}
+
+micro-nsfw-content * {
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+}
+
+micro-nsfw details svg {
+ stroke-width: 0.1rem;
+ margin-right: 0.15rem;
+}
+
+micro-nsfw details summary {
+ font-variant: all-small-caps;
+ letter-spacing: 0.025rem;
+ line-height: 1.25;
+ color: #8f0000;
+ color: var(--danger);
+ list-style: none;
+}
+
+micro-nsfw details > summary::-webkit-details-marker {
+ display: none;
+}
+
+micro-nsfw-content {
+ border-radius: 0.25rem;
+ display: block;
+ background: repeating-linear-gradient(
+ 180deg,
+ #ff9999,
+ #ff9999 1rem,
+ #eee 1rem,
+ #eee 2rem
+ );
+}
+
+micro-nsfw details[open] + micro-nsfw-content * {
+ filter: none;
+ transition: 1s;
+ visibility: visible;
+ opacity: 1;
+ background: none;
+ outline: none;
+ pointer-events: auto;
+}
+
+micro-nsfw details[open] + micro-nsfw-content {
+ background: none;
+}
+
+@supports (filter: blur(2rem)) {
+ micro-nsfw-content * {
+ filter: blur(2rem);
+ visibility: unset;
+ opacity: unset;
+ }
+ micro-nsfw-content {
+ outline: none;
+ background: unset;
+ }
+}
+
+tab-list {
+ display: flex;
+ justify-content: center;
+ border-bottom: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-darker);
+}
+
+tab-list a:not(:last-child) {
+ margin-right: 0.5rem;
+}
+
+tab-list a {
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-top-left-radius: 0.5rem;
+ border-top-right-radius: 0.5rem;
+ border-top: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-darker);
+ color: #333;
+ color: var(--foreground);
+ margin-top: 0.5rem;
+ padding: 0.5rem 1rem;
+ position: relative;
+ text-decoration: none;
+ top: 1px;
+ border-bottom: 2px solid var(--background);
+}
+
+tab-list a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+ color: inherit;
+}
+
+column-middle nav {
+ align-items: center;
+ backdrop-filter: blur(0.1rem);
+ background-color: rgba(255, 255, 255, 0.9);
+ background-color: var(--background-transparent);
+ border-bottom: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ display: flex;
+ height: 4rem;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 0;
+ width: 100%;
+ z-index: 100;
+}
+
+column-middle nav section h1,
+column-middle nav section small {
+ margin: 0;
+}
+
+micro-author micro-header,
+column-middle nav section h1,
+column-middle nav section small {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+column-middle nav section {
+ line-height: 1.25;
+ padding: 0 0.5rem;
+}
+
+icon-button a {
+ color: inherit;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 4rem;
+ flex-wrap: wrap;
+ text-decoration: none;
+ height: 100%;
+ padding: 0.5rem 0;
+}
+
+icon-button a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+icon-button svg {
+ width: 100%;
+}
+
+icon-button p {
+ margin: 0;
+}
+
+code-block pre code {
+ display: inline-block;
+ min-width: 100%;
+ border-top: 1px solid #bbb;
+ padding: 0.75rem;
+}
+
+code-block[data-lines] pre code {
+ padding: 0.1rem 0.75rem;
+}
+
+code-block header {
+ position: relative;
+ top: 1.85rem;
+ margin-top: -1rem;
+ height: 1rem;
+ padding: 0 0.5rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+code-block header language-label {
+ padding: 0.125rem 1rem;
+ font-variant: all-small-caps;
+}
+
+code-block pre {
+ border-radius: 0.5rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ display: block;
+ font-size: 95%;
+ margin: 0;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding: 2.65rem 0 0 0;
+ tab-size: 2;
+ word-break: break-word;
+ white-space: pre;
+}
+
+paginator-navigation {
+ padding: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+paginator-navigation a,
+paginator-navigation button {
+ margin: 0 0.25rem;
+ font-size: 85%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+paginator-navigation svg {
+ height: 1.316rem;
+}
+
+web-ring,
+author-list,
+gallery-images {
+ padding-bottom: 2rem;
+ display: block;
+}
+
+web-ring > h1,
+author-list > h1,
+gallery-images > h1 {
+ text-align: center;
+ margin-bottom: 1rem;
+ font-variant: all-small-caps;
+ font-size: 110%;
+}
+
+gallery-images section {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+gallery-images img {
+ width: 33.333%;
+ border-radius: 0;
+ height: 6rem;
+}
+
+gallery-images img:first-child {
+ border-top-left-radius: 0.5rem;
+}
+
+gallery-images img:nth-child(3) {
+ border-top-right-radius: 0.5rem;
+}
+
+gallery-images img:nth-child(4) {
+ border-bottom-left-radius: 0.5rem;
+}
+
+gallery-images img:last-child {
+ border-bottom-right-radius: 0.5rem;
+}
+
+web-ring {
+ border-radius: 1rem;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 1rem;
+}
+
+web-ring a {
+ text-decoration: none;
+}
+
+web-ring time {
+ font-variant: small-caps;
+ color: #444;
+ color: var(--fade);
+}
+
+web-ring header {
+ font-weight: 700;
+}
+
+web-ring p {
+ margin-bottom: 0 !important;
+}
+
+web-ring-item:not(:last-child) {
+ margin-bottom: 2rem;
+ display: block;
+}
+
+micro-tags a,
+web-ring section,
+author-list section {
+ background-color: #f9f9f9;
+ background-color: var(--widget-background);
+}
+
+web-ring section {
+ border-radius: 1rem;
+ padding: 1rem;
+}
+
+author-list section {
+ display: block;
+ border-radius: 1rem;
+}
+
+search-entry {
+ display: inherit;
+ width: 100%;
+}
+
+search-entry:focus-within {
+ position: absolute;
+}
+
+search-entry:focus-within form {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+search-entry form {
+ background-color: #fefefe;
+ background-color: var(--background);
+ border-radius: 0.5rem;
+ border: 1px solid;
+ border: 1px solid;
+ border-color: #ccc;
+ border-color: var(--border-darker);
+ display: inline-flex;
+ margin: 0 0.5rem;
+ width: 100%;
+}
+
+search-entry form button {
+ background-color: transparent;
+ border: none;
+ box-shadow: none;
+ cursor: pointer;
+ display: block;
+ padding: 0;
+ border-radius: 0;
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+ min-width: 4rem;
+ max-width: 4rem;
+ height: 2.85rem;
+ line-height: 1;
+}
+
+search-entry form input {
+ -webkit-appearance: none;
+ background-color: #fefefe;
+ background-color: var(--background);
+ border-bottom-left-radius: 0.5rem;
+ border-top-left-radius: 0.5rem;
+ border: none;
+ font-size: 100%;
+ line-height: 1.5;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ width: 100%;
+}
+
+search-entry form button svg {
+ stroke-width: 0.1rem;
+}
+
+search-entry form input:focus {
+ outline: none;
+ background-color: #fefefe !important;
+ background-color: var(--background) !important;
+}
+
+search-entry ul {
+ position: absolute;
+ background-color: inherit;
+ padding-top: 3rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ border-radius: 0.5rem;
+ z-index: -1;
+ margin-right: 0.5rem;
+ margin-left: -0.05rem;
+}
+
+search-entry ul li a span {
+ font-weight: 400;
+}
+
+search-entry ul li a {
+ font-weight: 700;
+ color: inherit;
+ text-decoration: none;
+ display: block;
+ padding: 0.25rem 0.75rem;
+}
+
+search-entry ul li:first-child a {
+ background-color: #ebf3ff;
+ background-color: var(--hover-alternate);
+}
+
+search-entry ul li a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+item-list {
+ font-size: 100%;
+}
+
+item-list h1 {
+ padding: 1rem;
+}
+
+item-list ul {
+ padding: 0 0.5rem 0 0.5rem;
+}
+
+item-list a span:first-child {
+ text-transform: uppercase;
+}
+
+item-list a span:first-child,
+item-list a span:last-child {
+ flex: 1;
+ color: #444;
+ color: var(--fade);
+}
+
+item-list a span:nth-child(2) {
+ flex: 3;
+ margin: 0 0.5rem;
+}
+
+item-list li a {
+ display: flex;
+ text-decoration: none;
+ align-items: baseline;
+ margin: 0.25rem 0;
+ padding: 0.5rem;
+}
+
+item-list li:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+item-list li:hover span:last-child {
+ text-decoration: underline;
+}
+
+source-array footer {
+ padding: 0 1rem;
+}
+
+source-array li a {
+ padding: 0.25rem 0.5rem;
+}
+
+context-menu {
+ background-color: #fff;
+ background-color: var(--background-bolder);
+ border: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-lighter);
+ border-radius: 4px;
+ box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1),
+ 0 0 0 1px rgba(10, 10, 10, 0.02);
+ padding-bottom: 0.5rem;
+ padding-top: 0.5rem;
+ min-width: 6.25rem;
+ position: absolute;
+ z-index: 101;
+ max-width: 15rem;
+ visibility: hidden;
+ opacity: 0;
+}
+
+context-profile context-menu {
+ padding: 1rem;
+ display: block;
+ text-align: left;
+}
+
+context-profile a {
+ display: inline;
+}
+
+context-profile h2 {
+ color: #333;
+ color: var(--fade);
+ font-weight: 400;
+}
+
+context-profile section {
+ display: flex;
+ align-items: center;
+}
+
+context-profile aside:nth-child(2) {
+ line-height: 1.25;
+}
+
+micro-thumbnail figure:hover context-menu {
+ visibility: visible;
+ opacity: 1;
+}
+
+diagram-container svg {
+ border: 1px solid;
+ border-radius: 0.5rem;
+ border-color: #ccc;
+ border-color: var(--border-darker);
+ margin: 0.5rem 0;
+ background-color: #fefefe;
+ background-color: var(--background);
+}
+
+twitter-tweet {
+ min-height: 20rem;
+ max-height: 20rem;
+ display: block;
+ overflow-y: auto;
+}
+
+twitter-tweet iframe {
+ border: none;
+}
+
+/* ----- Typography ----- */
+
+h1,
+h2,
+h3 {
+ font-size: 100%;
+ margin: 0;
+}
+
+p,
+ul,
+dd,
+dl,
+ol,
+figure,
+blockquote {
+ margin: 0;
+}
+
+p,
+dl,
+table,
+figure {
+ margin-top: 0.25rem;
+}
+
+p:not(:last-child),
+ol:not(:last-child),
+ul:not(:last-child),
+dl:not(:last-child),
+table:not(:last-child),
+figure:not(:last-child),
+code-block:not(:last-child),
+blockquote:not(:last-child) {
+ margin-bottom: 0.75rem;
+}
+
+figcaption {
+ font-size: 85%;
+ margin-top: 0.5rem;
+ text-align: center;
+ color: #444;
+ color: var(--fade);
+}
+
+sup {
+ vertical-align: super;
+ font-size: 85%;
+}
+
+sub {
+ vertical-align: sub;
+ font-size: 85%;
+}
+
+s,
+del,
+mark,
+ins {
+ padding: 0 0.15rem;
+ line-height: 1.5;
+}
+
+mark {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+ color: #332600;
+ color: var(--warning);
+}
+
+ins {
+ background-color: #d5f3c4;
+ background-color: var(--success-background);
+ color: #1d3c0c;
+ color: var(--success);
+ text-decoration: none;
+}
+
+del {
+ background-color: #fcd2cf;
+ background-color: var(--danger-background);
+ color: #8f0000;
+ color: var(--danger);
+}
+
+s {
+ text-decoration: none;
+ background-color: #333;
+ background-color: var(--spoiler);
+ color: transparent;
+}
+
+s label {
+ cursor: help;
+}
+
+[type="checkbox"]:checked ~ s {
+ background-color: inherit;
+ color: inherit;
+}
+
+blockquote {
+ color: var(--fade);
+ padding-left: 2rem;
+}
+
+video {
+ border-radius: 0.75rem;
+}
+
+kbd {
+ background-color: #eee;
+ border-radius: 3px;
+ border: 1px solid #b4b4b4;
+ box-shadow: 0 1px 1px #737373, 0 2px 0 0 #f2f2f2 inset;
+ color: #333;
+ display: inline-block;
+ font-size: 0.85em;
+ font-weight: 700;
+ line-height: 1;
+ padding: 2px 4px;
+ white-space: nowrap;
+}
+
+/* ----- Attributes ----- */
+
+[tabindex="-1"]:focus {
+ outline: none;
+}
+
+[hidden] {
+ display: none;
+}
+
+[disabled] {
+ cursor: not-allowed;
+ border-color: #ddd;
+ border-color: var(--border-lighter);
+ color: #999;
+ color: var(--disabled);
+}
+
+[id^="imgur-embed-iframe-pub"] {
+ min-width: 100%;
+}
+
+[role="doc-endnotes"] {
+ margin-bottom: 0.75rem;
+}
+
+[role="doc-endnotes"] hr {
+ border-top: 1px solid #ccc;
+ margin-bottom: 0.75rem;
+}
+
+a[href^="http"]:hover {
+ text-decoration: underline;
+}
+
+/* ----- Responsive ----- */
+
+@media screen and (max-width: 1220px) {
+ column-left {
+ flex: none;
+ margin: 0.5rem;
+ }
+ column-left nav a {
+ flex-direction: column-reverse;
+ justify-content: center;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ column-left nav a span {
+ padding-top: 0.5rem;
+ }
+}
+
+@media screen and (max-width: 1055px) {
+ column-left,
+ search-entry,
+ column-middle nav section {
+ display: none;
+ }
+ column-middle nav {
+ justify-content: space-between;
+ overflow-x: auto;
+ overflow-y: hidden;
+ }
+ icon-navigator {
+ display: inline !important;
+ }
+}
+
+@media screen and (max-width: 925px) {
+ column-right {
+ display: none;
+ }
+ micro-metadata {
+ line-height: inherit;
+ }
+ tab-list a {
+ font-size: 80%;
+ }
+ micro-header section {
+ display: block;
+ }
+ column-middle {
+ display: block;
+ max-width: 100%;
+ }
+ column-middle nav {
+ position: fixed;
+ }
+ navigation-separator hr[hidden] {
+ border-color: transparent;
+ display: flex;
+ height: 0;
+ margin: 0;
+ padding-top: 4rem;
+ border: none;
+ }
+ micro-card {
+ max-width: 600px;
+ margin: 0 auto;
+ }
+ html {
+ overflow-y: scroll;
+ margin: 0;
+ }
+ body {
+ display: block;
+ }
+ nav {
+ box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.2);
+ }
+ nav:active,
+ nav:hover,
+ nav:focus,
+ nav:focus-within,
+ nav:focus-visible {
+ box-shadow: 15px 0 15px -15px inset, -15px 0 15px -15px inset;
+ }
+}
+
+@media screen and (max-width: 460px) {
+ micro-card micro-summary > micro-thumbnail {
+ display: none;
+ }
+ micro-metadata micro-thumbnail[hidden] {
+ display: block;
+ }
+}
+
+/* ----- Self Frames ----- */
+
+@media (max-height: 16rem) {
+ micro-card {
+ width: 100vw;
+ }
+ footer,
+ column-middle nav,
+ navigation-separator hr[hidden] {
+ display: none;
+ }
+}
diff --git a/static/css/noscript.css b/static/css/noscript.css
new file mode 100644
index 0000000..9a18fb6
--- /dev/null
+++ b/static/css/noscript.css
@@ -0,0 +1,3 @@
+code-block button {
+ display: none;
+}
diff --git a/static/css/syntax-highlight-default.css b/static/css/syntax-highlight-default.css
new file mode 100644
index 0000000..c81e291
--- /dev/null
+++ b/static/css/syntax-highlight-default.css
@@ -0,0 +1,202 @@
+/* ----- Colors ----- */
+
+/* ----- Format ----- */
+
+.chroma .hl {
+ display: block;
+ padding: 0 0.5rem;
+ margin: 0 -0.5rem;
+}
+
+.chroma .ln a {
+ background-color: #edf2f7;
+ padding: 0.25rem 0.75rem;
+ margin: 0 1rem 0 -0.75rem;
+ user-select: none;
+}
+
+.chroma .ln a:hover {
+ outline: 0.15rem solid !important;
+ outline-offset: -0.3rem;
+}
+
+code-block button {
+ border: 1px solid #ccc;
+}
+
+.chroma .line:hover {
+ background-color: #f2f2f2;
+ display: block;
+ width: 100%;
+}
+
+/* ------ Tokens ------ */
+
+:root {
+ --code-chroma-background: #ffffff;
+ --code-chroma-foreground: #333333;
+ --code-chroma-error-background: #960050;
+ --code-chroma-error-foreground: #960050;
+ --code-chroma-label-background: #006894;
+ --code-chroma-label-foreground: #e6e6e6;
+ --code-chroma-line-highlight: #d9ddf2;
+ --code-chroma-line-number: #375462;
+ --code-chroma-line: var(--code-chroma-foreground);
+}
+
+/* ChromaForeground */ code-block, .chroma, .highlight { color: #333333; color: var(--code-chroma-foreground); }
+/* ChromaBackground */ code-block button, .chroma { background-color: #ffffff; background-color: var(--code-chroma-background); }
+/* ChromaErrorBackground */ .chroma .err { color: #960050; color: var(--code-chroma-error-background); }
+/* ChromaErrorForeground */ .chroma .err { color: #960050; color: var(--code-chroma-error-foreground); }
+/* ChromaLabelBackground */ .chroma code:before { background-color: #006894; background-color: var(--code-chroma-label-background); }
+/* ChromaLabelForeground */ .chroma code:before { color: #e6e6e6; color: var(--code-chroma-label-foreground); }
+/* ChromaLine */ .chroma .line { color: #333333; color: var(--code-chroma-line); }
+/* ChromaLineHighlight */ .chroma .hl { background-color: #d9ddf2; background-color: var(--code-chroma-line-highlight); }
+/* ChromaLineNumber */ .chroma .ln a { color: #375462 !important; color: var(--code-chroma-line-number) !important; }
+
+.chroma .hl {
+ background-color: #fff8c5;
+}
+
+:root {
+ --code-comment: #4f4f4f;
+ --code-comment-hashbang: var(--code-comment);
+ --code-comment-multiline: var(--code-comment);
+ --code-comment-preproc: #222222;
+ --code-comment-preproc-file: #005194;
+ --code-comment-single: var(--code-comment);
+ --code-comment-special: var(--code-comment);
+}
+
+/* Comment */ .chroma .c { color: #222222; color: var(--code-comment); }
+/* CommentHashbang */ .chroma .ch { color: #222222; color: var(--code-comment-hashbang); }
+/* CommentMultiline */ .chroma .cm { color: #222222; color: var(--code-comment-multiline); }
+/* CommentPreproc */ .chroma .cp { color: #222222; color: var(--code-comment-preproc); }
+/* CommentPreprocFile */ .chroma .cpf { color: #005194; color: var(--code-comment-preproc-file); }
+/* CommentSingle */ .chroma .c1 { color: #4f4f4f; color: var(--code-comment-single); }
+/* CommentSpecial */ .chroma .cs { color: #222222; color: var(--code-comment-special); }
+
+:root {
+ --code-generic-deleted: #8d1007;
+ --code-generic-inserted: #224e24;
+ --code-generic-subheading: #2d4958;
+ --code-generic-tag: var(--code-generic);
+ --code-generic: #2d4958;
+}
+
+/* GenericDeleted */ .chroma .gd { color: #8d1007; color: var(--code-generic-deleted); }
+/* GenericInserted */ .chroma .gi { color: #224e24; color: var(--code-generic-inserted); }
+/* GenericSubheading */ .chroma .gu { color: #2d4958; color: var(--code-generic-subheading); }
+/* GenericStrong */ .chroma .gs { font-weight: 700; }
+/* GenericEmph */ .chroma .ge { font-style: italic; }
+
+.chroma .gi { background-color: #e6ffed; padding: 0.25rem 0; }
+.chroma .gd { background-color: #ffebe9; padding: 0.25rem 0; }
+
+:root {
+ --code-keyword: #4f4f59;
+ --code-keyword-constant: #07518d;
+ --code-keyword-declaration: var(--code-keyword);
+ --code-keyword-namespace: var(--code-keyword);
+ --code-keyword-pseudo: var(--code-keyword);
+ --code-keyword-reserved: var(--code-keyword);
+ --code-keyword-type: #36590d;
+}
+
+/* Keyword */ .chroma .k { color: #4f4f59; color: var(--code-keyword); }
+/* KeywordConstant */ .chroma .kc { color: #07518d; color: var(--code-keyword-constant); }
+/* KeywordDeclaration */ .chroma .kd { color: #4f4f59; color: var(--code-keyword-declaration); }
+/* KeywordNamespace */ .chroma .kn { color: #4f4f59; color: var(--code-keyword-namespace); }
+/* KeywordPseudo */ .chroma .kp { color: #4f4f59; color: var(--code-keyword-pseudo); }
+/* KeywordReserved */ .chroma .kr { color: #4f4f59; color: var(--code-keyword-reserved); }
+/* KeywordType */ .chroma .kt { color: #36590d; color: var(--code-keyword-type); }
+
+:root {
+ --code-literal: #744601;
+ --code-literal-date: var(--code-literal);
+ --code-literal-number: var(--code-literal);
+ --code-literal-number-bin: var(--code-literal);
+ --code-literal-number-float: var(--code-literal);
+ --code-literal-number-hex: var(--code-literal);
+ --code-literal-number-integer: var(--code-literal);
+ --code-literal-number-integer-long: var(--code-literal);
+ --code-literal-number-oct: var(--code-literal);
+ --code-literal-string: #664d00;
+ --code-literal-string-affix: #664d00;
+ --code-literal-string-backtick: #664d00;
+ --code-literal-string-char: #664d00;
+ --code-literal-string-delimiter: #664d00;
+ --code-literal-string-doc: #4f4f59;
+ --code-literal-string-double: #664d00;
+ --code-literal-string-escape: #664d00;
+ --code-literal-string-heredoc: #664d00;
+ --code-literal-string-interpol: #664d00;
+ --code-literal-string-other: #664d00;
+ --code-literal-string-regex: #942e51;
+ --code-literal-string-single: #664d00;
+ --code-literal-string-symbol: #664d00;
+}
+
+/* Literal */ .chroma .l { color: #744601; color: var(--code-literal); }
+/* LiteralDate */ .chroma .ld { color: #744601; color: var(--code-literal-date); }
+/* LiteralNumber */ .chroma .m { color: #744601; color: var(--code-literal-number); }
+/* LiteralNumberBin */ .chroma .mb { color: #744601; color: var(--code-literal-number-bin); }
+/* LiteralNumberFloat */ .chroma .mf { color: #744601; color: var(--code-literal-number-float); }
+/* LiteralNumberHex */ .chroma .mh { color: #744601; color: var(--code-literal-number-hex); }
+/* LiteralNumberInteger */ .chroma .mi { color: #744601; color: var(--code-literal-number-integer); }
+/* LiteralNumberIntegerLong */ .chroma .il { color: #744601; color: var(--code-literal-number-integer-long); }
+/* LiteralNumberOct */ .chroma .mo { color: #744601; color: var(--code-literal-number-oct); }
+/* LiteralString */ .chroma .s { color: #664d00; color: var(--code-literal-string); }
+/* LiteralStringAffix */ .chroma .sa { color: #664d00; color: var(--code-literal-string-affix); }
+/* LiteralStringBacktick */ .chroma .sb { color: #664d00; color: var(--code-literal-string-backtick); }
+/* LiteralStringChar */ .chroma .sc { color: #664d00; color: var(--code-literal-string-char); }
+/* LiteralStringDelimiter */ .chroma .dl { color: #664d00; color: var(--code-literal-string-delimiter); }
+/* LiteralStringDoc */ .chroma .sd { color: #4f4f59; color: var(--code-literal-string-doc); }
+/* LiteralStringDouble */ .chroma .s2 { color: #664d00; color: var(--code-literal-string-double); }
+/* LiteralStringEscape */ .chroma .se { color: #664d00; color: var(--code-literal-string-escape); }
+/* LiteralStringHeredoc */ .chroma .sh { color: #664d00; color: var(--code-literal-string-heredoc); }
+/* LiteralStringInterpol */ .chroma .si { color: #664d00; color: var(--code-literal-string-interpol); }
+/* LiteralStringOther */ .chroma .sx { color: #664d00; color: var(--code-literal-string-other); }
+/* LiteralStringRegex */ .chroma .sr { color: #942e51; color: var(--code-literal-string-regex); }
+/* LiteralStringSingle */ .chroma .s1 { color: #664d00; color: var(--code-literal-string-single); }
+/* LiteralStringSymbol */ .chroma .ss { color: #664d00; color: var(--code-literal-string-symbol); }
+
+:root {
+ --code-name: #6d2caa;
+ --code-name-attribute: #6d2caa;
+ --code-name-builtin: #222222;
+ --code-name-class: var(--code-name);
+ --code-name-constant: var(--code-name);
+ --code-name-decorator: var(--code-name);
+ --code-name-exception: var(--code-name);
+ --code-name-function: var(--code-name);
+ --code-name-other: var(--code-name);
+ --code-name-tag: var(--code-name);
+ --code-name-variable: #005194;
+}
+
+/* Name */ .chroma .n { color: #6d2caa; color: var(--code-name); }
+/* NameAttribute */ .chroma .na { color: #6d2caa; color: var(--code-name-attribute); }
+/* NameBuiltin */ .chroma .nb { color: #222222; color: var(--code-name-builtin); }
+/* NameClass */ .chroma .nc { color: #6d2caa; color: var(--code-name-class); }
+/* NameConstant */ .chroma .no { color: #6d2caa; color: var(--code-name-constant); }
+/* NameDecorator */ .chroma .nd { color: #6d2caa; color: var(--code-name-decorator); }
+/* NameException */ .chroma .ne { color: #6d2caa; color: var(--code-name-exception); }
+/* NameFunction */ .chroma .nf { color: #6d2caa; color: var(--code-name-function); }
+/* NameOther */ .chroma .nx { color: #6d2caa; color: var(--code-name-other); }
+/* NameTag */ .chroma .nt { color: #6d2caa; color: var(--code-name-tag); }
+/* NameVariable */ .chroma .nv { color: #005194; color: var(--code-name-variable); }
+
+:root {
+ --code-operator: #4f4f59;
+ --code-operator-word: var(--code-operator);
+}
+
+/* Operator */ .chroma .o { color: #4f4f59; color: var(--code-operator); }
+/* OperatorWord */ .chroma .ow { color: #4f4f59; color: var(--code-operator-word); }
+
+:root {
+ --code-punctuation: #4f4f4f;
+}
+
+/* Punctuation */ .chroma .p { color: #4f4f4f; color: var(--code-punctuation); }
diff --git a/static/images/404.png b/static/images/404.png
new file mode 100644
index 0000000..b9b4d26
--- /dev/null
+++ b/static/images/404.png
Binary files differ
diff --git a/static/js/code-copy.ts b/static/js/code-copy.ts
new file mode 100644
index 0000000..8ef777b
--- /dev/null
+++ b/static/js/code-copy.ts
@@ -0,0 +1,22 @@
+(function () {
+ [...document.querySelectorAll("pre.chroma code :not(.ln a)")]
+ .forEach(
+ (element) => {
+ try {
+ const text = element.textContent || "";
+ setTimeout(
+ async () => {
+ await navigator.clipboard.writeText(text);
+ console.log("Info: Code block text copied succesfully.");
+ },
+ 3000,
+ );
+ } catch (err) {
+ console.error(
+ "Error: navigator.clipboard.writeText() failed.",
+ err,
+ );
+ }
+ },
+ );
+})();
diff --git a/static/js/fixedsearch.ts b/static/js/fixedsearch.ts
new file mode 100644
index 0000000..26b9ac1
--- /dev/null
+++ b/static/js/fixedsearch.ts
@@ -0,0 +1,194 @@
+(function () {
+ self.addEventListener("DOMContentLoaded", function () {
+ /*
+ Originally Based on fixedsearch, a super fast, client side search for Hugo.io with Fusejs.io
+ based on https://gist.github.com/cmod/5410eae147e4318164258742dd053993
+ */
+
+ const form = document.getElementById("search-form"); // search form
+ const query = document.getElementById("search-input"); // input box for search
+ const submit = document.getElementById("search-submit"); // form submit button
+ const dropdown = document.getElementById("search-results"); // targets the <ul>
+
+ form.addEventListener("focusin", function () {
+ initialize();
+ });
+
+ form.addEventListener("submit", function (event) {
+ event.preventDefault();
+ return false;
+ });
+
+ form.addEventListener("keydown", function (event) {
+ const head =
+ dropdown.firstChild.nextElementSibling.firstChild.nextElementSibling;
+ const tail = dropdown.lastElementChild.firstChild.nextElementSibling;
+
+ // ESC (27)
+ if (query.contains(event.target)) {
+ if (event.keyCode == 27) {
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ }
+
+ // DOWN (40)
+ if (event.keyCode == 40) {
+ event.preventDefault();
+ if (document.activeElement == query) head.focus();
+ else if (document.activeElement == tail) query.focus();
+ else {
+ document.activeElement.parentElement.nextElementSibling.firstChild
+ .nextElementSibling.focus();
+ }
+ }
+
+ // UP (38)
+ if (event.keyCode == 38) {
+ event.preventDefault();
+ if (document.activeElement == query) tail.focus();
+ else if (document.activeElement == head) query.focus();
+ else {
+ document.activeElement.parentElement.previousElementSibling.firstChild
+ .nextElementSibling.focus();
+ }
+ }
+
+ // BACKSPACE (8)
+ if (event.keyCode == 8) {
+ if (document.activeElement != query) {
+ event.preventDefault();
+ query.focus();
+ }
+ }
+
+ // ENTER (13)
+ if (event.keyCode == 13) {
+ if (dropdown && document.activeElement == query) {
+ event.preventDefault();
+ head.focus();
+ self.window.location = document.activeElement.href;
+ }
+ }
+ });
+
+ let scrolls = 0;
+
+ self.addEventListener("scroll", function () {
+ if (scrolls > 3) {
+ scrolls = 0;
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ scrolls++;
+ });
+
+ document.addEventListener("click", function (event) {
+ if (!form.contains(event.target)) {
+ dropdown.setAttribute("hidden", "");
+ }
+ });
+
+ function fetch_JSON(path, callback) {
+ const httpRequest = new XMLHttpRequest();
+ httpRequest.onreadystatechange = function () {
+ if (httpRequest.readyState === 4) {
+ if (httpRequest.status === 200) {
+ const data = JSON.parse(httpRequest.responseText);
+ if (callback) callback(data);
+ }
+ }
+ };
+ httpRequest.open("GET", path);
+ httpRequest.send();
+ }
+
+ /* Load script based on https://stackoverflow.com/a/55451823 */
+
+ function load_script(url) {
+ return new Promise(function (resolve, reject) {
+ const script = document.createElement("script");
+ script.onerror = reject;
+ script.onload = resolve;
+ if (document.currentScript) {
+ document.currentScript.parentNode.insertBefore(
+ script,
+ document.currentScript,
+ );
+ } else {
+ document.head.appendChild(script);
+ }
+ script.src = url;
+ });
+ }
+
+ let first_run = true; // allow us to delay loading json data unless search activated
+
+ function initialize() {
+ if (first_run) {
+ load_script(window.location.origin + "/js/fuzzysort.js")
+ .then(() => {
+ first_run = false;
+ fetch_JSON("/index.json", function (data) {
+ const options = {
+ key: ["title"],
+ };
+
+ query.addEventListener("keyup", function () {
+ search(query.value, data.items, options);
+ });
+
+ query.addEventListener("focusin", function () {
+ search(query.value, data.items, options);
+ });
+
+ search(query.value, data.items, options);
+ });
+ }).catch((error) => {
+ console.log("Error failed to load fuzzy sort: " + error);
+ });
+ }
+ }
+
+ function search(term, data, options) {
+ const results = fuzzysort.go(term, data, options);
+ let items = "";
+
+ if (results.length === 0 && term.length >= 0) {
+ let separator = "—";
+ if (term.length === 0) separator = "";
+ items = `
+ <li>
+ <a href="javascript: void(0)" tabindex="0">${term} ${separator} No Results Found</a>
+ </li>
+ `;
+ dropdown.removeAttribute("hidden");
+ } else {
+ dropdown.removeAttribute("hidden");
+ for (const string in results.slice(0, 10)) {
+ const decode = document.createElement("textarea");
+ decode.innerHTML = results[string].obj.title;
+
+ let highlight = fuzzysort.highlight(
+ fuzzysort.single(term, decode.value),
+ "<span>",
+ "</span>",
+ );
+
+ if (highlight === null) {
+ highlight = decode.value;
+ }
+
+ items = items +
+ `
+ <li>
+ <a href="${results[string].obj.url}" tabindex="0">${highlight}</a>
+ </li>
+ `;
+ }
+ }
+
+ dropdown.innerHTML = items;
+ }
+ });
+})();
diff --git a/static/js/fuzzysort.js b/static/js/fuzzysort.js
new file mode 100644
index 0000000..c6797d8
--- /dev/null
+++ b/static/js/fuzzysort.js
@@ -0,0 +1,636 @@
+/*
+ fuzzysort.js https://github.com/farzher/fuzzysort
+ SublimeText-like Fuzzy Search
+
+ fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
+ fuzzysort.single('test', 'test') // {score: 0}
+ fuzzysort.single('doesnt exist', 'target') // null
+
+ fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key:'file'})
+ // [{score:-18, obj:{file:'MeshRenderer.cpp'}}, {score:-6009, obj:{file:'Monitor.cpp'}}]
+
+ fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
+ // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
+
+ fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '<b>', '</b>')
+ // <b>F</b>uzzy <b>S</b>earch
+*/
+
+// UMD (Universal Module Definition) for fuzzysort
+(function(root, UMD) {
+ if(typeof define === 'function' && define.amd) {define([], UMD);}
+ else if(typeof module === 'object' && module.exports) {module.exports = UMD();}
+ else {root.fuzzysort = UMD();}
+})(this, function UMD() { function fuzzysortNew(instanceOptions) {
+
+ var fuzzysort = {
+
+ single: function(search, target, options) { if(search=='farzher'){return{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6]};}
+ if(!search) {return null;}
+ if(!isObj(search)) {search = fuzzysort.getPreparedSearch(search);}
+
+ if(!target) {return null;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ return algorithm(search, target, search[0]);
+ },
+
+ go: function(search, targets, options) { if(search=='farzher'){return[{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}];}
+ if(!search) {return noResults;}
+ search = fuzzysort.prepareSearch(search);
+ var searchLowerCode = search[0];
+
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ var resultsLen = 0; var limitedCount = 0;
+ var targetsLen = targets.length;
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn;
+ var keys = options.keys;
+ var keysLen = keys.length;
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
+ var objResults = new Array(keysLen);
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI];
+ var target = getValue(obj, key);
+ if(!target) { objResults[keyI] = null; continue; }
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode);
+ }
+ objResults.obj = obj; // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults);
+ if(score === null) {continue;}
+ if(score < threshold) {continue;}
+ objResults.score = score;
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(score > q.peek().score) {q.replaceTop(objResults);}
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key;
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
+ var target = getValue(obj, key);
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+
+ // no keys
+ } else {
+ for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i];
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+ }
+
+ if(resultsLen === 0) {return noResults;}
+ var results = new Array(resultsLen);
+ for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
+ results.total = resultsLen + limitedCount;
+ return results;
+ },
+
+ goAsync: function(search, targets, options) {
+ var canceled = false;
+ var p = new Promise(function(resolve, reject) { if(search=='farzher'){return resolve([{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}]);}
+ if(!search) {return resolve(noResults);}
+ search = fuzzysort.prepareSearch(search);
+ var searchLowerCode = search[0];
+
+ var q = fastpriorityqueue();
+ var iCurrent = targets.length - 1;
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ var resultsLen = 0; var limitedCount = 0;
+ function step() {
+ if(canceled) {return reject('canceled');}
+
+ var startMs = Date.now();
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn;
+ var keys = options.keys;
+ var keysLen = keys.length;
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var obj = targets[iCurrent];
+ var objResults = new Array(keysLen);
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI];
+ var target = getValue(obj, key);
+ if(!target) { objResults[keyI] = null; continue; }
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode);
+ }
+ objResults.obj = obj; // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults);
+ if(score === null) {continue;}
+ if(score < threshold) {continue;}
+ objResults.score = score;
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(score > q.peek().score) {q.replaceTop(objResults);}
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key;
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var obj = targets[iCurrent];
+ var target = getValue(obj, key);
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+
+ // no keys
+ } else {
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var target = targets[iCurrent];
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+ }
+
+ if(resultsLen === 0) {return resolve(noResults);}
+ var results = new Array(resultsLen);
+ for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
+ results.total = resultsLen + limitedCount;
+ resolve(results);
+ }
+
+ isNode?setImmediate(step):step(); //setTimeout here is too slow
+ });
+ p.cancel = function() { canceled = true; };
+ return p;
+ },
+
+ highlight: function(result, hOpen, hClose) {
+ if(typeof hOpen == 'function') {return fuzzysort.highlightCallback(result, hOpen);}
+ if(result === null) {return null;}
+ if(hOpen === undefined) {hOpen = '<b>';}
+ if(hClose === undefined) {hClose = '</b>';}
+ var highlighted = '';
+ var matchesIndex = 0;
+ var opened = false;
+ var target = result.target;
+ var targetLen = target.length;
+ var matchesBest = result.indexes;
+ for(var i = 0; i < targetLen; ++i) { var char = target[i];
+ if(matchesBest[matchesIndex] === i) {
+ ++matchesIndex;
+ if(!opened) { opened = true;
+ highlighted += hOpen;
+ }
+
+ if(matchesIndex === matchesBest.length) {
+ highlighted += char + hClose + target.substr(i+1);
+ break;
+ }
+ } else {
+ if(opened) { opened = false;
+ highlighted += hClose;
+ }
+ }
+ highlighted += char;
+ }
+
+ return highlighted;
+ },
+ highlightCallback: function(result, cb) {
+ if(result === null) {return null;}
+ var target = result.target;
+ var targetLen = target.length;
+ var indexes = result.indexes;
+ var highlighted = '';
+ var matchI = 0;
+ var indexesI = 0;
+ var opened = false;
+ var result = [];
+ for(var i = 0; i < targetLen; ++i) { var char = target[i];
+ if(indexes[indexesI] === i) {
+ ++indexesI;
+ if(!opened) { opened = true;
+ result.push(highlighted); highlighted = '';
+ }
+
+ if(indexesI === indexes.length) {
+ highlighted += char;
+ result.push(cb(highlighted, matchI++)); highlighted = '';
+ result.push(target.substr(i+1));
+ break;
+ }
+ } else {
+ if(opened) { opened = false;
+ result.push(cb(highlighted, matchI++)); highlighted = '';
+ }
+ }
+ highlighted += char;
+ }
+ return result;
+ },
+
+ prepare: function(target) {
+ if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null}; // hidden
+ },
+ prepareSlow: function(target) {
+ if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null}; // hidden
+ },
+ prepareSearch: function(search) {
+ if(!search) {search = '';}
+ return fuzzysort.prepareLowerCodes(search);
+ },
+
+
+
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+
+
+
+ getPrepared: function(target) {
+ if(target.length > 999) {return fuzzysort.prepare(target);} // don't cache huge targets
+ var targetPrepared = preparedCache.get(target);
+ if(targetPrepared !== undefined) {return targetPrepared;}
+ targetPrepared = fuzzysort.prepare(target);
+ preparedCache.set(target, targetPrepared);
+ return targetPrepared;
+ },
+ getPreparedSearch: function(search) {
+ if(search.length > 999) {return fuzzysort.prepareSearch(search);} // don't cache huge searches
+ var searchPrepared = preparedSearchCache.get(search);
+ if(searchPrepared !== undefined) {return searchPrepared;}
+ searchPrepared = fuzzysort.prepareSearch(search);
+ preparedSearchCache.set(search, searchPrepared);
+ return searchPrepared;
+ },
+
+ algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes;
+ var searchLen = searchLowerCodes.length;
+ var targetLen = targetLowerCodes.length;
+ var searchI = 0; // where we at
+ var targetI = 0; // where you at
+ var typoSimpleI = 0;
+ var matchesSimpleLen = 0;
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI;
+ ++searchI; if(searchI === searchLen) {break;}
+ searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))];
+ }
+
+ ++targetI; if(targetI >= targetLen) { // Failed to find searchI
+ // Check for typo or exit
+ // we go as far as possible before trying to transpose
+ // then we transpose backwards until we reach the beginning
+ for(;;) {
+ if(searchI <= 1) {return null;} // not allowed to transpose first char
+ if(typoSimpleI === 0) { // we haven't tried to transpose yet
+ --searchI;
+ var searchLowerCodeNew = searchLowerCodes[searchI];
+ if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
+ typoSimpleI = searchI;
+ } else {
+ if(typoSimpleI === 1) {return null;} // reached the end of the line for transposing
+ --typoSimpleI;
+ searchI = typoSimpleI;
+ searchLowerCode = searchLowerCodes[searchI + 1];
+ var searchLowerCodeNew = searchLowerCodes[searchI];
+ if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
+ }
+ matchesSimpleLen = searchI;
+ targetI = matchesSimple[matchesSimpleLen - 1] + 1;
+ break;
+ }
+ }
+ }
+
+ var searchI = 0;
+ var typoStrictI = 0;
+ var successStrict = false;
+ var matchesStrictLen = 0;
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes;
+ if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) {for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) { // We failed to push chars forward for a better match
+ // transpose, starting from the beginning
+ ++typoStrictI; if(typoStrictI > searchLen-2) {break;}
+ if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) {continue;} // doesn't make sense to transpose a repeat char
+ targetI = firstPossibleI;
+ continue;
+ }
+
+ --searchI;
+ var lastMatch = matchesStrict[--matchesStrictLen];
+ targetI = nextBeginningIndexes[lastMatch];
+
+ } else {
+ var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI;
+ ++searchI; if(searchI === searchLen) { successStrict = true; break; }
+ ++targetI;
+ } else {
+ targetI = nextBeginningIndexes[targetI];
+ }
+ }
+ }}
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
+ var score = 0;
+ var lastTargetI = -1;
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) {score -= targetI;}
+ lastTargetI = targetI;
+ }
+ if(!successStrict) {
+ score *= 1000;
+ if(typoSimpleI !== 0) {score += -20;}/*typoPenalty*/
+ } else {
+ if(typoStrictI !== 0) {score += -20;}/*typoPenalty*/
+ }
+ score -= targetLen - searchLen;
+ prepared.score = score;
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+
+ return prepared;
+ }
+ },
+
+ algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes;
+ var searchLen = searchLowerCodes.length;
+ var targetLen = targetLowerCodes.length;
+ var searchI = 0; // where we at
+ var targetI = 0; // where you at
+ var matchesSimpleLen = 0;
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI;
+ ++searchI; if(searchI === searchLen) {break;}
+ searchLowerCode = searchLowerCodes[searchI];
+ }
+ ++targetI; if(targetI >= targetLen) {return null;} // Failed to find searchI
+ }
+
+ var searchI = 0;
+ var successStrict = false;
+ var matchesStrictLen = 0;
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes;
+ if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) {for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) {break;} // We failed to push chars forward for a better match
+
+ --searchI;
+ var lastMatch = matchesStrict[--matchesStrictLen];
+ targetI = nextBeginningIndexes[lastMatch];
+
+ } else {
+ var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI;
+ ++searchI; if(searchI === searchLen) { successStrict = true; break; }
+ ++targetI;
+ } else {
+ targetI = nextBeginningIndexes[targetI];
+ }
+ }
+ }}
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
+ var score = 0;
+ var lastTargetI = -1;
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) {score -= targetI;}
+ lastTargetI = targetI;
+ }
+ if(!successStrict) {score *= 1000;}
+ score -= targetLen - searchLen;
+ prepared.score = score;
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+
+ return prepared;
+ }
+ },
+
+ prepareLowerCodes: function(str) {
+ var strLen = str.length;
+ var lowerCodes = []; // new Array(strLen) sparse array is too slow
+ var lower = str.toLowerCase();
+ for(var i = 0; i < strLen; ++i) {lowerCodes[i] = lower.charCodeAt(i);}
+ return lowerCodes;
+ },
+ prepareBeginningIndexes: function(target) {
+ var targetLen = target.length;
+ var beginningIndexes = []; var beginningIndexesLen = 0;
+ var wasUpper = false;
+ var wasAlphanum = false;
+ for(var i = 0; i < targetLen; ++i) {
+ var targetCode = target.charCodeAt(i);
+ var isUpper = targetCode>=65&&targetCode<=90;
+ var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57;
+ var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum;
+ wasUpper = isUpper;
+ wasAlphanum = isAlphanum;
+ if(isBeginning) {beginningIndexes[beginningIndexesLen++] = i;}
+ }
+ return beginningIndexes;
+ },
+ prepareNextBeginningIndexes: function(target) {
+ var targetLen = target.length;
+ var beginningIndexes = fuzzysort.prepareBeginningIndexes(target);
+ var nextBeginningIndexes = []; // new Array(targetLen) sparse array is too slow
+ var lastIsBeginning = beginningIndexes[0];
+ var lastIsBeginningI = 0;
+ for(var i = 0; i < targetLen; ++i) {
+ if(lastIsBeginning > i) {
+ nextBeginningIndexes[i] = lastIsBeginning;
+ } else {
+ lastIsBeginning = beginningIndexes[++lastIsBeginningI];
+ nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning;
+ }
+ }
+ return nextBeginningIndexes;
+ },
+
+ cleanup: cleanup,
+ new: fuzzysortNew,
+ };
+ return fuzzysort;
+} // fuzzysortNew
+
+// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
+var isNode = typeof require !== 'undefined' && typeof window === 'undefined';
+var MyMap = typeof Map === 'function' ? Map : function(){var s=Object.create(null);this.get=function(k){return s[k];};this.set=function(k,val){s[k]=val;return this;};this.clear=function(){s=Object.create(null);};};
+var preparedCache = new MyMap();
+var preparedSearchCache = new MyMap();
+var noResults = []; noResults.total = 0;
+var matchesSimple = []; var matchesStrict = [];
+function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = []; }
+function defaultScoreFn(a) {
+ var max = -9007199254740991;
+ for (var i = a.length - 1; i >= 0; --i) {
+ var result = a[i]; if(result === null) {continue;}
+ var score = result.score;
+ if(score > max) {max = score;}
+ }
+ if(max === -9007199254740991) {return null;}
+ return max;
+}
+
+// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
+// prop = 'key1.key2' 10ms
+// prop = ['key1', 'key2'] 27ms
+function getValue(obj, prop) {
+ var tmp = obj[prop]; if(tmp !== undefined) {return tmp;}
+ var segs = prop;
+ if(!Array.isArray(prop)) {segs = prop.split('.');}
+ var len = segs.length;
+ var i = -1;
+ while (obj && (++i < len)) {obj = obj[segs[i]];}
+ return obj;
+}
+
+function isObj(x) { return typeof x === 'object'; } // faster as a function
+
+// Hacked version of https://github.com/lemire/FastPriorityQueue.js
+var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c<o;){var f=c+1;e=c,f<o&&r[f].score<r[c].score&&(e=f),r[e-1>>1]=r[e],c=1+(e<<1);}for(var a=e-1>>1;e>0&&n.score<r[a].score;a=(e=a)-1>>1){r[e]=r[a];}r[e]=n;}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score<r[c].score;c=(n=c)-1>>1){r[n]=r[c];}r[n]=e;},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e;}},e.peek=function(e){if(0!==o){return r[0];}},e.replaceTop=function(o){r[0]=o,n();},e;};
+var q = fastpriorityqueue(); // reuse this, except for async, it needs to make its own
+
+return fuzzysortNew();
+}); // UMD
+
+// TODO: (performance) wasm version!?
+// TODO: (performance) threads?
+// TODO: (performance) avoid cache misses
+// TODO: (performance) preparedCache is a memory leak
+// TODO: (like sublime) backslash === forwardslash
+// TODO: (like sublime) spaces: "a b" should do 2 searches 1 for a and 1 for b
+// TODO: (scoring) garbage in targets that allows most searches to strict match need a penality
+// TODO: (performance) idk if allowTypo is optimized
diff --git a/static/js/index.js b/static/js/index.js
new file mode 100644
index 0000000..df649df
--- /dev/null
+++ b/static/js/index.js
@@ -0,0 +1,424 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+// This code was bundled using `deno bundle` and it's not recommended to edit it manually
+
+(function() {
+ const url = self.location.href.split("#")[0];
+ let settings = {
+ pager: {}
+ };
+ self.addEventListener("DOMContentLoaded", function() {
+ if (history.scrollRestoration) history.scrollRestoration = "manual";
+ if (localStorage["settings"]) {
+ settings = JSON.parse(localStorage["settings"]);
+ }
+ if (self.location.href.indexOf("#") >= 0) {
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ document.getElementById(location.hash.slice(1)).scrollIntoView();
+ return;
+ }
+ if (settings["pager"][url] > 0) {
+ self.scrollTo(0, settings["pager"][url]);
+ return;
+ }
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+ self.addEventListener("scroll", function() {
+ const currentPosition = self.pageYOffset;
+ settings["pager"][url] = currentPosition;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+ self.addEventListener("DOMContentLoaded", function() {
+ const up = document.getElementById("top");
+ const back = document.getElementById("back");
+ if (document.body.contains(up)) up.href = "javascript: void(0)";
+ if (document.body.contains(back)) back.href = "javascript: void(0)";
+ self.addEventListener("click", function(event) {
+ if (document.body.contains(up) && up.contains(event.target)) {
+ window.scrollTo(0, 0);
+ }
+ if (document.body.contains(back) && back.contains(event.target)) {
+ history.go(-1);
+ }
+ });
+ });
+})();
+(function() {
+ const options = "targetWindow,width=700,height=500,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes";
+ self.addEventListener("keydown", function(event) {
+ if (event.repeat && event.key === "d") {
+ selection(dictionary);
+ }
+ if (event.repeat && event.key === "s") {
+ selection(search);
+ }
+ if (event.repeat && event.key === "m") {
+ selection(manual);
+ }
+ });
+ function selection(execute) {
+ let phrase = "" + window.getSelection();
+ phrase = phrase.replace(/[!.:?,;"]/g, "");
+ phrase = phrase.replace(/^\s*(\S*?)\s*$/g, "$1");
+ if (phrase && phrase > "" && phrase.length > 1) {
+ execute(phrase);
+ }
+ }
+ function dictionary(word) {
+ window.open("https://www.merriam-webster.com/dictionary/" + encodeURIComponent(word), "Definitions", options);
+ }
+ function search(phrase) {
+ window.open("https://lite.duckduckgo.com/lite/?q=" + encodeURIComponent(phrase), "Search", options);
+ }
+ function manual(program) {
+ window.open("https://man.archlinux.org/search?q=" + encodeURIComponent(program), "Manual", options);
+ }
+})();
+(function() {
+ self.addEventListener("DOMContentLoaded", function() {
+ let mouseoverTimer;
+ let lastTouchTimestamp;
+ const prefetches = new Set();
+ const prefetchElement = document.createElement("link");
+ const isSupported = prefetchElement.relList && prefetchElement.relList.supports && prefetchElement.relList.supports("prefetch") && window.IntersectionObserver && "isIntersecting" in IntersectionObserverEntry.prototype;
+ const allowQueryString = "instantAllowQueryString" in document.body.dataset;
+ const allowExternalLinks = "instantAllowExternalLinks" in document.body.dataset;
+ const useWhitelist = "instantWhitelist" in document.body.dataset;
+ const mousedownShortcut = "instantMousedownShortcut" in document.body.dataset;
+ let delayOnHover = 65;
+ let useMousedown = false;
+ let useMousedownOnly = false;
+ let useViewport = false;
+ if ("instantIntensity" in document.body.dataset) {
+ const intensity = document.body.dataset.instantIntensity;
+ if (intensity.substr(0, "mousedown".length) == "mousedown") {
+ useMousedown = true;
+ if (intensity == "mousedown-only") {
+ useMousedownOnly = true;
+ }
+ } else if (intensity.substr(0, "viewport".length) == "viewport") {
+ if (!(navigator.connection && (navigator.connection.saveData || navigator.connection.effectiveType && navigator.connection.effectiveType.includes("2g")))) {
+ if (intensity == "viewport") {
+ if (document.documentElement.clientWidth * document.documentElement.clientHeight < 450000) {
+ useViewport = true;
+ }
+ } else if (intensity == "viewport-all") {
+ useViewport = true;
+ }
+ }
+ } else {
+ const milliseconds = parseInt(intensity);
+ if (!isNaN(milliseconds)) {
+ delayOnHover = milliseconds;
+ }
+ }
+ }
+ if (isSupported) {
+ const eventListenersOptions = {
+ capture: true,
+ passive: true
+ };
+ if (!useMousedownOnly) {
+ document.addEventListener("touchstart", touchstartListener, eventListenersOptions);
+ }
+ if (!useMousedown) {
+ document.addEventListener("mouseover", mouseoverListener, eventListenersOptions);
+ } else if (!mousedownShortcut) {
+ document.addEventListener("mousedown", mousedownListener, eventListenersOptions);
+ }
+ if (mousedownShortcut) {
+ document.addEventListener("mousedown", mousedownShortcutListener, eventListenersOptions);
+ }
+ if (useViewport) {
+ let triggeringFunction;
+ if (window.requestIdleCallback) {
+ triggeringFunction = function(callback) {
+ requestIdleCallback(callback, {
+ timeout: 1500
+ });
+ };
+ } else {
+ triggeringFunction = function(callback) {
+ callback();
+ };
+ }
+ triggeringFunction(function() {
+ const intersectionObserver = new IntersectionObserver(function(entries) {
+ entries.forEach(function(entry) {
+ if (entry.isIntersecting) {
+ const linkElement = entry.target;
+ intersectionObserver.unobserve(linkElement);
+ preload(linkElement.href);
+ }
+ });
+ });
+ document.querySelectorAll("a").forEach(function(linkElement) {
+ if (isPreloadable(linkElement)) {
+ intersectionObserver.observe(linkElement);
+ }
+ });
+ });
+ }
+ }
+ function touchstartListener(event) {
+ lastTouchTimestamp = performance.now();
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ preload(linkElement.href);
+ }
+ function mouseoverListener(event) {
+ if (performance.now() - lastTouchTimestamp < 1111) {
+ return;
+ }
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ linkElement.addEventListener("mouseout", mouseoutListener, {
+ passive: true
+ });
+ mouseoverTimer = setTimeout(function() {
+ preload(linkElement.href);
+ mouseoverTimer = undefined;
+ }, delayOnHover);
+ }
+ function mousedownListener(event) {
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ preload(linkElement.href);
+ }
+ function mouseoutListener(event) {
+ if (event.relatedTarget && event.target.closest("a") == event.relatedTarget.closest("a")) {
+ return;
+ }
+ if (mouseoverTimer) {
+ clearTimeout(mouseoverTimer);
+ mouseoverTimer = undefined;
+ }
+ }
+ function mousedownShortcutListener(event1) {
+ if (performance.now() - lastTouchTimestamp < 1111) {
+ return;
+ }
+ const linkElement = event1.target.closest("a");
+ if (event1.which > 1 || event1.metaKey || event1.ctrlKey) {
+ return;
+ }
+ if (!linkElement) {
+ return;
+ }
+ linkElement.addEventListener("click", function(event) {
+ if (event.detail == 1337) {
+ return;
+ }
+ event.preventDefault();
+ }, {
+ capture: true,
+ passive: false,
+ once: true
+ });
+ const customEvent = new MouseEvent("click", {
+ view: window,
+ bubbles: true,
+ cancelable: false,
+ detail: 1337
+ });
+ linkElement.dispatchEvent(customEvent);
+ }
+ function isPreloadable(linkElement) {
+ if (!linkElement || !linkElement.href) {
+ return;
+ }
+ if (useWhitelist && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (!allowExternalLinks && linkElement.origin != location.origin && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (![
+ "http:",
+ "https:"
+ ].includes(linkElement.protocol)) {
+ return;
+ }
+ if (linkElement.protocol == "http:" && location.protocol == "https:") {
+ return;
+ }
+ if (!allowQueryString && linkElement.search && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (linkElement.hash && linkElement.pathname + linkElement.search == location.pathname + location.search) {
+ return;
+ }
+ if ("noInstant" in linkElement.dataset) {
+ return;
+ }
+ return true;
+ }
+ function preload(url) {
+ if (prefetches.has(url)) {
+ return;
+ }
+ const prefetcher = document.createElement("link");
+ prefetcher.rel = "prefetch";
+ prefetcher.href = url;
+ document.head.appendChild(prefetcher);
+ prefetches.add(url);
+ }
+ });
+})();
+(function() {
+ self.addEventListener("DOMContentLoaded", function() {
+ const form = document.getElementById("search-form");
+ const query = document.getElementById("search-input");
+ document.getElementById("search-submit");
+ const dropdown = document.getElementById("search-results");
+ form.addEventListener("focusin", function() {
+ initialize();
+ });
+ form.addEventListener("submit", function(event) {
+ event.preventDefault();
+ return false;
+ });
+ form.addEventListener("keydown", function(event) {
+ const head = dropdown.firstChild.nextElementSibling.firstChild.nextElementSibling;
+ const tail = dropdown.lastElementChild.firstChild.nextElementSibling;
+ if (query.contains(event.target)) {
+ if (event.keyCode == 27) {
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ }
+ if (event.keyCode == 40) {
+ event.preventDefault();
+ if (document.activeElement == query) head.focus();
+ else if (document.activeElement == tail) query.focus();
+ else {
+ document.activeElement.parentElement.nextElementSibling.firstChild.nextElementSibling.focus();
+ }
+ }
+ if (event.keyCode == 38) {
+ event.preventDefault();
+ if (document.activeElement == query) tail.focus();
+ else if (document.activeElement == head) query.focus();
+ else {
+ document.activeElement.parentElement.previousElementSibling.firstChild.nextElementSibling.focus();
+ }
+ }
+ if (event.keyCode == 8) {
+ if (document.activeElement != query) {
+ event.preventDefault();
+ query.focus();
+ }
+ }
+ if (event.keyCode == 13) {
+ if (dropdown && document.activeElement == query) {
+ event.preventDefault();
+ head.focus();
+ self.window.location = document.activeElement.href;
+ }
+ }
+ });
+ let scrolls = 0;
+ self.addEventListener("scroll", function() {
+ if (scrolls > 3) {
+ scrolls = 0;
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ scrolls++;
+ });
+ document.addEventListener("click", function(event) {
+ if (!form.contains(event.target)) {
+ dropdown.setAttribute("hidden", "");
+ }
+ });
+ function fetch_JSON(path, callback) {
+ const httpRequest = new XMLHttpRequest();
+ httpRequest.onreadystatechange = function() {
+ if (httpRequest.readyState === 4) {
+ if (httpRequest.status === 200) {
+ const data = JSON.parse(httpRequest.responseText);
+ if (callback) callback(data);
+ }
+ }
+ };
+ httpRequest.open("GET", path);
+ httpRequest.send();
+ }
+ function load_script(url) {
+ return new Promise(function(resolve, reject) {
+ const script = document.createElement("script");
+ script.onerror = reject;
+ script.onload = resolve;
+ if (document.currentScript) {
+ document.currentScript.parentNode.insertBefore(script, document.currentScript);
+ } else {
+ document.head.appendChild(script);
+ }
+ script.src = url;
+ });
+ }
+ let first_run = true;
+ function initialize() {
+ if (first_run) {
+ load_script(window.location.origin + "/js/fuzzysort.js").then(()=>{
+ first_run = false;
+ fetch_JSON("/index.json", function(data) {
+ const options = {
+ key: [
+ "title"
+ ]
+ };
+ query.addEventListener("keyup", function() {
+ search(query.value, data.items, options);
+ });
+ query.addEventListener("focusin", function() {
+ search(query.value, data.items, options);
+ });
+ search(query.value, data.items, options);
+ });
+ }).catch((error)=>{
+ console.log("Error failed to load fuzzy sort: " + error);
+ });
+ }
+ }
+ function search(term, data, options) {
+ const results = fuzzysort.go(term, data, options);
+ let items = "";
+ if (results.length === 0 && term.length >= 0) {
+ let separator = "—";
+ if (term.length === 0) separator = "";
+ items = `
+ <li>
+ <a href="javascript: void(0)" tabindex="0">${term} ${separator} No Results Found</a>
+ </li>
+ `;
+ dropdown.removeAttribute("hidden");
+ } else {
+ dropdown.removeAttribute("hidden");
+ for(const string in results.slice(0, 10)){
+ const decode = document.createElement("textarea");
+ decode.innerHTML = results[string].obj.title;
+ let highlight = fuzzysort.highlight(fuzzysort.single(term, decode.value), "<span>", "</span>");
+ if (highlight === null) {
+ highlight = decode.value;
+ }
+ items = items + `
+ <li>
+ <a href="${results[string].obj.url}" tabindex="0">${highlight}</a>
+ </li>
+ `;
+ }
+ }
+ dropdown.innerHTML = items;
+ }
+ });
+})();
+console.log("Surface Control: OK");
diff --git a/static/js/index.ts b/static/js/index.ts
new file mode 100644
index 0000000..6000441
--- /dev/null
+++ b/static/js/index.ts
@@ -0,0 +1,6 @@
+import "./pager.ts";
+import "./plumber.ts";
+import "./instantpage.ts";
+import "./fixedsearch.ts";
+
+console.log("Surface Control: OK");
diff --git a/static/js/instantpage.ts b/static/js/instantpage.ts
new file mode 100644
index 0000000..7c78a6f
--- /dev/null
+++ b/static/js/instantpage.ts
@@ -0,0 +1,292 @@
+(function () {
+ self.addEventListener("DOMContentLoaded", function () {
+ /*! instant.page v5.1.0 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
+
+ let mouseoverTimer;
+ let lastTouchTimestamp;
+ const prefetches = new Set();
+ const prefetchElement = document.createElement("link");
+ const isSupported = prefetchElement.relList &&
+ prefetchElement.relList.supports &&
+ prefetchElement.relList.supports("prefetch") &&
+ window.IntersectionObserver &&
+ "isIntersecting" in IntersectionObserverEntry.prototype;
+ const allowQueryString = "instantAllowQueryString" in document.body.dataset;
+ const allowExternalLinks = "instantAllowExternalLinks" in
+ document.body.dataset;
+ const useWhitelist = "instantWhitelist" in document.body.dataset;
+ const mousedownShortcut = "instantMousedownShortcut" in
+ document.body.dataset;
+ const DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION = 1111;
+
+ let delayOnHover = 65;
+ let useMousedown = false;
+ let useMousedownOnly = false;
+ let useViewport = false;
+
+ if ("instantIntensity" in document.body.dataset) {
+ const intensity = document.body.dataset.instantIntensity;
+
+ if (intensity.substr(0, "mousedown".length) == "mousedown") {
+ useMousedown = true;
+ if (intensity == "mousedown-only") {
+ useMousedownOnly = true;
+ }
+ } else if (intensity.substr(0, "viewport".length) == "viewport") {
+ if (
+ !(navigator.connection &&
+ (navigator.connection.saveData ||
+ (navigator.connection.effectiveType &&
+ navigator.connection.effectiveType.includes("2g"))))
+ ) {
+ if (intensity == "viewport") {
+ /* Biggest iPhone resolution (which we want): 414 × 896 = 370944
+ * Small 7" tablet resolution (which we don’t want): 600 × 1024 = 614400
+ * Note that the viewport (which we check here) is smaller than the resolution due to the UI’s chrome */
+ if (
+ document.documentElement.clientWidth *
+ document.documentElement.clientHeight < 450000
+ ) {
+ useViewport = true;
+ }
+ } else if (intensity == "viewport-all") {
+ useViewport = true;
+ }
+ }
+ } else {
+ const milliseconds = parseInt(intensity);
+ if (!isNaN(milliseconds)) {
+ delayOnHover = milliseconds;
+ }
+ }
+ }
+
+ if (isSupported) {
+ const eventListenersOptions = {
+ capture: true,
+ passive: true,
+ };
+
+ if (!useMousedownOnly) {
+ document.addEventListener(
+ "touchstart",
+ touchstartListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (!useMousedown) {
+ document.addEventListener(
+ "mouseover",
+ mouseoverListener,
+ eventListenersOptions,
+ );
+ } else if (!mousedownShortcut) {
+ document.addEventListener(
+ "mousedown",
+ mousedownListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (mousedownShortcut) {
+ document.addEventListener(
+ "mousedown",
+ mousedownShortcutListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (useViewport) {
+ let triggeringFunction;
+ if (window.requestIdleCallback) {
+ triggeringFunction = function (callback) {
+ requestIdleCallback(callback, {
+ timeout: 1500,
+ });
+ };
+ } else {
+ triggeringFunction = function (callback) {
+ callback();
+ };
+ }
+
+ triggeringFunction(function () {
+ const intersectionObserver = new IntersectionObserver(
+ function (entries) {
+ entries.forEach(function (entry) {
+ if (entry.isIntersecting) {
+ const linkElement = entry.target;
+ intersectionObserver.unobserve(linkElement);
+ preload(linkElement.href);
+ }
+ });
+ },
+ );
+
+ document.querySelectorAll("a").forEach(function (linkElement) {
+ if (isPreloadable(linkElement)) {
+ intersectionObserver.observe(linkElement);
+ }
+ });
+ });
+ }
+ }
+
+ function touchstartListener(event) {
+ /* Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp`
+ * must be assigned on touchstart to be measured on mouseover. */
+ lastTouchTimestamp = performance.now();
+
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ preload(linkElement.href);
+ }
+
+ function mouseoverListener(event) {
+ if (
+ performance.now() - lastTouchTimestamp <
+ DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
+ ) {
+ return;
+ }
+
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ linkElement.addEventListener("mouseout", mouseoutListener, {
+ passive: true,
+ });
+
+ mouseoverTimer = setTimeout(function () {
+ preload(linkElement.href);
+ mouseoverTimer = undefined;
+ }, delayOnHover);
+ }
+
+ function mousedownListener(event) {
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ preload(linkElement.href);
+ }
+
+ function mouseoutListener(event) {
+ if (
+ event.relatedTarget &&
+ event.target.closest("a") == event.relatedTarget.closest("a")
+ ) {
+ return;
+ }
+
+ if (mouseoverTimer) {
+ clearTimeout(mouseoverTimer);
+ mouseoverTimer = undefined;
+ }
+ }
+
+ function mousedownShortcutListener(event) {
+ if (
+ performance.now() - lastTouchTimestamp <
+ DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
+ ) {
+ return;
+ }
+
+ const linkElement = event.target.closest("a");
+
+ if (event.which > 1 || event.metaKey || event.ctrlKey) {
+ return;
+ }
+
+ if (!linkElement) {
+ return;
+ }
+
+ linkElement.addEventListener("click", function (event) {
+ if (event.detail == 1337) {
+ return;
+ }
+
+ event.preventDefault();
+ }, { capture: true, passive: false, once: true });
+
+ const customEvent = new MouseEvent("click", {
+ view: window,
+ bubbles: true,
+ cancelable: false,
+ detail: 1337,
+ });
+ linkElement.dispatchEvent(customEvent);
+ }
+
+ function isPreloadable(linkElement) {
+ if (!linkElement || !linkElement.href) {
+ return;
+ }
+
+ if (useWhitelist && !("instant" in linkElement.dataset)) {
+ return;
+ }
+
+ if (
+ !allowExternalLinks && linkElement.origin != location.origin &&
+ !("instant" in linkElement.dataset)
+ ) {
+ return;
+ }
+
+ if (!["http:", "https:"].includes(linkElement.protocol)) {
+ return;
+ }
+
+ if (linkElement.protocol == "http:" && location.protocol == "https:") {
+ return;
+ }
+
+ if (
+ !allowQueryString && linkElement.search &&
+ !("instant" in linkElement.dataset)
+ ) {
+ return;
+ }
+
+ if (
+ linkElement.hash &&
+ linkElement.pathname + linkElement.search ==
+ location.pathname + location.search
+ ) {
+ return;
+ }
+
+ if ("noInstant" in linkElement.dataset) {
+ return;
+ }
+
+ return true;
+ }
+
+ function preload(url) {
+ if (prefetches.has(url)) {
+ return;
+ }
+
+ const prefetcher = document.createElement("link");
+ prefetcher.rel = "prefetch";
+ prefetcher.href = url;
+ document.head.appendChild(prefetcher);
+
+ prefetches.add(url);
+ }
+ });
+})();
diff --git a/static/js/pager.ts b/static/js/pager.ts
new file mode 100644
index 0000000..de60bcd
--- /dev/null
+++ b/static/js/pager.ts
@@ -0,0 +1,46 @@
+(function () {
+ const url = self.location.href.split("#")[0];
+ let settings = { pager: {} };
+
+ self.addEventListener("DOMContentLoaded", function () {
+ if (history.scrollRestoration) history.scrollRestoration = "manual";
+ if (localStorage["settings"]) {
+ settings = JSON.parse(localStorage["settings"]);
+ }
+ if (self.location.href.indexOf("#") >= 0) {
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ document.getElementById(location.hash.slice(1)).scrollIntoView();
+ return;
+ }
+ if (settings["pager"][url] > 0) {
+ self.scrollTo(0, settings["pager"][url]);
+ return;
+ }
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+
+ self.addEventListener("scroll", function () {
+ const currentPosition = self.pageYOffset;
+ settings["pager"][url] = currentPosition;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+
+ self.addEventListener("DOMContentLoaded", function () {
+ const up = document.getElementById("top");
+ const back = document.getElementById("back");
+
+ if (document.body.contains(up)) up.href = "javascript: void(0)";
+ if (document.body.contains(back)) back.href = "javascript: void(0)";
+
+ self.addEventListener("click", function (event) {
+ if (document.body.contains(up) && up.contains(event.target)) {
+ window.scrollTo(0, 0);
+ }
+ if (document.body.contains(back) && back.contains(event.target)) {
+ history.go(-1);
+ }
+ });
+ });
+})();
diff --git a/static/js/plumber.ts b/static/js/plumber.ts
new file mode 100644
index 0000000..8e551fd
--- /dev/null
+++ b/static/js/plumber.ts
@@ -0,0 +1,59 @@
+/**
+ * Plumber based on and inspired by
+ * Dictionary Access Copyright (C) 2006, Paul Lutus
+ * https://arachnoid.com/javascript/dictionary_access.js
+ * LICENSE: GPLv2+
+ */
+
+(function () {
+ const options =
+ "targetWindow,width=700,height=500,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes";
+
+ self.addEventListener("keydown", function (event) {
+ if (event.repeat && event.key === "d") {
+ selection(dictionary);
+ }
+ if (event.repeat && event.key === "s") {
+ selection(search);
+ }
+ if (event.repeat && event.key === "m") {
+ selection(manual);
+ }
+ });
+
+ function selection(execute) {
+ let phrase = "" + window.getSelection();
+ phrase = phrase.replace(/[!.:?,;"]/g, "");
+ phrase = phrase.replace(/^\s*(\S*?)\s*$/g, "$1");
+ if (phrase && phrase > "" && phrase.length > 1) {
+ execute(phrase);
+ }
+ }
+
+ function dictionary(word) {
+ window.open(
+ "https://www.merriam-webster.com/dictionary/" +
+ encodeURIComponent(word),
+ "Definitions",
+ options,
+ );
+ }
+
+ function search(phrase) {
+ window.open(
+ "https://lite.duckduckgo.com/lite/?q=" +
+ encodeURIComponent(phrase),
+ "Search",
+ options,
+ );
+ }
+
+ function manual(program) {
+ window.open(
+ "https://man.archlinux.org/search?q=" +
+ encodeURIComponent(program),
+ "Manual",
+ options,
+ );
+ }
+})();
diff --git a/themes/default/layouts/_default/_markup/render-codeblock-goat.html b/themes/default/layouts/_default/_markup/render-codeblock-goat.html
new file mode 100644
index 0000000..e83d7de
--- /dev/null
+++ b/themes/default/layouts/_default/_markup/render-codeblock-goat.html
@@ -0,0 +1,19 @@
+{{ $width := .Attributes.width }}
+{{ $height := .Attributes.height }}
+{{ $caption := default "GoAT Diagram" .Attributes.caption }}
+
+<diagram-container>
+ {{ with diagrams.Goat .Inner }}
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ {{ if or $width $height }}
+ {{ with $width }}width="{{ . }}"{{ end }}
+ {{ with $height }}height="{{ . }}"{{ end }}
+ {{ else }}
+ viewBox="0 0 {{ .Width }} {{ .Height }}"
+ {{ end }}>
+ {{ .Inner }}
+ </svg>
+ {{ end }}
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</diagram-container>
diff --git a/themes/default/layouts/_default/_markup/render-codeblock.html b/themes/default/layouts/_default/_markup/render-codeblock.html
new file mode 100644
index 0000000..9dba950
--- /dev/null
+++ b/themes/default/layouts/_default/_markup/render-codeblock.html
@@ -0,0 +1,19 @@
+{{ $options := .Attributes.options | default "default=1" }}
+{{ $caption := .Attributes.caption }}
+
+<code-block {{ if in (lower $options) "linenos" }}data-lines{{ end }}>
+ <header>
+ <language-label>
+ {{ .Type }}
+ </language-label>
+ <button>
+ {{ safeHTML (readFile "static/icons/feather/copy.svg") }}
+ <span>Copy</span>
+ </button>
+ </header>
+ {{ highlight .Inner .Type (print $options ",lineAnchors=" "code-" (truncate 7 "" (md5 .Inner))) }}
+</code-block>
+
+{{ with $caption }}
+ <figcaption>{{ . | markdownify }}</figcaption>
+{{ end }}
diff --git a/themes/default/layouts/_default/_markup/render-heading.html b/themes/default/layouts/_default/_markup/render-heading.html
new file mode 100644
index 0000000..37eddb2
--- /dev/null
+++ b/themes/default/layouts/_default/_markup/render-heading.html
@@ -0,0 +1,6 @@
+<h{{ .Level }} id="{{ .Anchor | safeURL }}">
+ <a
+ title="{{ .Text | safeHTML }}"
+ href="#{{ .Anchor | safeURL }}">{{ .Text | safeHTML }}
+ </a>
+</h{{ .Level }}>
diff --git a/themes/default/layouts/_default/_markup/render-image.html b/themes/default/layouts/_default/_markup/render-image.html
new file mode 100644
index 0000000..00f37cf
--- /dev/null
+++ b/themes/default/layouts/_default/_markup/render-image.html
@@ -0,0 +1,45 @@
+{{- $source := $.Destination | safeURL -}}
+{{- $localPath := print "content/" $source -}}
+{{- $remoteImage := resources.GetRemote $source -}}
+{{- $width := "" -}}
+{{- $height := "" -}}
+
+{{- with $remoteImage -}}
+ {{- with .Err -}}
+ {{- if fileExists $localPath -}}
+ {{- $localImage := imageConfig $localPath -}}
+ {{- with $localImage -}}
+ {{- $width = .Width -}}
+ {{- $height = .Height -}}
+ {{- end -}}
+ {{- else -}}
+ {{- $source = print "/images/404.png" -}}
+ {{- $localPath = print "static/" $source -}}
+ {{- $localImage := imageConfig $localPath -}}
+ {{- with $localImage -}}
+ {{- $width = .Width -}}
+ {{- $height = .Height -}}
+ {{- end -}}
+ {{- end -}}
+ {{- else -}}
+ {{- $width = .Width -}}
+ {{- $height = .Height -}}
+ {{ end }}
+{{ end }}
+
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
+<figure>
+ <a href="{{ $source }}">
+ <img loading="lazy"
+ src="{{ $source }}"
+ alt="{{ $.Text | htmlUnescape }}"
+ title="{{ $.Text | htmlUnescape }}"
+ width="{{ $width }}"
+ height="{{ $height }}"
+ />
+ </a>
+{{ if $.Title }}
+ <figcaption>{{ $.Title | markdownify }}</figcaption>
+{{ end -}}
+</figure>
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/_default/_markup/render-link.html b/themes/default/layouts/_default/_markup/render-link.html
new file mode 100644
index 0000000..9b1ffae
--- /dev/null
+++ b/themes/default/layouts/_default/_markup/render-link.html
@@ -0,0 +1,21 @@
+{{- $link := .Destination -}}
+{{ $isRemote := strings.HasPrefix $link "http" }}
+{{- if not $isRemote -}}
+{{ $url := urls.Parse .Destination }}
+{{- if $url.Path -}}
+{{ $fragment := "" }}
+{{- with $url.Fragment }}{{ $fragment = printf "#%s" . }}{{ end -}}
+{{- with .Page.GetPage $url.Path }}{{ $link = printf "%s%s" .RelPermalink $fragment }}{{ end }}{{ end -}}
+{{- end -}}
+<a
+ rel="noopener"
+ href="{{ $link | safeURL }}"
+ {{ with or .Title ($link | safeURL) -}}
+ title="{{ . }}"
+ {{ end }}
+ {{- if $isRemote -}}
+ target="_blank"
+ {{ end }}>
+ {{- .Text | safeHTML -}}
+</a>
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/_default/baseof.html b/themes/default/layouts/_default/baseof.html
new file mode 100644
index 0000000..01b6ea0
--- /dev/null
+++ b/themes/default/layouts/_default/baseof.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>
+ {{- block "title" . -}}{{- end -}}
+ {{- partial "site-title.html" . -}}
+ </title>
+ <meta name="description" content="{{- partial "site-description.html" . -}}">
+ {{- partial "head.html" . -}}
+ {{- block "styles" . -}}{{- end -}}
+ </head>
+ <body>
+ <skip-link>
+ <a href="#main">Skip to main content</a>
+ </skip-link>
+
+ <column-left>
+ {{- partial "navigator-left.html" . -}}
+ </column-left>
+
+ <column-middle>
+ <main id="main">
+ {{- block "middle" . -}}{{- end -}}
+ {{- partial "footer.html" . -}}
+ </main>
+ </column-middle>
+
+ <column-right>
+ {{- block "right" . -}}{{- end -}}
+ </column-right>
+ </body>
+</html>
diff --git a/themes/default/layouts/_default/feeds.html b/themes/default/layouts/_default/feeds.html
new file mode 100644
index 0000000..40442a1
--- /dev/null
+++ b/themes/default/layouts/_default/feeds.html
@@ -0,0 +1,151 @@
+{{ define "styles" }}
+{{ $author := partial "author-user.html" . }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/") | safeCSS }}
+
+ tab-list a[href="/{{ $author }}/feeds/"] {
+ display: inherit;
+ font-weight: 700;
+ text-decoration: underline;
+ text-decoration-color: #014cc6;
+ text-decoration-color: var(--link);
+ }
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Feeds"
+ "Subtitle" (partial "count-media.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ partial "profile.html" . }}
+
+ {{- $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") -}}
+
+ {{ $feeds := slice }}
+
+ {{ range $key, $value := (seq 3) }}
+ {{ range first 5 $author.webring.rss -}}
+
+ {{ with resources.GetRemote . }}
+ {{- with .Err -}}
+ {{- warnf "%s" . -}}
+ {{- end -}}
+ {{ end }}
+
+ {{ with resources.GetRemote . | transform.Unmarshal -}}
+
+ {{- $title := "Example Domain" -}}
+ {{- $link := "https://example.com/link" -}}
+ {{- $description := "No description found." -}}
+ {{- $content := "No content found." -}}
+ {{- $sourceTitle := "From Example Domain" -}}
+ {{- $sourceLink := "https://example.com/" -}}
+ {{- $sourceDescription := "The example domain." -}}
+ {{- $date := now.Format "January 2, 2006" -}}
+ {{- $dateTime := now.Format "2006-01-02T15:04:05Z" -}}
+ {{- $dateTitle := now.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+
+ {{ with .channel -}}
+
+ {{ if reflect.IsMap (index .link 0) -}}
+ {{ $sourceLink = index (index .link 0) "-href" | plainify | htmlUnescape -}}
+ {{ else -}}
+ {{ $sourceLink = index .link 0 | plainify | htmlUnescape -}}
+ {{ end -}}
+
+ {{ $sourceDescription = .description | plainify | htmlUnescape -}}
+
+ {{ $sourceTitle = .title | plainify | htmlUnescape -}}
+
+ {{ range first $value .item -}}
+ {{ $description = delimit (.description | plainify | htmlUnescape | findRE "^([^.]+)") " " -}}
+ {{ $content = .description | plainify | htmlUnescape -}}
+ {{ $title = .title | plainify | htmlUnescape -}}
+ {{ $link = .link | plainify | htmlUnescape -}}
+ {{ $date = .pubDate | plainify | htmlUnescape | time.Format "January 2, 2006" -}}
+ {{ $dateTime = .pubDate | plainify | htmlUnescape | time.Format "2006-01-02T15:04:05Z" -}}
+ {{ $dateTitle = .pubDate | plainify | htmlUnescape | time.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+ {{ end -}}
+
+ {{ else -}}
+
+ {{ $sourceLink = index (index .link 0) "-href" | plainify | htmlUnescape -}}
+ {{ $sourceTitle = .title | plainify | htmlUnescape -}}
+ {{ $sourceDescription = .subtitle | plainify | htmlUnescape -}}
+
+ {{ range first $value .entry -}}
+
+ {{ if reflect.IsMap .title -}}
+ {{ $title = index .title "#text" | plainify | htmlUnescape -}}
+ {{ else -}}
+ {{ $title = .title | plainify | htmlUnescape -}}
+ {{ end -}}
+
+ {{ $date = .updated | plainify | htmlUnescape | time.Format "January 2, 2006" -}}
+ {{ $dateTime = .updated | plainify | htmlUnescape | time.Format "2006-01-02T15:04:05Z" -}}
+ {{ $dateTitle = .updated | plainify | htmlUnescape | time.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+
+ {{ $link = .id -}}
+ {{ with .content -}}
+ {{ $description = delimit ((index . "#text") | plainify | htmlUnescape | findRE "^([^.]+)") " " -}}
+ {{ $content = (index . "#text") | plainify | htmlUnescape -}}
+ {{ end -}}
+ {{ with .summary -}}
+ {{ $description = delimit ((index . "#text") | plainify | htmlUnescape | findRE "^([^.]+)") " " -}}
+ {{ $content = (index . "#text") | plainify | htmlUnescape -}}
+ {{ end -}}
+ {{ end -}}
+
+ {{ end -}}
+
+ {{
+ $feeds = $feeds | append (dict
+ "FeedTitle" $title
+ "FeedLink" $link
+ "FeedDescription" $description
+ "FeedContent" $content
+ "FeedSourceTitle" $sourceTitle
+ "FeedSourceLink" $sourceLink
+ "FeedSourceDescription" $sourceDescription
+ "FeedDate" $date
+ "FeedDateTime" $dateTime
+ "FeedDateTitle" $dateTitle
+ )
+ }}
+
+ {{ end }}
+ {{ end }}
+ {{ end }}
+
+ {{ range sort $feeds "FeedDateTime" "desc" }}
+ {{ partial "card-feed.html" (dict
+ "FeedName" ((delimit (first 3 (split .FeedSourceTitle " ")) " ") | replaceRE "[^a-zA-Z ]" "")
+ "FeedSourceDomain" (.FeedSourceLink | replaceRE "^https?://([^/]+).*" "$1")
+ "FeedSourceLink" .FeedSourceLink
+ "FeedContent" (or .FeedDescription .FeedTitle)
+ "FeedDate" .FeedDate
+ "FeedDateTime" .FeedDateTime
+ "FeedDateTitle" .FeedDateTime
+ "FeedWordCount" ((or .FeedDescription .FeedTitle) | countwords)
+ "FeedLink" .FeedLink
+ )
+ }}
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/home.archive-messages.html b/themes/default/layouts/_default/home.archive-messages.html
new file mode 100644
index 0000000..cd0ba49
--- /dev/null
+++ b/themes/default/layouts/_default/home.archive-messages.html
@@ -0,0 +1,50 @@
+{{ define "title" }}Message History &mdash; {{ end }}
+{{ define "description" }}This page contains an archive of all messages &mdash; {{ end }}
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/archives/messages/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+{{ partial "navigator-middle.html"
+ (dict
+ "Title" "History"
+ "Subtitle" (partial "count-total-messages.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+}}
+
+<message-archive>
+ <item-list>
+ {{ $type := (partial "author-user.html" .) }}
+ {{ range (.Site.RegularPages.GroupByDate "2006") }}
+ {{ range (first 1 .Pages) }}
+ <h1>{{ .Date.Format "2006" }}</h1>
+ {{ end }}
+ <ul>
+ {{ range .Pages }}
+ {{ if (ne .Params.hidden true) }}
+ <li>
+ <a title="{{ partial "title.html" . }}" href="{{ .RelPermalink }}#{{ partial "card-id.html" . }}">
+ <span>{{ .Date.Format "02 Jan" }}</span>
+ <span>{{ partial "title.html" . }}</span>
+ <span>@{{ partial "author-user.html" . }}</span>
+ </a>
+ </li>
+ {{ end }}
+ {{ end }}
+ </ul>
+ {{ end }}
+ </item-list>
+</message-archive>
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/home.archive-tags.html b/themes/default/layouts/_default/home.archive-tags.html
new file mode 100644
index 0000000..cf26c40
--- /dev/null
+++ b/themes/default/layouts/_default/home.archive-tags.html
@@ -0,0 +1,47 @@
+{{ define "title" }}Tags Archive &mdash; {{ end }}
+{{ define "description" }}This page contains an archive of all tags &mdash; {{ end }}
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/archives/tags/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+{{ partial "navigator-middle.html"
+ (dict
+ "Title" "Records"
+ "Subtitle" (partial "count-tags.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+}}
+
+<item-list>
+ {{ range $taxonomy := .Site.Taxonomies }}
+ {{ range $tag, $article := $taxonomy }}
+ <h1><a href="/tags/{{ $tag }}/">#{{ $tag }}</a></h1>
+ <ul>
+ {{ range $article.Pages }}
+ <li>
+ <a
+ title="{{ partial "title.html" . }}"
+ href="/tags/{{ $tag }}/#{{ partial "card-id.html" . }}">
+ <span>{{ .Date.Format "02 Jan 2006" }}</span>
+ <span>{{ partial "title.html" . }}</span>
+ <span>@{{ partial "author-user.html" . }}</span>
+ </a>
+ </li>
+ {{ end }}
+ </ul>
+ {{ end }}
+ {{ end }}
+</item-list>
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/home.authors.html b/themes/default/layouts/_default/home.authors.html
new file mode 100644
index 0000000..af2a7c6
--- /dev/null
+++ b/themes/default/layouts/_default/home.authors.html
@@ -0,0 +1,40 @@
+{{ define "title" }}Authors &mdash; {{ end }}
+{{ define "description" }}This page contains a list of all authors &mdash; {{ end }}
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/authors/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Authors"
+ "Subtitle" (partial "count-authors.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ $authors := slice }}
+
+ {{- range $index, $data := where .Site.RegularPages ".Params.hidden" "!=" "true" -}}
+ {{ $authors = $authors | append $data.Type }}
+ {{- end -}}
+
+ {{ $authors = uniq $authors }}
+
+ {{ range $author := $authors }}
+ {{ $data := index $.Site.Data.authors $author | default "default" }}
+ {{ partial "author-card.html" (dict "Data" $data) }}
+ {{ end }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/home.browserconfig.xml b/themes/default/layouts/_default/home.browserconfig.xml
new file mode 100644
index 0000000..67bc7a3
--- /dev/null
+++ b/themes/default/layouts/_default/home.browserconfig.xml
@@ -0,0 +1,13 @@
+{{- $icon := resources.Get .Site.Params.webmanifest.logo -}}
+
+{{- $mstile150 := $icon.Fit "150x150" -}}
+
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="{{ $mstile150.Permalink }}"/>
+ <TileColor>#da532c</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
diff --git a/themes/default/layouts/_default/home.sources.html b/themes/default/layouts/_default/home.sources.html
new file mode 100644
index 0000000..aa77e2a
--- /dev/null
+++ b/themes/default/layouts/_default/home.sources.html
@@ -0,0 +1,120 @@
+{{ define "title" }}Sources &mdash; {{ end }}
+{{ define "description" }}This page contains a list of all source files &mdash; {{ end }}
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/sources/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ $_default := readDir (print "/themes/default/layouts/_default") }}
+ {{ $_markup := readDir (print "/themes/default/layouts/_default/_markup") }}
+ {{ $partials := readDir (print "/themes/default/layouts/partials") }}
+ {{ $shortcodes := readDir (print "/themes/default/layouts/shortcodes") }}
+
+ {{ $fileCount := add (len $shortcodes) (len $partials) }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Sources"
+ "Subtitle" (print $fileCount " " "Files")
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ <source-array>
+ <item-list>
+
+ <h1>Defaults</h1>
+ <ul>
+ {{ range sort $_default ".ModTime" "desc" }}
+ {{ if not .IsDir }}
+ <li>
+ <a download title="{{ .Name }}" href="/source/files/_default/{{ .Name }}">
+ <code>
+ <span>{{- .ModTime | time.Format "2006-01-02 15:04 MST" }}</span>
+ <span>{{ .Name }}</span>
+ <span>{{ .Size -}}</span>
+ </code>
+ </a>
+ </li>
+ {{ end }}
+ {{ end }}
+ </ul>
+
+ <h1>Markup</h1>
+ <ul>
+ {{ range sort $_markup ".ModTime" "desc" }}
+ {{ if not .IsDir }}
+ <li>
+ <a download title="{{ .Name }}" href="/source/files/_default/_markup/{{ .Name }}">
+ <code>
+ <span>{{- .ModTime | time.Format "2006-01-02 15:04 MST" }}</span>
+ <span>{{ .Name }}</span>
+ <span>{{ .Size -}}</span>
+ </code>
+ </a>
+ </li>
+ {{ end }}
+ {{ end }}
+ </ul>
+
+ <h1>Partials</h1>
+ <ul>
+ {{ range sort $partials ".ModTime" "desc" }}
+ {{ if not .IsDir }}
+ <li>
+ <a download title="{{ .Name }}" href="/source/files/partials/{{ .Name }}">
+ <code>
+ <span>{{- .ModTime | time.Format "2006-01-02 15:04 MST" }}</span>
+ <span>{{ .Name }}</span>
+ <span>{{ .Size -}}</span>
+ </code>
+ </a>
+ </li>
+ {{ end }}
+ {{ end }}
+ </ul>
+
+ <h1>Shortcodes</h1>
+ <ul>
+ {{ range sort $shortcodes ".ModTime" "desc" }}
+ {{ if not .IsDir }}
+ <li>
+ <a download title="{{ .Name }}" href="/source/files/shortcodes/{{ .Name }}">
+ <code>
+ <span>{{- .ModTime | time.Format "2006-01-02 15:04 MST" }}</span>
+ <span>{{ .Name }}</span>
+ <span>{{ .Size -}}</span>
+ </code>
+ </a>
+ </li>
+ {{ end }}
+ {{ end }}
+ </ul>
+ </item-list>
+
+ <footer>
+ <p>
+ Canory Version: 0.1.1
+ &middot; Dictionary Access by Paul Lutus: <a href="https://arachnoid.com/javascript/dictionary_access.js">GPLv2+ License</a>
+ &middot; Feather Icons by Cole Bemis: <a href="https://github.com/feathericons/feather/blob/8b5d6802fa8fd1eb3924b465ff718d2fa8d61efe/LICENSE">MIT License</a>
+ &middot; Tabler Icons by Paweł Kuna: <a href="https://github.com/tabler/tabler-icons/blob/60f39297d0f785f2e8635faca6857b2260b2d164/LICENSE">MIT License</a>
+ &middot; Instant Page by Alexandre Dieulot: <a href="https://github.com/instantpage/instant.page/blob/729e97d5b3245552e41d8f92ed03d08f1d62ea46/LICENSE">MIT License</a>
+ &middot; Fixed Search by Heracles Papatheodorou: <a href="https://gist.github.com/Arty2/8b0c43581013753438a3d35c15091a9f#file-license-md">License</a>
+ &middot; Fuzzy Sort by Stephen Kamenar: <a href="https://github.com/farzher/fuzzysort/blob/e7f484a124f09bf3cb9a4db366a95457d962dbed/LICENSE">MIT License</a>
+ </p>
+ </footer>
+
+ </source-array>
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/index.json b/themes/default/layouts/_default/index.json
new file mode 100644
index 0000000..9096730
--- /dev/null
+++ b/themes/default/layouts/_default/index.json
@@ -0,0 +1,40 @@
+{
+ "version": "https://jsonfeed.org/version/1.1",
+ "title": "{{ .Site.Title }}",
+ "home_page_url": "{{ .Site.BaseURL }}",
+ "feed_url": "{{ .Site.BaseURL }}/index.json",
+ "items": [
+ {{- range $index, $data := where .Site.RegularPages ".Params.hidden" "!=" "true" -}}
+ {{- if and (ne $data.Type "json") (not .ExpiryDate) -}}
+ {{- if and $index (gt $index 0) -}},{{- end }}
+ {
+ "id": "{{ md5 $data.Permalink }}",
+ "url": "{{ $data.Permalink }}",
+ "title": "{{ htmlEscape $data.Summary }}",
+ "summary": "{{ $data.Summary }}",
+ "date_modified": "{{ $data.Date | time.Format "2006-01-02T15:04:05Z" }}",
+ "date_published": "{{ $data.PublishDate | time.Format "2006-01-02T15:04:05Z" }}",
+ "_metadata": {
+ "slug": "{{ $data.Slug }}",
+ "type": "{{ $data.Type }}"
+ },
+ "author": {
+ "name": "{{ partial "author-user.html" . }}"
+ },
+ "tags": [
+ {{- range $tags, $tag := $data.Params.tags -}}
+ {{- if $tags -}}
+ ,
+ {{- end -}}
+ "
+ {{- $tag | htmlEscape -}}
+ "
+ {{- end -}}
+ ],
+ "content_text": {{ $data.Plain | jsonify }},
+ "content_html": {{ $data.Content | jsonify }}
+ }
+ {{- end -}}
+ {{ end }}
+ ]
+}
diff --git a/themes/default/layouts/_default/index.webmanifest b/themes/default/layouts/_default/index.webmanifest
new file mode 100644
index 0000000..f7b3f66
--- /dev/null
+++ b/themes/default/layouts/_default/index.webmanifest
@@ -0,0 +1,24 @@
+{{- $icon := resources.Get .Site.Params.webmanifest.logo -}}
+
+{{- $androidChrome192 := $icon.Fit "192x192" -}}
+{{- $androidChrome512 := $icon.Fit "512x512" -}}
+
+{
+ "name": "{{ .Site.Params.webmanifest.name }}",
+ "short_name": "{{ .Site.Params.webmanifest.shortName }}",
+ "icons": [
+ {
+ "src": "{{ $androidChrome192.Permalink }}",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "{{ $androidChrome512.Permalink }}",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "{{ .Site.Params.webmanifest.themeColor }}",
+ "background_color": "{{ .Site.Params.webmanifest.backgroundColor }}",
+ "display": "{{ .Site.Params.webmanifest.display }}"
+}
diff --git a/themes/default/layouts/_default/likes.html b/themes/default/layouts/_default/likes.html
new file mode 100644
index 0000000..b3be32c
--- /dev/null
+++ b/themes/default/layouts/_default/likes.html
@@ -0,0 +1,58 @@
+{{ define "styles" }}
+{{ $author := partial "author-user.html" . }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/") | safeCSS }}
+
+ tab-list a[href="/{{ $author }}/likes/"] {
+ display: inherit;
+ font-weight: 700;
+ text-decoration: underline;
+ text-decoration-color: #014cc6;
+ text-decoration-color: var(--link);
+ }
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Likes"
+ "Subtitle" (partial "count-likes.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ partial "profile.html" . }}
+
+ {{ $author := partial "author-user.html" . }}
+ {{ $count := 0 }}
+ {{ $result := false }}
+
+ {{ range .Site.Pages }}
+ {{ if and (eq .Type $author) .Params.Liked }}
+ {{ $result = true }}
+ {{ $count = add $count 1 }}
+ {{ if le $count 10 }}
+ {{ .Render "summary" }}
+ {{ end }}
+ {{ end }}
+ {{ end }}
+
+ {{ if not $result }}
+ <footer>
+ <code>No likes found!</code>
+ </footer>
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/list.html b/themes/default/layouts/_default/list.html
new file mode 100644
index 0000000..f35f275
--- /dev/null
+++ b/themes/default/layouts/_default/list.html
@@ -0,0 +1,29 @@
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Home"
+ "Subtitle" (partial "count-total-messages.html" .)
+ "Context" .
+ )
+ }}
+
+ {{ range where .Paginator.Pages ".Params.hidden" "ne" "true" }}
+ {{ .Render "summary" }}
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{ range first 1 (where .Site.Pages "Params.Author" .Site.Author.default.webring) }}
+ {{- partial "navigator-right.html" . -}}
+ {{ end }}
+{{ end }}
diff --git a/themes/default/layouts/_default/media.html b/themes/default/layouts/_default/media.html
new file mode 100644
index 0000000..8073928
--- /dev/null
+++ b/themes/default/layouts/_default/media.html
@@ -0,0 +1,40 @@
+{{ define "styles" }}
+{{ $author := partial "author-user.html" . }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/") | safeCSS }}
+
+ tab-list a[href="/{{ $author }}/media/"] {
+ display: inherit;
+ font-weight: 700;
+ text-decoration: underline;
+ text-decoration-color: #014cc6;
+ text-decoration-color: var(--link);
+ }
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Media"
+ "Subtitle" (partial "count-media.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ partial "profile.html" . }}
+
+ {{ partial "media-search.html" . }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/rss.xml b/themes/default/layouts/_default/rss.xml
new file mode 100644
index 0000000..4570a98
--- /dev/null
+++ b/themes/default/layouts/_default/rss.xml
@@ -0,0 +1,32 @@
+{{- $pctx := . -}}
+{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
+{{- $pages := $pctx.RegularPages -}}
+{{- $limit := .Site.Config.Services.RSS.Limit -}}
+{{- if ge $limit 1 -}}
+{{- $pages = $pages | first $limit -}}
+{{- end -}}
+{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+ <channel>
+ <title>{{ partial "site-title" . }}</title>
+ <link>{{ .Permalink }}</link>
+ <description>Recent from {{ partial "site-title" . }}</description>
+ <language>{{ .Site.LanguageCode }}</language>
+ {{- if not .Date.IsZero -}}
+ <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
+ {{ with .OutputFormats.Get "RSS" }}
+ {{- printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink "application/rss+xml" | safeHTML -}}
+ {{ end }}
+ {{- range $pages -}}
+ {{ if and (ne .Params.hidden true) (not .Params.Unlisted) (not .ExpiryDate) }}
+ <item>
+ <title>{{ htmlEscape .Summary }}</title>
+ <link>{{ .Permalink }}</link>
+ <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
+ <guid>{{ .Permalink }}</guid>
+ <description>{{ .Content | html }}</description>
+ </item>
+ {{ end }}
+ {{ end }}
+ </channel>
+</rss>
diff --git a/themes/default/layouts/_default/section.html b/themes/default/layouts/_default/section.html
new file mode 100644
index 0000000..e0b607c
--- /dev/null
+++ b/themes/default/layouts/_default/section.html
@@ -0,0 +1,45 @@
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/") | safeCSS }}
+
+ tab-list a[href="/{{ partial "author-user.html" . }}"] {
+ display: inherit;
+ font-weight: 700;
+ text-decoration: underline;
+ text-decoration-color: #014cc6;
+ text-decoration-color: var(--link);
+ }
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{- $messages := print (lang.FormatNumberCustom 0 .Paginator.TotalNumberOfElements) " " "Messages" -}}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" (partial "author-name.html" .)
+ "Subtitle" $messages
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ partial "profile.html" . }}
+
+ {{ range where .Paginator.Pages ".Params.hidden" "ne" "true" }}
+ {{ .Render "summary" }}
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{ range first 1 .Paginator.Pages }}
+ {{- partial "navigator-right.html" . -}}
+ {{ end }}
+{{ end }}
diff --git a/themes/default/layouts/_default/single.html b/themes/default/layouts/_default/single.html
new file mode 100644
index 0000000..4858f0a
--- /dev/null
+++ b/themes/default/layouts/_default/single.html
@@ -0,0 +1,22 @@
+{{ define "middle" }}
+
+{{ partial "navigator-middle.html"
+ (dict
+ "Title" "Message"
+ "Subtitle" "Single"
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+}}
+
+<micro-thread>
+ {{ partial "card.html" . }}
+</micro-thread>
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/sitemap.xml b/themes/default/layouts/_default/sitemap.xml
new file mode 100644
index 0000000..63f5119
--- /dev/null
+++ b/themes/default/layouts/_default/sitemap.xml
@@ -0,0 +1,22 @@
+{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
+ {{ range .Data.Pages }}
+ <url>
+ <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
+ <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
+ <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
+ <priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
+ <xhtml:link
+ rel="alternate"
+ hreflang="{{ .Language.Lang }}"
+ href="{{ .Permalink }}"
+ />{{ end }}
+ <xhtml:link
+ rel="alternate"
+ hreflang="{{ .Language.Lang }}"
+ href="{{ .Permalink }}"
+ />{{ end }}
+ </url>
+ {{ end }}
+</urlset>
diff --git a/themes/default/layouts/_default/summary.html b/themes/default/layouts/_default/summary.html
new file mode 100644
index 0000000..3b6a0b3
--- /dev/null
+++ b/themes/default/layouts/_default/summary.html
@@ -0,0 +1 @@
+{{ partial "card.html" . }}
diff --git a/themes/default/layouts/_default/taxonomy.html b/themes/default/layouts/_default/taxonomy.html
new file mode 100644
index 0000000..e43bc29
--- /dev/null
+++ b/themes/default/layouts/_default/taxonomy.html
@@ -0,0 +1,62 @@
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/tags/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{ $title := .Title }}
+
+ {{ if eq .Title "Tags" }}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" "Tags"
+ "Subtitle" (partial "count-tags.html" .)
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ $page := 1 }}
+ {{ $count := 0 }}
+
+ {{ range .Paginator.Pages }}
+ {{ $title := lower .Title }}
+ <item-list>
+ <h1><a href="/tags/{{ $title }}">#{{ .Title }}</a></h1>
+ <ul>
+ {{ range .Pages }}
+ {{ $count = add $count 1 }}
+ <li>
+ <a
+ title="{{ partial "title.html" . }}"
+ href="/tags/{{ $title }}/{{ if gt $page 1 }}page/{{ $page }}/{{ end }}#{{ partial "card-id.html" . }}">
+ <span>{{ .Date.Format "02 Jan 2006" }}</span>
+ <span>{{ partial "title.html" . }}</span>
+ <span>@{{ partial "author-user.html" . }}</span>
+ </a>
+ </li>
+ {{ if eq (mod $count $.Paginator.PageSize) 0 }}
+ {{ $page = add $page 1 }}
+ {{ end }}
+ {{ end }}
+ {{ $page = 1 }}
+ {{ $count = 0 }}
+ </ul>
+ </item-list>
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+ {{ end }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/_default/term.html b/themes/default/layouts/_default/term.html
new file mode 100644
index 0000000..30776ff
--- /dev/null
+++ b/themes/default/layouts/_default/term.html
@@ -0,0 +1,33 @@
+{{ define "styles" }}
+<style>
+ {{ partial "styles-navigator.html" (dict "Link" "/tags/") | safeCSS }}
+</style>
+{{ end }}
+
+{{ define "middle" }}
+
+ {{- $terms := print (lang.FormatNumberCustom 0 (len .Data.Pages)) " " "Messages" -}}
+
+ {{ partial "navigator-middle.html"
+ (dict
+ "Title" (print "#" .Title)
+ "Subtitle" $terms
+ "Icon" "arrow-left"
+ "IconLabel" "Back"
+ "Href" "/"
+ "Id" "back"
+ "Context" .
+ )
+ }}
+
+ {{ range .Paginator.Pages }}
+ {{ partial "card.html" . }}
+ {{ end }}
+
+ {{ partial "pagination.html" . }}
+
+{{ end }}
+
+{{ define "right" }}
+ {{- partial "navigator-right-default.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/partials/archive-link.html b/themes/default/layouts/partials/archive-link.html
new file mode 100644
index 0000000..8b08ba3
--- /dev/null
+++ b/themes/default/layouts/partials/archive-link.html
@@ -0,0 +1,2 @@
+/archives/{{ .Type }}/
+{{- /* Strip newline. */ -}}
diff --git a/themes/default/layouts/partials/author-card.html b/themes/default/layouts/partials/author-card.html
new file mode 100644
index 0000000..bbb79b0
--- /dev/null
+++ b/themes/default/layouts/partials/author-card.html
@@ -0,0 +1,23 @@
+<micro-author>
+ <micro-card>
+ <micro-summary>
+ <micro-thumbnail>
+ <figure>
+ <a title="{{ .Data.name }}" href="/{{ .Data.user }}#">
+ <picture>
+ <img alt="{{ .Data.name }}" src="/{{ .Data.user }}/media/profile/picture.png" />
+ </picture>
+ </a>
+ </figure>
+ </micro-thumbnail>
+ <micro-header>
+ <h1>{{ .Data.name }}</h1>
+ <h2><a title="" href="/{{ .Data.user }}">@{{ .Data.user }}</a></h2>
+ </micro-header>
+ <a title="RSS" href="/{{ .Data.user }}/rss.xml">
+ {{ safeHTML (readFile "static/icons/feather/rss.svg") }}
+ <span>Follow</span>
+ </a>
+ </micro-summary>
+ </micro-card>
+</micro-author>
diff --git a/themes/default/layouts/partials/author-domain-host.html b/themes/default/layouts/partials/author-domain-host.html
new file mode 100644
index 0000000..5f6b7f0
--- /dev/null
+++ b/themes/default/layouts/partials/author-domain-host.html
@@ -0,0 +1,2 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+{{- $author.domain.host | markdownify -}}
diff --git a/themes/default/layouts/partials/author-list.html b/themes/default/layouts/partials/author-list.html
new file mode 100644
index 0000000..759fa44
--- /dev/null
+++ b/themes/default/layouts/partials/author-list.html
@@ -0,0 +1,17 @@
+<author-list>
+ {{ $authors := slice }}
+
+ {{- range $index, $data := where .Site.RegularPages ".Params.hidden" "!=" "true" -}}
+ {{ $authors = $authors | append $data.Type }}
+ {{- end -}}
+
+ {{ $authors = uniq $authors }}
+
+ <h1>Authors</h1>
+ <section>
+ {{ range $author := first 5 $authors }}
+ {{ $data := index $.Site.Data.authors $author | default "default" }}
+ {{ partial "author-card.html" (dict "Data" $data) }}
+ {{ end }}
+ </section>
+</author-list>
diff --git a/themes/default/layouts/partials/author-media-path.html b/themes/default/layouts/partials/author-media-path.html
new file mode 100644
index 0000000..77090ea
--- /dev/null
+++ b/themes/default/layouts/partials/author-media-path.html
@@ -0,0 +1,2 @@
+{{ partial "author-user.html" . }}/media
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/partials/author-name.html b/themes/default/layouts/partials/author-name.html
new file mode 100644
index 0000000..405569c
--- /dev/null
+++ b/themes/default/layouts/partials/author-name.html
@@ -0,0 +1,2 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+{{- $author.name | markdownify -}}
diff --git a/themes/default/layouts/partials/author-user.html b/themes/default/layouts/partials/author-user.html
new file mode 100644
index 0000000..8d7036a
--- /dev/null
+++ b/themes/default/layouts/partials/author-user.html
@@ -0,0 +1,2 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+{{- $author.user | markdownify -}}
diff --git a/themes/default/layouts/partials/card-feed.html b/themes/default/layouts/partials/card-feed.html
new file mode 100644
index 0000000..fbcab8c
--- /dev/null
+++ b/themes/default/layouts/partials/card-feed.html
@@ -0,0 +1,82 @@
+<micro-card>
+ <micro-summary>
+ <micro-thumbnail>
+
+ {{- $imageCanonicalURL := (print "https://" .FeedSourceDomain "/favicon.ico") -}}
+ {{- $imageTitle := "No image found" -}}
+ {{- $imageSource := "/images/404.png" -}}
+
+ {{- with $image := resources.GetRemote $imageCanonicalURL -}}
+ {{- with .Err -}}
+ {{ warnf "%s" . }}
+ {{- $fallbackImage := imageConfig (print "static/images/404.png") -}}
+ {{- with $fallbackImage -}}
+ {{- $fallbackWidth := .Width -}}
+ {{- $fallbackHeight := .Height -}}
+ {{ end }}
+ {{- else -}}
+ {{- $imageTitle = $imageCanonicalURL -}}
+ {{- $imageSource = $image.RelPermalink -}}
+ {{ end }}
+ <figure>
+ <a title="{{ $imageTitle }}" href=" {{ $imageSource }}">
+ <picture>
+ <img alt="" src="{{ $imageSource }}" />
+ </picture>
+ </a>
+ </figure>
+ {{ else }}
+ <figure>
+ <a title="{{ $imageTitle }}" href=" {{ $imageSource }}">
+ <picture>
+ <img alt="" src="{{ $imageSource }}" />
+ </picture>
+ </a>
+ </figure>
+ {{ end }}
+
+ </micro-thumbnail>
+
+ <micro-content>
+ <micro-metadata>
+ <micro-thumbnail hidden>
+ <figure>
+ <a title="{{ $imageTitle }}" href=" {{ $imageSource }}">
+ <picture>
+ <img alt="" src="{{ $imageSource }}" />
+ </picture>
+ </a>
+ </figure>
+ </micro-thumbnail>
+ <section>
+ <b>{{ .FeedName }}</b>
+
+ &middot; <a
+ title="{{ .FeedSourceLink }}"
+ rel="noopener"
+ target="_blank"
+ href="{{ .FeedSourceLink }}">
+ @{{ .FeedSourceDomain }}
+ </a>
+
+ &middot; <a href="{{ .FeedLink }}">
+ <time
+ datetime="{{ .FeedDateTime }}"
+ title="{{ .FeedDateTime | time.Format "Monday, January 2 2006 at 15:04:05 MST" }}">
+ {{ .FeedDate }}
+ </time>
+ </a>
+
+ &middot; {{- partial "read-time.html" (dict "WordCount" .FeedWordCount) -}}
+
+ &middot; <word-limit>
+ <word-count title="{{ .FeedWordCount }} words">
+ {{ .FeedWordCount }} words
+ </word-count>
+ </word-limit>
+ </section>
+ </micro-metadata>
+ <p>{{ .FeedContent }}.</p>
+ </micro-content>
+ </micro-summary>
+</micro-card>
diff --git a/themes/default/layouts/partials/card-id.html b/themes/default/layouts/partials/card-id.html
new file mode 100644
index 0000000..b882d8b
--- /dev/null
+++ b/themes/default/layouts/partials/card-id.html
@@ -0,0 +1,7 @@
+{{- $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") -}}
+{{- $source := print $author.user "/messages/" .File.LogicalName -}}
+{{- $source = replace $source "/" "-" -}}
+{{ if .ExpiryDate }}
+ {{- $source = md5 $source -}}
+{{ end }}
+{{- $source -}}
diff --git a/themes/default/layouts/partials/card.html b/themes/default/layouts/partials/card.html
new file mode 100644
index 0000000..ef4fe11
--- /dev/null
+++ b/themes/default/layouts/partials/card.html
@@ -0,0 +1,35 @@
+<micro-card {{ if .Weight }}pinned{{ end }} id="{{ partial "card-id.html" . }}">
+ {{ if .ExpiryDate }}<micro-expires>{{ end }}
+ <micro-summary title="{{- partial "title.html" . -}}">
+ <micro-thumbnail>
+ {{ partial "profile-picture.html" . }}
+ </micro-thumbnail>
+ <micro-content>
+ <micro-metadata>
+ {{ partial "meta.html" . }}
+ </micro-metadata>
+ {{ if .Params.nsfw }}
+ <micro-nsfw>
+ <details>
+ <summary>
+ {{ safeHTML (readFile "static/icons/feather/alert-circle.svg") }}
+ <span>Danger: Unsafe Content. <b><u>Click here</u></b> to show/hide</span>
+ </summary>
+ </details>
+ <micro-nsfw-content>
+ {{ end }}
+ {{-
+ .Content |
+ replaceRE "<p>\n<figure>" "<figure>" |
+ replaceRE "</figure></p>" "</figure>" |
+ safeHTML
+ -}}
+ {{ if .Params.nsfw }}
+ </micro-nsfw-content>
+ </micro-nsfw>
+ {{ end }}
+ <micro-tags>{{ partial "tags.html" . }}</micro-tags>
+ </micro-content>
+ </micro-summary>
+ {{ if .ExpiryDate }}</micro-expires>{{ end }}
+</micro-card>
diff --git a/themes/default/layouts/partials/context-menu.html b/themes/default/layouts/partials/context-menu.html
new file mode 100644
index 0000000..40e0001
--- /dev/null
+++ b/themes/default/layouts/partials/context-menu.html
@@ -0,0 +1,4 @@
+<input type="checkbox" id="context-menu-{{ .Name }}" />
+<label for="context-menu-{{ .Name }}">{{ .Label }}</label>
+
+<context-menu>{{ .Content }}</context-menu>
diff --git a/themes/default/layouts/partials/context-profile.html b/themes/default/layouts/partials/context-profile.html
new file mode 100644
index 0000000..91446a9
--- /dev/null
+++ b/themes/default/layouts/partials/context-profile.html
@@ -0,0 +1,22 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+
+<context-profile>
+ <context-menu>
+ <section>
+ <aside>
+ <figure>
+ <a title="{{ $author.name }}" href="/{{ $author.user }}">
+ <picture>
+ <img alt="{{ $author.name }}" src="/{{ $author.user }}/media/profile/picture.png" />
+ </picture>
+ </a>
+ </figure>
+ </aside>
+ <aside>
+ <h1>{{ $author.name }}</h1>
+ <h2>@{{ $author.user }}</h2>
+ </aside>
+ </section>
+ <p>{{- $author.description | markdownify -}}</p>
+ </context-menu>
+</context-profile>
diff --git a/themes/default/layouts/partials/count-authors.html b/themes/default/layouts/partials/count-authors.html
new file mode 100644
index 0000000..7a39329
--- /dev/null
+++ b/themes/default/layouts/partials/count-authors.html
@@ -0,0 +1,6 @@
+{{ $authors := slice }}
+{{- range $index, $data := where .Site.RegularPages ".Params.hidden" "!=" "true" -}}
+ {{ $authors = $authors | append $data.Type }}
+{{- end -}}
+
+{{ print (lang.FormatNumberCustom 0 (len (uniq $authors))) " " "Authors" }}
diff --git a/themes/default/layouts/partials/count-likes.html b/themes/default/layouts/partials/count-likes.html
new file mode 100644
index 0000000..a808576
--- /dev/null
+++ b/themes/default/layouts/partials/count-likes.html
@@ -0,0 +1,10 @@
+{{ $author := partial "author-user.html" . }}
+{{ $count := 0 }}
+
+{{ range .Site.Pages }}
+ {{ if and (eq .Type $author) .Params.Liked }}
+ {{ $count = add $count 1 }}
+ {{ end }}
+{{ end }}
+
+{{ print (lang.FormatNumberCustom 0 $count) " " "Messages" }}
diff --git a/themes/default/layouts/partials/count-media.html b/themes/default/layouts/partials/count-media.html
new file mode 100644
index 0000000..1d5e49b
--- /dev/null
+++ b/themes/default/layouts/partials/count-media.html
@@ -0,0 +1,17 @@
+{{ $author := partial "author-user.html" . }}
+{{ $count := 0 }}
+
+{{ range .Site.Pages }}
+ {{ if or
+ (in .Content "<figure>")
+ (in .Content "<imgur-video>")
+ (in .Content "<youtube-video>")
+ (in .Content "<video-container>")
+ }}
+ {{ if eq .Type $author }}
+ {{ $count = add $count 1 }}
+ {{ end }}
+ {{ end }}
+{{ end }}
+
+{{ print (lang.FormatNumberCustom 0 $count) " " "Messages" }}
diff --git a/themes/default/layouts/partials/count-tags.html b/themes/default/layouts/partials/count-tags.html
new file mode 100644
index 0000000..0ef6802
--- /dev/null
+++ b/themes/default/layouts/partials/count-tags.html
@@ -0,0 +1 @@
+{{- print (lang.FormatNumberCustom 0 (len .Site.Taxonomies.tags)) " " "Tags" -}}
diff --git a/themes/default/layouts/partials/count-total-messages.html b/themes/default/layouts/partials/count-total-messages.html
new file mode 100644
index 0000000..10540a7
--- /dev/null
+++ b/themes/default/layouts/partials/count-total-messages.html
@@ -0,0 +1,2 @@
+{{ $messages := len .Site.RegularPages }}
+{{ print (lang.FormatNumberCustom 0 $messages) " " "Messages" }}
diff --git a/themes/default/layouts/partials/csp.html b/themes/default/layouts/partials/csp.html
new file mode 100644
index 0000000..9cba948
--- /dev/null
+++ b/themes/default/layouts/partials/csp.html
@@ -0,0 +1,40 @@
+{{ $upgrade := "" }}
+
+{{ if .Site.Params.production }}
+ {{ $upgrade = "upgrade-insecure-requests" }}
+{{ end }}
+
+<meta name="referrer" content="{{ .Site.Params.csp.referrer }}">
+
+{{ printf `
+<meta
+ http-equiv="Content-Security-Policy"
+ content="
+ %s
+ block-all-mixed-content;
+ default-src 'self';
+ child-src %s;
+ font-src %s;
+ form-action %s;
+ frame-src %s;
+ img-src %s;
+ media-src %s;
+ object-src %s;
+ prefetch-src %s;
+ script-src %s;
+ script-src-elem %s;
+ style-src %s;
+ ">`
+ ($upgrade)
+ (delimit .Site.Params.csp.childsrc " ")
+ (delimit .Site.Params.csp.fontsrc " ")
+ (delimit .Site.Params.csp.formaction " ")
+ (delimit .Site.Params.csp.framesrc " ")
+ (delimit .Site.Params.csp.imgsrc " ")
+ (delimit .Site.Params.csp.mediasrc " ")
+ (delimit .Site.Params.csp.objectsrc " ")
+ (delimit .Site.Params.csp.prefetchsrc " ")
+ (delimit .Site.Params.csp.scriptsrc " ")
+ (delimit .Site.Params.csp.scriptsrcelem " ")
+ (delimit .Site.Params.csp.stylesrc " ")
+ | safeHTML }}
diff --git a/themes/default/layouts/partials/draft.html b/themes/default/layouts/partials/draft.html
new file mode 100644
index 0000000..1760044
--- /dev/null
+++ b/themes/default/layouts/partials/draft.html
@@ -0,0 +1 @@
+{{ if .Draft }}<mark>&middot;<strong>DRAFT</strong></mark>{{ end }}
diff --git a/themes/default/layouts/partials/expiry-date.html b/themes/default/layouts/partials/expiry-date.html
new file mode 100644
index 0000000..bf111f9
--- /dev/null
+++ b/themes/default/layouts/partials/expiry-date.html
@@ -0,0 +1,11 @@
+{{ $diff := now.Sub .ExpiryDate }}
+{{ $duration := lang.FormatNumberCustom 0 (math.Round (mul (div $diff.Hours 24) -1)) }}
+
+<expiry-date>
+ <time
+ datetime= "{{ .ExpiryDate | time.Format "2006-01-02T15:04:05Z" }}"
+ title="Self destructs within {{ $duration }} days ({{ .ExpiryDate | time.Format "Monday, January 2 2006 at 15:04:05 MST" }})">
+ {{ safeHTML (readFile "static/icons/feather/trash-2.svg") }}
+ {{ .ExpiryDate | time.Format "Jan 2 2006" }}
+ </time>
+</expiry-date>
diff --git a/themes/default/layouts/partials/footer.html b/themes/default/layouts/partials/footer.html
new file mode 100644
index 0000000..5fe4295
--- /dev/null
+++ b/themes/default/layouts/partials/footer.html
@@ -0,0 +1,12 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+
+<footer>
+ {{- with .Site.Author.footer -}}
+ {{ if $.IsHome -}}
+ {{ . | markdownify }}
+ {{- else -}}
+ {{ $author.blogtitle }}
+ {{- end -}}
+ {{- end }}
+ | Copyright &copy; {{ now.Format "2006"}}
+</footer>
diff --git a/themes/default/layouts/partials/gallery-images.html b/themes/default/layouts/partials/gallery-images.html
new file mode 100644
index 0000000..1dfe611
--- /dev/null
+++ b/themes/default/layouts/partials/gallery-images.html
@@ -0,0 +1,8 @@
+{{ $folder := print "/content/" (partial "author-user.html" .) "/media/" }}
+
+ <gallery-images>
+ <h1>Picture Gallary</h1>
+ <section>
+ {{ partial "gallery-walk.html" $folder }}
+ </section>
+ </gallery-images>
diff --git a/themes/default/layouts/partials/gallery-walk.html b/themes/default/layouts/partials/gallery-walk.html
new file mode 100644
index 0000000..daa27ac
--- /dev/null
+++ b/themes/default/layouts/partials/gallery-walk.html
@@ -0,0 +1,41 @@
+{{- $folder := . }}
+
+{{- if fileExists $folder }}
+ {{- $files := readDir $folder }}
+ {{- if $files }}
+ {{- range $files }}
+
+ {{- if not .IsDir }}
+ {{- $name := index (split .Name ".") 0 }}
+ {{- $extension := lower (index (split .Name ".") 1) }}
+
+ {{- if or
+ (eq $extension "apng")
+ (eq $extension "avif")
+ (eq $extension "gif")
+ (eq $extension "jfif")
+ (eq $extension "jpeg")
+ (eq $extension "jpg")
+ (eq $extension "pjp")
+ (eq $extension "pjpeg")
+ (eq $extension "png")
+ (eq $extension "svg")
+ (eq $extension "webp")
+ }}
+
+ <img
+ title="{{- .Name }}"
+ alt="{{- .Name }}"
+ src="/{{- strings.TrimPrefix "content/" (trim $folder "/") }}/{{- .Name }}"
+ />
+
+ {{- end }}
+ {{- end }}
+
+ {{- if .IsDir }}
+ {{- partial "gallery-walk.html" (path.Join $folder .Name) }}
+ {{- end }}
+
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/themes/default/layouts/partials/handle.html b/themes/default/layouts/partials/handle.html
new file mode 100644
index 0000000..dcfb335
--- /dev/null
+++ b/themes/default/layouts/partials/handle.html
@@ -0,0 +1,5 @@
+<a
+ title="{{ partial "author-user.html" . }}@{{ partial "author-domain-host.html" . }}"
+ href="/{{ partial "author-user.html" . }}">
+ @{{ partial "author-user.html" . }}
+</a>
diff --git a/themes/default/layouts/partials/head.html b/themes/default/layouts/partials/head.html
new file mode 100644
index 0000000..856fb2f
--- /dev/null
+++ b/themes/default/layouts/partials/head.html
@@ -0,0 +1,50 @@
+<!-- HTML Meta Tags -->
+<meta charset="utf-8">
+<meta name="author" content="{{ partial "author-name.html" . -}}">
+{{ if or .Params.Unlisted .Params.ExpiryDate -}}
+<meta name="robots" content="noindex,nofollow">
+{{- else -}}
+<meta name="robots" content="index,follow">
+{{- end }}
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="keywords" content="{{- partial "site-title.html" . -}}">
+
+<!-- Search Engine Tags -->
+<meta itemprop="name" content="{{ partial "site-title.html" . -}}">
+<meta itemprop="description" content="{{ if .Params.summary }}{{ .Params.summary }}{{ else }}{{ .Summary }}{{ end }}">
+<meta itemprop="image" content="{{ .Site.BaseURL }}/images/{{ partial "meta-thumbnail-path.html" . }}.png">
+
+<!-- Open Graph Meta Tags -->
+<meta property="og:title" content="{{ partial "site-title.html" . -}}">
+<meta property="og:description" content="{{- partial "site-description.html" . -}}">
+<meta property="og:url" content="{{ .Permalink }}">
+<meta property="og:image" content="{{ .Site.BaseURL }}/images/{{ partial "meta-thumbnail-path.html" . }}.png">
+<meta property="og:type" content="website">
+<meta property="article:published_time" content="{{ .Params.PublishDate }}">
+<meta property="article:modified_time " content="{{ .Params.LastMod }}">
+
+<!-- Twitter Meta Tags -->
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="{{ partial "site-title.html" . -}}">
+<meta name="twitter:description" content="{{- partial "site-description.html" . -}}">
+<meta name="twitter:image" content="{{ .Site.BaseURL }}/images/{{ partial "meta-thumbnail-path.html" . }}.png">
+
+<!-- Progressive Enhancement -->
+{{- partial "manifest.html" . -}}
+
+<!-- Content Security Policy -->
+{{- partial "csp.html" . -}}
+
+<!-- Style Sheets -->
+<link rel="stylesheet" href="/css/default.css">
+<link rel="stylesheet" href="/css/syntax-highlight-default.css">
+
+<!-- Scripts -->
+<script src="/js/index.js"></script>
+
+<!-- No Scripts -->
+<noscript>
+ <style>
+ {{ safeCSS (readFile "static/css/noscript.css") }}
+ </style>
+</noscript>
diff --git a/themes/default/layouts/partials/js-back.html b/themes/default/layouts/partials/js-back.html
new file mode 100644
index 0000000..afa40f7
--- /dev/null
+++ b/themes/default/layouts/partials/js-back.html
@@ -0,0 +1,2 @@
+history.go(-1); return false;
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/partials/manifest.html b/themes/default/layouts/partials/manifest.html
new file mode 100644
index 0000000..4bd935a
--- /dev/null
+++ b/themes/default/layouts/partials/manifest.html
@@ -0,0 +1,16 @@
+{{- $icon := resources.Get .Site.Params.webmanifest.logo -}}
+
+{{- $icon32 := $icon.Fit "32x32" -}}
+{{- $icon16 := $icon.Fit "16x16" -}}
+{{- $appleTouchIcon := $icon.Fit "180x180" -}}
+
+{{ $ico := $icon32.Content | resources.FromString "/favicon.ico" }}
+
+<link rel="icon" href="{{ $ico.RelPermalink | relURL }}">
+<link rel="icon" type="image/png" sizes="16x16" href="{{ $icon16.RelPermalink | relURL }}">
+<link rel="icon" type="image/png" sizes="32x32" href="{{ $icon32.RelPermalink | relURL }}">
+<link rel="apple-touch-icon" sizes="180x180" href="{{ $appleTouchIcon.RelPermalink | relURL }}">
+<link rel="manifest" href="/site.webmanifest">
+<meta name="msapplication-TileColor" content="#da532c">
+<meta name="theme-color" content="#ffffff">
+
diff --git a/themes/default/layouts/partials/media-search.html b/themes/default/layouts/partials/media-search.html
new file mode 100644
index 0000000..5954d16
--- /dev/null
+++ b/themes/default/layouts/partials/media-search.html
@@ -0,0 +1,27 @@
+{{ $author := partial "author-user.html" . }}
+
+{{ $count := 0 }}
+{{ $result := false }}
+
+{{ range .Site.Pages }}
+ {{ if or
+ (in .Content "<figure>")
+ (in .Content "<imgur-video>")
+ (in .Content "<youtube-video>")
+ (in .Content "<video-container>")
+ }}
+ {{ if eq .Type $author }}
+ {{ $result = true }}
+ {{ $count = add $count 1 }}
+ {{ if le $count 10 }}
+ {{ .Render "summary" }}
+ {{ end }}
+ {{ end }}
+ {{ end }}
+{{ end }}
+
+{{ if not $result }}
+<footer>
+ <code>No media found!</code>
+</footer>
+{{ end }}
diff --git a/themes/default/layouts/partials/meta-thumbnail-path.html b/themes/default/layouts/partials/meta-thumbnail-path.html
new file mode 100644
index 0000000..90aa4c2
--- /dev/null
+++ b/themes/default/layouts/partials/meta-thumbnail-path.html
@@ -0,0 +1 @@
+{{- or .Slug "404" -}}
diff --git a/themes/default/layouts/partials/meta.html b/themes/default/layouts/partials/meta.html
new file mode 100644
index 0000000..6e10754
--- /dev/null
+++ b/themes/default/layouts/partials/meta.html
@@ -0,0 +1,64 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+{{ $source := print $author.user "/messages/" .File.LogicalName }}
+
+<micro-thumbnail hidden>
+ {{ partial "profile-picture.html" . }}
+</micro-thumbnail>
+
+<section>
+ {{ if or .Weight .Params.Liked }}
+ <anchored-entry>
+
+ {{ if .Weight }}
+ <anchored-pinned>
+ {{ safeHTML (readFile "static/icons/tabler/pinned.svg") }}
+ </anchored-pinned>
+ <span>Pinned</span>
+ {{ end }}
+
+ {{ if .Params.Liked }}
+ <anchored-liked>
+ {{ safeHTML (readFile "static/icons/feather/heart.svg") }}
+ </anchored-liked>
+ <span>Liked</span>
+ {{ end }}
+
+ </anchored-entry>
+ {{ end }}
+
+ <b>{{ partial "author-name.html" . }}</b>
+
+ &middot; {{ partial "handle.html" . }}
+
+ {{ if .Draft }}
+ &middot; <draft-label><em>Draft</em></draft-label>
+ {{ end }}
+
+ &middot; {{- partial "read-time.html" . -}}
+
+ &middot; {{- partial "word-count.html" . -}}
+
+ {{ if .Params.Unlisted }}
+ &middot; <unlisted-entry title="This message is off the record">
+ {{ safeHTML (readFile "static/icons/feather/eye-off.svg") }}
+ <span>unlisted</span></unlisted-entry>
+ {{ end }}
+
+ &middot; <a href="/{{ $author.user }}/messages/{{ path.Base .Permalink }}#{{ partial "card-id.html" . }}">
+ <time
+ datetime="{{ .Date | time.Format "2006-01-02T15:04:05Z" }}"
+ title="{{ .Date | time.Format "Monday, January 2 2006 at 15:04:05 MST" }}">
+ {{ .Date | time.Format "Jan 2 2006" }}
+ </time>
+ </a>
+
+ {{- if .ExpiryDate -}}
+ &middot; {{- partial "expiry-date.html" . -}}
+ {{- end -}}
+
+ &middot; <cite><a title="#{{ partial "card-id.html" . }}" href="#{{ partial "card-id.html" . }}">#id</a></cite>
+
+ {{ if not .ExpiryDate }}
+ &middot; <cite><a title="/raw/{{ $source }}" href="/raw/{{ $source }}">raw</a></cite>
+ {{- end -}}
+</section>
diff --git a/themes/default/layouts/partials/navigator-left.html b/themes/default/layouts/partials/navigator-left.html
new file mode 100644
index 0000000..0058b27
--- /dev/null
+++ b/themes/default/layouts/partials/navigator-left.html
@@ -0,0 +1,17 @@
+{{ if .Site.Menus.main }}
+<nav>
+ <ul>
+ {{ range .Site.Menus.main }}
+ <li>
+ <a id="nav-left-{{ path.Base .Identifier }}" href="{{ .URL }}">
+ <span>{{ .Name }}</span>
+ {{ with .Identifier }}
+ {{ $icon := print "static/icons/" . ".svg" }}
+ {{ safeHTML (readFile $icon) }}
+ {{ end }}
+ </a>
+ </li>
+ {{ end }}
+ </ul>
+</nav>
+{{ end }}
diff --git a/themes/default/layouts/partials/navigator-middle-walk.html b/themes/default/layouts/partials/navigator-middle-walk.html
new file mode 100644
index 0000000..4742822
--- /dev/null
+++ b/themes/default/layouts/partials/navigator-middle-walk.html
@@ -0,0 +1,15 @@
+{{ if .Context.Site.Menus.main }}
+ {{ range .Context.Site.Menus.main }}
+ <icon-navigator hidden>
+ <icon-button>
+ <a id="nav-middle-{{ path.Base .Identifier }}" href="{{ .URL }}">
+ {{ with .Identifier }}
+ {{ $icon := print "static/icons/" . ".svg" }}
+ {{ safeHTML (readFile $icon) }}
+ {{ end }}
+ <small>{{ delimit (first 1 (split .Name " ")) " " }}</small>
+ </a>
+ </icon-button>
+ </icon-navigator>
+ {{ end }}
+{{ end }}
diff --git a/themes/default/layouts/partials/navigator-middle.html b/themes/default/layouts/partials/navigator-middle.html
new file mode 100644
index 0000000..acbd2f1
--- /dev/null
+++ b/themes/default/layouts/partials/navigator-middle.html
@@ -0,0 +1,58 @@
+{{ $id := .Id }}
+{{ $href := .Href }}
+
+<nav>
+ <icon-button>
+ <a
+ id="{{ or $id "top" }}"
+ href="{{ or $href "#" }}"
+ >
+ {{ with .Icon }}
+ {{ safeHTML (readFile (print "static/icons/feather/" . ".svg")) }}
+ {{ else }}
+ {{ safeHTML (readFile "static/icons/feather/arrow-up.svg") }}
+ {{ end }}
+ {{ with .IconLabel }}
+ <small>{{ . }}</small>
+ {{ else }}
+ <small>Top</small>
+ {{ end }}
+ </a>
+ </icon-button>
+
+ <section>
+ {{ with .Title }}
+ <h1>{{ . }}</h1>
+ {{ end }}
+ {{ with .Subtitle }}
+ <small>{{ . }}</small>
+ {{ end }}
+ </section>
+
+ {{ partial "navigator-middle-walk.html" . }}
+
+ <search-entry>
+ <form
+ id="search-form"
+ action="https://lite.duckduckgo.com/lite/">
+ <input required
+ id="search-input"
+ name="q"
+ value=""
+ type="search"
+ autocomplete="off"
+ spellcheck="false"
+ role="searchbox"
+ aria-autocomplete="list"
+ aria-label="search input"
+ />
+ <button id="search-submit">
+ {{ safeHTML (readFile "static/icons/feather/search.svg") }}
+ <small>Search</small>
+ </button>
+ <ul hidden id="search-results"></ul>
+ </form>
+ </search-entry>
+ </nav>
+
+<navigation-separator><hr hidden></navigation-separator>
diff --git a/themes/default/layouts/partials/navigator-right-default.html b/themes/default/layouts/partials/navigator-right-default.html
new file mode 100644
index 0000000..0b57b8a
--- /dev/null
+++ b/themes/default/layouts/partials/navigator-right-default.html
@@ -0,0 +1,4 @@
+{{ range first 1 (where .Site.Pages "Params.Author" .Site.Author.default.webring) }}
+ {{- partial "author-list.html" . -}}
+ {{- partial "web-ring.html" . -}}
+{{ end }}
diff --git a/themes/default/layouts/partials/navigator-right.html b/themes/default/layouts/partials/navigator-right.html
new file mode 100644
index 0000000..4cbcfac
--- /dev/null
+++ b/themes/default/layouts/partials/navigator-right.html
@@ -0,0 +1,3 @@
+{{- partial "gallery-images.html" . -}}
+{{- partial "author-list.html" . -}}
+{{- partial "web-ring.html" . -}}
diff --git a/themes/default/layouts/partials/pagination.html b/themes/default/layouts/partials/pagination.html
new file mode 100644
index 0000000..4c1669e
--- /dev/null
+++ b/themes/default/layouts/partials/pagination.html
@@ -0,0 +1,83 @@
+<paginator-navigation>
+ {{- with .Paginator -}}
+
+ {{ if and .HasPrev .First }}
+ <a
+ href="{{ .First.URL }}"
+ aria-label="First"
+ title="First"
+ role="button">
+ First
+ </a>
+ {{ else }}
+ <button
+ disabled
+ title="Disabled"
+ aria-label="First">
+ First
+ </button>
+ {{- end -}}
+
+ {{ with .Prev }}
+ <a
+ href="{{ .URL }}"
+ aria-label="Back"
+ title="Back"
+ role="button">
+ {{ safeHTML (readFile "static/icons/feather/arrow-left.svg") }}
+ Back
+ </a>
+ {{ else }}
+ <button
+ disabled
+ title="Disabled"
+ aria-label="Back">
+ {{ safeHTML (readFile "static/icons/feather/arrow-left.svg") }}
+ Back
+ </button>
+ {{- end -}}
+
+ {{
+ $hidden := (sub .TotalNumberOfElements
+ (sub (len $.Site.RegularPages) (len (where $.Site.Pages "Type" "page"))))
+ }}
+
+
+ {{ if .Next }}
+ <a
+ href="{{ .Next.URL }}"
+ aria-label="Next"
+ title="Next"
+ role="button">
+ Next
+ {{ safeHTML (readFile "static/icons/feather/arrow-right.svg") }}
+ </a>
+ {{ else }}
+ <button
+ disabled
+ title="Disabled"
+ aria-label="Next">
+ Next
+ {{ safeHTML (readFile "static/icons/feather/arrow-right.svg") }}
+ </button>
+ {{- end -}}
+
+
+ {{ if and .HasNext .Last }}
+ <a
+ href="{{ .Last.URL }}"
+ aria-label="Last"
+ title="Next"
+ role="button">
+ Last
+ </a>
+ {{ else }}
+ <button
+ disabled
+ title="Disabled"
+ aria-label="Last">
+ Last
+ </button>
+ {{- end -}}
+ {{ end }}
+</paginator-navigation>
diff --git a/themes/default/layouts/partials/profile-picture.html b/themes/default/layouts/partials/profile-picture.html
new file mode 100644
index 0000000..fba35f7
--- /dev/null
+++ b/themes/default/layouts/partials/profile-picture.html
@@ -0,0 +1,9 @@
+<figure>
+ <picture>
+ <source srcset="/{{ partial "author-media-path.html" . }}/profile/picture.png" type="image/png" />
+ <source srcset="/{{ partial "author-media-path.html" . }}/profile/picture.jpg" type="image/jpeg" />
+ <source srcset="/{{ partial "author-media-path.html" . }}/profile/picture.webp" type="image/webp" />
+ <img alt="{{ .Title }}" src="/{{ partial "author-media-path.html" . }}/profile/picture.png" />
+ </picture>
+ {{ partial "context-profile.html" . }}
+</figure>
diff --git a/themes/default/layouts/partials/profile-tabs.html b/themes/default/layouts/partials/profile-tabs.html
new file mode 100644
index 0000000..94b80ff
--- /dev/null
+++ b/themes/default/layouts/partials/profile-tabs.html
@@ -0,0 +1,8 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+
+<tab-list>
+ <a href="/{{ $author.user }}">Messages</a>
+ <a href="/{{ $author.user }}/feeds/">Feeds</a>
+ <a href="/{{ $author.user }}/media/">Media</a>
+ <a href="/{{ $author.user }}/likes/">Likes</a>
+</tab-list>
diff --git a/themes/default/layouts/partials/profile.html b/themes/default/layouts/partials/profile.html
new file mode 100644
index 0000000..b20159f
--- /dev/null
+++ b/themes/default/layouts/partials/profile.html
@@ -0,0 +1,52 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+
+<micro-header>
+
+ <micro-header-image>
+ <picture>
+ <img alt="{{ .Title }}" src="/{{ partial "author-media-path.html" . }}/profile/header.jpg" />
+ </picture>
+ </micro-header-image>
+
+ <micro-header-picture>
+ <picture>
+ <img src="/{{ partial "author-media-path.html" . }}/profile/picture.png"/>
+ </picture>
+ </micro-header-picture>
+
+ <section>
+ <micro-header-title>
+ <h1>{{ $author.name }}</h1>
+ <h2>@{{ $author.user }}</h2>
+ </micro-header-title>
+ <micro-header-description>
+ <p>
+ {{ $author.description | markdownify }}
+ </p>
+ </micro-header-description>
+ </section>
+
+ <footer>
+ <p>
+ {{ safeHTML (readFile "static/icons/feather/map-pin.svg") }}
+ <span>{{- $author.place | markdownify -}}</span>
+ </p>
+ <p>
+ {{ safeHTML (readFile "static/icons/feather/globe.svg") }}
+ <a href="{{ $author.domain.url }}">
+ {{- $author.domain.host | markdownify -}}
+ </a>
+ </p>
+ <p>
+ {{ safeHTML (readFile "static/icons/feather/clock.svg") }}
+ <span>{{- $author.epoch | markdownify -}}</span>
+ </p>
+ <p>
+ {{ safeHTML (readFile "static/icons/feather/user.svg") }}
+ <span><b>{{ with $author.webring.rss }}{{ len . }}{{ else }} 0 {{ end }}</b> Following</span>
+ </p>
+ </footer>
+
+</micro-header>
+
+{{ partial "profile-tabs.html" . }}
diff --git a/themes/default/layouts/partials/read-time.html b/themes/default/layouts/partials/read-time.html
new file mode 100644
index 0000000..5b6ca62
--- /dev/null
+++ b/themes/default/layouts/partials/read-time.html
@@ -0,0 +1,6 @@
+{{ $seconds := mul (div .WordCount 180.0) 60.0 }}
+{{ $format := printf "%.0f" $seconds | strings.TrimRight "0" | strings.TrimRight "." }}
+
+<read-time title="Takes {{ $format }} seconds to read">
+ {{ $format }} sec
+</read-time>
diff --git a/themes/default/layouts/partials/site-description.html b/themes/default/layouts/partials/site-description.html
new file mode 100644
index 0000000..8d021de
--- /dev/null
+++ b/themes/default/layouts/partials/site-description.html
@@ -0,0 +1,8 @@
+{{- if .Params.summary -}}
+ {{ .Params.summary }}
+{{- else if .Summary -}}
+ {{ .Summary }}
+{{- else -}}
+ {{- block "description" . -}}{{- end -}}
+ {{- partial "site-title.html" . -}}
+{{- end -}}
diff --git a/themes/default/layouts/partials/site-title.html b/themes/default/layouts/partials/site-title.html
new file mode 100644
index 0000000..ed09ec3
--- /dev/null
+++ b/themes/default/layouts/partials/site-title.html
@@ -0,0 +1,15 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+
+{{- if not (eq .Title $.Site.Title) -}}
+ {{- with or .Title .Summary -}}
+ {{ . }} &mdash;
+ {{ end }}
+{{- end -}}
+
+{{- with $.Site.Title -}}
+ {{ if $.IsHome -}}
+ {{ . }}
+ {{- else -}}
+ {{ $author.blogtitle }}
+ {{- end -}}
+{{- end -}}
diff --git a/themes/default/layouts/partials/styles-navigator.html b/themes/default/layouts/partials/styles-navigator.html
new file mode 100644
index 0000000..725f8c4
--- /dev/null
+++ b/themes/default/layouts/partials/styles-navigator.html
@@ -0,0 +1,14 @@
+icon-navigator a[href="{{ .Link }}"],
+column-left nav a[href="{{ .Link }}"] {
+ font-weight: 700;
+}
+
+column-left nav a[href="{{ .Link }}"] {
+ background-color: #f0f6ff;
+ background-color: var(--hover-alternate);
+}
+
+icon-navigator a[href="{{ .Link }}"] svg {
+ fill: #e5f0ff;
+ fill: var(--active-alternate);
+}
diff --git a/themes/default/layouts/partials/tags.html b/themes/default/layouts/partials/tags.html
new file mode 100644
index 0000000..d601b27
--- /dev/null
+++ b/themes/default/layouts/partials/tags.html
@@ -0,0 +1,5 @@
+{{- with .Params.tags }}
+ {{- range $tags, $tag := sort . }}
+ <a href="{{ "tags/" | relURL }}{{ $tag | urlize }}/">#{{ $tag }}</a>
+ {{ end }}
+{{ end }}
diff --git a/themes/default/layouts/partials/title.html b/themes/default/layouts/partials/title.html
new file mode 100644
index 0000000..988167f
--- /dev/null
+++ b/themes/default/layouts/partials/title.html
@@ -0,0 +1 @@
+{{- (or .Title .Summary) | markdownify -}}
diff --git a/themes/default/layouts/partials/web-ring.html b/themes/default/layouts/partials/web-ring.html
new file mode 100644
index 0000000..4ddfa14
--- /dev/null
+++ b/themes/default/layouts/partials/web-ring.html
@@ -0,0 +1,138 @@
+{{- $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") -}}
+
+{{- if $author.webring.rss -}}
+
+ {{ $feeds := slice }}
+
+ {{ range first 5 $author.webring.rss -}}
+
+ {{ with resources.GetRemote . }}
+ {{- with .Err -}}
+ {{- warnf "%s" . -}}
+ {{- end -}}
+ {{ end }}
+
+ {{ with resources.GetRemote . | transform.Unmarshal -}}
+
+ {{- $title := "Example Domain" -}}
+ {{- $link := "https://example.com/link" -}}
+ {{- $description := "This domain is for use in illustrative examples in documents." -}}
+ {{- $content := "This content is for use in illustrative examples in documents." -}}
+ {{- $sourceTitle := "From Example Domain" -}}
+ {{- $sourceLink := "https://example.com/" -}}
+ {{- $sourceDescription := "The example domain." -}}
+ {{- $date := now.Format "January 2, 2006" -}}
+ {{- $dateTime := now.Format "2006-01-02T15:04:05Z" -}}
+ {{- $dateTitle := now.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+
+ {{ with .channel -}}
+
+ {{ if reflect.IsMap (index .link 0) -}}
+ {{ $sourceLink = index (index .link 0) "-href" | plainify | htmlUnescape -}}
+ {{ else -}}
+ {{ $sourceLink = index .link 0 | plainify | htmlUnescape -}}
+ {{ end -}}
+
+ {{ $sourceDescription = .description | plainify | htmlUnescape -}}
+
+ {{ $sourceTitle = .title | plainify | htmlUnescape -}}
+
+ {{ range first 1 .item -}}
+ {{ $description = .description | plainify | htmlUnescape | truncate 50 "..." -}}
+ {{ $content = .description | plainify | htmlUnescape -}}
+ {{ $title = .title | plainify | htmlUnescape -}}
+ {{ $link = .link | plainify | htmlUnescape -}}
+ {{ $date = .pubDate | plainify | htmlUnescape | time.Format "January 2, 2006" -}}
+ {{ $dateTime = .pubDate | plainify | htmlUnescape | time.Format "2006-01-02T15:04:05Z" -}}
+ {{ $dateTitle = .pubDate | plainify | htmlUnescape | time.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+ {{ end -}}
+
+ {{ else -}}
+
+ {{ $sourceLink = index (index .link 0) "-href" | plainify | htmlUnescape -}}
+ {{ $sourceTitle = .title | plainify | htmlUnescape -}}
+ {{ $sourceDescription = .subtitle | plainify | htmlUnescape -}}
+
+ {{ range first 1 .entry -}}
+
+ {{ if reflect.IsMap .title -}}
+ {{ $title = index .title "#text" | plainify | htmlUnescape -}}
+ {{ else -}}
+ {{ $title = .title | plainify | htmlUnescape -}}
+ {{ end -}}
+
+ {{ $date = .updated | plainify | htmlUnescape | time.Format "January 2, 2006" -}}
+ {{ $dateTime = .updated | plainify | htmlUnescape | time.Format "2006-01-02T15:04:05Z" -}}
+ {{ $dateTitle = .updated | plainify | htmlUnescape | time.Format "Monday, January 2 2006 at 15:04:05 MST" -}}
+
+ {{ $link = .id -}}
+ {{ with .content -}}
+ {{ $description = (index . "#text") | plainify | htmlUnescape | truncate 50 "..." -}}
+ {{ $content = (index . "#text") | plainify | htmlUnescape -}}
+ {{ end -}}
+ {{ with .summary -}}
+ {{ $description = (index . "#text") | plainify | htmlUnescape | truncate 50 "..." -}}
+ {{ $content = (index . "#text") | plainify | htmlUnescape -}}
+ {{ end -}}
+ {{ end -}}
+
+ {{ end -}}
+
+ {{
+ $feeds = $feeds | append (dict
+ "FeedTitle" $title
+ "FeedLink" $link
+ "FeedDescription" $description
+ "FeedContent" $content
+ "FeedSourceTitle" $sourceTitle
+ "FeedSourceLink" $sourceLink
+ "FeedSourceDescription" $sourceDescription
+ "FeedDate" $date
+ "FeedDateTime" $dateTime
+ "FeedDateTitle" $dateTitle
+ )
+ }}
+
+ {{ end }}
+ {{ end }}
+
+ <web-ring>
+ <h1>Web Ring</h1>
+ <section>
+ {{ range sort $feeds "FeedDateTime" "desc" }}
+ <web-ring-item>
+ <header>
+ <a
+ rel="noopener"
+ target="_blank"
+ title="{{ .FeedLink }}"
+ href="{{ .FeedLink }}">
+ {{ .FeedTitle }}
+ </a>
+ </header>
+
+ <p>
+ <time
+ title="{{ .FeedDateTitle }}"
+ datetime="{{ .FeedDateTime }}">
+ {{ .FeedDate }}
+ </time>
+ {{- with .FeedDescription -}}
+ {{ "--" | markdownify }}
+ {{ . }}
+ {{- end }}
+ </p>
+
+ <a
+ rel="noopener"
+ target="_blank"
+ title="{{ .FeedSourceDescription }}"
+ href="{{ .FeedSourceLink }}">
+ {{ .FeedSourceTitle }}
+ </a>
+ </web-ring-item>
+ {{ end }}
+ </section>
+ </web-ring>
+
+{{ end -}}
diff --git a/themes/default/layouts/partials/word-count.html b/themes/default/layouts/partials/word-count.html
new file mode 100644
index 0000000..f566b07
--- /dev/null
+++ b/themes/default/layouts/partials/word-count.html
@@ -0,0 +1,14 @@
+{{ $author := index .Site.Data.authors ((or .Params.author .Site.Author.default.user) | default "default") }}
+{{ $limit := $author.wordlimit }}
+{{ $wordLimit := gt .WordCount $limit }}
+{{ $overLimit := sub .WordCount $limit }}
+
+<word-limit title="{{- if $wordLimit -}}{{ $overLimit }} words over the limit{{ else }} {{ .WordCount }} words {{ end }}">
+ <word-count>
+ {{- if $wordLimit -}}
+ <em>{{ .WordCount }}</em>
+ {{- else -}}
+ {{ .WordCount }}
+ {{- end -}}
+ </word-count>/{{ $limit }} words
+</word-limit>
diff --git a/themes/default/layouts/shortcodes/abbr.html b/themes/default/layouts/shortcodes/abbr.html
new file mode 100644
index 0000000..d661bd5
--- /dev/null
+++ b/themes/default/layouts/shortcodes/abbr.html
@@ -0,0 +1,4 @@
+{{- $abbr := default "TL;DR:" (.Get "abbr" | default (.Get 0)) -}}
+{{- $title := default "Too long, didn't read" (.Get "title" | default (.Get 1)) -}}
+
+<abbr title="{{ $title }}">{{ $abbr | markdownify }}</abbr>
diff --git a/themes/default/layouts/shortcodes/asciicast.html b/themes/default/layouts/shortcodes/asciicast.html
new file mode 100644
index 0000000..10da091
--- /dev/null
+++ b/themes/default/layouts/shortcodes/asciicast.html
@@ -0,0 +1,17 @@
+{{- $id := default "" (.Get "id" | default (.Get 0)) -}}
+{{- $title := default "" (.Get "title" | default (.Get 1)) -}}
+{{- $theme := default "" (.Get "theme" | default (.Get 2)) -}}
+{{- $speed := default "" (.Get "speed" | default (.Get 3)) -}}
+{{- $preload := default "" (.Get "preload" | default (.Get 4)) -}}
+{{- $autoplay := default "" (.Get "autoplay" | default (.Get 5)) -}}
+{{- $startat := default "" (.Get "startat" | default (.Get 6)) -}}
+
+<ascii-cast>
+ <script
+ src="https://asciinema.org/a/14.js"
+ id="asciicast-14"
+ async
+ data-autoplay="false"
+ data-size="big"
+ ></script>
+</ascii-cast>
diff --git a/themes/default/layouts/shortcodes/audio.html b/themes/default/layouts/shortcodes/audio.html
new file mode 100644
index 0000000..4df2f51
--- /dev/null
+++ b/themes/default/layouts/shortcodes/audio.html
@@ -0,0 +1,18 @@
+{{- $src := default
+ "https://ia600701.us.archive.org/12/items/firstchaptercollection001_1109_librivox/firstchapter001_02_taletwocities_dickens_law.mp3"
+ (.Get "src" | default (.Get 0))
+-}}
+{{- $caption := default "A Tale of Two Cities by Charles Dickens" (.Get "caption" | default (.Get 1)) -}}
+{{- $controls := default "" (.Get "controls" | default (.Get 2)) -}}
+
+<audio-player>
+ <figure>
+ <figcaption>{{ $caption }}</figcaption>
+ <audio controls {{ $controls | safeHTMLAttr }} src="{{ $src }}">
+ <p>
+ Your browser doesn't support HTML5 <code>audio</code>. Here is a
+ <a href="{{ $src }}">link to the audio file</a> instead.
+ </p>
+ </audio>
+ </figure>
+</audio-player>
diff --git a/themes/default/layouts/shortcodes/gist.html b/themes/default/layouts/shortcodes/gist.html
new file mode 100644
index 0000000..c475672
--- /dev/null
+++ b/themes/default/layouts/shortcodes/gist.html
@@ -0,0 +1,9 @@
+{{- $user := default "gdb" (.Get "user" | default (.Get 0)) -}}
+{{- $gist := default "b6365e79be6052e7531e7ba6ea8caf23" (.Get "gist" | default (.Get 1)) -}}
+{{- $file := default "" (.Get "file" | default (.Get 2)) -}}
+
+<github-gist>
+ <script
+ src="https://gist.github.com/{{ $user }}/{{ $gist }}.js?file={{ $file }}">
+ </script>
+</github-gist>
diff --git a/themes/default/layouts/shortcodes/iframe.html b/themes/default/layouts/shortcodes/iframe.html
new file mode 100644
index 0000000..cd72763
--- /dev/null
+++ b/themes/default/layouts/shortcodes/iframe.html
@@ -0,0 +1,13 @@
+{{- $url := default "https://example.org/" (.Get "url" | default (.Get 0)) -}}
+{{- $title := default "Content Embed" (.Get "title" | default (.Get 1)) -}}
+{{- $caption := default $title (.Get "caption" | default (.Get 2)) -}}
+{{- $sandbox := default "" (.Get "sandbox" | default (.Get 3)) -}}
+
+<iframe-embed>
+ <iframe
+ sandbox="{{ $sandbox }}"
+ src="{{ $url }}"
+ title="{{ $title }}"
+ ></iframe>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</iframe-embed>
diff --git a/themes/default/layouts/shortcodes/imgur-video.html b/themes/default/layouts/shortcodes/imgur-video.html
new file mode 100644
index 0000000..e6fabdd
--- /dev/null
+++ b/themes/default/layouts/shortcodes/imgur-video.html
@@ -0,0 +1,20 @@
+{{- $id := default "uVrh84e" (.Get "id" | default (.Get 0)) -}}
+{{- $title := default "Imgur Gif" (.Get "title" | default (.Get 1)) -}}
+
+<imgur-video>
+ <blockquote
+ class="imgur-embed-pub"
+ lang="en"
+ data-id="{{ $id }}"
+ data-context="false">
+ <a href="//imgur.com/{{ $id }}"></a>
+ </blockquote>
+ <script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>
+ <noscript>
+ <a href="//imgur.com/{{ $id }}">imgur.com/{{ $id }}</a>
+ </noscript>
+ <figcaption>
+ {{ $title | markdownify }}:
+ {{ print "[Link](https://imgur.com/" $id ")" | markdownify }}
+ </figcaption>
+</imgur-video>
diff --git a/themes/default/layouts/shortcodes/imgur.html b/themes/default/layouts/shortcodes/imgur.html
new file mode 100644
index 0000000..f51bcef
--- /dev/null
+++ b/themes/default/layouts/shortcodes/imgur.html
@@ -0,0 +1,13 @@
+{{- $id := default "mkVcxUi" (.Get "id" | default (.Get 0)) -}}
+
+<imgur-image>
+ <blockquote
+ class="imgur-embed-pub"
+ lang="en"
+ data-id="a/{{ $id }}"
+ data-context="false"
+ >
+ <a href="//imgur.com/a/{{ $id }}"></a>
+ </blockquote>
+ <script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>
+</imgur-image>
diff --git a/themes/default/layouts/shortcodes/ins.html b/themes/default/layouts/shortcodes/ins.html
new file mode 100644
index 0000000..3c68f1f
--- /dev/null
+++ b/themes/default/layouts/shortcodes/ins.html
@@ -0,0 +1,3 @@
+{{- $text := default "inserted text" (.Get "text" | default (.Get 0)) -}}
+
+<ins title="{{ $text }}">{{ $text | markdownify }}</ins>
diff --git a/themes/default/layouts/shortcodes/kbd.html b/themes/default/layouts/shortcodes/kbd.html
new file mode 100644
index 0000000..b24fd9d
--- /dev/null
+++ b/themes/default/layouts/shortcodes/kbd.html
@@ -0,0 +1,3 @@
+{{- $key := default "Ctrl" (.Get "key" | default (.Get 0)) -}}
+
+<kbd title="{{ $key }}">{{ $key | markdownify }}</kbd>
diff --git a/themes/default/layouts/shortcodes/mark.html b/themes/default/layouts/shortcodes/mark.html
new file mode 100644
index 0000000..02af225
--- /dev/null
+++ b/themes/default/layouts/shortcodes/mark.html
@@ -0,0 +1,5 @@
+{{- $text := default "highligted text" (.Get "text" | default (.Get 0)) -}}
+{{- $title := default $text (.Get "title" | default (.Get 1)) -}}
+
+<mark title="{{ $title }}">{{ $text }}</mark>
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/shortcodes/odysee.html b/themes/default/layouts/shortcodes/odysee.html
new file mode 100644
index 0000000..91f03c0
--- /dev/null
+++ b/themes/default/layouts/shortcodes/odysee.html
@@ -0,0 +1,14 @@
+{{- $url := default "https://odysee.com/$/embed/odysee/7a416c44a6888d94fe045241bbac055c726332aa" (.Get "url" | default (.Get 0)) -}}
+{{- $title := default "Odysee Video" (.Get "title" | default (.Get 1)) -}}
+{{- $caption := default (print $title ": [Link](" $url ")") (.Get "caption" | default (.Get 2)) -}}
+
+<odysee-video>
+ <iframe
+ sandbox="allow-scripts allow-same-origin allow-presentation"
+ id="odysee-iframe"
+ src="{{ $url }}"
+ title="{{ $title }}"
+ allowfullscreen>
+ </iframe>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</odysee-video>
diff --git a/themes/default/layouts/shortcodes/pdf.html b/themes/default/layouts/shortcodes/pdf.html
new file mode 100644
index 0000000..f766190
--- /dev/null
+++ b/themes/default/layouts/shortcodes/pdf.html
@@ -0,0 +1,11 @@
+{{- $url := default "https://www.gutenberg.org/files/98/old/2city12p.pdf" (.Get "url" | default (.Get 0)) -}}
+{{- $title := default "PDF Document" (.Get "title" | default (.Get 1)) -}}
+{{- $caption := default (print $title ": [Download](" $url ")") (.Get "caption" | default (.Get 2)) -}}
+
+<pdf-container title="{{ $title }}">
+ <iframe loading="lazy"
+ sandbox="allow-scripts allow-presentation allow-same-origin"
+ src="https://docs.google.com/gview?url={{ $url }}&embedded=true">
+ </iframe>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</pdf-container>
diff --git a/themes/default/layouts/shortcodes/quote.html b/themes/default/layouts/shortcodes/quote.html
new file mode 100644
index 0000000..b933286
--- /dev/null
+++ b/themes/default/layouts/shortcodes/quote.html
@@ -0,0 +1,13 @@
+{{- $text := default "Speech is silver, silence is golden" (.Get "text" | default (.Get 0)) -}}
+{{- $cite := default "" (.Get "cite" | default (.Get 1)) -}}
+
+<q cite="$cite" title="{{ $text | markdownify }}">
+ {{- if $cite -}}
+ <a rel="noopener" href="{{ $cite }}" target="_blank">
+ {{- end -}}
+ {{ $text | markdownify }}
+ {{- if $cite -}}
+ </a>
+ {{- end -}}
+</q>
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/shortcodes/reddit.html b/themes/default/layouts/shortcodes/reddit.html
new file mode 100644
index 0000000..10ef0b9
--- /dev/null
+++ b/themes/default/layouts/shortcodes/reddit.html
@@ -0,0 +1,29 @@
+{{- $path := default "linux/comments/aeufh6/vlc_has_now_reached_3_billions_downloads_and/edsvibz" (.Get "path" | default (.Get 0)) -}}
+{{- $depth := default "2" (.Get "depth" | default (.Get 1)) -}}
+{{- $context := default "1" (.Get "context" | default (.Get 2)) -}}
+{{- $theme := default "light" (.Get "theme" | default (.Get 3)) -}}
+{{- $showtitle := default "true" (.Get "showtitle" | default (.Get 4)) -}}
+{{- $showedits := default "false" (.Get "showedits" | default (.Get 5)) -}}
+{{- $showmedia := default "false" (.Get "showmedia" | default (.Get 6)) -}}
+{{- $showmore := default "false" (.Get "showmore" | default (.Get 7)) -}}
+
+<reddit-comment>
+ <iframe loading="lazy"
+ id="reddit-embed"
+ src="https://www.redditmedia.com/r/{{ trim $path "/" }}/
+?depth={{ $depth }}
+&amp;embed=true
+&amp;showmore={{ $showmore }}
+&amp;showtitle={{ $showtitle }}
+&amp;context={{ $context }}
+&amp;showmedia={{ $showmedia }}
+&amp;theme={{ $theme }}
+&amp;showedits={{ $showedits }}
+&amp;created={{ now.Format "2006-01-02T15:04:05Z" }}"
+ sandbox="allow-scripts allow-same-origin allow-popups"
+ style="border: none;"
+ height="452"
+ width="640"
+ scrolling="no"
+ ></iframe>
+</reddit-comment>
diff --git a/themes/default/layouts/shortcodes/self.html b/themes/default/layouts/shortcodes/self.html
new file mode 100644
index 0000000..3b620b7
--- /dev/null
+++ b/themes/default/layouts/shortcodes/self.html
@@ -0,0 +1,13 @@
+{{- $url := default "https://example.org/" (.Get "url" | default (.Get 0)) -}}
+{{- $title := default "Self Embed" (.Get "title" | default (.Get 1)) -}}
+{{- $caption := default (print $title ": [Link](" $url ")") (.Get "caption" | default (.Get 2)) -}}
+{{- $sandbox := default "" (.Get "sandbox" | default (.Get 3)) -}}
+
+<self-embed>
+ <iframe
+ sandbox="{{ $sandbox }}"
+ src="{{ $url }}"
+ title="{{ $title }}"
+ ></iframe>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</self-embed>
diff --git a/themes/default/layouts/shortcodes/smallcaps.html b/themes/default/layouts/shortcodes/smallcaps.html
new file mode 100644
index 0000000..8c18621
--- /dev/null
+++ b/themes/default/layouts/shortcodes/smallcaps.html
@@ -0,0 +1,6 @@
+{{- $text := default "Small Caps" (.Get "text" | default (.Get 0)) -}}
+
+<samp title="{{ $text }}">
+ {{- $text | markdownify -}}
+</samp>
+{{- /* This comment removes trailing newlines and white spaces. */ -}}
diff --git a/themes/default/layouts/shortcodes/spoiler.html b/themes/default/layouts/shortcodes/spoiler.html
new file mode 100644
index 0000000..9267876
--- /dev/null
+++ b/themes/default/layouts/shortcodes/spoiler.html
@@ -0,0 +1,9 @@
+{{- $text := default "This is a spoiler." (.Get "text" | default (.Get 0)) -}}
+{{- $title := default "Click to show/hide text." (.Get "title" | default (.Get 1)) -}}
+
+{{ if gt (len .Inner) 1 }}
+ {{ $text = .Inner }}
+{{ end }}
+
+<input hidden type="checkbox" id="spoiler-text-{{ md5 $text }}" />
+<s title="{{ $title }}"><label for="spoiler-text-{{ md5 $text }}">{{ $text | markdownify }}</label></s>
diff --git a/themes/default/layouts/shortcodes/spoiler.rss.xml b/themes/default/layouts/shortcodes/spoiler.rss.xml
new file mode 100644
index 0000000..ad80424
--- /dev/null
+++ b/themes/default/layouts/shortcodes/spoiler.rss.xml
@@ -0,0 +1,4 @@
+{{- $text := default "This is a spoiler." (.Get "text" | default (.Get 0)) -}}
+{{- $title := default "Click to show/hide text." (.Get "title" | default (.Get 1)) -}}
+
+<s title="{{ $title }}">{{ $text | markdownify }}</s>
diff --git a/themes/default/layouts/shortcodes/tweet.html b/themes/default/layouts/shortcodes/tweet.html
new file mode 100644
index 0000000..b4adfba
--- /dev/null
+++ b/themes/default/layouts/shortcodes/tweet.html
@@ -0,0 +1,18 @@
+{{- $user := default "jack" (.Get "user" | default (.Get 0)) -}}
+{{- $status := default "20" (.Get "status" | default (.Get 1)) -}}
+{{- $caption := default "Twitter Tweet" (.Get "caption" | default (.Get 2)) -}}
+
+<twitter-tweet>
+ <blockquote class="twitter-tweet">
+ <a href="https://twitter.com/{{ $user }}/status/{{ $status }}">
+ https://twitter.com/{{ $user }}/status/{{ $status }}
+ </a>
+ </blockquote>
+ <script
+ async
+ src="https://platform.twitter.com/widgets.js"
+ charset="utf-8">
+ </script>
+</twitter-tweet>
+
+<figcaption>{{ $caption }}</figcaption>
diff --git a/themes/default/layouts/shortcodes/video.html b/themes/default/layouts/shortcodes/video.html
new file mode 100644
index 0000000..7c0c95f
--- /dev/null
+++ b/themes/default/layouts/shortcodes/video.html
@@ -0,0 +1,36 @@
+{{-
+ $source := default
+ "https://raw.githubusercontent.com/benhosmer/HTML5-Test-Videos/9a6c2db46472454c634963cbeb0900fd37901fac/big_buck_bunny"
+ (.Get "source" | default (.Get 0))
+-}}
+{{- $poster := default "" (.Get "poster" | default (.Get 1)) -}}
+{{- $width := default "" (.Get "width" | default (.Get 2)) -}}
+{{- $height := default "" (.Get "height" | default (.Get 3)) -}}
+{{- $preload := default "metadata" (.Get "preload" | default (.Get 4)) -}}
+{{- $controls := default "" (.Get "controls" | default (.Get 5)) -}}
+{{- $caption := default "`HTML5` Video" (.Get "caption" | default (.Get 6)) -}}
+{{- $title := default "HTML5 Video" (.Get "title" | default (.Get 7)) -}}
+{{- $start := default "" (.Get "start" | default (.Get 8)) -}}
+{{- $end := default "" (.Get "end" | default (.Get 9)) -}}
+
+<video-container>
+ <video
+ title="{{ $title }}"
+ preload="{{ $preload }}"
+ poster="{{ $poster }}"
+ width="{{ $width }}"
+ height="{{ $height }}"
+ controls {{ $controls | safeHTMLAttr }}>
+ <source src="{{ $source }}.mp4#t={{ $start }},{{ $end }}" type="video/mp4">
+ <source src="{{ $source }}.webm#t={{ $start }},{{ $end }}" type="video/webm">
+ <source src="{{ $source }}.flv#t={{ $start }},{{ $end }}" type="video/flv">
+ <source src="{{ $source }}.ogv#t={{ $start }},{{ $end }}" type="video/ogv">
+ <p>
+ Sorry, your browser doesn't support embedded videos. Here is a
+ <a href="{{ $source }}.mp4#t={{ $start }},{{ $end }}">
+ link to the video
+ </a> instead.
+ </p>
+ </video>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</video-container>
diff --git a/themes/default/layouts/shortcodes/vimeo.html b/themes/default/layouts/shortcodes/vimeo.html
new file mode 100644
index 0000000..009ebb0
--- /dev/null
+++ b/themes/default/layouts/shortcodes/vimeo.html
@@ -0,0 +1,12 @@
+{{- $id := default "1084537" (.Get "id" | default (.Get 0)) -}}
+{{- $title := default "Vimeo Video" (.Get "title" | default (.Get 1)) -}}
+
+<vimeo-video>
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
+ <iframe loading="lazy"
+ src="https://player.vimeo.com/video/{{ $id }}?dnt=1"
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;"
+ title="{{ $title }}" webkitallowfullscreen mozallowfullscreen allowfullscreen>
+ </iframe>
+ </div>
+</vimeo-video>
diff --git a/themes/default/layouts/shortcodes/wikipedia.html b/themes/default/layouts/shortcodes/wikipedia.html
new file mode 100644
index 0000000..7b9e51f
--- /dev/null
+++ b/themes/default/layouts/shortcodes/wikipedia.html
@@ -0,0 +1,16 @@
+{{- $id := default "Knowledge" (.Get "id" | default (.Get 0)) -}}
+{{- $title := default "Wikipedia Article" (.Get "title" | default (.Get 1)) -}}
+{{-
+ $caption := default
+ (print $title ": [Link](" "https://en.m.wikipedia.org/wiki/" $id ")")
+ (.Get "caption" | default (.Get 2))
+-}}
+
+<wikipedia-container title="{{ $title }}">
+ <iframe loading="lazy"
+ src="https://en.m.wikipedia.org/wiki/{{ $id }}"
+ title="{{ $title }}"
+ height="300"
+ ></iframe>
+ <figcaption>{{ $caption | markdownify }}</figcaption>
+</wikipedia-container>
diff --git a/themes/default/layouts/shortcodes/youtube.html b/themes/default/layouts/shortcodes/youtube.html
new file mode 100644
index 0000000..791e3da
--- /dev/null
+++ b/themes/default/layouts/shortcodes/youtube.html
@@ -0,0 +1,29 @@
+{{- $url := "www.youtube-nocookie.com" -}}
+{{- $id := default "aqz-KE-bpKQ" (.Get "id" | default (.Get 0)) -}}
+{{- $title := default "YouTube Video" (.Get "title" | default (.Get 1)) -}}
+{{- $start := default "" (.Get "start" | default (.Get 2)) -}}
+{{- $end := default "" (.Get "end" | default (.Get 3)) -}}
+{{- $muted := default "" (.Get "muted" | default (.Get 4)) -}}
+
+{{- if (and $start $end) -}}
+ {{ $start = print "&start=" $start }}
+ {{ $end = print "&end=" $end }}
+{{- end -}}
+
+{{- if $muted -}}
+ {{ $muted = "&mute=1" }}
+{{- end -}}
+
+<youtube-video>
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
+ <iframe loading="lazy" sandbox="allow-scripts allow-same-origin"
+ src="https://{{ $url }}/embed/{{ $id }}{{ "?controls=1" }}{{ $start }}{{ $end }}{{ $muted }}"
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;"
+ allowfullscreen title="{{ $title }}">
+ </iframe>
+ </div>
+ <figcaption>
+ {{ $title | markdownify }}:
+ {{ print "[Link](https://www.youtube.com/watch?v=" $id ")" | markdownify }}
+ </figcaption>
+</youtube-video>
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..10299e9
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "strict": true,
+ "strictBindCallApply": true,
+ "strictFunctionTypes": true,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": true,
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "dom.asynciterable",
+ "deno.ns"
+ ]
+ }
+}