diff --git a/package-lock.json b/package-lock.json
index 4a64265..d7e4a48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
-  "name": "gallery-badmanners-xyz",
-  "version": "1.7.2",
+  "name": "gallery.badmanners.xyz",
+  "version": "1.7.3",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "gallery-badmanners-xyz",
-      "version": "1.7.2",
+      "name": "gallery.badmanners.xyz",
+      "version": "1.7.3",
       "hasInstallScript": true,
       "dependencies": {
         "@astrojs/check": "^0.9.2",
diff --git a/package.json b/package.json
index f74b3d0..dfc0616 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
-  "name": "gallery-badmanners-xyz",
+  "name": "gallery.badmanners.xyz",
   "type": "module",
-  "version": "1.7.2",
+  "version": "1.7.3",
   "scripts": {
     "postinstall": "astro sync",
     "dev": "astro dev",
diff --git a/scripts/export-story.ts b/scripts/export-story.ts
index 9290f10..605ac68 100644
--- a/scripts/export-story.ts
+++ b/scripts/export-story.ts
@@ -8,11 +8,16 @@ import fetchRetryWrapper from "fetch-retry";
 
 function getRTFStyles(rtfSource: string) {
   const matches = rtfSource.matchAll(
-    /\\s(\d+)(?:\\sbasedon\d+)?\\snext\d+((?:\\[a-z0-9]+ ?)+)(?: ([A-Z][a-zA-Z ]*));/g,
+    /\\s(?<styleNumber>\d+)(?:\\sbasedon\d+)?\\snext\d+(?<partialRTFStyle>(?:\\[a-z0-9]+ ?)+)(?: (?<styleName>[A-Z][a-zA-Z ]*));/g,
   );
   let hasMatches = false;
   const rtfStyles: Record<number | string, string> = {};
-  for (const [_, styleNumber, partialRTFStyle, styleName] of matches) {
+  for (const match of matches) {
+    const { styleNumber, partialRTFStyle, styleName } = match.groups as {
+      styleNumber: string;
+      partialRTFStyle: string;
+      styleName: string;
+    };
     hasMatches = true;
     const rtfStyle = `\\s${styleNumber}${partialRTFStyle}`;
     rtfStyles[Number.parseInt(styleNumber)] = rtfStyle;
diff --git a/src/assets/LICENSE b/src/assets/LICENSE
deleted file mode 100644
index 9badf7a..0000000
--- a/src/assets/LICENSE
+++ /dev/null
@@ -1,402 +0,0 @@
-Attribution-NonCommercial-NoDerivatives 4.0 International
-
-=======================================================================
-
-Creative Commons Corporation ("Creative Commons") is not a law firm and
-does not provide legal services or legal advice. Distribution of
-Creative Commons public licenses does not create a lawyer-client or
-other relationship. Creative Commons makes its licenses and related
-information available on an "as-is" basis. Creative Commons gives no
-warranties regarding its licenses, any material licensed under their
-terms and conditions, or any related information. Creative Commons
-disclaims all liability for damages resulting from their use to the
-fullest extent possible.
-
-Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and
-conditions that creators and other rights holders may use to share
-original works of authorship and other material subject to copyright
-and certain other rights specified in the public license below. The
-following considerations are for informational purposes only, are not
-exhaustive, and do not form part of our licenses.
-
-     Considerations for licensors: Our public licenses are
-     intended for use by those authorized to give the public
-     permission to use material in ways otherwise restricted by
-     copyright and certain other rights. Our licenses are
-     irrevocable. Licensors should read and understand the terms
-     and conditions of the license they choose before applying it.
-     Licensors should also secure all rights necessary before
-     applying our licenses so that the public can reuse the
-     material as expected. Licensors should clearly mark any
-     material not subject to the license. This includes other CC-
-     licensed material, or material used under an exception or
-     limitation to copyright. More considerations for licensors:
-    wiki.creativecommons.org/Considerations_for_licensors
-
-     Considerations for the public: By using one of our public
-     licenses, a licensor grants the public permission to use the
-     licensed material under specified terms and conditions. If
-     the licensor's permission is not necessary for any reason--for
-     example, because of any applicable exception or limitation to
-     copyright--then that use is not regulated by the license. Our
-     licenses grant only permissions under copyright and certain
-     other rights that a licensor has authority to grant. Use of
-     the licensed material may still be restricted for other
-     reasons, including because others have copyright or other
-     rights in the material. A licensor may make special requests,
-     such as asking that all changes be marked or described.
-     Although not required by our licenses, you are encouraged to
-     respect those requests where reasonable. More considerations
-     for the public:
-    wiki.creativecommons.org/Considerations_for_licensees
-
-=======================================================================
-
-Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
-International Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree
-to be bound by the terms and conditions of this Creative Commons
-Attribution-NonCommercial-NoDerivatives 4.0 International Public
-License ("Public License"). To the extent this Public License may be
-interpreted as a contract, You are granted the Licensed Rights in
-consideration of Your acceptance of these terms and conditions, and the
-Licensor grants You such rights in consideration of benefits the
-Licensor receives from making the Licensed Material available under
-these terms and conditions.
-
-
-Section 1 -- Definitions.
-
-  a. Adapted Material means material subject to Copyright and Similar
-     Rights that is derived from or based upon the Licensed Material
-     and in which the Licensed Material is translated, altered,
-     arranged, transformed, or otherwise modified in a manner requiring
-     permission under the Copyright and Similar Rights held by the
-     Licensor. For purposes of this Public License, where the Licensed
-     Material is a musical work, performance, or sound recording,
-     Adapted Material is always produced where the Licensed Material is
-     synched in timed relation with a moving image.
-
-  b. Copyright and Similar Rights means copyright and/or similar rights
-     closely related to copyright including, without limitation,
-     performance, broadcast, sound recording, and Sui Generis Database
-     Rights, without regard to how the rights are labeled or
-     categorized. For purposes of this Public License, the rights
-     specified in Section 2(b)(1)-(2) are not Copyright and Similar
-     Rights.
-
-  c. Effective Technological Measures means those measures that, in the
-     absence of proper authority, may not be circumvented under laws
-     fulfilling obligations under Article 11 of the WIPO Copyright
-     Treaty adopted on December 20, 1996, and/or similar international
-     agreements.
-
-  d. Exceptions and Limitations means fair use, fair dealing, and/or
-     any other exception or limitation to Copyright and Similar Rights
-     that applies to Your use of the Licensed Material.
-
-  e. Licensed Material means the artistic or literary work, database,
-     or other material to which the Licensor applied this Public
-     License.
-
-  f. Licensed Rights means the rights granted to You subject to the
-     terms and conditions of this Public License, which are limited to
-     all Copyright and Similar Rights that apply to Your use of the
-     Licensed Material and that the Licensor has authority to license.
-
-  g. Licensor means the individual(s) or entity(ies) granting rights
-     under this Public License.
-
-  h. NonCommercial means not primarily intended for or directed towards
-     commercial advantage or monetary compensation. For purposes of
-     this Public License, the exchange of the Licensed Material for
-     other material subject to Copyright and Similar Rights by digital
-     file-sharing or similar means is NonCommercial provided there is
-     no payment of monetary compensation in connection with the
-     exchange.
-
-  i. Share means to provide material to the public by any means or
-     process that requires permission under the Licensed Rights, such
-     as reproduction, public display, public performance, distribution,
-     dissemination, communication, or importation, and to make material
-     available to the public including in ways that members of the
-     public may access the material from a place and at a time
-     individually chosen by them.
-
-  j. Sui Generis Database Rights means rights other than copyright
-     resulting from Directive 96/9/EC of the European Parliament and of
-     the Council of 11 March 1996 on the legal protection of databases,
-     as amended and/or succeeded, as well as other essentially
-     equivalent rights anywhere in the world.
-
-  k. You means the individual or entity exercising the Licensed Rights
-     under this Public License. Your has a corresponding meaning.
-
-
-Section 2 -- Scope.
-
-  a. License grant.
-
-       1. Subject to the terms and conditions of this Public License,
-          the Licensor hereby grants You a worldwide, royalty-free,
-          non-sublicensable, non-exclusive, irrevocable license to
-          exercise the Licensed Rights in the Licensed Material to:
-
-            a. reproduce and Share the Licensed Material, in whole or
-               in part, for NonCommercial purposes only; and
-
-            b. produce and reproduce, but not Share, Adapted Material
-               for NonCommercial purposes only.
-
-       2. Exceptions and Limitations. For the avoidance of doubt, where
-          Exceptions and Limitations apply to Your use, this Public
-          License does not apply, and You do not need to comply with
-          its terms and conditions.
-
-       3. Term. The term of this Public License is specified in Section
-          6(a).
-
-       4. Media and formats; technical modifications allowed. The
-          Licensor authorizes You to exercise the Licensed Rights in
-          all media and formats whether now known or hereafter created,
-          and to make technical modifications necessary to do so. The
-          Licensor waives and/or agrees not to assert any right or
-          authority to forbid You from making technical modifications
-          necessary to exercise the Licensed Rights, including
-          technical modifications necessary to circumvent Effective
-          Technological Measures. For purposes of this Public License,
-          simply making modifications authorized by this Section 2(a)
-          (4) never produces Adapted Material.
-
-       5. Downstream recipients.
-
-            a. Offer from the Licensor -- Licensed Material. Every
-               recipient of the Licensed Material automatically
-               receives an offer from the Licensor to exercise the
-               Licensed Rights under the terms and conditions of this
-               Public License.
-
-            b. No downstream restrictions. You may not offer or impose
-               any additional or different terms or conditions on, or
-               apply any Effective Technological Measures to, the
-               Licensed Material if doing so restricts exercise of the
-               Licensed Rights by any recipient of the Licensed
-               Material.
-
-       6. No endorsement. Nothing in this Public License constitutes or
-          may be construed as permission to assert or imply that You
-          are, or that Your use of the Licensed Material is, connected
-          with, or sponsored, endorsed, or granted official status by,
-          the Licensor or others designated to receive attribution as
-          provided in Section 3(a)(1)(A)(i).
-
-  b. Other rights.
-
-       1. Moral rights, such as the right of integrity, are not
-          licensed under this Public License, nor are publicity,
-          privacy, and/or other similar personality rights; however, to
-          the extent possible, the Licensor waives and/or agrees not to
-          assert any such rights held by the Licensor to the limited
-          extent necessary to allow You to exercise the Licensed
-          Rights, but not otherwise.
-
-       2. Patent and trademark rights are not licensed under this
-          Public License.
-
-       3. To the extent possible, the Licensor waives any right to
-          collect royalties from You for the exercise of the Licensed
-          Rights, whether directly or through a collecting society
-          under any voluntary or waivable statutory or compulsory
-          licensing scheme. In all other cases the Licensor expressly
-          reserves any right to collect such royalties, including when
-          the Licensed Material is used other than for NonCommercial
-          purposes.
-
-
-Section 3 -- License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the
-following conditions.
-
-  a. Attribution.
-
-       1. If You Share the Licensed Material, You must:
-
-            a. retain the following if it is supplied by the Licensor
-               with the Licensed Material:
-
-                 i. identification of the creator(s) of the Licensed
-                    Material and any others designated to receive
-                    attribution, in any reasonable manner requested by
-                    the Licensor (including by pseudonym if
-                    designated);
-
-                ii. a copyright notice;
-
-               iii. a notice that refers to this Public License;
-
-                iv. a notice that refers to the disclaimer of
-                    warranties;
-
-                 v. a URI or hyperlink to the Licensed Material to the
-                    extent reasonably practicable;
-
-            b. indicate if You modified the Licensed Material and
-               retain an indication of any previous modifications; and
-
-            c. indicate the Licensed Material is licensed under this
-               Public License, and include the text of, or the URI or
-               hyperlink to, this Public License.
-
-          For the avoidance of doubt, You do not have permission under
-          this Public License to Share Adapted Material.
-
-       2. You may satisfy the conditions in Section 3(a)(1) in any
-          reasonable manner based on the medium, means, and context in
-          which You Share the Licensed Material. For example, it may be
-          reasonable to satisfy the conditions by providing a URI or
-          hyperlink to a resource that includes the required
-          information.
-
-       3. If requested by the Licensor, You must remove any of the
-          information required by Section 3(a)(1)(A) to the extent
-          reasonably practicable.
-
-
-Section 4 -- Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that
-apply to Your use of the Licensed Material:
-
-  a. for the avoidance of doubt, Section 2(a)(1) grants You the right
-     to extract, reuse, reproduce, and Share all or a substantial
-     portion of the contents of the database for NonCommercial purposes
-     only and provided You do not Share Adapted Material;
-
-  b. if You include all or a substantial portion of the database
-     contents in a database in which You have Sui Generis Database
-     Rights, then the database in which You have Sui Generis Database
-     Rights (but not its individual contents) is Adapted Material; and
-
-  c. You must comply with the conditions in Section 3(a) if You Share
-     all or a substantial portion of the contents of the database.
-
-For the avoidance of doubt, this Section 4 supplements and does not
-replace Your obligations under this Public License where the Licensed
-Rights include other Copyright and Similar Rights.
-
-
-Section 5 -- Disclaimer of Warranties and Limitation of Liability.
-
-  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
-     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
-     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
-     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
-     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
-     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
-     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
-     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
-     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
-     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
-
-  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
-     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
-     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
-     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
-     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
-     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
-     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
-     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
-     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
-
-  c. The disclaimer of warranties and limitation of liability provided
-     above shall be interpreted in a manner that, to the extent
-     possible, most closely approximates an absolute disclaimer and
-     waiver of all liability.
-
-
-Section 6 -- Term and Termination.
-
-  a. This Public License applies for the term of the Copyright and
-     Similar Rights licensed here. However, if You fail to comply with
-     this Public License, then Your rights under this Public License
-     terminate automatically.
-
-  b. Where Your right to use the Licensed Material has terminated under
-     Section 6(a), it reinstates:
-
-       1. automatically as of the date the violation is cured, provided
-          it is cured within 30 days of Your discovery of the
-          violation; or
-
-       2. upon express reinstatement by the Licensor.
-
-     For the avoidance of doubt, this Section 6(b) does not affect any
-     right the Licensor may have to seek remedies for Your violations
-     of this Public License.
-
-  c. For the avoidance of doubt, the Licensor may also offer the
-     Licensed Material under separate terms or conditions or stop
-     distributing the Licensed Material at any time; however, doing so
-     will not terminate this Public License.
-
-  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
-     License.
-
-
-Section 7 -- Other Terms and Conditions.
-
-  a. The Licensor shall not be bound by any additional or different
-     terms or conditions communicated by You unless expressly agreed.
-
-  b. Any arrangements, understandings, or agreements regarding the
-     Licensed Material not stated herein are separate from and
-     independent of the terms and conditions of this Public License.
-
-
-Section 8 -- Interpretation.
-
-  a. For the avoidance of doubt, this Public License does not, and
-     shall not be interpreted to, reduce, limit, restrict, or impose
-     conditions on any use of the Licensed Material that could lawfully
-     be made without permission under this Public License.
-
-  b. To the extent possible, if any provision of this Public License is
-     deemed unenforceable, it shall be automatically reformed to the
-     minimum extent necessary to make it enforceable. If the provision
-     cannot be reformed, it shall be severed from this Public License
-     without affecting the enforceability of the remaining terms and
-     conditions.
-
-  c. No term or condition of this Public License will be waived and no
-     failure to comply consented to unless expressly agreed to by the
-     Licensor.
-
-  d. Nothing in this Public License constitutes or may be interpreted
-     as a limitation upon, or waiver of, any privileges and immunities
-     that apply to the Licensor or You, including from the legal
-     processes of any jurisdiction or authority.
-
-=======================================================================
-
-Creative Commons is not a party to its public
-licenses. Notwithstanding, Creative Commons may elect to apply one of
-its public licenses to material it publishes and in those instances
-will be considered the “Licensor.” The text of the Creative Commons
-public licenses is dedicated to the public domain under the CC0 Public
-Domain Dedication. Except for the limited purpose of indicating that
-material is shared under a Creative Commons public license or as
-otherwise permitted by the Creative Commons policies published at
-creativecommons.org/policies, Creative Commons does not authorize the
-use of the trademark "Creative Commons" or any other trademark or logo
-of Creative Commons without its prior written consent including,
-without limitation, in connection with any unauthorized modifications
-to any of its public licenses or any other arrangements,
-understandings, or agreements concerning use of licensed material. For
-the avoidance of doubt, this paragraph does not form part of the
-public licenses.
-
-Creative Commons may be contacted at creativecommons.org.
diff --git a/src/components/Authors.astro b/src/components/Authors.astro
index c4ddae3..14310e5 100644
--- a/src/components/Authors.astro
+++ b/src/components/Authors.astro
@@ -3,6 +3,7 @@ import { t, type Lang } from "../i18n";
 
 type Props = {
   lang: Lang;
+  children: any;
 };
 
 const { lang } = Astro.props;
diff --git a/src/components/Commissioners.astro b/src/components/Commissioners.astro
index 4c34b2b..39ec7d7 100644
--- a/src/components/Commissioners.astro
+++ b/src/components/Commissioners.astro
@@ -3,6 +3,7 @@ import { t, type Lang } from "../i18n";
 
 type Props = {
   lang: Lang;
+  children: any;
 };
 
 const { lang } = Astro.props;
diff --git a/src/components/CopyrightedCharacters.astro b/src/components/CopyrightedCharacters.astro
index 7d63011..f7d4d72 100644
--- a/src/components/CopyrightedCharacters.astro
+++ b/src/components/CopyrightedCharacters.astro
@@ -24,7 +24,7 @@ const charactersPerUser = copyrightedCharacters ? await formatCopyrightedCharact
               t(lang, "characters/characters_are_copyrighted_by", user, characters[0] === "" ? [] : characters)
             }
           >
-            <UserComponent lang={lang} user={user} />
+            <UserComponent class="p-name" lang={lang} user={user} />
           </CopyrightedCharactersItem>
         ))}
       </ul>
diff --git a/src/components/Requesters.astro b/src/components/Requesters.astro
index 9c4f8e2..1999ef4 100644
--- a/src/components/Requesters.astro
+++ b/src/components/Requesters.astro
@@ -3,6 +3,7 @@ import { t, type Lang } from "../i18n";
 
 type Props = {
   lang: Lang;
+  children: any;
 };
 
 const { lang } = Astro.props;
diff --git a/src/components/UserComponent.astro b/src/components/UserComponent.astro
index c82c8b4..e8929d1 100644
--- a/src/components/UserComponent.astro
+++ b/src/components/UserComponent.astro
@@ -6,19 +6,20 @@ import { getUsernameForLang } from "../utils/get_username_for_lang";
 type Props = {
   lang: Lang;
   user: CollectionEntry<"users">;
+  class?: string;
 };
 
-const { user, lang } = Astro.props;
+const { user, lang, class: className } = Astro.props;
 const username = getUsernameForLang(user, lang);
 const link = user.data.preferredLink ? user.data.links[user.data.preferredLink]!.link : null;
 ---
 
 {
   link ? (
-    <a href={link} class="text-link underline" target="_blank">
+    <a href={link} class:list={["h-card u-url text-link underline", className]} target="_blank">
       {username}
     </a>
   ) : (
-    <span>{username}</span>
+    <span class:list={["h-card", className]}>{username}</span>
   )
 }
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index ec36876..8fa7084 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -85,8 +85,8 @@ const UI_STRINGS = {
     tok: "nimi kulupu",
   },
   "published_content/copyright_year": {
-    en: (year: string | number) => `© ${year}`,
-    tok: (year: string | number) => `© tenpo pi sike suno ${year}`,
+    en: (year: string | number) => `© <time datetime="${year}">${year}</time>`,
+    tok: (year: string | number) => `© <time datetime="${year}">tenpo pi sike suno ${year}</time>`,
   },
   "published_content/licenses": {
     en: "Licenses",
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 3616a03..7e0c3a5 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -21,7 +21,7 @@ const { pageTitle = "Gallery", lang = "en" } = Astro.props;
     <link rel="manifest" href="/site.webmanifest" />
     <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#37b340" />
     <meta name="msapplication-TileColor" content="#37b340" />
-    <meta name="theme-color" content="#ffffff" />
+    <meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="generator" content={Astro.generator} />
     <title>{pageTitle} | Bad Manners</title>
diff --git a/src/layouts/GalleryLayout.astro b/src/layouts/GalleryLayout.astro
index 7317d32..8d0fa53 100644
--- a/src/layouts/GalleryLayout.astro
+++ b/src/layouts/GalleryLayout.astro
@@ -12,9 +12,7 @@ type Props = {
 
 const { pageTitle, enablePagefind } = Astro.props;
 const logo = await getImage({ src: logoBM, width: 192 });
-const currentYear = new Date().getFullYear();
-const FIRST_YEAR = 2024;
-const copyrightYear = currentYear > FIRST_YEAR ? `${FIRST_YEAR}–${currentYear}` : `${FIRST_YEAR}`;
+const currentYear = new Date().getFullYear().toString();
 ---
 
 <BaseLayout pageTitle={pageTitle}>
@@ -24,7 +22,6 @@ const copyrightYear = currentYear > FIRST_YEAR ? `${FIRST_YEAR}–${currentYear}
     <meta property="og:url" content={Astro.url} />
     <meta property="og:image" content={logo.src} />
     <meta property="og:image:alt" content="Logo for Bad Manners" />
-    <meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
   </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"
@@ -42,7 +39,7 @@ const copyrightYear = currentYear > FIRST_YEAR ? `${FIRST_YEAR}–${currentYear}
       <span class="my-2 text-2xl font-semibold">Bad Manners</span>
       <Navigation />
       <div class="pt-4 text-center text-xs text-black dark:text-white">
-        <span>&copy; {copyrightYear} | </span>
+        <span>&copy; {currentYear == "2024" ? <time datetime="2024">2024</time> : <><time datetime="2024">2024</time>&ndash;<time datetime={currentYear}>{currentYear}</time></>} | </span>
         <a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
       </div>
       <div class="mt-2 flex items-center gap-x-1 pb-10">
diff --git a/src/layouts/GameLayout.astro b/src/layouts/GameLayout.astro
index 800ed77..453ea7e 100644
--- a/src/layouts/GameLayout.astro
+++ b/src/layouts/GameLayout.astro
@@ -54,7 +54,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
   />
   <Fragment slot="section-information">
     <Authors lang={props.lang}>
-      {authorsList.map((author) => <UserComponent user={author} lang={props.lang} />)}
+      {authorsList.map((author) => <UserComponent 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 96185fd..473232a 100644
--- a/src/layouts/PublishedContentLayout.astro
+++ b/src/layouts/PublishedContentLayout.astro
@@ -99,7 +99,6 @@ const thumbnail =
         </Fragment>
       ) : null
     }
-    <meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
   </Fragment>
   <div
     id="top"
@@ -173,7 +172,7 @@ const thumbnail =
       </div>
     </div>
     <main
-      class="mx-auto max-w-3xl rounded-lg bg-stone-50 px-2 pb-4 pt-1 shadow-sm dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
+      class="h-entry mx-auto max-w-3xl rounded-lg bg-stone-50 px-2 pb-4 pt-1 shadow-sm dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
       data-pagefind-body={props.isDraft ? undefined : ""}
       data-pagefind-meta={`type:${props.publishedContentType}`}
     >
@@ -226,14 +225,14 @@ const thumbnail =
       }
       <h1
         id="section-title"
-        class="px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100"
+        class="p-name px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100"
         aria-label={props.labelTitleSection}
       >
         {props.title}
       </h1>
       <section
         id="section-information"
-        class="mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
+        class="p-summary mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
         aria-label={props.labelInformationSection}
       >
         <slot name="section-information" />
@@ -256,13 +255,13 @@ const thumbnail =
               alt={t(props.lang, "published_content/cover_art_alt", props.title)}
               width={props.thumbnailWidth}
               height={props.thumbnailHeight}
-              class="mx-auto my-5 shadow-lg"
+              class="u-photo mx-auto my-5 shadow-lg"
             />
           </Fragment>
         ) : null
       }
       <hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
