diff --git a/api/scrobble.js b/api/scrobble.js
index 1f8152d2..4869a01a 100644
--- a/api/scrobble.js
+++ b/api/scrobble.js
@@ -1,11 +1,20 @@
 import { createClient } from '@supabase/supabase-js'
 import { DateTime } from 'luxon'
+import slugify from 'slugify'
 
 const SUPABASE_URL = Netlify.env.get('SUPABASE_URL')
 const SUPABASE_KEY = Netlify.env.get('SUPABASE_KEY')
 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
 
-const sanitizeMediaString = (string) => string.normalize('NFD').replace(/[\u0300-\u036f\u2010—\.\?\(\)\[\]\{\}]/g, '').replace(/\.{3}/g, '')
+const sanitizeMediaString = (str) => {
+  const sanitizedString = str.normalize('NFD').replace(/[\u0300-\u036f\u2010—\.\?\(\)\[\]\{\}]/g, '').replace(/\.{3}/g, '')
+
+  return slugify(sanitizedString, {
+    replacement: '-',
+    remove: /[#,&,+()$~%.'":*?<>{}]/g,
+    lower: true,
+  })
+}
 
 export default async (request) => {
   const ACCOUNT_ID_PLEX = process.env.ACCOUNT_ID_PLEX
@@ -23,10 +32,10 @@ export default async (request) => {
     const album = payload['Metadata']['parentTitle']
     const track = payload['Metadata']['title']
     const listenedAt = Math.floor(DateTime.now().toSeconds())
-    const artistKey = sanitizeMediaString(artist).replace(/\s+/g, '-').toLowerCase()
-    const albumKey = `${artistKey}-${sanitizeMediaString(album).replace(/\s+/g, '-').toLowerCase()}`
+    const artistKey = sanitizeMediaString(artist)
+    const albumKey = `${artistKey}-${sanitizeMediaString(album)}`
 
-    const { data: artistData, error: artistError } = await supabase
+    const { error: artistError } = await supabase
       .from('artists')
       .select('*')
       .eq('name_string', artist)
@@ -52,7 +61,7 @@ export default async (request) => {
       return new Response(JSON.stringify({ status: 'error', message: artistError.message }), { headers: { "Content-Type": "application/json" } })
     }
 
-    const { data: albumData, error: albumError } = await supabase
+    const { error: albumError } = await supabase
       .from('albums')
       .select('*')
       .eq('key', albumKey)
diff --git a/cache/jsonfeed-to-mastodon.json b/cache/jsonfeed-to-mastodon.json
index d38a46f0..f93a27b6 100644
--- a/cache/jsonfeed-to-mastodon.json
+++ b/cache/jsonfeed-to-mastodon.json
@@ -12477,11 +12477,11 @@
     ],
     "lastTootTimestamp": 1713031404184
   },
-  "https://coryd.dev/movies/9411": {
+  "https://coryd.dev/watching/movies/9411": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvZmFsbGVuLTE5OTg=",
     "title": "🎥: Fallen",
-    "url": "https://coryd.dev/movies/9411",
-    "content_text": "🎥: Fallen #Movies #Watching #Trakt https://coryd.dev/movies/9411",
+    "url": "https://coryd.dev/watching/movies/9411",
+    "content_text": "🎥: Fallen #Movies #Watching #Trakt https://coryd.dev/watching/movies/9411",
     "date_published": "Sat, 13 Apr 2024 23:43:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112267248945790971"
@@ -12510,11 +12510,11 @@
     ],
     "lastTootTimestamp": 1713143465132
   },
-  "https://coryd.dev/movies/359410": {
+  "https://coryd.dev/watching/movies/359410": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvcm9hZC1ob3VzZS0yMDI0",
     "title": "🎥: Road House",
-    "url": "https://coryd.dev/movies/359410",
-    "content_text": "🎥: Road House #Movies #Watching #Trakt https://coryd.dev/movies/359410",
+    "url": "https://coryd.dev/watching/movies/359410",
+    "content_text": "🎥: Road House #Movies #Watching #Trakt https://coryd.dev/watching/movies/359410",
     "date_published": "Sun, 14 Apr 2024 17:06:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112271865248733450"
@@ -12653,11 +12653,11 @@
     ],
     "lastTootTimestamp": 1713645991312
   },
-  "https://coryd.dev/movies/693134": {
+  "https://coryd.dev/watching/movies/693134": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvZHVuZS1wYXJ0LXR3by0yMDI0",
     "title": "🎥: Dune: Part Two",
-    "url": "https://coryd.dev/movies/693134",
-    "content_text": "🎥: Dune: Part Two #Movies #Watching #SciFi #Adventure https://coryd.dev/movies/693134",
+    "url": "https://coryd.dev/watching/movies/693134",
+    "content_text": "🎥: Dune: Part Two #Movies #Watching #SciFi #Adventure https://coryd.dev/watching/movies/693134",
     "date_published": "Fri, 19 Apr 2024 14:00:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112305063432909748"
@@ -12829,11 +12829,11 @@
     ],
     "lastTootTimestamp": 1713981835960
   },
-  "https://coryd.dev/movies/560016": {
+  "https://coryd.dev/watching/movies/560016": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvbW9ua2V5LW1hbi0yMDI0",
     "title": "🎥: Monkey Man",
-    "url": "https://coryd.dev/movies/560016",
-    "content_text": "🎥: Monkey Man #Movies #Watching  #Thriller #Action https://coryd.dev/movies/560016",
+    "url": "https://coryd.dev/watching/movies/560016",
+    "content_text": "🎥: Monkey Man #Movies #Watching  #Thriller #Action https://coryd.dev/watching/movies/560016",
     "date_published": "Wed, 24 Apr 2024 18:14:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112328455422041088"
@@ -12884,11 +12884,11 @@
     ],
     "lastTootTimestamp": 1714233838679
   },
-  "https://coryd.dev/movies/938614": {
+  "https://coryd.dev/watching/movies/938614": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvbGF0ZS1uaWdodC13aXRoLXRoZS1kZXZpbC0yMDI0",
     "title": "🎥: Late Night with the Devil",
-    "url": "https://coryd.dev/movies/938614",
-    "content_text": "🎥: Late Night with the Devil #Movies #Watching  #Horror https://coryd.dev/movies/938614",
+    "url": "https://coryd.dev/watching/movies/938614",
+    "content_text": "🎥: Late Night with the Devil #Movies #Watching  #Horror https://coryd.dev/watching/movies/938614",
     "date_published": "Sat, 27 Apr 2024 18:07:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112345809656993382"
@@ -12983,11 +12983,11 @@
     ],
     "lastTootTimestamp": 1714449885234
   },
