diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..306ca86
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,9 @@
+# Accord's Library v3.0
+
+## Short term
+
+- Translate new wording keys
+
+## Long term
+
+- Anonymous comments
diff --git a/bun.lockb b/bun.lockb
index e94cbf9..e116f58 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 19dabde..13142be 100644
--- a/package.json
+++ b/package.json
@@ -21,27 +21,27 @@
   },
   "dependencies": {
     "@astrojs/check": "^0.5.6",
-    "@astrojs/node": "^8.2.1",
-    "@fontsource-variable/murecho": "^5.0.17",
-    "@fontsource-variable/vollkorn": "^5.0.19",
+    "@astrojs/node": "^8.2.3",
+    "@fontsource-variable/murecho": "^5.0.18",
+    "@fontsource-variable/vollkorn": "^5.0.20",
     "accept-language": "^3.0.18",
-    "astro": "4.4.6",
+    "astro": "4.4.15",
     "astro-icon": "^1.1.0",
     "node-cache": "^5.1.2",
     "tippy.js": "^6.3.7",
     "ua-parser-js": "^1.0.37"
   },
   "devDependencies": {
-    "@iconify-json/material-symbols": "^1.1.73",
+    "@iconify-json/material-symbols": "^1.1.74",
     "@types/ua-parser-js": "^0.7.39",
     "astro-meta-tags": "^0.2.1",
-    "autoprefixer": "^10.4.17",
-    "bun-types": "^1.0.29",
+    "autoprefixer": "^10.4.18",
+    "bun-types": "^1.0.30",
     "npm-check-updates": "^16.14.15",
-    "postcss-preset-env": "^9.4.0",
+    "postcss-preset-env": "^9.5.0",
     "prettier": "^3.2.5",
     "prettier-plugin-astro": "^0.13.0",
     "ts-node": "^10.9.2",
-    "typescript": "^5.3.3"
+    "typescript": "^5.4.2"
   }
 }
diff --git a/public/img/background-image.webp b/public/img/background-image.webp
new file mode 100644
index 0000000..04fdd5f
Binary files /dev/null and b/public/img/background-image.webp differ
diff --git a/public/img/bg-home.webp b/public/img/bg-home.webp
deleted file mode 100644
index 7391d19..0000000
Binary files a/public/img/bg-home.webp and /dev/null differ
diff --git a/public/img/bg-home2.webp b/public/img/bg-home2.webp
deleted file mode 100644
index 105477e..0000000
Binary files a/public/img/bg-home2.webp and /dev/null differ
diff --git a/src/components/AppLayout/components/AppLayoutBackgroundImg.astro b/src/components/AppLayout/components/AppLayoutBackgroundImg.astro
index ab3655c..eaf5adc 100644
--- a/src/components/AppLayout/components/AppLayoutBackgroundImg.astro
+++ b/src/components/AppLayout/components/AppLayoutBackgroundImg.astro
@@ -10,37 +10,48 @@ interface Props {
 
 const { src, alt } = Astro.props;
 const uniqueId = getRandomId();
-
-const styleNoScript = `
-<style>
-  #${uniqueId} {
-      opacity: 1;
-      transition: unset;
-  }
-</style>`;
 ---
 
 {/* ------------------------------------------- HTML ------------------------------------------- */}
 
-<img id={uniqueId} src={src} alt={alt} class="when-no-print" />
-<noscript set:html={styleNoScript} />
+<img id={uniqueId} src={src} alt={alt} class="when-no-print when-js" />
+<img src={src} alt={alt} class="when-no-print when-no-js" />
 
 {/* ------------------------------------------- CSS -------------------------------------------- */}
 
 <style>
   img {
-    opacity: 0;
-    transition: 3s opacity;
     position: absolute;
     top: 0;
     left: 0;
     right: 0;
-    z-index: -1;
+    bottom: 0;
+
+    width: 100%;
     height: 100vh;
+
     object-fit: cover;
     object-position: 50% 0;
-    width: 100%;
-    mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 100%);
+
+    mask-image: linear-gradient(to bottom, rgba(0 0 0 / 30%) 0%, transparent 100%);
+
+    @media (min-width: 110vh) {
+      mask-image: linear-gradient(
+        to bottom,
+        rgba(0 0 0 / 30%) 0%,
+        rgba(0 0 0 / 5%) 100vh,
+        transparent 100%
+      );
+      height: 100%;
+      max-height: 100vw;
+    }
+
+    user-select: none;
+
+    &.when-js {
+      opacity: 0;
+      transition: 3s opacity;
+    }
   }
 </style>
 
diff --git a/src/components/AppLayout/components/Html.astro b/src/components/AppLayout/components/Html.astro
index 69dcc56..bd50a08 100644
--- a/src/components/AppLayout/components/Html.astro
+++ b/src/components/AppLayout/components/Html.astro
@@ -38,19 +38,12 @@ const { currentTheme } = Astro.locals;
     <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#27231e" />
     <link rel="manifest" href="/site.webmanifest" />
 
-    <style is:global>
-      .when-no-js {
-        display: none;
-      }
-    </style>
-
     <noscript>
       <style is:global>
         .when-js {
           display: none !important;
-        }
-        .when-no-js {
-          display: initial !important;
+          visibility: none !important;
+          opacity: 0 !important;
         }
       </style>
     </noscript>
@@ -224,10 +217,17 @@ const { currentTheme } = Astro.locals;
     .high-contrast-text {
       text-shadow: 0 0 0.6em var(--color-elevation-0);
     }
+
+    body {
+      margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
+    }
   }
 
   html {
+    position: relative;
     color: var(--color-base-1000);
+    min-height: 100vb;
+    display: flex;
 
     @media screen {
       background-color: var(--color-base-150);
@@ -235,8 +235,7 @@ const { currentTheme } = Astro.locals;
   }
 
   body {
-    margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
-    min-height: 100vb;
+    flex: 1;
     box-sizing: border-box;
     display: flex;
     flex-direction: column;
@@ -327,6 +326,24 @@ const { currentTheme } = Astro.locals;
     }
   }
 
+  .pressable-link {
+    text-decoration: underline dotted 0.1em;
+    text-decoration-color: transparent;
+
+    transition-duration: 150ms;
+    transition-property: text-decoration-color, color;
+
+    &:hover {
+      color: var(--color-base-750);
+      text-decoration-color: var(--color-base-650);
+    }
+
+    &:active {
+      color: var(--color-base-650);
+      text-decoration-color: var(--color-base-550);
+    }
+  }
+
   .pressable-label {
     text-decoration: none;
     flex-shrink: 0;
@@ -448,3 +465,13 @@ const { currentTheme } = Astro.locals;
     }
   }
 </style>
+
+{/* ------------------------------------------- JS --------------------------------------------- */}
+
+<script is:inline>
+  Array.from(document.querySelectorAll(".when-no-js")).forEach((node) => node.remove());
+
+  document.addEventListener("astro:before-swap", ({ newDocument }) => {
+    Array.from(newDocument.querySelectorAll(".when-no-js")).forEach((node) => node.remove());
+  });
+</script>
diff --git a/src/components/AppLayout/components/Topbar/Topbar.astro b/src/components/AppLayout/components/Topbar/Topbar.astro
index d7fc708..893590a 100644
--- a/src/components/AppLayout/components/Topbar/Topbar.astro
+++ b/src/components/AppLayout/components/Topbar/Topbar.astro
@@ -44,7 +44,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
       <a href="/settings">
         <Button
           icon="material-symbols:settings-outline"
-          ariaLabel={t("header.topbar.search.tooltip")}
+          ariaLabel={t("header.topbar.settings.tooltip")}
         />
       </a>
     </div>
diff --git a/src/components/AppLayout/components/Topbar/components/ParentPageLink.astro b/src/components/AppLayout/components/Topbar/components/ParentPageLink.astro
index 78b238d..df1808d 100644
--- a/src/components/AppLayout/components/Topbar/components/ParentPageLink.astro
+++ b/src/components/AppLayout/components/Topbar/components/ParentPageLink.astro
@@ -17,13 +17,20 @@ switch (parentPage.collection) {
     href = getLocalizedUrl(`/folders/${parentPage.slug}`);
     break;
 
+  case Collections.Collectibles:
+    href = getLocalizedUrl(`/collectibles/${parentPage.slug}`);
+    break;
+
   default:
+    href = "/404";
     break;
 }
 ---
 
 {/* ------------------------------------------- HTML ------------------------------------------- */}
 
+{/* TODO: Not use the tag but actual translation */}
+
 <a href={href}><span>{parentPage.tag}</span>{translation.name}</a>
 
 {/* ------------------------------------------- CSS -------------------------------------------- */}
diff --git a/src/components/AppLayout/components/Topbar/components/ParentPagesButton.astro b/src/components/AppLayout/components/Topbar/components/ParentPagesButton.astro
index bf6e0df..74fae03 100644
--- a/src/components/AppLayout/components/Topbar/components/ParentPagesButton.astro
+++ b/src/components/AppLayout/components/Topbar/components/ParentPagesButton.astro
@@ -18,6 +18,7 @@ const { t } = await getI18n(Astro.locals.currentLocale);
 
 <Tooltip trigger="click">
   <div id="tooltip-content" slot="tooltip-content">