-      <article id="content" class="pr-1 font-serif" aria-label={props.labelArticleSection}>
+      <article id="content" class="e-content pr-1 font-serif" aria-label={props.labelArticleSection}>
         <slot name="section-article" />
       </article>
       <hr class="mx-auto mb-6 mt-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
@@ -275,18 +274,19 @@ const thumbnail =
             {t(props.lang, "published_content/draft_warning")}
           </p>
         ) : props.pubDate ? (
-          <p
+          <time
             id="publish-date"
-            class="mt-2 px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
+            datetime={props.pubDate.toISOString().slice(0, 10)}
+            class="dt-published block mt-2 px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
             aria-label={t(props.lang, "published_content/publish_date_aria_label")}
             aria-description={
               t(props.lang, "published_content/publish_date_aria_description", props.pubDate) || undefined
             }
             data-pagefind-index-attrs="aria-description"
-            data-pagefind-meta={`date:${props.pubDate.toISOString().slice(0, 10)}`}
+            data-pagefind-meta="date[datetime]"
           >
             {t(props.lang, "published_content/publish_date", props.pubDate)}
-          </p>
+          </time>
         ) : null
       }
       <section id="description" class="px-2 font-serif" aria-describedby="title-description">
@@ -420,7 +420,7 @@ const thumbnail =
             <h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
               {t(props.lang, "published_content/tags")}
             </h2>