-  "https://coryd.dev/movies/520594": {
+  "https://coryd.dev/watching/movies/520594": {
     "id": "aHR0cHM6Ly90cmFrdC50di9tb3ZpZXMvam9obi1tdWxhbmV5LWtpZC1nb3JnZW91cy1hdC1yYWRpby1jaXR5LTIwMTg=",
     "title": "🎥: John Mulaney: Kid Gorgeous at Radio City",
-    "url": "https://coryd.dev/movies/520594",
-    "content_text": "🎥: John Mulaney: Kid Gorgeous at Radio City #Movies #Watching  undefined https://coryd.dev/movies/520594",
+    "url": "https://coryd.dev/watching/movies/520594",
+    "content_text": "🎥: John Mulaney: Kid Gorgeous at Radio City #Movies #Watching  undefined https://coryd.dev/watching/movies/520594",
     "date_published": "Wed, 01 May 2024 05:36:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112364796498005793"
@@ -13368,11 +13368,11 @@
     ],
     "lastTootTimestamp": 1715875416427
   },
-  "https://coryd.dev/movies/1051896": {
+  "https://coryd.dev/watching/movies/1051896": {
     "id": "aHR0cHM6Ly93d3cudGhlbW92aWVkYi5vcmcvbW92aWUvMTA1MTg5Ng==",
     "title": "🎥: Arcadian (⭐️⭐️⭐️)",
-    "url": "https://coryd.dev/movies/1051896",
-    "content_text": "🎥: Arcadian (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/movies/1051896",
+    "url": "https://coryd.dev/watching/movies/1051896",
+    "content_text": "🎥: Arcadian (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/watching/movies/1051896",
     "date_published": "Fri, 17 May 2024 16:00:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112457275817717857"
@@ -13423,11 +13423,11 @@
     ],
     "lastTootTimestamp": 1716064039940
   },
-  "https://coryd.dev/movies/1148027": {
+  "https://coryd.dev/watching/movies/1148027": {
     "id": "aHR0cHM6Ly93d3cudGhlbW92aWVkYi5vcmcvbW92aWUvMTE0ODAyNw==",
     "title": "🎥: New Life (⭐️⭐️⭐️⭐️)",
-    "url": "https://coryd.dev/movies/1148027",
-    "content_text": "🎥: New Life (⭐️⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/movies/1148027",
+    "url": "https://coryd.dev/watching/movies/1148027",
+    "content_text": "🎥: New Life (⭐️⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/watching/movies/1148027",
     "date_published": "Sat, 18 May 2024 20:13:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112464524447203970"
@@ -13478,11 +13478,11 @@
     ],
     "lastTootTimestamp": 1716171323109
   },
-  "https://coryd.dev/movies/29427": {
+  "https://coryd.dev/watching/movies/29427": {
     "id": "aHR0cHM6Ly93d3cudGhlbW92aWVkYi5vcmcvbW92aWUvMjk0Mjc=",
     "title": "🎥: The Crazies (⭐️⭐️⭐️)",
-    "url": "https://coryd.dev/movies/29427",
-    "content_text": "🎥: The Crazies (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/movies/29427",
+    "url": "https://coryd.dev/watching/movies/29427",
+    "content_text": "🎥: The Crazies (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/watching/movies/29427",
     "date_published": "Mon, 20 May 2024 15:15:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112474735239294133"
@@ -13522,11 +13522,11 @@
     ],
     "lastTootTimestamp": 1716307431247
   },