+    {/* TODO: Translate */}
     <p>This content is part of these pages:</p>
     {parentPages.map((parentPage) => <ParentPageLink parentPage={parentPage} />)}
   </div>
diff --git a/src/components/Blocks/components/SpacerBlock.astro b/src/components/Blocks/components/SpacerBlock.astro
index efa5318..4977aa0 100644
--- a/src/components/Blocks/components/SpacerBlock.astro
+++ b/src/components/Blocks/components/SpacerBlock.astro
@@ -1,5 +1,5 @@
 ---
-import type { SpacerBlock } from "src/shared/payload/payload-sdk";
+import { SpacerSizes, type SpacerBlock } from "src/shared/payload/payload-sdk";
 
 interface Props {
   block: SpacerBlock;
@@ -7,11 +7,11 @@ interface Props {
 
 const { block } = Astro.props;
 
-const spaceSizeToRem: Record<SpacerBlock["size"], number> = {
-  Small: 1,
-  Medium: 2,
-  Large: 4,
-  XLarge: 8,
+const spaceSizeToRem: Record<SpacerSizes, number> = {
+  [SpacerSizes.Small]: 1,
+  [SpacerSizes.Medium]: 2,
+  [SpacerSizes.Large]: 4,
+  [SpacerSizes.XLarge]: 8,
 };
 ---
 
diff --git a/src/components/Metadata.astro b/src/components/Metadata.astro
index e62a3f8..344bd95 100644
--- a/src/components/Metadata.astro
+++ b/src/components/Metadata.astro
@@ -20,7 +20,7 @@ if (values.length === 0) return;
     <p>{title}</p>
   </div>
   <div id="values">
-    {values.map((value) => <div class="pill">{value}</div>)}
+    {values.map((value) => <div>{value}</div>)}
   </div>
 </div>
 
@@ -54,12 +54,13 @@ if (values.length === 0) return;
       flex-wrap: wrap;
       gap: 6px;
 
-      & > .pill {
+      & > div {
         border: 1px solid var(--color-base-1000);
         border-radius: 9999px;
         padding-top: 0.15em;
         padding-bottom: 0.25em;
         padding-inline: 0.6em;
+        backdrop-filter: blur(10px);
       }
     }
   }
diff --git a/src/components/Previews/CollectiblePreview.astro b/src/components/Previews/CollectiblePreview.astro
new file mode 100644
index 0000000..35d4022
--- /dev/null
+++ b/src/components/Previews/CollectiblePreview.astro
@@ -0,0 +1,73 @@
+---
+import { getI18n } from "src/i18n/i18n";
+import type { EndpointCollectiblePreview } from "src/shared/payload/payload-sdk";
+
+interface Props {
+  collectible: EndpointCollectiblePreview;
+}
+
+const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
+
+const {
+  collectible: { slug, translations, thumbnail },
+} = Astro.props;
+
+const { title, pretitle, subtitle } = getLocalizedMatch(translations);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<a href={getLocalizedUrl(`/collectibles/${slug}`)} class="pressable">
+  {
+    thumbnail && (
+      <img src={thumbnail.url} width={thumbnail.width} height={thumbnail.height} alt="" />
+    )
+  }
+
+  <p>
+    {pretitle && <span id="pretitle">{pretitle}&nbsp;</span>}
+    <span id="title">{title}&nbsp;</span>
+    {subtitle && <span id="subtitle">{subtitle}</span>}
+  </p>
+</a>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  a {
+    padding: 1em;
+    border-radius: 1em;
+    color: var(--color-base-1000);
+  }
+
+  img {
+    width: 100%;
+    height: auto;
+    margin-bottom: 1em;
+  }
+
+  p {
+    line-height: 0.8;
+    display: grid;
+    overflow-wrap: anywhere;
+    font-size: clamp(0.5em, 0.35em + 0.75vw, 1em);
+    font-weight: 800;
+
+    & > #pretitle {
+      font-family: var(--font-sans-serifs);
+      font-weight: 400;
+      margin-bottom: 0.8em;
+    }
+
+    & > #title {
+      font-family: var(--font-serif);
+      font-size: 200%;
+    }
+
+    & > #subtitle {
+      font-family: var(--font-serif);
+      font-weight: 600;
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/components/Previews/PagePreview.astro b/src/components/Previews/PagePreview.astro
new file mode 100644
index 0000000..929a8c2
--- /dev/null
+++ b/src/components/Previews/PagePreview.astro
@@ -0,0 +1,73 @@
+---
+import { getI18n } from "src/i18n/i18n";
+import type { EndpointPagePreview } from "src/shared/payload/payload-sdk";
+
+interface Props {
+  page: EndpointPagePreview;
+}
+
+const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
+
+const {
+  page: { slug, translations, thumbnail },
+} = Astro.props;
+
+const { title, pretitle, subtitle } = getLocalizedMatch(translations);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<a href={getLocalizedUrl(`/pages/${slug}`)} class="pressable">
+  {
+    thumbnail && (
+      <img src={thumbnail.url} width={thumbnail.width} height={thumbnail.height} alt="" />
+    )
+  }
+
+  <p>
+    {pretitle && <span id="pretitle">{pretitle}&nbsp;</span>}
+    <span id="title">{title}&nbsp;</span>
+    {subtitle && <span id="subtitle">{subtitle}</span>}
+  </p>
+</a>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  a {
+    padding: 1em;
+    border-radius: 1em;
+    color: var(--color-base-1000);
+  }
+
+  img {
+    width: 100%;
+    height: auto;
+    margin-bottom: 1em;
+  }
+
+  p {
+    line-height: 0.8;
+    display: grid;
+    overflow-wrap: anywhere;
+    font-size: clamp(0.5em, 0.35em + 0.75vw, 1em);
+    font-weight: 800;
+
+    & > #pretitle {
+      font-family: var(--font-sans-serifs);
+      font-weight: 400;
+      margin-bottom: 0.8em;
+    }
+
+    & > #title {
+      font-family: var(--font-serif);
+      font-size: 200%;
+    }
+
+    & > #subtitle {
+      font-family: var(--font-serif);
+      font-weight: 600;
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/components/TableOfContent/components/TableOfContentItem.astro b/src/components/TableOfContent/components/TableOfContentItem.astro
index 52c5502..cd54a13 100644
--- a/src/components/TableOfContent/components/TableOfContentItem.astro
+++ b/src/components/TableOfContent/components/TableOfContentItem.astro
@@ -11,7 +11,7 @@ const { entry } = Astro.props;
 {/* ------------------------------------------- HTML ------------------------------------------- */}
 
 <li data-prefix={entry.prefix}>