-            <ul class="flex flex-wrap gap-x-2 gap-y-3 px-3">
+            <ul class="p-category flex flex-wrap gap-x-2 gap-y-3 px-3">
               {tags.map(({ id, name }) => (
                 <li>
                   <a
@@ -441,7 +441,7 @@ const thumbnail =
       class="pt-6 text-center text-xs text-black dark:text-white"
       aria-label={t(props.lang, "published_content/copyright_aria_label")}
     >
-      <span>{t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())} | </span>
+      <span set:html={t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())}></span><span>&nbsp;|</span>
       <a class="hover:underline focus:underline" href="/licenses.txt" target="_blank"
         >{t(props.lang, "published_content/licenses")}</a
       >
diff --git a/src/layouts/StoryLayout.astro b/src/layouts/StoryLayout.astro
index 6b775c0..ee3cd63 100644
--- a/src/layouts/StoryLayout.astro
+++ b/src/layouts/StoryLayout.astro
@@ -65,13 +65,13 @@ const wordCount = props.wordCount?.toString();
   />
   <Fragment slot="section-information">
     <Authors lang={props.lang}>
-      {authorsList.map((author) => <UserComponent user={author} lang={props.lang} />)}
+      {authorsList.map((author) => <UserComponent class="p-author" user={author} lang={props.lang} />)}
     </Authors>
     {
       requestersList && (
         <Requesters lang={props.lang}>
           {requestersList.map((requester) => (
-            <UserComponent user={requester} lang={props.lang} />
+            <UserComponent class="p-name" user={requester} lang={props.lang} />
           ))}
         </Requesters>
       )
@@ -80,7 +80,7 @@ const wordCount = props.wordCount?.toString();
       commissionersList && (
         <Commissioners lang={props.lang}>
           {commissionersList.map((commissioner) => (
-            <UserComponent user={commissioner} lang={props.lang} />
+            <UserComponent class="p-name" user={commissioner} lang={props.lang} />
           ))}
         </Commissioners>
       )
diff --git a/src/pages/games.astro b/src/pages/games.astro
index 7431d5b..da5af07 100644
--- a/src/pages/games.astro
+++ b/src/pages/games.astro
@@ -18,25 +18,23 @@ const games = (
   <ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
     {
       games.map((game) => (
-        <li>
+        <li class="h-entry">
           <a
-            class="text-link hover:underline focus:underline"
+            class="u-url text-link hover:underline focus:underline"
             href={`/games/${game.slug}`}
             title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
           >
             {game.data.thumbnail ? (
               <div class="flex aspect-[630/500] max-w-[288px] justify-center">
-                <Image class="m-auto" src={game.data.thumbnail} alt={`Thumbnail for ${game.data.title}`} width={288} />
+                <Image class="u-photo m-auto" src={game.data.thumbnail} alt={`Thumbnail for ${game.data.title}`} width={288} />
               </div>
             ) : null}
             <div class="max-w-[288px] text-sm">
-              <>
-                <span>{game.data.title}</span>
-                <br />
-                <span class="italic">
-                  {game.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
-                </span>
-              </>
+              <span class="p-name">{game.data.title}</span>
+              <br />
+              <time class="dt-published italic" datetime={game.data.pubDate.toISOString().slice(0, 10)}>
+                {game.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
+              </time>
             </div>
           </a>
         </li>
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 8bb66e4..57333b9 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -79,19 +79,19 @@ const latestItems: LatestItemsEntry[] = [
     <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
       {
         latestItems.map((entry) => (
-          <li class="break-inside-avoid">
-            <a class="text-link hover:underline focus:underline" href={entry.href} title={entry.altText}>
+          <li class="h-entry break-inside-avoid">
+            <a class="u-url text-link hover:underline focus:underline" href={entry.href} title={entry.altText}>
               {entry.thumbnail ? (
                 <div class="flex aspect-square max-w-[192px] justify-center">
-                  <Image class="m-auto" src={entry.thumbnail} alt={`Thumbnail for ${entry.title}`} width={192} />
+                  <Image class="u-photo m-auto" src={entry.thumbnail} alt={`Thumbnail for ${entry.title}`} width={192} />
                 </div>
               ) : null}
               <div class="max-w-[192px] text-sm">
-                <span>{entry.title}</span>
+                <span class="p-name">{entry.title}</span>
                 <br />
                 <span class="italic">
-                  {entry.type} &ndash;{" "}
-                  {entry.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
+                  <span class="p-category">{entry.type}</span> &ndash;{" "}
+                  <time class="dt-published" datetime={entry.pubDate.toISOString().slice(0, 10)}>{entry.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}</time>
                 </span>
               </div>
             </a>
diff --git a/src/pages/stories/[page].astro b/src/pages/stories/[page].astro
index ca11c38..e6db5f2 100644
--- a/src/pages/stories/[page].astro
+++ b/src/pages/stories/[page].astro
@@ -68,16 +68,16 @@ const totalPages = Math.ceil(page.total / page.size);
   <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
     {
       page.data.map((story) => (
-        <li class="break-inside-avoid">
+        <li class="h-entry break-inside-avoid">
           <a
-            class="text-link hover:underline focus:underline"
+            class="u-url text-link hover:underline focus:underline"
             href={`/stories/${story.slug}`}
             title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
           >
             {story.data.thumbnail ? (
               <div class="flex aspect-square max-w-[192px] justify-center">
                 <Image
-                  class="m-auto"
+                  class="u-photo m-auto"
                   src={story.data.thumbnail}
                   alt={`Thumbnail for ${story.data.title}`}
                   width={192}
@@ -85,11 +85,11 @@ const totalPages = Math.ceil(page.total / page.size);
               </div>
             ) : null}
             <div class="max-w-[192px] text-sm">
-              <span>{story.data.title}</span>
+              <span class="p-name">{story.data.title}</span>
               <br />
-              <span class="italic">
+              <time class="italic" datetime={story.data.pubDate.toISOString().slice(0, 10)}>
                 {story.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
-              </span>
+              </time>
             </div>
           </a>
         </li>
diff --git a/src/pages/stories/the-lost-of-the-marshes.astro b/src/pages/stories/the-lost-of-the-marshes.astro
index acda096..56f8e9b 100644
--- a/src/pages/stories/the-lost-of-the-marshes.astro
+++ b/src/pages/stories/the-lost-of-the-marshes.astro
@@ -62,18 +62,18 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
     <ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
       {
         mainChapters.map((story) => (
-          <li class="break-inside-avoid">
-            <a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
+          <li class="h-entry break-inside-avoid">
+            <a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
               {story.data.thumbnail ? (
                 <Image
-                  class="w-48"
+                  class="u-photo w-48"
                   src={story.data.thumbnail}
                   alt={`Thumbnail for ${story.data.title}`}
                   width={story.data.thumbnailWidth}
                   height={story.data.thumbnailHeight}
                 />
               ) : null}
-              <div class="max-w-48 text-sm">{story.data.title}</div>
+              <div class="p-name max-w-48 text-sm">{story.data.title}</div>
             </a>
           </li>
         ))
@@ -85,18 +85,18 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
     <ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
       {
         bonusChapters.map((story) => (
-          <li class="break-inside-avoid">
-            <a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
+          <li class="h-entry break-inside-avoid">
+            <a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
               {story.data.thumbnail ? (
                 <Image
-                  class="w-48"
+                  class="u-photo w-48"
                   src={story.data.thumbnail}
                   alt={`Thumbnail for ${story.data.title}`}
                   width={story.data.thumbnailWidth}
                   height={story.data.thumbnailHeight}
                 />
               ) : null}
-              <div class="max-w-48 text-sm">{story.data.title}</div>
+              <div class="p-name max-w-48 text-sm">{story.data.title}</div>
             </a>
           </li>
         ))
diff --git a/src/pages/tags/[slug].astro b/src/pages/tags/[slug].astro
index 4343132..620d1f5 100644
--- a/src/pages/tags/[slug].astro
+++ b/src/pages/tags/[slug].astro
@@ -130,16 +130,16 @@ const totalWorksWithTag = t(
         </h2>
         <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
           {props.stories.map((story) => (
-            <li class="break-inside-avoid">
+            <li class="h-entry break-inside-avoid">
               <a
-                class="text-link hover:underline focus:underline"
+                class="u-url text-link hover:underline focus:underline"
                 href={`/stories/${story.slug}`}
                 title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
               >
                 {story.data.thumbnail ? (
                   <div class="flex aspect-square max-w-[192px] justify-center">
                     <Image
-                      class="m-auto"
+                      class="u-photo m-auto"
                       src={story.data.thumbnail}
                       alt={`Thumbnail for ${story.data.title}`}
                       width={192}
@@ -147,9 +147,9 @@ const totalWorksWithTag = t(
                   </div>
                 ) : null}
                 <div class="max-w-[192px] text-sm">
-                  <span>{story.data.title}</span>
+                  <span class="p-name">{story.data.title}</span>
                   <br />
-                  <span class="italic">
+                  <time class="dt-published italic" datetime={story.data.pubDate.toISOString().slice(0, 10)}>
                     {story.data.pubDate.toLocaleDateString("en-US", {
                       month: "short",
                       day: "numeric",
@@ -172,16 +172,16 @@ const totalWorksWithTag = t(
         </h2>
         <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
           {props.games.map((game) => (
-            <li class="break-inside-avoid">
+            <li class="h-entry break-inside-avoid">
               <a
-                class="text-link hover:underline focus:underline"
+                class="u-url text-link hover:underline focus:underline"
                 href={`/games/${game.slug}`}
                 title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
               >
                 {game.data.thumbnail ? (
                   <div class="flex aspect-[630/500] max-w-[192px] justify-center">
                     <Image
-                      class="m-auto"
+                      class="u-photo m-auto"
                       src={game.data.thumbnail}
                       alt={`Thumbnail for ${game.data.title}`}
                       width={192}
@@ -189,11 +189,11 @@ const totalWorksWithTag = t(
                   </div>
                 ) : null}
                 <div class="max-w-[192px] text-sm">
-                  <span>{game.data.title}</span>
+                  <span class="p-name">{game.data.title}</span>
                   <br />
-                  <span class="italic">
+                  <time class="dt-published italic" datetime={game.data.pubDate.toISOString().slice(0, 10)}>
                     {game.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
-                  </span>
+                  </time>
                 </div>
               </a>
             </li>
diff --git a/src/utils/qualify_local_urls_in_markdown.ts b/src/utils/qualify_local_urls_in_markdown.ts
index 559a0d8..002bb08 100644
--- a/src/utils/qualify_local_urls_in_markdown.ts
+++ b/src/utils/qualify_local_urls_in_markdown.ts
@@ -91,7 +91,7 @@ export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lan
           continue;
         default:
           const unknown: never = contentPrefix;
-          console.warn(`WARNING: Unknown local link qualifier ${unknown}; ignoring...`);
+          console.warn(`WARNING: Unknown local link prefix ${unknown}; ignoring...`);
       }
     }
     replacements.push(website && site ? `[${text}](${new URL(link, site).toString()})` : match[0]);