-  "https://coryd.dev/movies/384018": {
+  "https://coryd.dev/watching/movies/384018": {
     "id": "aHR0cHM6Ly93d3cudGhlbW92aWVkYi5vcmcvbW92aWUvMzg0MDE4",
     "title": "🎥: Fast & Furious Presents: Hobbs & Shaw (⭐️⭐️⭐️)",
-    "url": "https://coryd.dev/movies/384018",
-    "content_text": "🎥: Fast & Furious Presents: Hobbs & Shaw (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/movies/384018",
+    "url": "https://coryd.dev/watching/movies/384018",
+    "content_text": "🎥: Fast & Furious Presents: Hobbs & Shaw (⭐️⭐️⭐️) #Movies #Watching https://coryd.dev/watching/movies/384018",
     "date_published": "Tue, 21 May 2024 15:49:00 GMT",
     "toots": [
       "https://social.lol/users/cory/statuses/112480396568749721"
diff --git a/package-lock.json b/package-lock.json
index 6d3ce5f3..13c2f94a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,16 @@
 {
   "name": "coryd.dev",
-  "version": "16.8.0",
+  "version": "17.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "coryd.dev",
-      "version": "16.8.0",
+      "version": "17.0.0",
       "license": "MIT",
       "dependencies": {
         "@cdransf/api-text": "^1.2.2",
-        "@cdransf/select-pagination": "^1.1.0",
+        "@cdransf/select-pagination": "^1.1.1",
         "@cdransf/theme-toggle": "^1.2.3",
         "@daviddarnes/mastodon-post": "^1.3.0",
         "@zachleat/webcare-webshare": "^1.0.3",
@@ -22,9 +22,9 @@
         "@11ty/eleventy-fetch": "^4.0.1",
         "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
         "@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0",
-        "@aws-sdk/client-s3": "^3.582.0",
-        "@cdransf/eleventy-plugin-tabler-icons": "^1.4.0",
-        "@supabase/supabase-js": "^2.43.3",
+        "@aws-sdk/client-s3": "^3.583.0",
+        "@cdransf/eleventy-plugin-tabler-icons": "^1.5.0",
+        "@supabase/supabase-js": "^2.43.4",
         "dotenv-flow": "^4.1.0",
         "gray-matter": "^4.0.3",
         "html-minifier-terser": "^7.2.0",
@@ -398,18 +398,18 @@
       "dev": true
     },
     "node_modules/@aws-sdk/client-s3": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.582.0.tgz",
-      "integrity": "sha512-yp3oIN48sQSJ01JF707KcOLAb7+UxcU6uYH0J48AG61z18tJ0SdE7KG2QPEFbK1RRyYXdHd8VLkbTVP+iwCLmw==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.583.0.tgz",
+      "integrity": "sha512-pS7wncugSuIQ8RgtRIE9Dystdmd3mMnjfjiO1iA1UhGXkyAgoJzQ4jH0r+5X+eWmYHYQcfy9fUQXT2gqV3t9GA==",
       "dev": true,
       "dependencies": {
         "@aws-crypto/sha1-browser": "3.0.0",
         "@aws-crypto/sha256-browser": "3.0.0",
         "@aws-crypto/sha256-js": "3.0.0",
-        "@aws-sdk/client-sso-oidc": "3.582.0",
-        "@aws-sdk/client-sts": "3.582.0",
+        "@aws-sdk/client-sso-oidc": "3.583.0",
+        "@aws-sdk/client-sts": "3.583.0",
         "@aws-sdk/core": "3.582.0",
-        "@aws-sdk/credential-provider-node": "3.582.0",
+        "@aws-sdk/credential-provider-node": "3.583.0",
         "@aws-sdk/middleware-bucket-endpoint": "3.577.0",
         "@aws-sdk/middleware-expect-continue": "3.577.0",
         "@aws-sdk/middleware-flexible-checksums": "3.577.0",
@@ -420,11 +420,11 @@
         "@aws-sdk/middleware-sdk-s3": "3.582.0",
         "@aws-sdk/middleware-signing": "3.577.0",
         "@aws-sdk/middleware-ssec": "3.577.0",
-        "@aws-sdk/middleware-user-agent": "3.577.0",
+        "@aws-sdk/middleware-user-agent": "3.583.0",
         "@aws-sdk/region-config-resolver": "3.577.0",
         "@aws-sdk/signature-v4-multi-region": "3.582.0",
         "@aws-sdk/types": "3.577.0",
-        "@aws-sdk/util-endpoints": "3.577.0",
+        "@aws-sdk/util-endpoints": "3.583.0",
         "@aws-sdk/util-user-agent-browser": "3.577.0",
         "@aws-sdk/util-user-agent-node": "3.577.0",
         "@aws-sdk/xml-builder": "3.575.0",
@@ -467,9 +467,9 @@
       }
     },
     "node_modules/@aws-sdk/client-sso": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.582.0.tgz",
-      "integrity": "sha512-C6G2vNREANe5uUCYrTs8vvGhIrrS1GRoTjr0f5qmkZDuAtuBsQNoTF6Rt+0mDwXXBYW3FcNhZntaNCGVhXlugA==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.583.0.tgz",
+      "integrity": "sha512-FNJ2MmiBtZZwgkj4+GLVrzqwmD6D8FBptrFZk7PnGkSf7v1Q8txYNI6gY938RRhYJ4lBW4cNbhPvWoDxAl90Hw==",
       "dev": true,
       "dependencies": {
         "@aws-crypto/sha256-browser": "3.0.0",
@@ -478,10 +478,10 @@
         "@aws-sdk/middleware-host-header": "3.577.0",
         "@aws-sdk/middleware-logger": "3.577.0",
         "@aws-sdk/middleware-recursion-detection": "3.577.0",
-        "@aws-sdk/middleware-user-agent": "3.577.0",
+        "@aws-sdk/middleware-user-agent": "3.583.0",
         "@aws-sdk/region-config-resolver": "3.577.0",
         "@aws-sdk/types": "3.577.0",
-        "@aws-sdk/util-endpoints": "3.577.0",
+        "@aws-sdk/util-endpoints": "3.583.0",
         "@aws-sdk/util-user-agent-browser": "3.577.0",
         "@aws-sdk/util-user-agent-node": "3.577.0",
         "@smithy/config-resolver": "^3.0.0",
@@ -516,23 +516,23 @@
       }
     },
     "node_modules/@aws-sdk/client-sso-oidc": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.582.0.tgz",
-      "integrity": "sha512-g4uiD4GUR03CqY6LwdocJxO+fHSBk/KNXBGJv1ENCcPmK3jpEI8xBggIQOQl3NWjDeP07bpIb8+UhgSoYAYtkg==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.583.0.tgz",
+      "integrity": "sha512-LO3wmrFXPi2kNE46lD1XATfRrvdNxXd4DlTFouoWmr7lvqoUkcbmtkV2r/XChZA2z0HiDauphC1e8b8laJVeSg==",
       "dev": true,
       "dependencies": {
         "@aws-crypto/sha256-browser": "3.0.0",
         "@aws-crypto/sha256-js": "3.0.0",
-        "@aws-sdk/client-sts": "3.582.0",
+        "@aws-sdk/client-sts": "3.583.0",
         "@aws-sdk/core": "3.582.0",
-        "@aws-sdk/credential-provider-node": "3.582.0",
+        "@aws-sdk/credential-provider-node": "3.583.0",
         "@aws-sdk/middleware-host-header": "3.577.0",
         "@aws-sdk/middleware-logger": "3.577.0",
         "@aws-sdk/middleware-recursion-detection": "3.577.0",
-        "@aws-sdk/middleware-user-agent": "3.577.0",
+        "@aws-sdk/middleware-user-agent": "3.583.0",
         "@aws-sdk/region-config-resolver": "3.577.0",
         "@aws-sdk/types": "3.577.0",
-        "@aws-sdk/util-endpoints": "3.577.0",
+        "@aws-sdk/util-endpoints": "3.583.0",
         "@aws-sdk/util-user-agent-browser": "3.577.0",
         "@aws-sdk/util-user-agent-node": "3.577.0",
         "@smithy/config-resolver": "^3.0.0",
@@ -567,23 +567,23 @@
       }
     },
     "node_modules/@aws-sdk/client-sts": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.582.0.tgz",
-      "integrity": "sha512-3gaYyQkt8iTSStnjv6kJoPGDJUaPbhcgBOrXhUNbWUgAlgw7Y1aI1MYt3JqvVN4jtiCLwjuiAQATU/8elbqPdQ==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.583.0.tgz",
+      "integrity": "sha512-xDMxiemPDWr9dY2Q4AyixkRnk/hvS6fs6OWxuVCz1WO47YhaAfOsEGAgQMgDLLaOfj/oLU5D14uTNBEPGh4rBA==",
       "dev": true,
       "dependencies": {
         "@aws-crypto/sha256-browser": "3.0.0",
         "@aws-crypto/sha256-js": "3.0.0",
-        "@aws-sdk/client-sso-oidc": "3.582.0",
+        "@aws-sdk/client-sso-oidc": "3.583.0",
         "@aws-sdk/core": "3.582.0",
-        "@aws-sdk/credential-provider-node": "3.582.0",
+        "@aws-sdk/credential-provider-node": "3.583.0",
         "@aws-sdk/middleware-host-header": "3.577.0",
         "@aws-sdk/middleware-logger": "3.577.0",
         "@aws-sdk/middleware-recursion-detection": "3.577.0",
-        "@aws-sdk/middleware-user-agent": "3.577.0",
+        "@aws-sdk/middleware-user-agent": "3.583.0",
         "@aws-sdk/region-config-resolver": "3.577.0",
         "@aws-sdk/types": "3.577.0",
-        "@aws-sdk/util-endpoints": "3.577.0",
+        "@aws-sdk/util-endpoints": "3.583.0",
         "@aws-sdk/util-user-agent-browser": "3.577.0",
         "@aws-sdk/util-user-agent-node": "3.577.0",
         "@smithy/config-resolver": "^3.0.0",
@@ -671,14 +671,14 @@
       }
     },
     "node_modules/@aws-sdk/credential-provider-ini": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.582.0.tgz",
