diff --git a/package-lock.json b/package-lock.json
index eb0546d..8204d38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "gallery.badmanners.xyz",
-  "version": "1.7.6",
+  "version": "1.7.7",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "gallery.badmanners.xyz",
-      "version": "1.7.6",
+      "version": "1.7.7",
       "hasInstallScript": true,
       "dependencies": {
         "@astrojs/check": "^0.9.2",
diff --git a/package.json b/package.json
index c187973..ea7cd19 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "gallery.badmanners.xyz",
   "type": "module",
-  "version": "1.7.6",
+  "version": "1.7.7",
   "scripts": {
     "postinstall": "astro sync",
     "dev": "astro dev",
diff --git a/src/components/AgeRestrictedModal.astro b/src/components/AgeRestrictedModal.astro
index 421ce24..f7feefd 100644
--- a/src/components/AgeRestrictedModal.astro
+++ b/src/components/AgeRestrictedModal.astro
@@ -1,10 +1,10 @@
 ---
 import AgeRestrictedScriptInline from "./AgeRestrictedScriptInline.astro";
-import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
+import { IconTriangleExclamation } from "./icons";
 ---
 
 <div
-  style={{display: "none"}}
+  style={{ display: "none" }}
   id="modal-age-restricted"
   class="fixed inset-0 bg-stone-100 dark:bg-stone-900"
   role="dialog"
@@ -14,7 +14,10 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
     <div class="text-bm-500 dark:text-bm-400">
       <IconTriangleExclamation width="3rem" height="3rem" />
     </div>
-    <div id="title-age-restricted" class="pb-3 pt-2 text-3xl font-light text-stone-700 sm:pb-4 sm:pt-2 dark:text-stone-50">
+    <div
+      id="title-age-restricted"
+      class="pb-3 pt-2 text-3xl font-light text-stone-700 sm:pb-4 sm:pt-2 dark:text-stone-50"
+    >
       Age verification
     </div>
     <div
@@ -28,7 +31,7 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
     </p>
     <div class="flex w-full max-w-md flex-col-reverse justify-evenly gap-y-5 px-6 pt-5 sm:max-w-2xl sm:flex-row">
       <button
-        style={{display: "none"}}
+        style={{ display: "none" }}
         data-modal-reject
         id="age-verification-reject"
         class="rounded bg-stone-400 py-3 text-lg text-stone-900 hover:bg-stone-500 hover:text-stone-50 focus:bg-stone-500 focus:text-stone-50 sm:px-9 dark:bg-stone-300 dark:text-stone-900 dark:hover:bg-stone-600 dark:hover:text-stone-50 dark:focus:bg-stone-600 dark:focus:text-stone-50"
@@ -36,7 +39,7 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
         Cancel
       </button>
       <button
-        style={{display: "none"}}
+        style={{ display: "none" }}
         data-modal-accept
         id="age-verification-accept"
         class="rounded bg-bm-500 py-3 text-lg text-stone-900 hover:bg-stone-500 hover:text-stone-50 focus:bg-stone-500 focus:text-stone-50 sm:px-9 dark:bg-bm-400 dark:text-stone-900 dark:hover:bg-stone-600 dark:hover:text-stone-50 dark:focus:bg-stone-600 dark:focus:text-stone-50"
@@ -99,4 +102,4 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
   } else {
     ageRestrictedModalSetup();
   }
-</script>
\ No newline at end of file
+</script>
diff --git a/src/components/MastodonComments.astro b/src/components/MastodonComments.astro
index b9b2c73..3deb3f8 100644
--- a/src/components/MastodonComments.astro
+++ b/src/components/MastodonComments.astro
@@ -1,7 +1,6 @@
 ---
 import type { Lang } from "../i18n";
-import IconFavorites from "./icons/IconFavorites.astro";
-import IconReblogs from "./icons/IconReblogs.astro";
+import { IconStar, IconRetweet } from "./icons";
 
 type Props = {
   lang: Lang;
@@ -112,11 +111,11 @@ const { link, instance, user, postId } = Astro.props;
     <div class="ml-1 flex flex-row pb-2 pt-1">
       <div class="flex" aria-label="Favorites">
         <span data-favorites></span>
-        <IconFavorites width="1.25rem" height="1.25rem" class="ml-2" />
+        <IconStar width="1.25rem" height="1.25rem" class="ml-2" />
       </div>
       <div class="ml-4 flex" aria-label="Reblogs">
         <span data-reblogs></span>
-        <IconReblogs width="1.25rem" height="1.25rem" class="ml-2" />
+        <IconRetweet width="1.25rem" height="1.25rem" class="ml-2" />
       </div>
     </div>
     <div data-comment-thread class="-mb-2" aria-hidden="true" aria-label="Replies"></div>
diff --git a/src/components/UserComponent.astro b/src/components/UserComponent.astro
index aa2a56f..8c61e6d 100644
--- a/src/components/UserComponent.astro
+++ b/src/components/UserComponent.astro
@@ -18,7 +18,12 @@ const link = user.data.preferredLink ? user.data.links[user.data.preferredLink]!
 
 {
   link ? (
-    <a rel={clsx(rel, "nofollow")} href={link} class:list={[className, "h-card u-url p-name text-link underline"]} target="_blank">
+    <a
+      rel={clsx(rel, "nofollow")}
+      href={link}
+      class:list={[className, "h-card u-url p-name text-link underline"]}
+      target="_blank"
+    >
       {username}
     </a>
   ) : (
diff --git a/src/components/icons/IconArrowBack.astro b/src/components/icons/IconArrowBack.astro
index 73775a6..6fe4dc5 100644
--- a/src/components/icons/IconArrowBack.astro
+++ b/src/components/icons/IconArrowBack.astro
@@ -10,6 +10,6 @@ type Props = {
 
 <SVGIcon {...Astro.props} viewBox="0 0 512 512">
   <path
-              d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
-            ></path>
+    d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconArrowUp.astro b/src/components/icons/IconArrowUp.astro
index b301af0..35baa2c 100644
--- a/src/components/icons/IconArrowUp.astro
+++ b/src/components/icons/IconArrowUp.astro
@@ -10,6 +10,6 @@ type Props = {
 
 <SVGIcon {...Astro.props} viewBox="0 0 384 512">
   <path
-              d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
-            ></path>
+    d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconBook.astro b/src/components/icons/IconBook.astro
index f99898a..b25dd45 100644
--- a/src/components/icons/IconBook.astro
+++ b/src/components/icons/IconBook.astro
@@ -9,5 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 448 512">
-  <path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z" />
+  <path
+    d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconChevronLeft.astro b/src/components/icons/IconChevronLeft.astro
index e2e6566..39f26d7 100644
--- a/src/components/icons/IconChevronLeft.astro
+++ b/src/components/icons/IconChevronLeft.astro
@@ -9,5 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 320 512">
-  <path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
+  <path
+    d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconChevronRight.astro b/src/components/icons/IconChevronRight.astro
index b9ea4ab..c2bff64 100644
--- a/src/components/icons/IconChevronRight.astro
+++ b/src/components/icons/IconChevronRight.astro
@@ -9,5 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 320 512">
-  <path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
+  <path
+    d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconCircleInfo.astro b/src/components/icons/IconCircleInfo.astro
new file mode 100644
index 0000000..f68b4fe
--- /dev/null
+++ b/src/components/icons/IconCircleInfo.astro
@@ -0,0 +1,15 @@
+---
+import SVGIcon from "./SVGIcon.astro";
+
+type Props = {
+  width: string;
+  height: string;
+  class?: string;
+};
+---
+
+<SVGIcon {...Astro.props} viewBox="0 0 512 512">
+  <path
+    d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
+  ></path>
+</SVGIcon>
diff --git a/src/components/icons/IconFavorites.astro b/src/components/icons/IconFavorites.astro
deleted file mode 100644
index 55b4e6a..0000000
--- a/src/components/icons/IconFavorites.astro
+++ /dev/null
@@ -1,15 +0,0 @@
----
-import SVGIcon from "./SVGIcon.astro";
-
-type Props = {
-  width: string;
-  height: string;
-  class?: string;
-};
----
-
-<SVGIcon {...Astro.props} viewBox="0 0 576 512">
-  <path
-            d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
-          ></path>
-</SVGIcon>
diff --git a/src/components/icons/IconGamepad.astro b/src/components/icons/IconGamepad.astro
index c54e8c8..2c6b451 100644
--- a/src/components/icons/IconGamepad.astro
+++ b/src/components/icons/IconGamepad.astro
@@ -9,5 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 640 512">
-  <path d="M192 64C86 64 0 150 0 256S86 448 192 448l256 0c106 0 192-86 192-192s-86-192-192-192L192 64zM496 168a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM392 304a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM168 200c0-13.3 10.7-24 24-24s24 10.7 24 24l0 32 32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0 0 32c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-32-32 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l32 0 0-32z" />
+  <path
+    d="M192 64C86 64 0 150 0 256S86 448 192 448l256 0c106 0 192-86 192-192s-86-192-192-192L192 64zM496 168a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM392 304a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM168 200c0-13.3 10.7-24 24-24s24 10.7 24 24l0 32 32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0 0 32c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-32-32 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l32 0 0-32z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconHome.astro b/src/components/icons/IconHome.astro
index c736c7c..edd9982 100644
--- a/src/components/icons/IconHome.astro
+++ b/src/components/icons/IconHome.astro
@@ -10,6 +10,6 @@ type Props = {
 
 <SVGIcon {...Astro.props} viewBox="0 0 576 512">
   <path
-              d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
-            ></path>
+    d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
+  ></path>
 </SVGIcon>
diff --git a/src/components/icons/IconInformation.astro b/src/components/icons/IconInformation.astro
deleted file mode 100644
index 5b93432..0000000
--- a/src/components/icons/IconInformation.astro
+++ /dev/null
@@ -1,15 +0,0 @@
----
-import SVGIcon from "./SVGIcon.astro";
-
-type Props = {
-  width: string;
-  height: string;
-  class?: string;
-};
----
-
-<SVGIcon {...Astro.props} viewBox="0 0 512 512">
-  <path
-              d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
-            ></path>
-</SVGIcon>
diff --git a/src/components/icons/IconMagnifyingGlass.astro b/src/components/icons/IconMagnifyingGlass.astro
index f78e299..51fdb15 100644
--- a/src/components/icons/IconMagnifyingGlass.astro
+++ b/src/components/icons/IconMagnifyingGlass.astro
@@ -9,5 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 512 512">
-  <path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z" />
-</SVGIcon>
\ No newline at end of file
+  <path
+    d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"
+  ></path>
+</SVGIcon>
diff --git a/src/components/icons/IconReblogs.astro b/src/components/icons/IconReblogs.astro
deleted file mode 100644
index 750f730..0000000
--- a/src/components/icons/IconReblogs.astro
+++ /dev/null
@@ -1,15 +0,0 @@
----
-import SVGIcon from "./SVGIcon.astro";
-
-type Props = {
-  width: string;
-  height: string;
-  class?: string;
-};
----
-
-<SVGIcon {...Astro.props} viewBox="0 0 512 512">
-  <path
-            d="M0 224c0 17.7 14.3 32 32 32s32-14.3 32-32c0-53 43-96 96-96H320v32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S320 19.1 320 32V64H160C71.6 64 0 135.6 0 224zm512 64c0-17.7-14.3-32-32-32s-32 14.3-32 32c0 53-43 96-96 96H192V352c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V448H352c88.4 0 160-71.6 160-160z"
-          ></path>
-</SVGIcon>
diff --git a/src/components/icons/IconRetweet.astro b/src/components/icons/IconRetweet.astro
new file mode 100644
index 0000000..b302ded
--- /dev/null
+++ b/src/components/icons/IconRetweet.astro
@@ -0,0 +1,15 @@
+---
+import SVGIcon from "./SVGIcon.astro";
+
+type Props = {
+  width: string;
+  height: string;
+  class?: string;
+};
+---
+
+<SVGIcon {...Astro.props} viewBox="0 0 576 512">
+  <path
+    d="M272 416c17.7 0 32-14.3 32-32s-14.3-32-32-32l-112 0c-17.7 0-32-14.3-32-32l0-128 32 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-64-64c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l32 0 0 128c0 53 43 96 96 96l112 0zM304 96c-17.7 0-32 14.3-32 32s14.3 32 32 32l112 0c17.7 0 32 14.3 32 32l0 128-32 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8l-32 0 0-128c0-53-43-96-96-96L304 96z"
+  ></path>
+</SVGIcon>
diff --git a/src/components/icons/IconStar.astro b/src/components/icons/IconStar.astro
new file mode 100644
index 0000000..48cf511
--- /dev/null
+++ b/src/components/icons/IconStar.astro
@@ -0,0 +1,15 @@
+---
+import SVGIcon from "./SVGIcon.astro";
+
+type Props = {
+  width: string;
+  height: string;
+  class?: string;
+};
+---
+
+<SVGIcon {...Astro.props} viewBox="0 0 576 512">
+  <path
+    d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
+  ></path>
+</SVGIcon>
diff --git a/src/components/icons/IconTags.astro b/src/components/icons/IconTags.astro
index 2bdf27c..8bb11fb 100644
--- a/src/components/icons/IconTags.astro
+++ b/src/components/icons/IconTags.astro
@@ -9,6 +9,7 @@ type Props = {
 ---
 
 <SVGIcon {...Astro.props} viewBox="0 0 512 512">
-  <path d="M345 39.1L472.8 168.4c52.4 53 52.4 138.2 0 191.2L360.8 472.9c-9.3 9.4-24.5 9.5-33.9 .2s-9.5-24.5-.2-33.9L438.6 325.9c33.9-34.3 33.9-89.4 0-123.7L310.9 72.9c-9.3-9.4-9.2-24.6 .2-33.9s24.6-9.2 33.9 .2zM0 229.5L0 80C0 53.5 21.5 32 48 32l149.5 0c17 0 33.3 6.7 45.3 18.7l168 168c25 25 25 65.5 0 90.5L277.3 442.7c-25 25-65.5 25-90.5 0l-168-168C6.7 262.7 0 246.5 0 229.5zM144 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
-  />
-</SVGIcon>
\ No newline at end of file
+  <path
+    d="M345 39.1L472.8 168.4c52.4 53 52.4 138.2 0 191.2L360.8 472.9c-9.3 9.4-24.5 9.5-33.9 .2s-9.5-24.5-.2-33.9L438.6 325.9c33.9-34.3 33.9-89.4 0-123.7L310.9 72.9c-9.3-9.4-9.2-24.6 .2-33.9s24.6-9.2 33.9 .2zM0 229.5L0 80C0 53.5 21.5 32 48 32l149.5 0c17 0 33.3 6.7 45.3 18.7l168 168c25 25 25 65.5 0 90.5L277.3 442.7c-25 25-65.5 25-90.5 0l-168-168C6.7 262.7 0 246.5 0 229.5zM144 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
+  ></path>
+</SVGIcon>
diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts
new file mode 100644
index 0000000..f973712
--- /dev/null
+++ b/src/components/icons/index.ts
@@ -0,0 +1,18 @@
+export { default as IconArrowBack } from "./IconArrowBack.astro";
+export { default as IconArrowUp } from "./IconArrowUp.astro";
+export { default as IconBook } from "./IconBook.astro";
+export { default as IconBriefcase } from "./IconBriefcase.astro";
+export { default as IconChevronLeft } from "./IconChevronLeft.astro";
+export { default as IconChevronRight } from "./IconChevronRight.astro";
+export { default as IconCircleInfo } from "./IconCircleInfo.astro";
+export { default as IconGamepad } from "./IconGamepad.astro";
+export { default as IconHome } from "./IconHome.astro";
+export { default as IconMagnifyingGlass } from "./IconMagnifyingGlass.astro";
+export { default as IconMoon } from "./IconMoon.astro";
+export { default as IconRetweet } from "./IconRetweet.astro";
+export { default as IconSquareRSS } from "./IconSquareRSS.astro";
+export { default as IconStar } from "./IconStar.astro";
+export { default as IconSun } from "./IconSun.astro";
+export { default as IconTags } from "./IconTags.astro";
+export { default as IconTriangleExclamation } from "./IconTriangleExclamation.astro";
+export { default as SVGIcon } from "./SVGIcon.astro";
diff --git a/src/layouts/GalleryLayout.astro b/src/layouts/GalleryLayout.astro
index b665dab..bb2a2bd 100644
--- a/src/layouts/GalleryLayout.astro
+++ b/src/layouts/GalleryLayout.astro
@@ -3,15 +3,17 @@ import { getImage } from "astro:assets";
 import BaseLayout from "./BaseLayout.astro";
 import logoBM from "../assets/images/logo_bm.png";
 import { t } from "../i18n";
-import IconHome from "../components/icons/IconHome.astro";
-import IconBriefcase from "../components/icons/IconBriefcase.astro";
-import IconSquareRSS from "../components/icons/IconSquareRSS.astro";
-import IconSun from "../components/icons/IconSun.astro";
-import IconMoon from "../components/icons/IconMoon.astro";
-import IconMagnifyingGlass from "../components/icons/IconMagnifyingGlass.astro";
-import IconTags from "../components/icons/IconTags.astro";
-import IconGamepad from "../components/icons/IconGamepad.astro";
-import IconBook from "../components/icons/IconBook.astro";
+import {
+  IconHome,
+  IconBriefcase,
+  IconSquareRSS,
+  IconSun,
+  IconMoon,
+  IconMagnifyingGlass,
+  IconTags,
+  IconGamepad,
+  IconBook,
+} from "../components/icons";
 
 type Props = {
   pageTitle: string;
@@ -29,107 +31,85 @@ const isCurrentRoute = (path: string) =>
 <BaseLayout pageTitle={pageTitle}>
   <Fragment slot="head">
     <meta property="og:title" content={pageTitle || "Bad Manners"} />
-    <slot name="head-description" />
     <meta property="og:url" content={Astro.url} />
     <meta property="og:image" content={logo.src} />
     <meta property="og:image:alt" content="Logo for Bad Manners" />
+    <slot name="head" />
   </Fragment>
   <div
     class="flex min-h-screen flex-col bg-stone-200 text-stone-800 md:flex-row dark:bg-stone-800 dark:text-stone-200 print:bg-none"
   >
     <nav
-      class="h-card static mb-4 flex flex-col items-center bg-bm-300 pt-10 pb-10 text-center text-stone-900 shadow-xl md:fixed md:inset-y-0 md:left-0 md:mb-0 md:w-60 md:pt-20 dark:bg-green-900 dark:text-stone-100 print:bg-none print:shadow-none"
+      class="h-card static mb-4 flex flex-col items-center bg-bm-300 pb-10 pt-10 text-center text-stone-900 shadow-xl md:fixed md:inset-y-0 md:left-0 md:mb-0 md:w-60 md:pt-20 dark:bg-green-900 dark:text-stone-100 print:bg-none print:shadow-none"
     >
       <img
         loading="eager"
         src={logo.src}
-        alt="Logo for Bad Manners"
+        alt="A pixelated metal briefcase over a background with a green gradient."
         class="u-logo my-4 w-full max-w-[192px] rounded-sm border-2 border-green-950 shadow-md"
         width={192}
       />
       <span class="p-name my-2 text-2xl font-semibold">Bad Manners</span>
       <ul class="flex flex-col gap-y-2">
         <li>
-          <a
-            class="u-url group text-link"
-            href="https://badmanners.xyz/"
-            data-age-restricted
-            rel="me"
-          >
+          <a class="u-url text-link group" href="https://badmanners.xyz/" data-age-restricted rel="me">
             <IconHome width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Main website</span>
+            <span class="group-hover:underline group-focus:underline">Main website</span>
           </a>
         </li>
         <li>
-          <a
-            class="u-url group text-link"
-            href="/"
-            aria-current={isCurrentRoute("/") ? "page" : undefined}
-          >
+          <a class="u-url text-link group" href="/" aria-current={isCurrentRoute("/") ? "page" : undefined}>
             <IconBriefcase width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Gallery</span>
+            <span class="group-hover:underline group-focus:underline">Gallery</span>
           </a>
         </li>
         <li>
           <a
-            class="u-url group text-link"
+            class="u-url text-link group"
             href="/stories/1"
             aria-current={isCurrentRoute("/stories/1") ? "page" : undefined}
           >
             <IconBook width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Stories</span>
+            <span class="group-hover:underline group-focus:underline">Stories</span>
           </a>
         </li>
         <li>
-          <a
-            class="u-url group text-link"
-            href="/games"
-            aria-current={isCurrentRoute("/games") ? "page" : undefined}
-          >
+          <a class="u-url text-link group" href="/games" aria-current={isCurrentRoute("/games") ? "page" : undefined}>
             <IconGamepad width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Games</span>
+            <span class="group-hover:underline group-focus:underline">Games</span>
           </a>
         </li>
         <li>
-          <a
-            class="u-url group text-link"
-            href="/tags"
-            aria-current={isCurrentRoute("/tags") ? "page" : undefined}
-          >
+          <a class="u-url text-link group" href="/tags" aria-current={isCurrentRoute("/tags") ? "page" : undefined}>
             <IconTags width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Tags</span>
+            <span class="group-hover:underline group-focus:underline">Tags</span>
           </a>
         </li>
         <li>
           <a
-            class="u-url group text-link"
+            class="u-url text-link group"
             href="/search"
             rel="search"
             aria-current={isCurrentRoute("/search") ? "page" : undefined}
           >
             <IconMagnifyingGlass width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">Search</span>
+            <span class="group-hover:underline group-focus:underline">Search</span>
           </a>
         </li>
         <li>
-          <a
-            class="u-url group text-link"
-            href="/feed.xml"
-          >
+          <a class="u-url text-link group" href="/feed.xml">
             <IconSquareRSS width="1.25rem" height="1.25rem" class="inline align-middle" />
-            <span class="group-focus:underline group-hover:underline">RSS feed</span>
+            <span class="group-hover:underline group-focus:underline">RSS feed</span>
           </a>
         </li>
         <li>
-          <button
-            data-dark-mode
-            style={{ display: "none" }}
-            class="group text-link"
-          >
-            <IconSun width="1.25rem" height="1.25rem" class="align-middle hidden dark:inline" />
+          <button data-dark-mode style={{ display: "none" }} class="text-link group">
+            <IconSun width="1.25rem" height="1.25rem" class="hidden align-middle dark:inline" />
             <IconMoon width="1.25rem" height="1.25rem" class="inline align-middle dark:hidden" />
-            <span class="group-focus:underline group-hover:underline">{t("en", "published_content/toggle_dark_mode")}</span>
-          </a>
+            <span class="group-hover:underline group-focus:underline"
+              >{t("en", "published_content/toggle_dark_mode")}</span
+            >
+          </button>
         </li>
       </ul>
       <div class="pt-4 text-center text-xs text-black dark:text-white">
@@ -144,7 +124,7 @@ const isCurrentRoute = (path: string) =>
             )
           } |
         </span>
-        <a class="text-link hover:underline focus:underline" href="/licenses.txt" rel="license">Licenses</a>
+        <a class="text-link hover:underline focus:underline" href="/licenses.toml" rel="license">Licenses</a>
       </div>
     </nav>
     <main
diff --git a/src/layouts/GameLayout.astro b/src/layouts/GameLayout.astro
index 1ff4082..e5c76b3 100644
--- a/src/layouts/GameLayout.astro
+++ b/src/layouts/GameLayout.astro
@@ -48,13 +48,17 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
   labelArticleSection={t(props.lang, "game/article_aria_label")}
 >
   <meta
-    slot="head-description"
+    slot="head"
     property="og:description"
     content={t(props.lang, "game/warnings", props.platforms, props.contentWarning)}
   />
   <Fragment slot="section-information">
     <Authors lang={props.lang}>
-      {authorsList.map((author) => <UserComponent rel="author nofollow" class="p-author" user={author} lang={props.lang} />)}
+      {
+        authorsList.map((author) => (
+          <UserComponent rel="author nofollow" class="p-author" user={author} lang={props.lang} />
+        ))
+      }
     </Authors>
     <div id="platforms">
       <p>{t(props.lang, "game/platforms", props.platforms)}</p>
diff --git a/src/layouts/PublishedContentLayout.astro b/src/layouts/PublishedContentLayout.astro
index 1a28200..e6993d5 100644
--- a/src/layouts/PublishedContentLayout.astro
+++ b/src/layouts/PublishedContentLayout.astro
@@ -11,13 +11,15 @@ import Prose from "../components/Prose.astro";
 import MastodonComments from "../components/MastodonComments.astro";
 import type { CopyrightedCharacters as CopyrightedCharactersType } from "../content/config";
 import { qualifyLocalURLsInMarkdown } from "../utils/qualify_local_urls_in_markdown";
-import IconSun from "../components/icons/IconSun.astro";
-import IconMoon from "../components/icons/IconMoon.astro";
-import IconInformation from "../components/icons/IconInformation.astro";
-import IconArrowBack from "../components/icons/IconArrowBack.astro";
-import IconChevronLeft from "../components/icons/IconChevronLeft.astro";
-import IconChevronRight from "../components/icons/IconChevronRight.astro";
-import IconArrowUp from "../components/icons/IconArrowUp.astro";
+import {
+  IconSun,
+  IconMoon,
+  IconCircleInfo,
+  IconArrowBack,
+  IconChevronLeft,
+  IconChevronRight,
+  IconArrowUp,
+} from "../components/icons";
 
 interface RelatedContent {
   link: string;
@@ -91,8 +93,10 @@ const thumbnail =
 
 <BaseLayout pageTitle={props.title} lang={props.lang}>
   <Fragment slot="head">
+    { props.isDraft ? (
+      <meta name="robots" content="noindex" />
+    ) : null }
     <meta property="og:title" content={props.title} data-pagefind-meta="title[content]" />
-    <slot name="head-description" />
     <meta property="og:url" content={Astro.url} data-pagefind-meta="url[content]" />
     {
       thumbnail ? (
@@ -106,6 +110,7 @@ const thumbnail =
         </Fragment>
       ) : null
     }
+    <slot name="head" />
   </Fragment>
   <div
     id="top"
@@ -136,7 +141,7 @@ const thumbnail =
           class="text-link my-1 border-l border-stone-300 p-2 dark:border-stone-700"
           aria-labelledby="label-go-to-description"
         >
-          <IconInformation width="1.25rem" height="1.25rem" />
+          <IconCircleInfo width="1.25rem" height="1.25rem" />
           <span class="sr-only" id="label-go-to-description"
             >{t(props.lang, "published_content/go_to_description")}</span
           >
@@ -395,7 +400,7 @@ const thumbnail =
       <span
         set:html={t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())}
       /><span>&nbsp;|</span>
-      <a class="hover:underline focus:underline" href="/licenses.txt" rel="license"
+      <a class="hover:underline focus:underline" href="/licenses.toml" rel="license"
         >{t(props.lang, "published_content/licenses")}</a
       >
     </div>
diff --git a/src/layouts/StoryLayout.astro b/src/layouts/StoryLayout.astro
index 46913cf..9477911 100644
--- a/src/layouts/StoryLayout.astro
+++ b/src/layouts/StoryLayout.astro
@@ -59,7 +59,7 @@ const wordCount = props.wordCount?.toString();
   labelArticleSection={t(props.lang, "story/article_aria_label")}
 >
   <meta
-    slot="head-description"
+    slot="head"
     property="og:description"
     content={t(props.lang, "story/warnings", wordCount, props.contentWarning)}
   />
diff --git a/src/pages/404.astro b/src/pages/404.astro
index d15204b..fbdaa0f 100644
--- a/src/pages/404.astro
+++ b/src/pages/404.astro
@@ -3,7 +3,7 @@ import GalleryLayout from "../layouts/GalleryLayout.astro";
 ---
 
 <GalleryLayout pageTitle="404">
-  <meta slot="head-description" property="og:description" content="Not found" />
+  <meta slot="head" property="og:description" content="Not found" />
   <h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">404 &ndash; Not Found</h1>
   <p class="my-4">The requested link couldn't be found. Make sure that the URL is correct.</p>
 </GalleryLayout>
diff --git a/src/pages/api/export-story/[...slug].ts b/src/pages/api/export-story/[...slug].ts
index 53883dd..8179e4c 100644
--- a/src/pages/api/export-story/[...slug].ts
+++ b/src/pages/api/export-story/[...slug].ts
@@ -8,6 +8,7 @@ import { getUsernameForLang } from "../../../utils/get_username_for_lang";
 import { isAnonymousUser } from "../../../utils/is_anonymous_user";
 import { qualifyLocalURLsInMarkdown } from "../../../utils/qualify_local_urls_in_markdown";
 import { getWebsiteLinkForUser } from "../../../utils/get_website_link_for_user";
+import { toPlainMarkdown } from "../../../utils/to_plain_markdown";
 
 interface ExportWebsiteInfo {
   website: PostWebsite;
@@ -86,13 +87,19 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
           }
           const acc = await promise;
           const newData = await qualifyLocalURLsInMarkdown(data, lang, site, exportWebsite);
-          return acc ? `${acc}\n\n${newData}` : newData;
+          return `${acc}\n\n${newData}`;
         }, Promise.resolve(""));
         switch (exportFormat) {
           case "bbcode":
-            return { descriptionFilename: `description_${exportWebsite}.txt`, descriptionText: markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n") };
+            return {
+              descriptionFilename: `description_${exportWebsite}.txt`,
+              descriptionText: markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n"),
+            };
           case "markdown":
-            return { descriptionFilename: `description_${exportWebsite}.md`, descriptionText: storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim() };
+            return {
+              descriptionFilename: `description_${exportWebsite}.md`,
+              descriptionText: toPlainMarkdown(storyDescription).replaceAll(/\n\n\n+/g, "\n\n").trim(),
+            };
           default:
             const unknown: never = exportFormat;
             throw new Error(`Unknown export format "${unknown}"`);
diff --git a/src/pages/games.astro b/src/pages/games.astro
index 525efdb..1f5fd55 100644
--- a/src/pages/games.astro
+++ b/src/pages/games.astro
@@ -18,7 +18,7 @@ const games = await Promise.all(
 ---
 
 <GalleryLayout pageTitle="Games" class="h-feed">
-  <meta slot="head-description" property="og:description" content="Bad Manners || A game that I've gone and done." />
+  <meta slot="head" property="og:description" content="Bad Manners || A game that I've gone and done." />
   <h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Games</h1>
   <p class="p-summary my-4">A game that I've gone and done.</p>
   <ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 09fbaff..240b328 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -74,7 +74,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
 ---
 
 <GalleryLayout pageTitle="Gallery" class="h-feed">
-  <meta slot="head-description" property="og:description" content="Bad Manners || Welcome to my gallery!" />
+  <meta slot="head" property="og:description" content="Bad Manners || Welcome to my gallery!" />
   <h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">My gallery</h1>
   <div class="p-summary">
     <p class="my-4">
@@ -91,8 +91,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
         class="text-link underline"
         href="https://badmanners.xyz/"
         data-age-restricted
-        rel="me"
-        >my main website</a
+        rel="me">my main website</a
       >.
     </p>
   </div>
diff --git a/src/pages/search.astro b/src/pages/search.astro
index 8556985..fb80d41 100644
--- a/src/pages/search.astro
+++ b/src/pages/search.astro
@@ -4,7 +4,7 @@ import GalleryLayout from "../layouts/GalleryLayout.astro";
 ---
 
 <GalleryLayout pageTitle="Search">
-  <meta slot="head-description" property="og:description" content="Bad Manners || Search" />
+  <meta slot="head" property="og:description" content="Bad Manners || Search" />
   <h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Search</h1>
   <SearchComponent id="search" className="pagefind-ui my-4" />
 </GalleryLayout>
diff --git a/src/pages/stories/[page].astro b/src/pages/stories/[page].astro
index d30463a..a8d3443 100644
--- a/src/pages/stories/[page].astro
+++ b/src/pages/stories/[page].astro
@@ -29,7 +29,7 @@ const totalPages = Math.ceil(page.total / page.size);
 ---
 
 <GalleryLayout pageTitle="Stories" class="h-feed">
-  <meta slot="head-description" property="og:description" content={`Bad Manners || ${page.total} stories.`} />
+  <meta slot="head" property="og:description" content={`Bad Manners || ${page.total} stories.`} />
   <h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Stories</h1>
   <div class="p-summary">
     <p class="my-4">The bulk of my content!</p>
@@ -44,7 +44,11 @@ const totalPages = Math.ceil(page.total / page.size);
   <div class="mx-auto mb-6 mt-2 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
     {
       page.url.prev && (
-        <a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" rel="prev" href={page.url.prev}>
+        <a
+          class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
+          rel="prev"
+          href={page.url.prev}
+        >
           Previous page
         </a>
       )
@@ -129,7 +133,11 @@ const totalPages = Math.ceil(page.total / page.size);
   <div class="mx-auto my-6 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
     {
       page.url.prev && (
-        <a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" rel="prev" href={page.url.prev}>
+        <a
+          class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
+          rel="prev"
+          href={page.url.prev}
+        >
           Previous page
         </a>
       )
diff --git a/src/pages/stories/the-lost-of-the-marshes.astro b/src/pages/stories/the-lost-of-the-marshes.astro
index 9155b18..12cb29e 100644
--- a/src/pages/stories/the-lost-of-the-marshes.astro
+++ b/src/pages/stories/the-lost-of-the-marshes.astro
@@ -22,7 +22,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
 
 <GalleryLayout pageTitle={series.data.name} enablePagefind={true} class="h-feed">
   <meta
-    slot="head-description"
+    slot="head"
     property="og:description"
     content="The Lost of the Marshes || The story of Quince, Nikili, and Suu."
   />
@@ -123,7 +123,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
     <Image
       class="mx-auto w-full max-w-4xl break-before-page"
       src={mapImage}
-      alt="A geopolitical map for the setting of The Lost of the Marshes"
+      alt="A geopolitical map for the setting of The Lost of the Marshes. The center is covered by desert plains, except for two rivers that run from north to south through the marshes: the First River to the west, and the Second River to the East. The First River starts at the Eastern Toroza Mountains, passing through the Labla state before reaching the Quuwa Marshes inside of Kaati. Along the way, it passes next to the cities of Kaati to its east and Fereh to its west, respectively. The Second River passes by Sinipin to its west. South of the Quuwa marshes and between both rivers are Logas to the east and Kuir to the west, until both rivers reach the Hamora Lake inside of the Hamora Marshes. To the north of the lake is Zugul, and out of the lake comes the southeastern-bound Last River. Halfway down the Last River is Kyorna to the northwest, before it eventually reaches the Bronze Gulf. To the east of the Kaati state is Zuit on the Szogors Mountains, including Saisa; to the south is the state of Munigad; and to the west, an empty desert called Fool's Hope."
       data-pagefind-meta="image[src],image_alt[alt]"
     />
   </section>
diff --git a/src/pages/tags.astro b/src/pages/tags.astro
index 9d5cf9a..93b952d 100644
--- a/src/pages/tags.astro
+++ b/src/pages/tags.astro
@@ -72,7 +72,7 @@ if (uncategorizedTagsSet.size > 0) {
 <GalleryLayout pageTitle="Tags">
   <meta
     property="og:description"
-    slot="head-description"
+    slot="head"
     content="Bad Manners || Find all content with a specific tag."
   />
   <h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">All available tags</h1>
diff --git a/src/pages/tags/[badSlug].astro b/src/pages/tags/[badSlug].astro
index e5d5da6..2bb7fb5 100644
--- a/src/pages/tags/[badSlug].astro
+++ b/src/pages/tags/[badSlug].astro
@@ -22,7 +22,10 @@ const { badTag } = Astro.props;
 ---
 
 <GalleryLayout pageTitle={`Works tagged "${badTag}"`}>
-  <meta slot="head-description" content="No." property="og:description" />
+  <Fragment slot="head">
+    <meta content="No." property="og:description" />
+    <meta name="robots" content="noindex" />
+  </Fragment>
   <h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{badTag}"</h1>
   <p class="my-4">No.</p>
 </GalleryLayout>
diff --git a/src/pages/tags/[slug].astro b/src/pages/tags/[slug].astro
index 09b169a..e871ad9 100644
--- a/src/pages/tags/[slug].astro
+++ b/src/pages/tags/[slug].astro
@@ -121,7 +121,7 @@ const totalWorksWithTag = t(
 
 <GalleryLayout pageTitle={`Works tagged "${props.tag}"`}>
   <meta
-    slot="head-description"
+    slot="head"
     content={`Bad Manners || ${totalWorksWithTag || props.tag}`}
     property="og:description"
   />
diff --git a/src/utils/get_website_link_for_user.ts b/src/utils/get_website_link_for_user.ts
index 3539ce2..0de0c4f 100644
--- a/src/utils/get_website_link_for_user.ts
+++ b/src/utils/get_website_link_for_user.ts
@@ -2,10 +2,10 @@ import type { CollectionEntry } from "astro:content";
 import { DEFAULT_LANG, type UserWebsite } from "../content/config";
 import { getUsernameForLang } from "./get_username_for_lang";
 
-type WebsiteUserData<W extends UserWebsite> = CollectionEntry<"users">["data"]["links"][W];
+type WebsiteUserData<W extends UserWebsite> = NonNullable<CollectionEntry<"users">["data"]["links"][W]>;
 
-function getUserDataForWebsite<W extends UserWebsite>(user: CollectionEntry<"users">, website: W) {
-  const link: WebsiteUserData<W> | undefined = user.data.links[website];
+function getUserDataForWebsite<W extends UserWebsite>(user: CollectionEntry<"users">, website: W): WebsiteUserData<W> {
+  const link = user.data.links[website];
   if (link) {
     return link;
   }
diff --git a/src/utils/markdown_to_bbcode.ts b/src/utils/markdown_to_bbcode.ts
index 2988b05..8ca7cb7 100644
--- a/src/utils/markdown_to_bbcode.ts
+++ b/src/utils/markdown_to_bbcode.ts
@@ -1,5 +1,6 @@
 import { Marked, type RendererObject } from "marked";
 import { decode } from "tiny-decode";
+import { parsePartialHTMLTag } from "./parse_partial_html_tag";
 
 const renderer: RendererObject = {
   code({ text }) {
@@ -11,15 +12,21 @@ const renderer: RendererObject = {
   html(token) {
     const { type, raw } = token;
     if (type === "html") {
-      if (raw === "</a>") {
-        return "[/url]";
-      }
-      const match = raw.match(/^<a(?:\w+[a-z-]+(?:="[^"]*"))*\w+href="([^"]+)"(?:\w+[a-z-]+(?:="[^"]*"))*\w*>$/);
-      if (match&&match[1]) {
-        return `[url=${match[1]}]`;
+      const tag = parsePartialHTMLTag(raw);
+      switch (tag.tag) {
+        case "a":
+          if (tag.type === "open") {
+            if (tag.attributes?.href) {
+              return `[url=${tag.attributes.href}]`;
+            }
+          } else if (tag.type === "close") {
+            return "[/url]";
+          }
+          console.error("ERROR: unhandled anchor in markdownToBbcode.html", token, tag);
+          throw new Error("Unhandled anchor HTML token in BBCode renderer");
       }
     }
-    console.error("ERROR: unhandled token markdownToBbcode.html", token);
+    console.error("ERROR: unhandled token in markdownToBbcode.html", token);
     throw new Error("Unknown HTML token not supported by BBCode renderer");
   },
   heading({ tokens }) {
@@ -36,7 +43,7 @@ const renderer: RendererObject = {
     return `[li]${this.parser.parseInline(tokens)}[/li]\n`;
   },
   checkbox(token) {
-    console.error("ERROR: unhandled token markdownToBbcode.checkbox", token);
+    console.error("ERROR: unhandled token in markdownToBbcode.checkbox", token);
     throw new Error("Not supported by BBCode: checkbox");
   },
   paragraph({ tokens }) {
@@ -73,7 +80,7 @@ const renderer: RendererObject = {
     return `[url=${href}]${this.parser.parseInline(tokens)}[/url]`;
   },
   image({ href }) {
-    return `[img]${href}[/url]`;
+    return `[img]${href}[/img]`;
   },
 };
 
diff --git a/src/utils/markdown_to_plaintext.ts b/src/utils/markdown_to_plaintext.ts
index a0e77ed..644891c 100644
--- a/src/utils/markdown_to_plaintext.ts
+++ b/src/utils/markdown_to_plaintext.ts
@@ -24,7 +24,7 @@ const renderer: RendererObject = {
     return `- ${this.parser.parseInline(tokens)}\n`;
   },
   checkbox(token) {
-    console.error("ERROR: unhandled token markdownToPlaintext.checkbox", token);
+    console.error("ERROR: unhandled token in markdownToPlaintext.checkbox", token);
     throw new Error("Not supported by plaintext renderer: checkbox");
   },
   paragraph({ tokens }) {
@@ -54,15 +54,14 @@ const renderer: RendererObject = {
     return "\n\n";
   },
   del(token) {
-    console.error("ERROR: unhandled token markdownToPlaintext.del", token);
+    console.error("ERROR: unhandled token in markdownToPlaintext.del", token);
     throw new Error("Not supported by plaintext renderer: del");
   },
   link({ tokens }) {
     return this.parser.parseInline(tokens);
   },
-  image(token) {
-    console.error("ERROR: unhandled token markdownToPlaintext.image", token);
-    throw new Error("Not supported by plaintext renderer: img");
+  image() {
+    return "";
   },
 };
 
diff --git a/src/utils/parse_partial_html_tag.ts b/src/utils/parse_partial_html_tag.ts
new file mode 100644
index 0000000..679390b
--- /dev/null
+++ b/src/utils/parse_partial_html_tag.ts
@@ -0,0 +1,53 @@
+interface ParsedHTMLTag {
+  tag: string;
+  type: "open" | "close" | "both";
+  attributes?: Record<string, string|null>;
+}
+
+const OPEN_TAG_START_REGEX = /^<\s*([a-z-]+)\s*/;
+const ATTRIBUTE_REGEX = /^([a-z-]+)(?:=("[^"]*"))?\s*/;
+const END_OPEN_REGEX = /^>$/;
+const END_OPEN_CLOSE_REGEX = /^\/>$/;
+const CLOSE_TAG_REGEX = /^<\/\s*([a-z-]+)\s*>$/;
+
+/**
+ * Parses the opening or closing half of an HTML tag. To parse well-formed HTML, use DOMParser.parseFromString().
+ */
+export function parsePartialHTMLTag(text: string): ParsedHTMLTag {
+  let partialText = text;
+  const openTag = partialText.match(OPEN_TAG_START_REGEX);
+  if (openTag) {
+    const result: ParsedHTMLTag = {
+      tag: openTag[1],
+      type: "open",
+    };
+    partialText = partialText.slice(openTag[0].length);
+    while (true) {
+      const attribute = partialText.match(ATTRIBUTE_REGEX);
+      if (!attribute) {
+        break;
+      }
+      if (!result.attributes) {
+        result.attributes = {};
+      }
+      result.attributes[attribute[1]] = attribute[2] ? JSON.parse(attribute[2]) : null;
+      partialText = partialText.slice(attribute[0].length);
+    }
+    if (partialText.match(END_OPEN_REGEX)) {
+      return result;
+    }
+    if (partialText.match(END_OPEN_CLOSE_REGEX)) {
+      result.type = "both";
+      return result;
+    }
+  } else {
+    const closeTag = partialText.match(CLOSE_TAG_REGEX);
+    if (closeTag) {
+      return {
+        tag: closeTag[1],
+        type: "close",
+      }
+    }
+  }
+  throw new Error(`Unable to parse partial HTML tag: ${text}`);
+}
\ No newline at end of file
diff --git a/src/utils/to_plain_markdown.ts b/src/utils/to_plain_markdown.ts
new file mode 100644
index 0000000..9684d18
--- /dev/null
+++ b/src/utils/to_plain_markdown.ts
@@ -0,0 +1,5 @@
+/**
+ * Replaces HTML in Markdown text with its simplified representation.
+ */
+export const toPlainMarkdown = (text: string) =>
+  text.replaceAll(/<\s*a(?:\s+\S+)*(?:\s+href="([^"]+)")(?:\s+\S+)*\s*>\s*([^<]+)\s*<\/a>/g, "[$2]($1)");