-  <a href={`#${entry.prefix}`}>{entry.title}</a>
+  <a href={`#${entry.prefix}`} class="pressable-link">{entry.title}</a>
   {
     entry.children.length > 0 && (
       <ol>
@@ -28,21 +28,6 @@ const { entry } = Astro.props;
 <style>
   a {
     font-weight: 500;
-    text-decoration: underline dotted 0.1em;
-    text-decoration-color: transparent;
-
-    transition-duration: 150ms;
-    transition-property: text-decoration-color, color;
-
-    &:hover {
-      color: var(--color-base-750);
-      text-decoration-color: var(--color-base-650);
-    }
-
-    &:active {
-      color: var(--color-base-650);
-      text-decoration-color: var(--color-base-550);
-    }
   }
 
   li {
diff --git a/src/components/TagGroups.astro b/src/components/TagGroups.astro
index 215e0e9..2bcd8a7 100644
--- a/src/components/TagGroups.astro
+++ b/src/components/TagGroups.astro
@@ -10,19 +10,22 @@ const { tagGroups } = Astro.props;
 
 {/* ------------------------------------------- HTML ------------------------------------------- */}
 
-<div>{tagGroups.map((tag) => <TagGroup {...tag} />)}</div>
+<div>
+  {tagGroups.map((tag) => <TagGroup {...tag} />)}
+  <slot />
+</div>
 
 {/* ------------------------------------------- CSS -------------------------------------------- */}
 
 <style>
   div {
-    @media (max-width: 35rem) {
-      margin-block: 5em;
-      gap: 2em;
-    }
-
-    margin-block: 2em;
     display: grid;
-    gap: 1em;
+    gap: 2em;
+    margin-block: 2em;
+
+    @media (max-width: 35rem) {
+      gap: 3.5em;
+      margin-block: 3.5em;
+    }
   }
 </style>
diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts
index bcaf130..a660765 100644
--- a/src/i18n/i18n.ts
+++ b/src/i18n/i18n.ts
@@ -138,5 +138,48 @@ export const getI18n = async (locale: string) => {
     return getLocalizedMatch(tag.translations).name;
   };
 
-  return { t, getLocalizedMatch, getLocalizedUrl, formatTag, formatTagsGroup };
+  const formatPrice = (price: { amount: number; currency: string }): string =>
+    price.amount.toLocaleString(locale, { style: "currency", currency: price.currency });
+
+  const formatDate = (date: Date): string =>
+    date.toLocaleDateString(locale, { dateStyle: "medium" });
+
+  const formatInches = (sizeInMm: number): string => {
+    return (
+      (sizeInMm * 0.039370078740157).toLocaleString(locale, { maximumFractionDigits: 2 }) + " in"
+    );
+  };
+
+  const formatMillimeters = (sizeInMm: number): string => {
+    return sizeInMm.toLocaleString(locale, { maximumFractionDigits: 0 }) + " mm";
+  };
+
+  const formatPounds = (weightInGrams: number): string => {
+    return (
+      (weightInGrams * 0.002204623).toLocaleString(locale, { maximumFractionDigits: 2 }) + " lb"
+    );
+  };
+
+  const formatGrams = (weightInGrams: number): string => {
+    return weightInGrams.toLocaleString(locale, { maximumFractionDigits: 0 }) + " g";
+  };
+
+  const formatNumber = (number: number, options?: Intl.NumberFormatOptions): string => {
+    return number.toLocaleString(locale, options);
+  };
+
+  return {
+    t,
+    getLocalizedMatch,
+    getLocalizedUrl,
+    formatTag,
+    formatTagsGroup,
+    formatPrice,
+    formatDate,
+    formatInches,
+    formatPounds,
+    formatGrams,
+    formatMillimeters,
+    formatNumber,
+  };
 };
diff --git a/src/i18n/wordings-keys.ts b/src/i18n/wordings-keys.ts
index 1fca0e4..38962d6 100644
--- a/src/i18n/wordings-keys.ts
+++ b/src/i18n/wordings-keys.ts
@@ -50,4 +50,28 @@ export type WordingKey =
   | "footer.license.description"
   | "footer.license.icons.tooltip"
   | "footer.disclaimer"
-  | "header.nav.parentPages.label";
+  | "header.nav.parentPages.label"
+  | "collectibles.releaseDate"
+  | "collectibles.size"
+  | "collectibles.size.width"
+  | "collectibles.size.height"
+  | "collectibles.size.thickness"
+  | "collectibles.availability.available"
+  | "collectibles.availability.notAvailable.future"
+  | "collectibles.availability.notAvailable.past"
+  | "collectibles.availability.notAvailable.noPrice"
+  | "collectibles.availability.notAvailable"
+  | "collectibles.price"
+  | "collectibles.price.free"
+  | "collectibles.bookFormat"
+  | "collectibles.bookFormat.pageCount"
+  | "collectibles.bookFormat.binding.paperback"
+  | "collectibles.bookFormat.binding.hardcover"
+  | "collectibles.bookFormat.binding.readingDirection.leftToRight"
+  | "collectibles.bookFormat.binding.readingDirection.rightToLeft"
+  | "collectibles.gallery"
+  | "collectibles.scans"
+  | "collectibles.imageCount"
+  | "header.topbar.settings.tooltip"
+  | "collectibles.contents"
+  | "collectibles.weight";
diff --git a/src/pages/[locale]/api/pages/partial.astro b/src/pages/[locale]/api/pages/partial.astro
index be768e8..425acb5 100644
--- a/src/pages/[locale]/api/pages/partial.astro
+++ b/src/pages/[locale]/api/pages/partial.astro
@@ -76,9 +76,13 @@ const translation = getLocalizedMatch(page.translations);
         <Credits translators={translation.translators} proofreaders={translation.proofreaders} />
       </div>
 
-      <div class="when-not-large meta-container">
-        <TableOfContent toc={translation.toc} />
-      </div>
+      {
+        translation.toc.length > 0 && (
+          <div class="when-not-large meta-container">
+            <TableOfContent toc={translation.toc} />
+          </div>
+        )
+      }
 
       <hr />
       <div id="text">
@@ -110,10 +114,14 @@ const translation = getLocalizedMatch(page.translations);
             />
           )
         }
-        <Credits translators={translation.translators} proofreaders={translation.proofreaders} />
+        <Credits
+          translators={translation.translators}
+          transcribers={translation.transcribers}
+          proofreaders={translation.proofreaders}
+        />
       </div>
 
-      <TableOfContent toc={translation.toc} />
+      {translation.toc.length > 0 && <TableOfContent toc={translation.toc} />}
     </div>
   </div>
 </MasoTarget>
