diff --git a/README.md b/README.md
index 4e879456..2afa6152 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Pleroma-FE 
+# Akkoma-FE 
 
 ![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
 
-This is a fork of Pleroma-FE from the Pleroma project, with support for new Akkoma features such as:
+This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkoma features such as:
 - MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
 - Custom emoji reactions
 
@@ -21,15 +21,15 @@ This is a fork of Pleroma-FE from the Pleroma project, with support for new Akko
 
 # For Translators
 
-The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Pleroma-FE. 
+The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE. 
 
 Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
 
-Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
+Akkoma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
 
 # FOR ADMINS
 
-To use Pleroma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Pleroma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
+To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Akkoma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
 
 ## Build Setup
 
@@ -65,4 +65,4 @@ Edit config.json for configuration.
 
 ### Login methods
 
-```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
+```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations. 
diff --git a/index.html b/index.html
index e9aee08e..4249de5c 100644
--- a/index.html
+++ b/index.html
@@ -10,6 +10,7 @@
     <link rel="stylesheet" href="/static/font/css/lato.css">
     <link rel="stylesheet" href="/static/mfm.css">
     <link rel="stylesheet" href="/static/custom.css">
+    <link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
     <!--server-generated-meta-->
     <link rel="icon" type="image/png" href="/favicon.png">
     <link rel="manifest" href="/manifest.json">
diff --git a/package.json b/package.json
index e712cffd..efafb67b 100644
--- a/package.json
+++ b/package.json
@@ -22,9 +22,10 @@
     "@fortawesome/free-regular-svg-icons": "^6.1.2",
     "@fortawesome/free-solid-svg-icons": "^6.2.0",
     "@fortawesome/vue-fontawesome": "3.0.1",
-    "@kazvmoe-infra/pinch-zoom-element": "1.2.0",
+    "@floatingghost/pinch-zoom-element": "^1.3.1",
     "@vuelidate/core": "^2.0.0",
     "@vuelidate/validators": "^2.0.0",
+    "blurhash": "^2.0.4",
     "body-scroll-lock": "2.7.1",
     "chromatism": "3.0.0",
     "click-outside-vue3": "4.0.1",
diff --git a/src/App.scss b/src/App.scss
index 7e6d0dfc..38574cab 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,6 +1,7 @@
 // stylelint-disable rscss/class-format
 @import './_variables.scss';
-
+@import '@fortawesome/fontawesome-svg-core/styles.css';
+@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
 :root {
   --navbar-height: 3.5rem;
   --post-line-height: 1.4;
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index f54537d2..204bb68a 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -4,6 +4,8 @@ import { createRouter, createWebHistory } from 'vue-router'
 import vClickOutside from 'click-outside-vue3'
 
 import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
+import { config } from '@fortawesome/fontawesome-svg-core';
+config.autoAddCss = false
 
 import App from '../App.vue'
 import routes from './routes'
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 1a35a77d..7090260e 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -17,6 +17,7 @@ import {
   faPencilAlt,
   faAlignRight
 } from '@fortawesome/free-solid-svg-icons'
+import Blurhash from '../blurhash/Blurhash.vue'
 
 library.add(
   faFile,
@@ -61,7 +62,8 @@ const Attachment = {
   },
   components: {
     StillImage,
-    VideoAttachment
+    VideoAttachment,
+    Blurhash
   },
   computed: {
     classNames () {
@@ -82,6 +84,9 @@ const Attachment = {
     useContainFit () {
       return this.$store.getters.mergedConfig.useContainFit
     },
+    useBlurhash () {
+      return this.$store.getters.mergedConfig.useBlurhash
+    },
     placeholderName () {
       if (this.attachment.description === '' || !this.attachment.description) {
         return this.type.toUpperCase()
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 947b1bfc..0ccdb776 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -64,7 +64,15 @@
         :title="attachment.description"
         @click.prevent.stop="toggleHidden"
       >
+        <Blurhash
+          v-if="useBlurhash && attachment.blurhash"
+          :height="512"
+          :width="1024"
+          :hash="attachment.blurhash"
+          :punch="1"
+        />
         <img
+          v-else
           :key="nsfwImage"
           class="nsfw"
           :src="nsfwImage"
diff --git a/src/components/blurhash/Blurhash.vue b/src/components/blurhash/Blurhash.vue
new file mode 100644
index 00000000..c2e7f5b9
--- /dev/null
+++ b/src/components/blurhash/Blurhash.vue
@@ -0,0 +1,66 @@
+<template>
+  <canvas
+    ref="canvas"
+    class="blurhash"
+  />
+</template>
+
+<script>
+import { decode } from "blurhash";
+
+export default {
+  name: 'Blurhash',
+  props: {
+    hash: {
+      type: String,
+      required: true,
+    },
+    width: {
+      type: Number,
+      required: true,
+    },
+    height: {
+      type: Number,
+      required: true,
+    },
+    punch: {
+      type: Number,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      canvas: null,
+      ctx: null,
+    };
+  },
+  mounted() {
+    this.canvas = this.$refs.canvas;
+    this.ctx = this.canvas.getContext('2d');
+    this.canvas.width = 1024;
+    this.canvas.height = 512;
+    this.draw();
+  },
+  methods: {
+    draw() {
+      const pixels = decode(this.hash, this.width, this.height, this.punch);
+      const imageData = this.ctx.createImageData(this.width, this.height);
+      imageData.data.set(pixels);
+      this.ctx.putImageData(imageData, 0, 0);
+      fetch("/static/blurhash-overlay.png")
+        .then((response) => response.blob())
+        .then((blob) => {
+          const img = new Image();
+          img.src = URL.createObjectURL(blob);
+          img.onload = () => {
+            this.ctx.drawImage(img, 0, 0, this.width, this.height);
+          };
+        });
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 119da7c4..82fc831c 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -18,7 +18,7 @@
     overflow: hidden;
     left: -70%;
     max-width: 100%;
-    @media (min-width: 800px) and (max-width: 1300px) {
+    @media (min-width: 800px) and (max-width: 1280px) {
       left: -50%;
       min-width: 50%;
       max-width: 130%;
diff --git a/src/components/followed_tag_card/FollowedTagCard.vue b/src/components/followed_tag_card/FollowedTagCard.vue
new file mode 100644
index 00000000..d9394ddc
--- /dev/null
+++ b/src/components/followed_tag_card/FollowedTagCard.vue
@@ -0,0 +1,77 @@
+<template>
+  <div class="followed-tag-card">
+    <span>
+      <router-link :to="{ name: 'tag-timeline', params: {tag: tag.name}}">
+        <span class="tag-link">#{{ tag.name }}</span>
+      </router-link>
+      <span class="unfollow-tag">
+        <button
+          v-if="isFollowing"
+          class="button-default unfollow-tag-button"
+          :title="$t('user_card.unfollow_tag')"
+          @click="unfollowTag(tag.name)"
+        >
+          {{ $t('user_card.unfollow_tag') }}
+        </button>
+        <button
+          v-else
+          class="button-default follow-tag-button"
+          :title="$t('user_card.follow_tag')"
+          @click="followTag(tag.name)"
+        >
+          {{ $t('user_card.follow_tag') }}
+        </button>
+      </span>
+    </span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FollowedTagCard',
+  props: {
+    tag: {
+      type: Object,
+      required: true
+    },
+  },
+  // this is a hack to update the state of the button
+  // for some reason, List does not update on changes to the tag object
+  data: () => ({
+    isFollowing: true
+  }),
+  mounted () {
+    this.isFollowing = this.tag.following
+  },
+  methods: {
+    unfollowTag (tag) {
+      this.$store.dispatch('unfollowTag', tag)
+      this.isFollowing = false
+    },
+    followTag (tag) {
+      this.$store.dispatch('followTag', tag)
+      this.isFollowing = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .followed-tag-card {
+    margin-left: 1rem;
+    margin-top: 1rem;
+    margin-bottom: 1rem;
+  }
+  .unfollow-tag {
+    position: absolute;
+    right: 1rem;
+  }
+
+  .tag-link {
+    font-size: large;
+  }
+
+  .unfollow-tag-button, .follow-tag-button {
+    font-size: medium;
+  }
+</style>
diff --git a/src/components/pinch_zoom/pinch_zoom.js b/src/components/pinch_zoom/pinch_zoom.js
index 82670ddf..b7e8f673 100644
--- a/src/components/pinch_zoom/pinch_zoom.js
+++ b/src/components/pinch_zoom/pinch_zoom.js
@@ -1,4 +1,4 @@
-import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
+import PinchZoom from '@floatingghost/pinch-zoom-element'
 
 export default {
   methods: {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index af02383f..bdb6932e 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -452,6 +452,15 @@
               {{ $t('settings.preload_images') }}
             </BooleanSetting>
           </li>
+          <li>
+            <BooleanSetting
+              path="useBlurhash"
+              expert="1"
+              :disabled="!hideNsfw"
+            >
+              {{ $t('settings.use_blurhash') }}
+            </BooleanSetting>
+          </li>
           <li>
             <BooleanSetting
               path="useOneClickNsfw"
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 42c5ec9e..1cadddda 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -10,11 +10,14 @@ import withLoadMore from '../../hocs/with_load_more/with_load_more'
 import { debounce } from 'lodash'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
-  faCircleNotch
+  faCircleNotch,
+  faCircleCheck
 } from '@fortawesome/free-solid-svg-icons'
+import FollowedTagCard from '../followed_tag_card/FollowedTagCard.vue'
 
 library.add(
-  faCircleNotch
+  faCircleNotch,
+  faCircleCheck
 )
 
 const FollowerList = withLoadMore({
@@ -33,6 +36,14 @@ const FriendList = withLoadMore({
   additionalPropNames: ['userId']
 })(List)
 
+const FollowedTagList = withLoadMore({
+  fetch: (props, $store) => $store.dispatch('fetchFollowedTags', props.userId),
+  select: (props, $store) => get($store.getters.findUser(props.userId), 'followedTagIds', []).map(id => $store.getters.findTag(id)),
+  destroy: (props, $store) => $store.dispatch('clearFollowedTags', props.userId),
+  childPropName: 'items',
+  additionalPropNames: ['userId']
+})(List)
+
 const isUserPage = ({ name }) => name === 'user-profile' || name === 'external-user-profile'
 
 const UserProfile = {
@@ -41,6 +52,7 @@ const UserProfile = {
       error: false,
       userId: null,
       tab: 'statuses',
+      followsTab: 'users',
       footerRef: null,
       note: null,
       noteLoading: false
@@ -165,6 +177,9 @@ const UserProfile = {
       this.tab = tab
       this.$router.replace({ hash: `#${tab}` })
     },
+    onFollowsTabSwitch (tab) {
+      this.followsTab = tab
+    },
     linkClicked ({ target }) {
       if (target.tagName === 'SPAN') {
         target = target.parentNode
@@ -200,6 +215,7 @@ const UserProfile = {
     }
   },
   components: {
+    FollowedTagCard,
     UserCard,
     Timeline,
     FollowerList,
@@ -207,7 +223,8 @@ const UserProfile = {
     FollowCard,
     TabSwitcher,
     Conversation,
-    RichContent
+    RichContent,
+    FollowedTagList,
   }
 }
 
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index ffb4797b..6312ae81 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -37,6 +37,15 @@
               :html="field.value"
               :emoji="user.emoji"
             />
+            <span
+              v-if="field.verified_at"
+              class="user-profile-field-validated"
+            >
+              <FAIcon
+                icon="check-circle"
+                :title="$t('user_profile.field_validated')"
+              />
+            </span>
           </dd>
         </dl>
       </div>
@@ -95,19 +104,45 @@
           v-if="followsTabVisible"
           key="followees"
           :label="$t('user_card.followees')"
-          :disabled="!user.friends_count"
         >
-          <FriendList :user-id="userId">
-            <template #item="{item}">
-              <FollowCard :user="item" />
-            </template>
-          </FriendList>
+          <tab-switcher
+            :active-tab="followsTab"
+            :render-only-focused="true"
+            :on-switch="onFollowsTabSwitch"
+          >
+            <div
+              key="users"
+              :label="$t('user_card.followed_users')"
+            >
+              <FriendList :user-id="userId">
+                <template #item="{item}">
+                  <FollowCard :user="item" />
+                </template>
+              </FriendList>
+            </div>
+            <div
+              v-if="isUs"
+              key="tags"
+              :label="$t('user_card.followed_tags')"
+            >
+              <FollowedTagList
+                :user-id="userId"
+                :get-key="(item) => item.name"
+              >
+                <template #item="{item}">
+                  <FollowedTagCard :tag="item" />
+                </template>
+                <template #empty>
+                  {{ $t('user_card.not_following_any_hashtags') }}
+                </template>
+              </FollowedTagList>
+            </div>
+          </tab-switcher>
         </div>
         <div
           v-if="followersTabVisible"
           key="followers"
           :label="$t('user_card.followers')"
-          :disabled="!user.followers_count"
         >
           <FollowerList :user-id="userId">
             <template #item="{item}">
@@ -225,6 +260,11 @@
         padding: 0.5em 1.5em;
         box-sizing: border-box;
       }
+
+      .user-profile-field-validated {
+        margin-left: 1rem;
+        color: green;
+      }
     }
   }
 
diff --git a/src/hocs/with_load_more/with_load_more.jsx b/src/hocs/with_load_more/with_load_more.jsx
index c0ae1856..c55eccf5 100644
--- a/src/hocs/with_load_more/with_load_more.jsx
+++ b/src/hocs/with_load_more/with_load_more.jsx
@@ -59,7 +59,8 @@ const withLoadMore = ({
               this.loading = false
               this.bottomedOut = isEmpty(newEntries)
             })
-            .catch(() => {
+            .catch((e) => {
+              console.error(e)
               this.loading = false
               this.error = true
             })
@@ -88,7 +89,7 @@ const withLoadMore = ({
       const children = this.$slots
       return (
         <div class="with-load-more">
-          <WrappedComponent {...props}>
+          <WrappedComponent {...props} >
             {children}
           </WrappedComponent>
           <div class="with-load-more-footer">
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 905018d3..06932a4c 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -942,6 +942,7 @@
             "title": "Version"
         },
         "virtual_scrolling": "Optimize timeline rendering",
+        "use_blurhash": "Use blurhashes for NSFW thumbnails",
         "word_filter": "Word filter",
         "wordfilter": "Wordfilter"
     },
@@ -1059,6 +1060,7 @@
         "show_new": "Show new",
         "socket_broke": "Realtime connection lost: CloseEvent code {0}",
         "socket_reconnected": "Realtime connection established",
+        "follow_tag": "Follow hashtag",
         "unfollow_tag": "Unfollow hashtag",
         "up_to_date": "Up-to-date"
     },
@@ -1141,6 +1143,8 @@
         "follow_unfollow": "Unfollow",
         "followees": "Following",
         "followers": "Followers",
+        "followed_tags": "Followed hashtags",
+        "followed_users": "Followed users",
         "following": "Following!",
         "follows_you": "Follows you!",
         "hidden": "Hidden",
@@ -1179,6 +1183,9 @@
         "unfollow_confirm_accept_button": "Yes, unfollow",
         "unfollow_confirm_cancel_button": "No, don't unfollow",
         "unfollow_confirm_title": "Unfollow user",
+        "not_following_any_hashtags": "You are not following any hashtags",
+        "follow_tag": "Follow hashtag",
+        "unfollow_tag": "Unfollow hashtag",
         "unmute": "Unmute",
         "unmute_progress": "Unmuting…",
         "unsubscribe": "Unsubscribe"
@@ -1186,7 +1193,8 @@
     "user_profile": {
         "profile_does_not_exist": "Sorry, this profile does not exist.",
         "profile_loading_error": "Sorry, there was an error loading this profile.",
-        "timeline_title": "User timeline"
+        "timeline_title": "User timeline",
+        "field_validated": "Link Verified"
     },
     "user_reporting": {
         "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
diff --git a/src/modules/config.js b/src/modules/config.js
index 717c9264..9cb2a290 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -123,7 +123,8 @@ export const defaultState = {
   maxDepthInThread: undefined, // instance default
   translationLanguage: undefined, // instance default,
   supportedTranslationLanguages: {}, // instance default
-  userProfileDefaultTab: 'statuses'
+  userProfileDefaultTab: 'statuses',
+  useBlurhash: true,
 }
 
 // caching the instance default properties
diff --git a/src/modules/interface.js b/src/modules/interface.js
index ae1a31c3..33528c0d 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -186,7 +186,7 @@ const interfaceMod = {
       if (thirdColumnMode === 'none' || !rootState.users.currentUser) {
         commit('setLayoutType', normalOrMobile)
       } else {
-        const wideLayout = width >= 1300
+        const wideLayout = width >= 1280
         commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
       }
     },
diff --git a/src/modules/tags.js b/src/modules/tags.js
index cff54b7d..63f6888d 100644
--- a/src/modules/tags.js
+++ b/src/modules/tags.js
@@ -2,9 +2,15 @@ import { merge } from 'lodash'
 
 const tags = {
   state: {
-    // Contains key = id, value = number of trackers for this poll
+    // Contains key = name, value = tag json
     tags: {}
   },
+  getters: {
+    findTag: state => query => {
+      const result = state.tags[query]
+      return result
+    },
+  },
   mutations: {
     setTag (state, { name, data }) {
       state.tags[name] = data
@@ -17,17 +23,17 @@ const tags = {
         return tag
       })
     },
-    followTag (store, tagName) {
-      return store.rootState.api.backendInteractor.followHashtag({ tag: tagName })
+    followTag ({ rootState, commit }, tagName) {
+      return rootState.api.backendInteractor.followHashtag({ tag: tagName })
         .then((resp) => {
-          store.commit('setTag', { name: tagName, data: resp })
+          commit('setTag', { name: tagName, data: resp })
           return resp
         })
     },
-    unfollowTag ({ rootState, commit }, tag) {
-      return rootState.api.backendInteractor.unfollowHashtag({ tag })
+    unfollowTag ({ rootState, commit }, tagName) {
+      return rootState.api.backendInteractor.unfollowHashtag({ tag: tagName })
         .then((resp) => {
-          commit('setTag', { name: tag, data: resp })
+          commit('setTag', { name: tagName, data: resp })
           return resp
         })
     }
diff --git a/src/modules/users.js b/src/modules/users.js
index 49b299c4..0d44db89 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -5,9 +5,9 @@ import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'loda
 import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
 
 // TODO: Unify with mergeOrAdd in statuses.js
-export const mergeOrAdd = (arr, obj, item) => {
+export const mergeOrAdd = (arr, obj, item, key = 'id') => {
   if (!item) { return false }
-  const oldItem = obj[item.id]
+  const oldItem = obj[item[key]]
   if (oldItem) {
     // We already have this, so only merge the new info.
     mergeWith(oldItem, item, mergeArrayLength)
@@ -15,7 +15,7 @@ export const mergeOrAdd = (arr, obj, item) => {
   } else {
     // This is a new item, prepare it
     arr.push(item)
-    obj[item.id] = item
+    obj[item[key]] = item
     if (item.screen_name && !item.screen_name.includes('@')) {
       obj[item.screen_name.toLowerCase()] = item
     }
@@ -157,6 +157,14 @@ export const mutations = {
     const user = state.usersObject[id]
     user.followerIds = uniq(concat(user.followerIds || [], followerIds))
   },
+  saveFollowedTagIds (state, { id, followedTagIds }) {
+    const user = state.usersObject[id]
+    user.followedTagIds = uniq(concat(user.followedTagIds || [], followedTagIds))
+  },
+  saveFollowedTagPagination (state, { id, pagination }) {
+    const user = state.usersObject[id]
+    user.followedTagPagination = pagination
+  },
   // Because frontend doesn't have a reason to keep these stuff in memory
   // outside of viewing someones user profile.
   clearFriends (state, userId) {
@@ -171,6 +179,12 @@ export const mutations = {
       user['followerIds'] = []
     }
   },
+  clearFollowedTags (state, userId) {
+    const user = state.usersObject[userId]
+    if (user) {
+      user['followedTagIds'] = []
+    }
+  },
   addNewUsers (state, users) {
     each(users, (user) => {
       if (user.relationship) {
@@ -277,7 +291,7 @@ export const getters = {
   relationship: state => id => {
     const rel = id && state.relationships[id]
     return rel || { id, loading: true }
-  }
+  },
 }
 
 export const defaultState = {
@@ -288,7 +302,9 @@ export const defaultState = {
   usersObject: {},
   signUpPending: false,
   signUpErrors: [],
-  relationships: {}
+  relationships: {},
+  tags: [],
+  tagsObject: {}
 }
 
 const users = {
@@ -408,12 +424,27 @@ const users = {
           return followers
         })
     },
+    fetchFollowedTags ({ rootState, commit }, id) {
+      const user = rootState.users.usersObject[id]
+      const pagination = user.followedTagPagination
+
+      return rootState.api.backendInteractor.getFollowedHashtags({ pagination })
+        .then(({ data: tags, pagination }) => {
+          each(tags, tag => commit('setTag', { name: tag.name, data: tag }))
+          commit('saveFollowedTagIds', { id, followedTagIds: tags.map(tag => tag.name) })
+          commit('saveFollowedTagPagination', { id, pagination })
+          return tags
+        })
+    },
     clearFriends ({ commit }, userId) {
       commit('clearFriends', userId)
     },
     clearFollowers ({ commit }, userId) {
       commit('clearFollowers', userId)
     },
+    clearFollowedTags ({ commit }, userId) {
+      commit('clearFollowedTags', userId)
+    },
     subscribeUser ({ rootState, commit }, id) {
       return rootState.api.backendInteractor.subscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 477aeb48..7d3394d6 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,6 +1,7 @@
 import { each, map, concat, last, get } from 'lodash'
 import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseChat, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
 import { RegistrationError, StatusCodeError } from '../errors/errors'
+import { Url } from 'url'
 
 /* eslint-env browser */
 const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
@@ -116,6 +117,7 @@ const AKKOMA_SETTING_PROFILE_LIST = `/api/v1/akkoma/frontend_settings/pleroma-fe
 const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}`
 const MASTODON_FOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/follow`
 const MASTODON_UNFOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/unfollow`
+const MASTODON_FOLLOWED_TAGS_URL = '/api/v1/followed_tags'
 
 const oldfetch = window.fetch
 
@@ -1580,6 +1582,28 @@ const unfollowHashtag = ({ tag, credentials }) => {
   })
 }
 
+const getFollowedHashtags = ({ credentials, pagination: savedPagination }) => {
+  const queryParams = new URLSearchParams()
+  if (savedPagination?.maxId) {
+    queryParams.append('max_id', savedPagination.maxId)
+  }
+  const url = `${MASTODON_FOLLOWED_TAGS_URL}?${queryParams.toString()}`
+  let pagination = {};
+  return fetch(url, {
+    credentials
+  }).then((data) => {
+    pagination = parseLinkHeaderPagination(data.headers.get('Link'), {
+      flakeId: false
+    });
+    return data.json()
+  }).then((data) => {
+    return {
+      pagination,
+      data
+    }
+  });
+}
+
 export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
   return Object.entries({
     ...(credentials
@@ -1903,7 +1927,8 @@ const apiService = {
   deleteNoteFromReport,
   getHashtag,
   followHashtag,
-  unfollowHashtag
+  unfollowHashtag,
+  getFollowedHashtags,
 }
 
 export default apiService
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index e0d7bc61..9e30deb0 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -68,13 +68,15 @@ export const parseUser = (data) => {
     output.fields_html = data.fields.map(field => {
       return {
         name: escape(field.name),
-        value: field.value
+        value: field.value,
+        verified_at: field.verified_at
       }
     })
     output.fields_text = data.fields.map(field => {
       return {
         name: unescape(field.name.replace(/<[^>]*>/g, '')),
-        value: unescape(field.value.replace(/<[^>]*>/g, ''))
+        value: unescape(field.value.replace(/<[^>]*>/g, '')),
+        verified_at: field.verified_at
       }
     })
 
@@ -234,13 +236,14 @@ export const parseAttachment = (data) => {
   if (masto) {
     // Not exactly same...
     output.mimetype = data.pleroma ? data.pleroma.mime_type : data.type
-    output.meta = data.meta // not present in BE yet
+    output.meta = data.meta
     output.id = data.id
   } else {
     output.mimetype = data.mimetype
     // output.meta = ??? missing
   }
 
+  output.blurhash = data.blurhash
   output.url = data.url
   output.large_thumb_url = data.preview_url
   output.description = data.description
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 543aa874..7f3efd97 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -4,12 +4,10 @@ import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/th
 
 export const applyTheme = (input) => {
   const { rules } = generatePreset(input)
-  const head = document.head
   const body = document.body
   body.classList.add('hidden')
 
-  const styleEl = document.createElement('style')
-  head.appendChild(styleEl)
+  const styleEl = document.getElementById('theme-holder')
   const styleSheet = styleEl.sheet
 
   styleSheet.toString()
diff --git a/static/blurhash-overlay.png b/static/blurhash-overlay.png
new file mode 100755
index 00000000..209a9438
Binary files /dev/null and b/static/blurhash-overlay.png differ
diff --git a/static/logo-512.png b/static/logo-512.png
deleted file mode 100755
index c8397e35..00000000
Binary files a/static/logo-512.png and /dev/null differ
diff --git a/static/theme-holder.css b/static/theme-holder.css
new file mode 100644
index 00000000..3e884036
--- /dev/null
+++ b/static/theme-holder.css
@@ -0,0 +1 @@
+// This file intentionally left blank
diff --git a/yarn.lock b/yarn.lock
index bbceba0b..ebe66732 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1350,6 +1350,13 @@
     minimatch "^3.0.4"
     strip-json-comments "^3.1.1"
 
+"@floatingghost/pinch-zoom-element@^1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@floatingghost/pinch-zoom-element/-/pinch-zoom-element-1.3.1.tgz#5f327ad17ddf1f56777098aca088fdbf99cbd049"
+  integrity sha512-KnE7aBQdd/Fj1TzU5uzgwD9YAQ58DTMUks/PoTEBFW4zi0lBM9cN/j45wzcnzsT2VXG1S6qM7NMmq7NGm2//Fg==
+  dependencies:
+    pointer-tracker "^2.0.3"
+
 "@fortawesome/fontawesome-common-types@6.2.0":
   version "6.2.0"
   resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz"
@@ -1516,13 +1523,6 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
-"@kazvmoe-infra/pinch-zoom-element@1.2.0":
-  version "1.2.0"
-  resolved "https://registry.npmjs.org/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.2.0.tgz"
-  integrity sha512-HBrhH5O/Fsp2bB7EGTXzCsBAVcMjknSagKC5pBdGpKsF8meHISR0kjDIdw4YoE0S+0oNMwJ6ZUZyIBrdywxPPw==
-  dependencies:
-    pointer-tracker "^2.0.3"
-
 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
   version "5.1.1-v1"
   resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -2494,6 +2494,11 @@ binary-extensions@^2.0.0:
   resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+blurhash@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.4.tgz#60642a823b50acaaf3732ddb6c7dfd721bdfef2a"
+  integrity sha512-r/As72u2FbucLoK5NTegM/GucxJc3d8GvHc4ngo13IO/nt2HU4gONxNLq1XPN6EM/V8Y9URIa7PcSz2RZu553A==
+
 body-parser@1.19.2:
   version "1.19.2"
   resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz"