-      "integrity": "sha512-GWcjHx6ErcZAi5GZ7kItX7E6ygYmklm9tD9dbCWdsnis7IiWfYZNMXFQEwKCubUmhT61zjGZGDUiRcqVeZu1Aw==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.583.0.tgz",
+      "integrity": "sha512-8I0oWNg/yps6ctjhEeL/qJ9BIa/+xXP7RPDQqFKZ2zBkWbmLLOoMWXRvl8uKUBD6qCe+DGmcu9skfVXeXSesEQ==",
       "dev": true,
       "dependencies": {
         "@aws-sdk/credential-provider-env": "3.577.0",
         "@aws-sdk/credential-provider-process": "3.577.0",
-        "@aws-sdk/credential-provider-sso": "3.582.0",
+        "@aws-sdk/credential-provider-sso": "3.583.0",
         "@aws-sdk/credential-provider-web-identity": "3.577.0",
         "@aws-sdk/types": "3.577.0",
         "@smithy/credential-provider-imds": "^3.0.0",
@@ -691,20 +691,20 @@
         "node": ">=16.0.0"
       },
       "peerDependencies": {
-        "@aws-sdk/client-sts": "^3.582.0"
+        "@aws-sdk/client-sts": "^3.583.0"
       }
     },
     "node_modules/@aws-sdk/credential-provider-node": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.582.0.tgz",
-      "integrity": "sha512-T8OLA/2xayRMT8z2eIZgo8tBAamTsBn7HWc8mL1a9yzv5OCPYvucNmbO915DY8u4cNbMl2dcB9frfVxIrahCXw==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.583.0.tgz",
+      "integrity": "sha512-yBNypBXny7zJH85SzxDj8s1mbLXv9c/Vbq0qR3R3POj2idZ6ywB/qlIRC1XwBuv49Wvg8kA1wKXk3K3jrpcVIw==",
       "dev": true,
       "dependencies": {
         "@aws-sdk/credential-provider-env": "3.577.0",
         "@aws-sdk/credential-provider-http": "3.582.0",
-        "@aws-sdk/credential-provider-ini": "3.582.0",
+        "@aws-sdk/credential-provider-ini": "3.583.0",
         "@aws-sdk/credential-provider-process": "3.577.0",
-        "@aws-sdk/credential-provider-sso": "3.582.0",
+        "@aws-sdk/credential-provider-sso": "3.583.0",
         "@aws-sdk/credential-provider-web-identity": "3.577.0",
         "@aws-sdk/types": "3.577.0",
         "@smithy/credential-provider-imds": "^3.0.0",
@@ -734,12 +734,12 @@
       }
     },
     "node_modules/@aws-sdk/credential-provider-sso": {
-      "version": "3.582.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.582.0.tgz",
-      "integrity": "sha512-PSiBX6YvJaodGSVg6dReWfeYgK5Tl4fUi0GMuD9WXo/ckfxAPdDFtIfVR6VkSPUrkZj26uw1Pwqeefp2H5phag==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.583.0.tgz",
+      "integrity": "sha512-G/1EvL9tBezSiU+06tG4K/kOvFfPjnheT4JSXqjPM7+vjKzgp2jxp1J9MMd69zs4jVWon932zMeGgjrCplzMEg==",
       "dev": true,
       "dependencies": {
-        "@aws-sdk/client-sso": "3.582.0",
+        "@aws-sdk/client-sso": "3.583.0",
         "@aws-sdk/token-providers": "3.577.0",
         "@aws-sdk/types": "3.577.0",
         "@smithy/property-provider": "^3.0.0",
@@ -932,13 +932,13 @@
       }
     },
     "node_modules/@aws-sdk/middleware-user-agent": {
-      "version": "3.577.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.577.0.tgz",
-      "integrity": "sha512-P55HAXgwmiHHpFx5JEPvOnAbfhN7v6sWv9PBQs+z2tC7QiBcPS0cdJR6PfV7J1n4VPK52/OnrK3l9VxdQ7Ms0g==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.583.0.tgz",
+      "integrity": "sha512-xVNXXXDWvBVI/AeVtSdA9SVumqxiZaESk/JpUn9GMkmtTKfter0Cweap+1iQ9j8bRAO0vNhmIkbcvdB1S4WVUw==",
       "dev": true,
       "dependencies": {
         "@aws-sdk/types": "3.577.0",
-        "@aws-sdk/util-endpoints": "3.577.0",
+        "@aws-sdk/util-endpoints": "3.583.0",
         "@smithy/protocol-http": "^4.0.0",
         "@smithy/types": "^3.0.0",
         "tslib": "^2.6.2"
@@ -1026,9 +1026,9 @@
       }
     },
     "node_modules/@aws-sdk/util-endpoints": {
-      "version": "3.577.0",
-      "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.577.0.tgz",
-      "integrity": "sha512-FjuUz1Kdy4Zly2q/c58tpdqHd6z7iOdU/caYzoc8jwgAHBDBbIJNQLCU9hXJnPV2M8pWxQDyIZsoVwtmvErPzw==",
+      "version": "3.583.0",
+      "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.583.0.tgz",
+      "integrity": "sha512-ZC9mb2jq6BFXPYsUsD2tmYcnlmd+9PGNwnFNn8jk4abna5Jjk2wDknN81ybktmBR5ttN9W8ugmktuKtvAMIDCQ==",
       "dev": true,
       "dependencies": {
         "@aws-sdk/types": "3.577.0",
@@ -1115,15 +1115,15 @@
       "integrity": "sha512-3yzlvCsAwqrHjNHsgalT9HfuHac37hX80Nff0Pfw2RH9XsfCLoVJABfKeXA1BBr6/wSdOHUgOQyEYTubIiTGfg=="
     },
     "node_modules/@cdransf/eleventy-plugin-tabler-icons": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/@cdransf/eleventy-plugin-tabler-icons/-/eleventy-plugin-tabler-icons-1.4.0.tgz",
-      "integrity": "sha512-W6PhrBggErZ6PRXOAgqSwLRMB5/LqmPDEt2qecrbIuou0Yb3z08v8c69HC9aJVIFTGfs7IaZ1yyP7ZEp5/rLjg==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@cdransf/eleventy-plugin-tabler-icons/-/eleventy-plugin-tabler-icons-1.5.0.tgz",
+      "integrity": "sha512-KS7B7lSmD9kLsvkmwAn0qGqazcIsoaB6hUlPgIeSUdjY5VtG6geKOmyvEzaiByrS0bN+/2MNSeqTm9Wl3RxCSw==",
       "dev": true
     },
     "node_modules/@cdransf/select-pagination": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@cdransf/select-pagination/-/select-pagination-1.1.0.tgz",