diff --git a/src/pages/[locale]/collectibles/[slug].astro b/src/pages/[locale]/collectibles/[slug].astro
new file mode 100644
index 0000000..b343449
--- /dev/null
+++ b/src/pages/[locale]/collectibles/[slug].astro
@@ -0,0 +1,269 @@
+---
+import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
+import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
+import RichText from "components/RichText/RichText.astro";
+import TagGroups from "components/TagGroups.astro";
+import { getI18n } from "src/i18n/i18n";
+import { payload } from "src/shared/payload/payload-sdk";
+import { fetchOr404 } from "src/utils/responses";
+import ImageTile from "./_components/ImageTile.astro";
+import PriceInfo from "./_components/PriceInfo.astro";
+import SizeInfo from "./_components/SizeInfo.astro";
+import ReleaseDateInfo from "./_components/ReleaseDateInfo.astro";
+import PageInfo from "./_components/PageInfo.astro";
+import AvailabilityInfo from "./_components/AvailabilityInfo.astro";
+import WeightInfo from "./_components/WeightInfo.astro";
+import SubitemSection from "./_components/SubitemSection.astro";
+import ContentsSection from "./_components/ContentsSection/ContentsSection.astro";
+
+const { slug } = Astro.params;
+const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
+
+const collectible = await fetchOr404(() => payload.getCollectible(slug!));
+if (collectible instanceof Response) {
+  return collectible;
+}
+
+const {
+  translations,
+  thumbnail,
+  size,
+  price,
+  releaseDate,
+  pageInfo,
+  urls,
+  weight,
+  backgroundImage,
+  gallery,
+  scans,
+  subitems,
+  parentPages,
+  tagGroups,
+  contents,
+} = collectible;
+
+const translation = getLocalizedMatch(translations);
+
+const galleryFirstImage = gallery[0];
+const scansFirstImage = scans[0];
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<AppEmptyLayout
+  parentPages={parentPages}
+  backgroundIllustration={backgroundImage?.url ?? thumbnail?.url}>
+  <div id="layout">
+    <div id="left">
+      <AppLayoutTitle
+        title={translation.title}
+        pretitle={translation.pretitle}
+        subtitle={translation.subtitle}
+      />
+
+      <div id="images" class="when-not-large">
+        {
+          thumbnail && (
+            <img
+              id="thumbnail"
+              src={thumbnail.url}
+              width={thumbnail.width}
+              height={thumbnail.height}
+            />
+          )
+        }
+
+        <div id="gallery-scans" class="when-no-print">
+          {
+            galleryFirstImage && (
+              <ImageTile
+                image={galleryFirstImage.url}
+                title="Gallery"
+                subtitle={`${gallery.length} images`}
+              />
+            )
+          }
+
+          {
+            scansFirstImage && (
+              <ImageTile
+                image={scansFirstImage.url}
+                title="Scans"
+                subtitle={`${scans.length} images`}
+              />
+            )
+          }
+        </div>
+      </div>
+
+      {
+        translation.description && (
+          <div id="summary" class="high-contrast-text">
+            <RichText content={translation.description} />
+          </div>
+        )
+      }
+
+      <TagGroups tagGroups={tagGroups}>
+        {releaseDate && <ReleaseDateInfo releaseDate={releaseDate} />}
+
+        {price && <PriceInfo price={price} />}
+
+        <AvailabilityInfo urls={urls} price={price !== undefined} releaseDate={releaseDate} />
+
+        {size && <SizeInfo size={size} />}
+
+        {weight && <WeightInfo weight={weight} />}
+
+        {pageInfo && <PageInfo pageInfo={pageInfo} />}
+      </TagGroups>
+
+      {subitems.length > 0 && <SubitemSection subitems={subitems} />}
+
+      {contents.length > 0 && <ContentsSection contents={contents} />}
+    </div>
+
+    <div id="right" class="when-large">
+      <div id="images">
+        <div id="gallery-scans" class="when-no-print">
+          {
+            galleryFirstImage && (
+              <ImageTile
+                image={galleryFirstImage.url}
+                title={t("collectibles.gallery")}
+                subtitle={t("collectibles.imageCount", { count: gallery.length })}
+              />
+            )
+          }
+
+          {
+            scansFirstImage && (
+              <ImageTile
+                image={scansFirstImage.url}
+                title={t("collectibles.scans")}
+                subtitle={t("collectibles.imageCount", { count: scans.length })}
+              />
+            )
+          }
+        </div>
+
+        {
+          thumbnail && (
+            <img
+              id="thumbnail"
+              src={thumbnail.url}
+              width={thumbnail.width}
+              height={thumbnail.height}
+            />
+          )
+        }
+      </div>
+    </div>
+  </div>
+</AppEmptyLayout>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #layout {
+    display: grid;
+    justify-content: space-between;
+    container-type: inline-size;
+
+    @media (min-width: 80rem) {
+      grid-template-columns: 35rem 35rem;
+    }
+
+    & > #left {
+      & > #images {
+        display: grid;
+        place-content: start;
+        place-items: start;
+        margin-block: 2em;
+        gap: clamp(1em, 0.5em + 3vw, 2em);
+        grid-template-columns: 1fr;
+
+        @media (max-width: 23rem) {
+          gap: 2.5em;
+        }
+
+        @media (min-width: 52rem) {
+          grid-template-columns: 35rem 10rem;
+        }
+
+        & > #thumbnail {
+          width: 100%;
+          height: auto;
+          box-shadow: 0 5px 20px -10px var(--color-shadow);
+          max-width: 35rem;
+        }
+
+        & > #gallery-scans {
+          display: flex;
+          max-width: 35rem;
+          flex-direction: column;
+          gap: 2.5em;
+          width: 100%;
+
+          > :global(div) {
+            aspect-ratio: 2 / 1;
+          }
+
+          @media (min-width: 23rem) {
+            gap: clamp(1em, 0.5em + 3vw, 2em);
+            flex-direction: row;
+
+            > :global(div) {
+              aspect-ratio: 1 / 1;
+            }
+
+            @media (min-width: 52rem) {
+              max-width: 15rem;
+              flex-direction: column;
+            }
+          }
+        }
+      }
+
+      & > #summary {
+        backdrop-filter: blur(5px);
+        padding: 1.5em;
+        margin: -1.5em;
+        margin-block: 1em;
+        border-radius: 3em;
+      }
+    }
+
+    & > #right {
+      & > #images {
+        display: grid;
+        grid-template-columns: 10rem 1fr;
+        gap: 1em;
+
+        & > #gallery-scans {
+          display: flex;
+          flex-direction: column;
+          gap: 1em;
+        }
+
+        & > #thumbnail {
+          width: 100%;
+          height: auto;
+          box-shadow: 0 5px 20px -10px var(--color-shadow);
+        }
+      }
+    }
+  }
+
+  .when-large {
+    @media (max-width: 80rem) {
+      display: none !important;
+    }
+  }
+
+  .when-not-large {
+    @media (min-width: 80rem) {
+      display: none !important;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/AvailabilityInfo.astro b/src/pages/[locale]/collectibles/_components/AvailabilityInfo.astro
new file mode 100644
index 0000000..7adfd73
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/AvailabilityInfo.astro
@@ -0,0 +1,106 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
+
+interface Props {
+  urls: EndpointCollectible["urls"];
+  releaseDate?: string | undefined;
+  price?: boolean;
+}
+
+const { price = false, urls, releaseDate } = Astro.props;
+const { t } = await getI18n(Astro.locals.currentLocale);
+
+const title = (() => {
+  if (urls.length > 0) return t("collectibles.availability.available");
+
+  if (price) {
+    if (!releaseDate) return t("collectibles.availability.notAvailable");
+    const release = new Date(releaseDate);
+    if (release > new Date()) {
+      return t("collectibles.availability.notAvailable.future");
+    } else {
+      return t("collectibles.availability.notAvailable.past");
+    }
+  }
+
+  return t("collectibles.availability.notAvailable.noPrice");
+})();
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="container">
+  <div id="title">
+    <Icon name="material-symbols:shopping-cart-outline" width={24} height={24} />
+    <p>{title}</p>
+  </div>
+
+  {
+    urls.length > 0 && (
+      <div id="values">
+        {urls.map(({ label, url }) => (
+          <a target="_blank" rel="noopener noreferrer" href={url}>
+            {label}
+          </a>
+        ))}
+      </div>
+    )
+  }
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #container {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 0.5em 1em;
+    align-items: center;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+      }
+    }
+
+    & > #values {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 6px;
+      margin-top: 0.35em;
+
+      & > a {
+        border: 1px solid var(--color-base-1000);
+        border-radius: 9999px;
+        padding-top: 0.15em;
+        padding-bottom: 0.25em;
+        padding-inline: 0.6em;
+        backdrop-filter: blur(10px);
+
+        transition-duration: 150ms;
+        transition-property: border-color, color;
+
+        &:hover {
+          color: var(--color-base-750);
+          border-color: var(--color-base-750);
+        }
+
+        &:active {
+          color: var(--color-base-650);
+          border-color: var(--color-base-650);
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/ContentsSection/ContentRow.astro b/src/pages/[locale]/collectibles/_components/ContentsSection/ContentRow.astro
new file mode 100644
index 0000000..811bf51
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/ContentsSection/ContentRow.astro
@@ -0,0 +1,93 @@
+---
+import ErrorMessage from "components/ErrorMessage.astro";
+import RichText from "components/RichText/RichText.astro";
+import { getI18n } from "src/i18n/i18n";
+import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
+import { formatInlineTitle } from "src/utils/format";
+
+interface Props {
+  content: EndpointCollectible["contents"][number];
+}
+
+const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
+const {
+  content: { content, range },
+} = Astro.props;
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="row">
+  <div id="title">
+    {
+      content.relationTo === "generic-contents" ? (
+        <p>{getLocalizedMatch(content.value.translations).name}</p>
+      ) : content.relationTo === "pages" ? (
+        <a href={getLocalizedUrl(`/pages/${content.value.slug}`)} class="pressable-link">
+          {formatInlineTitle(getLocalizedMatch(content.value.translations))}
+        </a>
+      ) : (
+        <ErrorMessage
+          title="Unknown content type"
+          description="Please contact website technical administrator."
+        />
+      )
+    }
+  </div>
+
+  <div id="dots"></div>
+
+  <div id="range">
+    {
+      range && (
+        <>
+          {range.type === "pageRange" ? (
+            range.start
+          ) : range.type === "timeRange" ? (
+            range.start
+          ) : range.type === "other" ? (
+            <RichText content={getLocalizedMatch(range.translations).note} />
+          ) : (
+            <ErrorMessage
+              title="Unknown range type"
+              description="Please contact website technical administrator."
+            />
+          )}
+        </>
+      )
+    }
+  </div>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #row {
+    display: grid;
+    grid-template-columns: auto 1fr auto;
+    gap: 1em;
+    align-items: center;
+    padding: 1em;
+    border-radius: 1em;
+    box-shadow: 0 1px 2px 0 var(--color-shadow-2);
+
+    backdrop-filter: blur(10px);
+    background-color: color-mix(in srgb, var(--color-elevation-2) 50%, transparent);
+
+    border-top: 1px solid var(--color-elevation-2);
+
+    & > #title {
+      padding-bottom: 0.2em;
+    }
+
+    & > #dots {
+      width: 100%;
+      min-width: clamp(1em, 0.5em + 5vw, 5em);
+      border-bottom: 0.15em dotted var(--color-base-500);
+      height: 0.6em;
+    }
+
+    # > #range {
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/ContentsSection/ContentsSection.astro b/src/pages/[locale]/collectibles/_components/ContentsSection/ContentsSection.astro
new file mode 100644
index 0000000..4207d92
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/ContentsSection/ContentsSection.astro
@@ -0,0 +1,47 @@
+---
+import { Icon } from "astro-icon/components";
+import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
+import ContentRow from "./ContentRow.astro";
+import { getI18n } from "src/i18n/i18n";
+
+interface Props {
+  contents: EndpointCollectible["contents"];
+}
+
+const { contents } = Astro.props;
+const { t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="title">
+  <Icon name="material-symbols:list-alt-outline" width={24} height={24} />
+  <p>{t("collectibles.contents")}</p>
+</div>
+
+<div id="contents">
+  {contents.map((content) => <ContentRow content={content} />)}
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #title {
+    margin-top: 6em;
+    margin-bottom: 2em;
+
+    display: flex;
+    place-items: center;
+    gap: 8px;
+
+    & > p {
+      font-size: 1.5em;
+      font-weight: 600;
+    }
+  }
+
+  #contents {
+    display: grid;
+    gap: 8px;
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/ImageTile.astro b/src/pages/[locale]/collectibles/_components/ImageTile.astro
new file mode 100644
index 0000000..ff1c7c0
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/ImageTile.astro
@@ -0,0 +1,58 @@
+---
+interface Props {
+  image: string;
+  title: string;
+  subtitle: string;
+}
+
+const { image, title, subtitle } = Astro.props;
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="tile">
+  <img src={image} />
+
+  <div class="high-contrast-text">
+    <p class="title">{title}</p>
+    <p>{subtitle}</p>
+  </div>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #tile {
+    position: relative;
+    width: 100%;
+    aspect-ratio: 1 / 1;
+    display: grid;
+    background-color: var(--color-elevation-0);
+    place-items: center;
+    overflow: hidden;
+    border-radius: 12px;
+    box-shadow: 0 5px 20px -10px var(--color-shadow);
+
+    & > div {
+      text-align: center;
+      backdrop-filter: blur(5px);
+      padding: 1em;
+      border-radius: 1em;
+
+      & > .title {
+        font-size: 130%;
+        font-weight: 500;
+        margin-bottom: 0.2em;
+      }
+    }
+
+    & > img {
+      position: absolute;
+      inset: 0;
+      opacity: 0.5;
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/PageInfo.astro b/src/pages/[locale]/collectibles/_components/PageInfo.astro
new file mode 100644
index 0000000..082196d
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/PageInfo.astro
@@ -0,0 +1,87 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+import {
+  CollectibleBindingTypes,
+  CollectiblePageOrders,
+  type EndpointCollectible,
+} from "src/shared/payload/payload-sdk";
+
+interface Props {
+  pageInfo: NonNullable<EndpointCollectible["pageInfo"]>;
+}
+
+const {
+  pageInfo: { pageCount, bindingType, pageOrder },
+} = Astro.props;
+
+const { t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="container">
+  <div id="title">
+    <Icon name="material-symbols:note-stack-outline" width={24} height={24} />
+    <p>{t("collectibles.bookFormat")}</p>
+  </div>
+
+  <div id="values">
+    <p>{t("collectibles.bookFormat.pageCount", { count: pageCount })}</p>
+    {
+      bindingType && (
+        <p>
+          {t(
+            bindingType === CollectibleBindingTypes.Hardcover
+              ? "collectibles.bookFormat.binding.hardcover"
+              : "collectibles.bookFormat.binding.paperback"
+          )}
+        </p>
+      )
+    }
+    {
+      pageOrder && (
+        <p>
+          {t(
+            pageOrder === CollectiblePageOrders.LeftToRight
+              ? "collectibles.bookFormat.binding.readingDirection.leftToRight"
+              : "collectibles.bookFormat.binding.readingDirection.rightToLeft"
+          )}
+        </p>
+      )
+    }
+  </div>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #container {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 0.5em 1em;
+    align-items: start;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+      }
+    }
+
+    & > #values {
+      display: flex;
+      flex-direction: column;
+      gap: 0.5em;
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/PriceInfo.astro b/src/pages/[locale]/collectibles/_components/PriceInfo.astro
new file mode 100644
index 0000000..c9cf82e
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/PriceInfo.astro
@@ -0,0 +1,70 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+import { convert } from "src/utils/currencies";
+
+interface Props {
+  price: {
+    amount: number;
+    currency: string;
+  };
+}
+
+const { price } = Astro.props;
+
+const { formatPrice, t } = await getI18n(Astro.locals.currentLocale);
+
+const preferredCurrency = Astro.locals.currentCurrency;
+
+const convertedPrice: Props["price"] = {
+  amount: convert(price.currency, preferredCurrency, price.amount),
+  currency: preferredCurrency,
+};
+
+let priceText = price.amount === 0 ? t("collectibles.price.free") : formatPrice(price);
+
+if (price.amount > 0 && price.currency !== convertedPrice.currency) {
+  priceText += ` (${formatPrice(convertedPrice)})`;
+}
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="container">
+  <div id="title">
+    <Icon name="material-symbols:sell-outline" width={24} height={24} />
+    <p>{t("collectibles.price")}</p>
+  </div>
+
+  {(<p>{priceText}</p>)}
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #container {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 0.5em 1em;
+    align-items: start;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+      }
+    }
+
+    & > p {
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/ReleaseDateInfo.astro b/src/pages/[locale]/collectibles/_components/ReleaseDateInfo.astro
new file mode 100644
index 0000000..f2b0fa8
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/ReleaseDateInfo.astro
@@ -0,0 +1,53 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+
+interface Props {
+  releaseDate: string;
+}
+
+const { releaseDate } = Astro.props;
+
+const { formatDate, t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="container">
+  <div id="title">
+    <Icon name="material-symbols:calendar-month-outline" width={24} height={24} />
+    <p>{t("collectibles.releaseDate")}</p>
+  </div>
+
+  <p>{formatDate(new Date(releaseDate))}</p>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #container {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 0.5em 1em;
+    align-items: center;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+      }
+    }
+
+    & > p {
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/SizeInfo.astro b/src/pages/[locale]/collectibles/_components/SizeInfo.astro
new file mode 100644
index 0000000..5409097
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/SizeInfo.astro
@@ -0,0 +1,91 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+
+interface Props {
+  size: {
+    width: number;
+    height: number;
+    thickness?: number;
+  };
+}
+
+const { size } = Astro.props;
+
+const { formatInches, formatMillimeters, t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="size">
+  <div id="title">
+    <Icon name="material-symbols:measuring-tape-outline" width={24} height={24} />
+    <p>{t("collectibles.size")}</p>
+  </div>
+
+  <div id="values">
+    <div>
+      <p>{t("collectibles.size.width")}</p>
+      <p>{formatMillimeters(size.width)}</p>
+      <p>{formatInches(size.width)}</p>
+    </div>
+    <div>
+      <p>{t("collectibles.size.height")}</p>
+      <p>{formatMillimeters(size.height)}</p>
+      <p>{formatInches(size.height)}</p>
+    </div>
+    {
+      size.thickness && (
+        <div>
+          <p>{t("collectibles.size.thickness")}</p>
+          <p>{formatMillimeters(size.thickness)}</p>
+          <p>{formatInches(size.thickness)}</p>
+        </div>
+      )
+    }
+  </div>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #size {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 1em 2em;
+    align-items: start;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+        translate: 0px -0.1em;
+      }
+    }
+
+    & > #values {
+      display: flex;
+      gap: 1em 1.5em;
+
+      & > div {
+        display: flex;
+        flex-direction: column;
+        gap: 0.6em;
+
+        & > p:first-child {
+          font-size: 120%;
+          font-weight: 500;
+          margin-top: 3px;
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/SubitemSection.astro b/src/pages/[locale]/collectibles/_components/SubitemSection.astro
new file mode 100644
index 0000000..d58607a
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/SubitemSection.astro
@@ -0,0 +1,48 @@
+---
+import { Icon } from "astro-icon/components";
+import CollectiblePreview from "components/Previews/CollectiblePreview.astro";
+import { getI18n } from "src/i18n/i18n";
+import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
+
+interface Props {
+  subitems: EndpointCollectible["subitems"];
+}
+
+const { subitems } = Astro.props;
+const { t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="title">
+  <Icon name="material-symbols:box-outline" width={24} height={24} />
+  <p>{t("collectibles.contents")}</p>
+</div>
+
+<div id="values">
+  {subitems.map((subitem) => <CollectiblePreview collectible={subitem} />)}
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #title {
+    margin-top: 6em;
+    margin-bottom: 2em;
+
+    display: flex;
+    place-items: center;
+    gap: 8px;
+
+    & > p {
+      font-size: 1.5em;
+      font-weight: 600;
+    }
+  }
+
+  #values {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+    gap: clamp(6px, 2vmin, 16px);
+  }
+</style>
diff --git a/src/pages/[locale]/collectibles/_components/WeightInfo.astro b/src/pages/[locale]/collectibles/_components/WeightInfo.astro
new file mode 100644
index 0000000..a4a7b70
--- /dev/null
+++ b/src/pages/[locale]/collectibles/_components/WeightInfo.astro
@@ -0,0 +1,52 @@
+---
+import { Icon } from "astro-icon/components";
+import { getI18n } from "src/i18n/i18n";
+
+interface Props {
+  weight: number;
+}
+
+const { weight } = Astro.props;
+const { formatPounds, formatGrams, t } = await getI18n(Astro.locals.currentLocale);
+---
+
+{/* ------------------------------------------- HTML ------------------------------------------- */}
+
+<div id="container">
+  <div id="title">
+    <Icon name="material-symbols:scale-outline" width={24} height={24} />
+    <p>{t("collectibles.weight")}</p>
+  </div>
+
+  <p>{formatGrams(weight)}{" "}({formatPounds(weight)})</p>
+</div>
+
+{/* ------------------------------------------- CSS -------------------------------------------- */}
+
+<style>
+  #container {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 0.5em 1em;
+    align-items: center;
+
+    @media (max-width: 35em) {
+      grid-template-columns: 1fr;
+    }
+
+    & > #title {
+      display: flex;
+      place-items: center;
+      gap: 8px;
+
+      & > p {
+        font-size: 1.5em;
+        font-weight: 600;
+      }
+    }
+
+    & > p {
+      margin-top: 0.5em;
+    }
+  }
+</style>
diff --git a/src/pages/[locale]/folders/[slug].astro b/src/pages/[locale]/folders/[slug].astro
index 0a56c86..e114bad 100644
--- a/src/pages/[locale]/folders/[slug].astro
+++ b/src/pages/[locale]/folders/[slug].astro
@@ -6,9 +6,11 @@ import FoldersSection from "./_components/FoldersSection.astro";
 import { fetchOr404 } from "src/utils/responses";
 import ErrorMessage from "components/ErrorMessage.astro";
 import { getI18n } from "src/i18n/i18n";
+import CollectiblePreview from "components/Previews/CollectiblePreview.astro";
+import PagePreview from "components/Previews/PagePreview.astro";
 
 const { slug } = Astro.params;
-const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
+const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
 
 const folder = await fetchOr404(() => payload.getFolder(slug!));
 if (folder instanceof Response) {
@@ -50,19 +52,15 @@ const meta = getLocalizedMatch(folder.translations);
       )
     }
 
-    <div>
+    <div id="files">
       {
         folder.files.map(({ relationTo, value }) => {
           switch (relationTo) {
-            case "library-items":
-              return <p>Library item not supported yet! {value.slug}</p>;
+            case "collectibles":
+              return <CollectiblePreview collectible={value} />;
 
             case "pages":
-              return (
-                <a class="pressable" href={getLocalizedUrl(`/pages/${value.slug}`)}>
-                  {value.slug}
-                </a>
-              );
+              return <PagePreview page={value} />;
 
             default:
               return (
@@ -85,9 +83,16 @@ const meta = getLocalizedMatch(folder.translations);
     display: grid;
     gap: 4em;
 
-    #sections {
+    & > #sections {
       display: grid;
       gap: 2.5em;
     }
+
+    & > #files {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+      gap: clamp(6px, 2vmin, 16px);
+      place-items: start;
+    }
   }
 </style>
diff --git a/src/pages/[locale]/index.astro b/src/pages/[locale]/index.astro
index 48b29d9..1059a24 100644
--- a/src/pages/[locale]/index.astro
+++ b/src/pages/[locale]/index.astro
@@ -14,7 +14,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
 
 <AppLayout
   title="Accord’s Library"
-  backgroundIllustration="/img/bg-home2.webp"
+  backgroundIllustration="/img/background-image.webp"
   hideFooterLinks
   hideHomeButton>
   <div id="title" slot="header-title">
diff --git a/src/pages/[locale]/pages/[slug].astro b/src/pages/[locale]/pages/[slug].astro
index 13e1c94..ee007a0 100644
--- a/src/pages/[locale]/pages/[slug].astro
+++ b/src/pages/[locale]/pages/[slug].astro
@@ -14,6 +14,8 @@ if (page instanceof Response) {
 
 {/* ------------------------------------------- HTML ------------------------------------------- */}
 
-<AppEmptyLayout parentPages={page.parentPages} backgroundIllustration={page.thumbnail?.url}>
+<AppEmptyLayout
+  parentPages={page.parentPages}
+  backgroundIllustration={page.backgroundImage?.url ?? page.thumbnail?.url}>
   <Page slug={page.slug} lang={Astro.locals.currentLocale} page={page} />
 </AppEmptyLayout>
diff --git a/src/pages/[locale]/settings/index.astro b/src/pages/[locale]/settings/index.astro
index bad5942..e6029ef 100644
--- a/src/pages/[locale]/settings/index.astro
+++ b/src/pages/[locale]/settings/index.astro
@@ -2,6 +2,8 @@
 import AppLayout from "components/AppLayout/AppLayout.astro";
 import { getI18n } from "src/i18n/i18n";
 import { cache } from "src/utils/cachedPayload";
+import { formatCurrency } from "src/utils/currencies";
+import { formatLocale } from "src/utils/format";
 
 const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
 const { t } = await getI18n(currentLocale);
@@ -20,7 +22,7 @@ const { t } = await getI18n(currentLocale);
             class:list={{ current: currentLocale === id }}
             href={`?action-lang=${id}`}
             data-astro-prefetch="tap">
-            {id}
+            {formatLocale(id)}
           </a>
         ))
       }