-      "integrity": "sha512-h7uUAFbyr7exKqvkK1F1sdl5G6esEixJ0CpWApBRLRvdE5kh3BiiCgKU4H6syIXHer47n5L9UxgMvfcL4B6KUg=="
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@cdransf/select-pagination/-/select-pagination-1.1.1.tgz",
+      "integrity": "sha512-2GUQSQ++chkKgQYRAIYnj1ugLswMWTzjdDwRh/SsfX7PJpyI8Th9GjDEm0sKS1SyLCjkfj6Bl31f66AlD0cEKw=="
     },
     "node_modules/@cdransf/theme-toggle": {
       "version": "1.2.3",
@@ -2470,9 +2470,9 @@
       }
     },
     "node_modules/@supabase/postgrest-js": {
-      "version": "1.15.3",
-      "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.3.tgz",
-      "integrity": "sha512-kvfFhuurvnn7+XoXvwb6j64BqqS/xguXfM5FY8xPK24Kza+Bl4NPxGSMu3NWZiWPBgW2C27X2yHz/T+VYlXkEA==",
+      "version": "1.15.2",
+      "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.2.tgz",
+      "integrity": "sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==",
       "dev": true,
       "dependencies": {
         "@supabase/node-fetch": "^2.6.14"
@@ -2500,15 +2500,15 @@
       }
     },
     "node_modules/@supabase/supabase-js": {
-      "version": "2.43.3",
-      "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.43.3.tgz",
-      "integrity": "sha512-jrlx9sIty4lpG9ukhD9wimzHrFzeLTq/JKhqDqVLKo449NYlZbpcnmhqSjfZmve/0mIq1rbYmVvKQ90Hq4/cVg==",
+      "version": "2.43.4",
+      "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.43.4.tgz",
+      "integrity": "sha512-/pLPaxiIsn5Vaz3s32HC6O/VNwfeddnzS0bZRpOW0AKcPuXroD8pT9G8mpiBlZfpKsMmq6k7tlhW7Sr1PAQ1lw==",
       "dev": true,
       "dependencies": {
         "@supabase/auth-js": "2.64.2",
         "@supabase/functions-js": "2.3.1",
         "@supabase/node-fetch": "2.6.15",
-        "@supabase/postgrest-js": "1.15.3",
+        "@supabase/postgrest-js": "1.15.2",
         "@supabase/realtime-js": "2.9.5",
         "@supabase/storage-js": "2.5.5"
       }
@@ -3346,9 +3346,9 @@
       "dev": true
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.778",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.778.tgz",
-      "integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA==",
+      "version": "1.4.781",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.781.tgz",
+      "integrity": "sha512-aBI40ltvcWJQDW+V803FY6HjXAfi5xCWzpa3vSM/NGg7GfKEvI7ftzW4Gb2XKTRO4WsxDG7YG8ykrr/pG9bkKQ==",
       "dev": true
     },
     "node_modules/encodeurl": {
@@ -4073,6 +4073,7 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
       "dev": true,
       "dependencies": {
         "once": "^1.3.0",
diff --git a/package.json b/package.json
index 96b995f2..3489dde1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "coryd.dev",
-  "version": "16.8.0",
+  "version": "17.0.0",
   "description": "The source for my personal site. Built using 11ty.",
   "type": "module",
   "scripts": {
@@ -22,7 +22,7 @@
   "license": "MIT",
   "dependencies": {
     "@cdransf/api-text": "^1.2.2",
-    "@cdransf/select-pagination": "^1.1.0",
+    "@cdransf/select-pagination": "^1.1.1",
     "@cdransf/theme-toggle": "^1.2.3",
     "@daviddarnes/mastodon-post": "^1.3.0",
     "@zachleat/webcare-webshare": "^1.0.3",
@@ -34,9 +34,9 @@
     "@11ty/eleventy-fetch": "^4.0.1",
     "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
     "@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0",
-    "@aws-sdk/client-s3": "^3.582.0",
-    "@cdransf/eleventy-plugin-tabler-icons": "^1.4.0",
-    "@supabase/supabase-js": "^2.43.3",
+    "@aws-sdk/client-s3": "^3.583.0",
+    "@cdransf/eleventy-plugin-tabler-icons": "^1.5.0",
+    "@supabase/supabase-js": "^2.43.4",
     "dotenv-flow": "^4.1.0",
     "gray-matter": "^4.0.3",
     "html-minifier-terser": "^7.2.0",
diff --git a/src/_data/artists.js b/src/_data/artists.js
new file mode 100644
index 00000000..02f9f673
--- /dev/null
+++ b/src/_data/artists.js
@@ -0,0 +1,75 @@
+import { createClient } from '@supabase/supabase-js'
+
+const SUPABASE_URL = process.env.SUPABASE_URL || 'YOUR_SUPABASE_URL'
+const SUPABASE_KEY = process.env.SUPABASE_KEY || 'YOUR_SUPABASE_KEY'
+const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
+
+const regionNames = new Intl.DisplayNames(['en'], { type: 'region' })
+const getCountryName = (countryCode) => regionNames.of(countryCode.trim()) || countryCode.trim()
+
+const parseCountryField = (countryField) => {
+  if (!countryField) return null
+
+  const delimiters = [',', '/', '&', 'and']
+  let countries = [countryField]
+
+  delimiters.forEach(delimiter => {
+    countries = countries.flatMap(country => country.split(delimiter))
+  })
+
+  return countries.map(getCountryName).join(', ')
+}
+
+const PAGE_SIZE = 50
+
+const fetchPaginatedData = async (table, selectFields) => {
+  let data = []
+  let page = 0
+  let hasMoreRecords = true
+
+  while (hasMoreRecords) {
+    const { data: pageData, error } = await supabase
+      .from(table)
+      .select(selectFields)
+      .order('id', { ascending: true })
+      .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
+
+    if (error) {
+      console.error(`Error fetching ${table}:`, error)
+      break
+    }
+
+    data = data.concat(pageData)
+
+    if (pageData.length < PAGE_SIZE) {
+      hasMoreRecords = false
+    } else {
+      page++
+    }
+  }
+
+  return data
+}
+
+export default async function () {
+  const artists = await fetchPaginatedData('artists', 'mbid, name_string, image, genre, total_plays, country, description, favorite')
+  const albums = await fetchPaginatedData('albums', 'mbid, name, release_year, artist_mbid, total_plays')
+
+  const albumsByArtist = albums.reduce((acc, album) => {
+    if (!acc[album.artist_mbid]) acc[album.artist_mbid] = []
+    acc[album.artist_mbid].push({
+      id: album.id,
+      name: album.name,
+      release_year: album.release_year,
+      total_plays: album.total_plays > 0 ? album.total_plays : '-'
+    })
+    return acc
+  }, {})
+
+  artists.forEach(artist => {
+    artist.albums = albumsByArtist[artist.mbid]?.sort((a, b) => a['release_year'] - b['release_year']) || []
+    artist.country = parseCountryField(artist.country)
+  })
+
+  return artists
+}
\ No newline at end of file
diff --git a/src/_data/movies.js b/src/_data/movies.js
index 0845ae2a..5f215139 100644
--- a/src/_data/movies.js
+++ b/src/_data/movies.js
@@ -51,7 +51,7 @@ export default async function () {
       lastWatched: item['last_watched'],
       dateAdded: item['last_watched'],
       year: item['year'],
-      url: `https://coryd.dev/movies/${item['tmdb_id']}`,
+      url: `https://coryd.dev/watching/movies/${item['tmdb_id']}`,
       description: `${item['title']} (${item['year']})<br/>Watched at: ${DateTime.fromISO(item['last_watched'], { zone: 'utc' }).setZone('America/Los_Angeles').toFormat('MMMM d, yyyy, h:mma')}`,
       image: `https://coryd.dev/media/movies/poster-${item['tmdb_id']}.jpg`,
       backdrop: `https://coryd.dev/media/movies/backdrops/backdrop-${item['tmdb_id']}.jpg`,
diff --git a/src/_data/music.js b/src/_data/music.js
index 2827d5c1..64bbc073 100644
--- a/src/_data/music.js
+++ b/src/_data/music.js
@@ -1,10 +1,32 @@
 import { createClient } from '@supabase/supabase-js'
 import { DateTime } from 'luxon'
+import slugify from 'slugify'
 
 const SUPABASE_URL = process.env.SUPABASE_URL
 const SUPABASE_KEY = process.env.SUPABASE_KEY
 const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
 
+const slugifyString = (str) => slugify(str, {
+  replacement: '-',
+  remove: /[#,&,+()$~%.'":*?<>{}]/g,
+  lower: true,
+})
+
+const regionNames = new Intl.DisplayNames(['en'], { type: 'region' })
+const getCountryName = (countryCode) => regionNames.of(countryCode.trim()) || countryCode.trim()
+const parseCountryField = (countryField) => {
+  if (!countryField) return null
+
+  const delimiters = [',', '/', '&', 'and']
+  let countries = [countryField]
+
+  delimiters.forEach(delimiter => {
+    countries = countries.flatMap(country => country.split(delimiter))
+  })
+
+  return countries.map(getCountryName).join(', ')
+}
+
 const fetchDataForPeriod = async (startPeriod, fields, table) => {
   const PAGE_SIZE = 1000
   let rows = []
@@ -68,22 +90,22 @@ const aggregateData = (data, groupByField, groupByType) => {
         aggregation[key] = {
           title: item[groupByField],
           plays: 0,
-          mbid: item['albums']?.mbid || '',
-          url: item['albums']?.mbid ? `https://musicbrainz.org/release/${item['albums'].mbid}` : `https://musicbrainz.org/search?query=${encodeURIComponent(item['album_name'])}&type=release`,
-          image: item['albums']?.image || '',
+          mbid: item['albums']['mbid'],
+          url: `https://coryd.dev/music/artists/${slugifyString(item['artist_name'])}-${slugifyString(parseCountryField(item['artists']['country']))}`,
+          image: item['albums']?.['image'] || '',
           timestamp: item['listened_at'],
           type: groupByType,
-          genre: item['artists']?.genre || 'Unknown'
+          genre: item['artists']?.['genre'] || ''
         }
       } else {
         aggregation[key] = {
           title: item[groupByField],
           plays: 0,
-          mbid: item[groupByType]?.mbid || '',
-          url: item[groupByType]?.mbid ? `https://musicbrainz.org/${groupByType === 'albums' ? 'release' : 'artist'}/${item[groupByType].mbid}` : `https://musicbrainz.org/search?query=${encodeURIComponent(item[groupByField])}&type=${groupByType === 'albums' ? 'release' : 'artist'}`,
+          mbid: item[groupByType]?.['mbid'] || '',
+          url: `https://coryd.dev/music/artists/${slugifyString(item['artist_name'])}-${slugifyString(parseCountryField(item['artists']['country']))}`,
           image: item[groupByType]?.image || '',
           type: groupByType,
-          genre: item['artists']?.genre || 'Unknown'
+          genre: item['artists']?.['genre'] || ''
         }
       }
       if (
@@ -129,7 +151,7 @@ export default async function() {
     album_name,
     album_key,
     listened_at,
-    artists (mbid, image, genre),
+    artists (mbid, image, genre, country),
     albums (mbid, image)
   `
 
diff --git a/src/_data/tv.js b/src/_data/tv.js
index 560da610..a2fe765d 100644
--- a/src/_data/tv.js
+++ b/src/_data/tv.js
@@ -90,7 +90,7 @@ export default async function () {
 
       showEpisodesMap[showTmdbId].episodes.push({
         name: showTitle,
-        url: `https://coryd.dev/shows/${showTmdbId}`,
+        url: `https://coryd.dev/watching/shows/${showTmdbId}`,
         subtext: `${showTitle} • S${seasonNumber}E${episodeNumber}`,
         episode: episodeNumber,
         season: seasonNumber,
@@ -114,7 +114,7 @@ export default async function () {
       if (show.episodes.length > 1) {
         episodeData.push({
           name: show.title,
-          url: `https://coryd.dev/shows/${show['tmdbId']}`,
+          url: `https://coryd.dev/watching/shows/${show['tmdbId']}`,
           subtext: `S${startingSeason}E${startingEpisode} - S${endingSeason}E${endingEpisode}`,
           startingEpisode,
           startingSeason,
@@ -140,7 +140,7 @@ export default async function () {
 
   const favoriteShows = shows.filter(show => show['favorite'])
   const collectedShows = shows.filter(show => show['collected'])
-  const toWatch = shows.map(show => ({...show, url: `https://coryd.dev/shows/${show['tmdb_id']}`})).filter(show => !show.episodes.some(episode => episode.last_watched_at)).sort((a, b) => a['title'].localeCompare(b['title']))
+  const toWatch = shows.map(show => ({...show, url: `https://coryd.dev/watching/shows/${show['tmdb_id']}`})).filter(show => !show.episodes.some(episode => episode.last_watched_at)).sort((a, b) => a['title'].localeCompare(b['title']))
 
   return {
     shows,
diff --git a/src/assets/scripts/text-toggle.js b/src/assets/scripts/text-toggle.js
new file mode 100644
index 00000000..a7951027
--- /dev/null
+++ b/src/assets/scripts/text-toggle.js
@@ -0,0 +1,14 @@
+window.onload = () => {
+  const button = document.querySelector('[data-toggle-button]')
+  const content = document.querySelector('[data-toggle-content]')
+
+  button.addEventListener('click', () => {
+    if (content.classList.contains('text-toggle-hidden')) {
+      content.classList.remove('text-toggle-hidden')
+      button.textContent = 'Show less'
+    } else {
+      content.classList.add('text-toggle-hidden')
+      button.textContent = 'Show more'
+    }
+  });
+}
\ No newline at end of file
diff --git a/src/assets/styles/base/index.css b/src/assets/styles/base/index.css
index 7f413fc1..9bfa515a 100644
--- a/src/assets/styles/base/index.css
+++ b/src/assets/styles/base/index.css
@@ -210,19 +210,17 @@ hr {
 /* tables */
 table {
   display: block;
-  overflow-x: scroll;
-  overscroll-behavior: contain;
+  overflow-x: auto;
   width: 100%;
-  max-width: fit-content;
-  margin: 0 auto;
-  white-space: nowrap;
   border: 1px solid var(--gray-light);
+  white-space: nowrap;
 }
 
 table,
 th,
 td {
   border-collapse: collapse;
+  width: 100%;
 }
 
 :is(th, td):not(:last-child) {
@@ -237,8 +235,13 @@ tr:not(:last-child) {
 th,
 td {
   padding: var(--sizing-sm);
-  min-width: calc(var(--sizing-3xl) * 2);
+  min-width: max-content;
   word-break: break-word;
+  width: 100%;
+}
+
+td {
+  min-width: calc(var(--sizing-3xl) * 2);
 }
 
 th {
@@ -342,6 +345,7 @@ nav .active svg {
 .coffee svg { stroke: var(--brand-buy-me-a-coffee) !important; }
 .heart-handshake svg { stroke: var(--webrings) !important; }
 .rss svg { stroke: var(--brand-rss) !important; }
+.favorite svg { stroke: var(--favorite) !important }
 
 /* layout */
 .default-wrapper {
diff --git a/src/assets/styles/components/text-toggle.css b/src/assets/styles/components/text-toggle.css
new file mode 100644
index 00000000..db031529
--- /dev/null
+++ b/src/assets/styles/components/text-toggle.css
@@ -0,0 +1,21 @@
+[data-toggle-content].text-toggle-hidden {
+  position: relative;
+  height: calc(var(--sizing-3xl) * 5);
+  overflow: hidden;
+  margin-bottom: var(--sizing-base);
+}
+
+[data-toggle-content].text-toggle-hidden::after {
+  position: absolute;
+  z-index: 1;
+  content: '';
+  top: 0;
+  left: 0;
+  box-shadow: inset 0 -100px 50px -60px var(--background-color);
+  width: 100%;
+  height: 100%;
+}
+
+[data-toggle-button] {
+  margin-bottom: var(--sizing-base) !important;
+}
\ No newline at end of file
diff --git a/src/assets/styles/defaults/vars.css b/src/assets/styles/defaults/vars.css
index 66825ebc..1a0c7682 100644
--- a/src/assets/styles/defaults/vars.css
+++ b/src/assets/styles/defaults/vars.css
@@ -60,6 +60,7 @@
   --webrings: #da70d6;
   --moon: #6a5acd;
   --sun: #ffa500;
+  --favorite: #ff69b4;
 
   /* fonts */
   --font-mono: MonoLisa, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, ui-monospace, monospace;
diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css
index bd83f146..8397ee88 100644
--- a/src/assets/styles/index.css
+++ b/src/assets/styles/index.css
@@ -21,6 +21,7 @@
 @import url('./pages/blogroll.css') layer(page);
 @import url('./pages/contact.css') layer(page);
 @import url('./pages/links.css') layer(page);
+@import url('./pages/music.css') layer(page);
 @import url('./pages/post.css') layer(page);
 @import url('./pages/watching.css') layer(page);
 @import url('./pages/webrings.css') layer(page);
@@ -37,5 +38,6 @@
 @import url('./components/paginator.css') layer(components);
 @import url('./components/progress-bar.css') layer(components);
 @import url('./components/share-button.css') layer(components);
+@import url('./components/text-toggle.css') layer(components);
 @import url('./components/theme-toggle.css') layer(components);
 @import url('./components/music-chart.css') layer(components);
\ No newline at end of file
diff --git a/src/assets/styles/pages/music.css b/src/assets/styles/pages/music.css
new file mode 100644
index 00000000..dfd1b60e
--- /dev/null
+++ b/src/assets/styles/pages/music.css
@@ -0,0 +1,57 @@
+.artist-focus {
+  border-bottom: 0;
+
+  & img {
+    border: 1px solid var(--accent-color);
+  }
+
+  & .artist-display {
+    display: flex;
+    flex-direction: column;
+    gap: var(--sizing-xs);
+    margin-top: var(--sizing-base);
+    margin-bottom: var(--sizing-base);
+
+    .artist-meta {
+      display: flex;
+      flex-direction: column;
+      gap: var(--sizing-xs);
+
+      & p {
+        &.title {
+          font-size: var(--font-size-xl);
+        }
+
+        &.sub-meta {
+          font-size: var(--font-size-xs);
+          line-height: var(--line-height-xs);
+        }
+
+        &.title,
+        &.sub-meta {
+          margin: 0;
+        }
+
+        &.sub-meta svg {
+          width: 16px;
+          height: 16px;
+          margin-right: var(--sizing-xs);
+        }
+
+        &.favorite {
+          color: var(--favorite);
+        }
+      }
+    }
+  }
+}
+
+@media screen and (min-width: 768px) {
+  .artist-focus {
+    & .artist-display {
+      margin-top: 0;
+      flex-direction: row;
+      gap: var(--sizing-md);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/pages/main/music/artists/artist.html b/src/pages/main/music/artists/artist.html
new file mode 100644
index 00000000..bd78942d
--- /dev/null
+++ b/src/pages/main/music/artists/artist.html
@@ -0,0 +1,54 @@
+---
+layout: default
+pagination:
+  data: artists
+  size: 1
+  alias: artist
+permalink: /music/artists/{{ artist.name_string | slugify | downcase }}-{{ artist.country | slugify | downcase}}/
+schema: artist
+---
+{%- capture alt -%}
+  {{ artist.name_string }} • {{ artist.country }}
+{%- endcapture -%}
+{% capture js %}
+  {% render "../../../../assets/scripts/text-toggle.js" %}
+{% endcapture %}
+<script>{{ js }}</script>
+<noscript><style>[data-toggle-content].text-toggle-hidden {height: unset !important;overflow: unset !important;margin-bottom: unset !important;}[data-toggle-content].text-toggle-hidden::after {display: none !important;}</style></noscript>
+<a class="back-link-header link-icon flex-centered" href="/music">{% tablericon "arrow-left" "Go back" %} Go back</a>
+<article class="artist-focus">
+  <div class="artist-display">
+    <img src="https://coryd.dev/.netlify/images/?url={{ artist.image }}&fm=webp&q=85&w=240&h=240&fit=cover" alt="{{ alt }}" loading="eager" decoding="async" width="240" height="240" />
+    <div class="artist-meta">
+      <p class="title"><strong>{{ artist.name_string }}</strong></p>
+      {%- if artist.favorite -%}
+        <p class="sub-meta favorite flex-centered">{% tablericon "heart" "Favorite" %} This is one of my favorite artists!</p>
+      {%- endif -%}
+      {%- if artist.total_plays > 0 -%}
+        <p class="sub-meta"><strong class="highlight-text">{{ artist.total_plays }} plays</strong></p>
+      {%- endif -%}
+      <p class="sub-meta">{{ artist.genre }}</p>
+      <p class="sub-meta">
+        <a class="brain" href="https://musicbrainz.org/artist/{{ artist.mbid }}">{% tablericon "brain" "MusicBrainz" %}</a>
+      </p>
+    </div>
+  </div>
+  {%- if artist.description -%}
+  <div data-toggle-content class="text-toggle-hidden">{{ artist.description | markdown }}</div>
+  <button data-toggle-button>Show more</button>
+  {%- endif -%}
+  <table>
+    <tr>
+      <th>Year</th>
+      <th>Title</th>
+      <th>Plays</th>
+    </tr>
+    {% for album in artist.albums %}
+    <tr>
+      <td>{{ album.release_year }}</td>
+      <td>{{ album.name }}</td>
+      <td>{{ album.total_plays }}</td>
+    </tr>
+    {% endfor %}
+  </table>
+</article>
\ No newline at end of file
diff --git a/src/pages/main/watching/movie.html b/src/pages/main/watching/movie.html
index 7b41a383..4b957387 100644
--- a/src/pages/main/watching/movie.html
+++ b/src/pages/main/watching/movie.html
@@ -4,7 +4,7 @@ pagination:
   data: movies.movies
   size: 1
   alias: movie
-permalink: /movies/{{ movie.id }}/
+permalink: /watching/movies/{{ movie.id }}/
 schema: movie
 ---
 {%- capture alt -%}
diff --git a/src/pages/main/watching/show.html b/src/pages/main/watching/show.html
index 4780c0a0..51176993 100644
--- a/src/pages/main/watching/show.html
+++ b/src/pages/main/watching/show.html
@@ -4,7 +4,7 @@ pagination:
   data: tv.shows
   size: 1
   alias: show
-permalink: /shows/{{ show.tmdb_id }}/
+permalink: /watching/shows/{{ show.tmdb_id }}/
 schema: show
 ---
 {%- capture alt -%}