@@ -50,15 +52,15 @@ const { t } = await getI18n(currentLocale);
     </div>
 
     <div class="section">
-      <h2>{t("settings.theme.title")}</h2>
-      <p>{t("settings.theme.description")}</p><br />
+      <h2>{t("settings.currency.title")}</h2>
+      <p>{t("settings.currency.description")}</p><br />
       {
         cache.currencies.map((id) => (
           <a
             class:list={{ current: currentCurrency === id }}
             href={`?action-currency=${id}`}
             data-astro-prefetch="tap">
-            {id}
+            {`${id} (${formatCurrency(id)})`}
           </a>
         ))
       }
@@ -72,6 +74,7 @@ const { t } = await getI18n(currentLocale);
   .section {
     display: flex;
     flex-direction: column;
+    align-items: start;
     gap: 0.5em;
 
     & > .current {
diff --git a/src/shared/payload/payload-sdk.ts b/src/shared/payload/payload-sdk.ts
index 68cd782..6a571d1 100644
--- a/src/shared/payload/payload-sdk.ts
+++ b/src/shared/payload/payload-sdk.ts
@@ -35,41 +35,35 @@ export type RecorderBiographies =
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "CategoryTranslations".
  */
-export type CategoryTranslations =
-  | {
-      language: string | Language;
-      name: string;
-      id?: string | null;
-    }[]
-  | null;
+export type CategoryTranslations = {
+  language: string | Language;
+  name: string;
+  id?: string | null;
+}[];
 
 export interface Config {
   collections: {
     folders: Folder;
     'folders-thumbnails': FoldersThumbnail;
-    'library-items': LibraryItem;
     pages: Page;
     'chronology-items': ChronologyItem;
     'chronology-eras': ChronologyEra;
     weapons: Weapon;
     'weapons-groups': WeaponsGroup;
     'weapons-thumbnails': WeaponsThumbnail;
-    'library-items-thumbnails': LibraryItemThumbnail;
-    'library-items-scans': LibraryItemScans;
-    'library-items-gallery': LibraryItemGallery;
     'recorders-thumbnails': RecordersThumbnail;
-    files: File;
     notes: Note;
     videos: Video;
     'videos-channels': VideosChannel;
     languages: Language;
     currencies: Currency;
     recorders: Recorder;
-    keys: Key;
     tags: Tag;
     'tags-groups': TagsGroup;
     images: Image;
     wordings: Wording;
+    collectibles: Collectible;
+    'generic-contents': GenericContent;
     'payload-preferences': PayloadPreference;
     'payload-migrations': PayloadMigration;
   };
@@ -123,8 +117,8 @@ export interface Folder {
   files?:
     | (
         | {
-            relationTo: 'library-items';
-            value: string | LibraryItem;
+            relationTo: 'collectibles';
+            value: string | Collectible;
           }
         | {
             relationTo: 'pages';
@@ -170,21 +164,41 @@ export interface Language {
 }
 /**
  * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "library-items".
+ * via the `definition` "collectibles".
  */
-export interface LibraryItem {
+export interface Collectible {
   id: string;
-  itemType?: ('Textual' | 'Audio' | 'Video' | 'Game' | 'Other') | null;
-  language: string | Language;
   slug: string;
-  thumbnail?: string | LibraryItemThumbnail | null;
-  pretitle?: string | null;
-  title: string;
-  subtitle?: string | null;
-  digital: boolean;
+  thumbnail?: string | Image | null;
+  nature: 'Physical' | 'Digital';
+  languages?: (string | Language)[] | null;
+  tags?: (string | Tag)[] | null;
+  translations: {
+    language: string | Language;
+    pretitle?: string | null;
+    title: string;
+    subtitle?: string | null;
+    description?: {
+      root: {
+        children: {
+          type: string;
+          version: number;
+          [k: string]: unknown;
+        }[];
+        direction: ('ltr' | 'rtl') | null;
+        format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
+        indent: number;
+        type: string;
+        version: number;
+      };
+      [k: string]: unknown;
+    } | null;
+    id?: string | null;
+  }[];
+  backgroundImage?: string | Image | null;
   gallery?:
     | {
-        image?: string | LibraryItemGallery | null;
+        image: string | Image;
         id?: string | null;
       }[]
     | null;
@@ -195,141 +209,136 @@ export interface LibraryItem {
     typesetters?: (string | Recorder)[] | null;
     coverEnabled?: boolean | null;
     cover?: {
-      front?: string | LibraryItemScans | null;
-      spine?: string | LibraryItemScans | null;
-      back?: string | LibraryItemScans | null;
-      insideFront?: string | LibraryItemScans | null;
-      insideBack?: string | LibraryItemScans | null;
-      flapFront?: string | LibraryItemScans | null;
-      flapBack?: string | LibraryItemScans | null;
-      insideFlapFront?: string | LibraryItemScans | null;
-      insideFlapBack?: string | LibraryItemScans | null;
+      front?: string | Image | null;
+      spine?: string | Image | null;
+      back?: string | Image | null;
+      insideFront?: string | Image | null;
+      insideBack?: string | Image | null;
+      flapFront?: string | Image | null;
+      flapBack?: string | Image | null;
+      insideFlapFront?: string | Image | null;
+      insideFlapBack?: string | Image | null;
     };
     dustjacketEnabled?: boolean | null;
     dustjacket?: {
-      front?: string | LibraryItemScans | null;
-      spine?: string | LibraryItemScans | null;
-      back?: string | LibraryItemScans | null;
-      insideFront?: string | LibraryItemScans | null;
-      insideSpine?: string | LibraryItemScans | null;
-      insideBack?: string | LibraryItemScans | null;
-      flapFront?: string | LibraryItemScans | null;
-      flapBack?: string | LibraryItemScans | null;
-      insideFlapFront?: string | LibraryItemScans | null;
-      insideFlapBack?: string | LibraryItemScans | null;
+      front?: string | Image | null;
+      spine?: string | Image | null;
+      back?: string | Image | null;
+      insideFront?: string | Image | null;
+      insideSpine?: string | Image | null;
+      insideBack?: string | Image | null;
+      flapFront?: string | Image | null;
+      flapBack?: string | Image | null;
+      insideFlapFront?: string | Image | null;
+      insideFlapBack?: string | Image | null;
     };
     obiEnabled?: boolean | null;
     obi?: {
-      front?: string | LibraryItemScans | null;
-      spine?: string | LibraryItemScans | null;
-      back?: string | LibraryItemScans | null;
-      insideFront?: string | LibraryItemScans | null;
-      insideSpine?: string | LibraryItemScans | null;
-      insideBack?: string | LibraryItemScans | null;
-      flapFront?: string | LibraryItemScans | null;
-      flapBack?: string | LibraryItemScans | null;
-      insideFlapFront?: string | LibraryItemScans | null;
-      insideFlapBack?: string | LibraryItemScans | null;
+      front?: string | Image | null;
+      spine?: string | Image | null;
+      back?: string | Image | null;
+      insideFront?: string | Image | null;
+      insideSpine?: string | Image | null;
+      insideBack?: string | Image | null;
+      flapFront?: string | Image | null;
+      flapBack?: string | Image | null;
+      insideFlapFront?: string | Image | null;
+      insideFlapBack?: string | Image | null;
     };
     pages?:
       | {
           page: number;
-          image: string | LibraryItemScans;
-          id?: string | null;
-        }[]
-      | null;
-    archiveFile?: (string | null) | File;
-  };
-  textual?: {
-    subtype?: (string | null) | Key;
-    pageCount?: number | null;
-    bindingType?: ('Paperback' | 'Hardcover') | null;
-    pageOrder?: ('LeftToRight' | 'RightToLeft') | null;
-  };
-  audio?: {
-    audioSubtype?: (string | null) | Key;
-    tracks?:
-      | {
-          title: string;
-          file: string | File;
+          image: string | Image;
           id?: string | null;
         }[]
       | null;
   };
-  video?: {
-    subtype?: (string | null) | Key;
-  };
-  game?: {
-    demo?: boolean | null;
-    platform?: (string | null) | Key;
-    audioLanguages?: (string | Language)[] | null;
-    subtitleLanguages?: (string | Language)[] | null;
-    interfacesLanguages?: (string | Language)[] | null;
-  };
-  releaseDate?: string | null;
-  categories?: (string | Key)[] | null;
-  sizeEnabled?: boolean | null;
-  size?: {
-    width: number;
-    height: number;
-    thickness?: number | null;
-  };
-  priceEnabled?: boolean | null;
-  price?: {
-    amount: number;
-    currency: string | Currency;
-  };
-  translations?:
-    | {
-        language: string | Language;
-        description: {
-          root: {
-            children: {
-              type: string;
-              version: number;
-              [k: string]: unknown;
-            }[];
-            direction: ('ltr' | 'rtl') | null;
-            format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
-            indent: number;
-            type: string;
-            version: number;
-          };
-          [k: string]: unknown;
-        };
-        id?: string | null;
-      }[]
-    | null;
   urls?:
     | {
         url: string;
         id?: string | null;
       }[]
     | null;
-  parentItems?: (string | LibraryItem)[] | null;
-  subitems?: (string | LibraryItem)[] | null;
+  releaseDate?: string | null;
+  priceEnabled?: boolean | null;
+  price?: {
+    amount: number;
+    currency: string | Currency;
+  };
+  sizeEnabled?: boolean | null;
+  size?: {
+    width: number;
+    height: number;
+    thickness?: number | null;
+  };
+  weightEnabled?: boolean | null;
+  weight?: {
+    amount: number;
+  };
+  pageInfoEnabled?: boolean | null;
+  pageInfo?: {
+    pageCount: number;
+    bindingType?: ('Paperback' | 'Hardcover') | null;
+    pageOrder?: ('Left to right' | 'Right to left') | null;
+  };
+  folders?: (string | Folder)[] | null;
+  parentItems?: (string | Collectible)[] | null;
+  subitems?: (string | Collectible)[] | null;
   contents?:
     | {
-        content: string | Page;
-        pageStart?: number | null;
-        pageEnd?: number | null;
-        timeStart?: number | null;
-        timeEnd?: number | null;
-        note?: {
-          root: {
-            children: {
-              type: string;
-              version: number;
-              [k: string]: unknown;
-            }[];
-            direction: ('ltr' | 'rtl') | null;
-            format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
-            indent: number;
-            type: string;
-            version: number;
-          };
-          [k: string]: unknown;
-        } | null;
+        content:
+          | {
+              relationTo: 'pages';
+              value: string | Page;
+            }
+          | {
+              relationTo: 'generic-contents';
+              value: string | GenericContent;
+            };
+        range?:
+          | (
+              | {
+                  start: number;
+                  end: number;
+                  id?: string | null;
+                  blockName?: string | null;
+                  blockType: 'pageRange';
+                }
+              | {
+                  start: string;
+                  end: string;
+                  id?: string | null;
+                  blockName?: string | null;
+                  blockType: 'timeRange';
+                }
+              | {
+                  translations?:
+                    | {
+                        language: string | Language;
+                        note: {
+                          root: {
+                            children: {
+                              type: string;
+                              version: number;
+                              [k: string]: unknown;
+                            }[];
+                            direction: ('ltr' | 'rtl') | null;
+                            format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
+                            indent: number;
+                            type: string;
+                            version: number;
+                          };
+                          [k: string]: unknown;
+                        };
+                        id?: string | null;
+                      }[]
+                    | null;
+                  id?: string | null;
+                  blockName?: string | null;
+                  blockType: 'other';
+                }
+            )[]
+          | null;
         id?: string | null;
       }[]
     | null;
@@ -340,11 +349,10 @@ export interface LibraryItem {
 }
 /**
  * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "library-items-thumbnails".
+ * via the `definition` "images".
  */
-export interface LibraryItemThumbnail {
+export interface Image {
   id: string;
-  libraryItem?: (string | LibraryItem)[] | null;
   updatedAt: string;
   createdAt: string;
   url?: string | null;
@@ -370,48 +378,40 @@ export interface LibraryItemThumbnail {
       filesize?: number | null;
       filename?: string | null;
     };
-    square?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
   };
 }
 /**
  * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "library-items-gallery".
+ * via the `definition` "tags".
  */
-export interface LibraryItemGallery {
+export interface Tag {
   id: string;
+  name?: string | null;
+  slug: string;
+  translations: {
+    language: string | Language;
+    name: string;
+    id?: string | null;
+  }[];
+  group: string | TagsGroup;
+  updatedAt: string;
+  createdAt: string;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "tags-groups".
+ */
+export interface TagsGroup {
+  id: string;
+  slug: string;
+  icon?: string | null;
+  translations: {
+    language: string | Language;
+    name: string;
+    id?: string | null;
+  }[];
   updatedAt: string;
   createdAt: string;
-  url?: string | null;
-  filename?: string | null;
-  mimeType?: string | null;
-  filesize?: number | null;
-  width?: number | null;
-  height?: number | null;
-  sizes?: {
-    thumb?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-    small?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-  };
 }
 /**
  * This interface was referenced by `Config`'s JSON-Schema
@@ -468,86 +468,6 @@ export interface RecordersThumbnail {
     };
   };
 }
-/**
- * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "library-items-scans".
- */
-export interface LibraryItemScans {
-  id: string;
-  updatedAt: string;
-  createdAt: string;
-  url?: string | null;
-  filename?: string | null;
-  mimeType?: string | null;
-  filesize?: number | null;
-  width?: number | null;
-  height?: number | null;
-  sizes?: {
-    thumb?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-    og?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-    medium?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-    large?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-  };
-}
-/**
- * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "files".
- */
-export interface File {
-  id: string;
-  filename: string;
-  type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio';
-  updatedAt: string;
-  createdAt: string;
-}
-/**
- * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "keys".
- */
-export interface Key {
-  id: string;
-  name: string;
-  type:
-    | 'Contents'
-    | 'LibraryAudio'
-    | 'LibraryVideo'
-    | 'LibraryTextual'
-    | 'LibraryGroup'
-    | 'Library'
-    | 'Weapons'
-    | 'GamePlatforms'
-    | 'Categories'
-    | 'Wordings';
-  translations?: CategoryTranslations;
-}
 /**
  * This interface was referenced by `Config`'s JSON-Schema
  * via the `definition` "currencies".
@@ -561,9 +481,10 @@ export interface Currency {
  */
 export interface Page {
   id: string;
-  type: 'Content' | 'Article' | 'Generic';
   slug: string;
+  type: 'Content' | 'Post' | 'Generic';
   thumbnail?: string | Image | null;
+  backgroundImage?: string | Image | null;
   tags?: (string | Tag)[] | null;
   authors?: (string | Recorder)[] | null;
   translations: {
@@ -608,7 +529,7 @@ export interface Page {
     id?: string | null;
   }[];
   folders?: (string | Folder)[] | null;
-  collectibles?: (string | LibraryItem)[] | null;
+  collectibles?: (string | Collectible)[] | null;
   updatedBy: string | Recorder;
   updatedAt: string;
   createdAt: string;
@@ -616,71 +537,16 @@ export interface Page {
 }
 /**
  * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "images".
+ * via the `definition` "generic-contents".
  */
-export interface Image {
+export interface GenericContent {
   id: string;
-  updatedAt: string;
-  createdAt: string;
-  url?: string | null;
-  filename?: string | null;
-  mimeType?: string | null;
-  filesize?: number | null;
-  width?: number | null;
-  height?: number | null;
-  sizes?: {
-    thumb?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-    og?: {
-      url?: string | null;
-      width?: number | null;
-      height?: number | null;
-      mimeType?: string | null;
-      filesize?: number | null;
-      filename?: string | null;
-    };
-  };
-}
-/**
- * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "tags".
- */
-export interface Tag {
-  id: string;
-  name?: string | null;
-  slug: string;
-  translations?:
-    | {
-        language: string | Language;
-        name: string;
-        id?: string | null;
-      }[]
-    | null;
-  group: string | TagsGroup;
-  updatedAt: string;
-  createdAt: string;
-}
-/**
- * This interface was referenced by `Config`'s JSON-Schema
- * via the `definition` "tags-groups".
- */
-export interface TagsGroup {
-  id: string;
-  slug: string;
-  icon?: string | null;
-  translations?:
-    | {
-        language: string | Language;
-        name: string;
-        id?: string | null;
-      }[]
-    | null;
+  name: string;
+  translations: {
+    language: string | Language;
+    name: string;
+    id?: string | null;
+  }[];
   updatedAt: string;
   createdAt: string;
 }
@@ -786,10 +652,8 @@ export interface Weapon {
   id: string;
   slug: string;
   thumbnail?: string | WeaponsThumbnail | null;
-  type: string | Key;
   group?: (string | null) | WeaponsGroup;
   appearances: {
-    categories: (string | Key)[];
     translations: {
       language: string | Language;
       sourceLanguage: string | Language;
@@ -1004,7 +868,7 @@ export interface VideosChannel {
 export interface Wording {
   id: string;
   name: string;
-  translations?: CategoryTranslations;
+  translations: CategoryTranslations;
   updatedAt: string;
   createdAt: string;
 }
@@ -1047,7 +911,7 @@ export interface PayloadMigration {
  * via the `definition` "SpacerBlock".
  */
 export interface SpacerBlock {
-  size: 'Small' | 'Medium' | 'Large' | 'XLarge';
+  size: 'Small' | 'Medium' | 'Large' | 'Extra Large';
   blockType: 'spacerBlock';
 }
 /**
@@ -1139,12 +1003,7 @@ export enum Collections {
   ChronologyItems = "chronology-items",
   Currencies = "currencies",
   Files = "files",
-  Keys = "keys",
   Languages = "languages",
-  LibraryItems = "library-items",
-  LibraryItemsThumbnails = "library-items-thumbnails",
-  LibraryItemsScans = "library-items-scans",
-  LibraryItemsGallery = "library-items-gallery",
   Notes = "notes",
   Pages = "pages",
   PagesThumbnails = "pages-thumbnails",
@@ -1160,7 +1019,9 @@ export enum Collections {
   Tags = "tags",
   TagsGroups = "tags-groups",
   Images = "images",
-  Wordings = "wordings"
+  Wordings = "wordings",
+  Collectibles = "collectibles",
+  GenericContents = "generic-contents",
 }
 
 export enum CollectionGroups {
@@ -1169,19 +1030,6 @@ export enum CollectionGroups {
   Meta = "Meta",
 }
 
-export enum KeysTypes {
-  Contents = "Contents",
-  LibraryAudio = "Library / Audio",
-  LibraryVideo = "Library / Video",
-  LibraryTextual = "Library / Textual",
-  LibraryGroup = "Library / Group",
-  Library = "Library",
-  Weapons = "Weapons",
-  GamePlatforms = "Game Platforms",
-  Categories = "Categories",
-  Wordings = "Wordings",
-}
-
 export enum LanguageCodes {
   en = "English",
   fr = "French",
@@ -1191,31 +1039,27 @@ export enum LanguageCodes {
   "zh" = "Chinese",
 }
 
-export enum FileTypes {
-  LibraryScans = "Library / Scans",
-  LibrarySoundtracks = "Library / Soundtracks",
-  ContentVideo = "Content / Video",
-  ContentAudio = "Content / Audio",
-}
-
-export enum LibraryItemsTypes {
-  Textual = "Textual",
-  Audio = "Audio",
-  Video = "Video",
-  Game = "Game",
-  Other = "Other",
-}
-
-export enum LibraryItemsTextualBindingTypes {
+export enum CollectibleBindingTypes {
   Paperback = "Paperback",
   Hardcover = "Hardcover",
 }
 
-export enum LibraryItemsTextualPageOrders {
+export enum CollectiblePageOrders {
   LeftToRight = "Left to right",
   RightToLeft = "Right to left",
 }
 
+export enum CollectibleNature {
+  Physical = "Physical",
+  Digital = "Digital",
+}
+
+export enum CollectibleContentType {
+  None = "None",
+  Indexes = "Index-based",
+  Pages = "Page-based",
+}
+
 export enum RecordersRoles {
   Admin = "Admin",
   Recorder = "Recorder",
@@ -1235,7 +1079,7 @@ export enum VideoSources {
 
 export enum PageType {
   Content = "Content",
-  Article = "Article",
+  Post = "Post",
   Generic = "Generic",
 }
 
@@ -1583,12 +1427,12 @@ export type EndpointFolder = EndpointFolderPreview & {
       };
   files: (
     | {
-        relationTo: "library-items";
-        value: LibraryItem;
+        relationTo: "collectibles";
+        value: EndpointCollectiblePreview;
       }
     | {
         relationTo: "pages";
-        value: Page;
+        value: EndpointPagePreview;
       }
   )[];
 };
@@ -1616,17 +1460,6 @@ export type EndpointRecorder = {
   }[];
 };
 
-export type EndpointKey = {
-  id: string;
-  name: string;
-  type: Key["type"];
-  translations: {
-    language: string;
-    name: string;
-    short: string;
-  }[];
-};
-
 export type EndpointWording = {
   name: string;
   translations: {
@@ -1654,7 +1487,7 @@ export type EndpointTagsGroup = {
   tags: EndpointTag[];
 };
 
-export type EndpointPage = {
+export type EndpointPagePreview = {
   slug: string;
   type: PageType;
   thumbnail?: PayloadImage;
@@ -1662,18 +1495,24 @@ export type EndpointPage = {
   tagGroups: TagGroup[];
   translations: {
     language: string;
-    sourceLanguage: string;
     pretitle?: string;
     title: string;
     subtitle?: string;
+  }[];
+  status: "draft" | "published";
+};
+
+export type EndpointPage = EndpointPagePreview & {
+  backgroundImage?: PayloadImage;
+  translations: (EndpointPagePreview["translations"][number] & {
+    sourceLanguage: string;
     summary?: RichTextContent;
     content: RichTextContent;
     transcribers: string[];
     translators: string[];
     proofreaders: string[];
     toc: TableOfContentEntry[];
-  }[];
-  status: "draft" | "published";
+  })[];
   parentPages: ParentPage[];
 };
 
@@ -1684,6 +1523,82 @@ export type ParentPage = {
   tag: string;
 };
 
+export type EndpointCollectiblePreview = {
+  slug: string;
+  thumbnail?: PayloadImage;
+  translations: {
+    language: string;
+    pretitle?: string;
+    title: string;
+    subtitle?: string;
+    description?: RichTextContent;
+  }[];
+  tagGroups: TagGroup[];
+  status: "draft" | "published";
+  releaseDate?: string;
+  languages: string[];
+};
+
+export type EndpointCollectible = EndpointCollectiblePreview & {
+  backgroundImage?: PayloadImage;
+  nature: CollectibleNature;
+  gallery: PayloadImage[];
+  scans: PayloadImage[];
+  urls: { url: string; label: string }[];
+  price?: {
+    amount: number;
+    currency: string;
+  };
+  size?: {
+    width: number;
+    height: number;
+    thickness?: number;
+  };
+  weight?: number;
+  pageInfo?: {
+    pageCount: number;
+    bindingType?: CollectibleBindingTypes;
+    pageOrder?: CollectiblePageOrders;
+  };
+  subitems: EndpointCollectiblePreview[];
+  contents: {
+    content:
+      | {
+          relationTo: "pages";
+          value: EndpointPagePreview;
+        }
+      | {
+          relationTo: "generic-contents";
+          value: {
+            translations: {
+              language: string;
+              name: string;
+            }[];
+          };
+        };
+
+    range?:
+      | {
+          type: "pageRange";
+          start: number;
+          end: number;
+        }
+      | {
+          type: "timeRange";
+          start: string;
+          end: string;
+        }
+      | {
+          type: "other";
+          translations: {
+            language: string;
+            note: RichTextContent;
+          }[];
+        };
+  }[];
+  parentPages: ParentPage[];
+};
+
 export type TagGroup = { slug: string; icon: string; values: string[] };
 
 export type TableOfContentEntry = {
@@ -1723,4 +1638,6 @@ export const payload = {
     await (await request(payloadApiUrl(Collections.TagsGroups, `all`))).json(),
   getPage: async (slug: string): Promise<EndpointPage> =>
     await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
+  getCollectible: async (slug: string): Promise<EndpointCollectible> =>
+    await (await request(payloadApiUrl(Collections.Collectibles, `slug/${slug}`))).json(),
 };
diff --git a/src/utils/format.ts b/src/utils/format.ts
index af81bcf..d22cc40 100644
--- a/src/utils/format.ts
+++ b/src/utils/format.ts
@@ -12,3 +12,23 @@ export const formatRecorder = (recorderId: string): string => {
 
   return result.username;
 };
+
+export const formatInlineTitle = ({
+  pretitle,
+  title,
+  subtitle,
+}: {
+  pretitle?: string | undefined;
+  title: string;
+  subtitle?: string | undefined;
+}): string => {
+  let result = "";
+  if (pretitle) {
+    result += `${pretitle}: `;
+  }
+  result += title;
+  if (subtitle) {
+    result += ` — ${subtitle}`;
+  }
+  return result;
+};