diff --git a/build/build.js b/build/build.js
index 8242bc5f6f3180715318fcbd9169af2a1796fbf6..99ca49c060e5137ef77bb58bdadc39d225afe633 100644
--- a/build/build.js
+++ b/build/build.js
@@ -9,7 +9,7 @@ var ora = require('ora')
 var webpack = require('webpack')
 var webpackConfig = require('./webpack.prod.conf')
 
-console.log(
+console.info(
   '  Tip:\n' +
   '  Built files are meant to be served over an HTTP server.\n' +
   '  Opening index.html over file:// won\'t work.\n'
diff --git a/build/check-versions.js b/build/check-versions.js
index a269a5bc36f2e0324786ec2addb899f36d8e5792..ed6256b3af4cd4eca63fc1af9c9833d490275c2d 100644
--- a/build/check-versions.js
+++ b/build/check-versions.js
@@ -27,14 +27,12 @@ module.exports = function () {
   }
 
   if (warnings.length) {
-    console.log('')
-    console.log(chalk.yellow('To use this template, you must update following to modules:'))
-    console.log()
+    console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n'))
     for (var i = 0; i < warnings.length; i++) {
       var warning = warnings[i]
-      console.log('  ' + warning)
+      console.warn('  ' + warning)
     }
-    console.log()
+    console.warn()
     process.exit(1)
   }
 }
diff --git a/build/dev-server.js b/build/dev-server.js
index e51ba948496cad48b65f1129897b98a4a5254495..145072e70ac6c3b2f46a6ae958b9b252d93b417e 100644
--- a/build/dev-server.js
+++ b/build/dev-server.js
@@ -72,10 +72,10 @@ app.use(staticPath, express.static('./static'))
 
 module.exports = app.listen(port, function (err) {
   if (err) {
-    console.log(err)
+    console.error(err)
     return
   }
   var uri = 'http://localhost:' + port
-  console.log('Listening at ' + uri + '\n')
+  console.info('Listening at ' + uri + '\n')
   // opn(uri)
 })
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
index 97799f828cbef1edda87dadb328af9ee1ed23e50..2369e0e87aa5731e3c9be0894a823d296f771757 100644
--- a/build/webpack.dev.conf.js
+++ b/build/webpack.dev.conf.js
@@ -23,7 +23,8 @@ module.exports = merge(baseWebpackConfig, {
       'COMMIT_HASH': JSON.stringify('DEV'),
       'DEV_OVERRIDES': JSON.stringify(config.dev.settings),
       '__VUE_OPTIONS_API__': true,
-      '__VUE_PROD_DEVTOOLS__': false
+      '__VUE_PROD_DEVTOOLS__': false,
+      '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': false
     }),
     // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
     new webpack.HotModuleReplacementPlugin(),
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
index 7a108f68d850a4d131d6dd2c612140cef29be75d..4282e4bd0cf5c7050bea879a1fb959183e130bc4 100644
--- a/build/webpack.prod.conf.js
+++ b/build/webpack.prod.conf.js
@@ -50,7 +50,8 @@ var webpackConfig = merge(baseWebpackConfig, {
       'COMMIT_HASH': JSON.stringify(commitHash),
       'DEV_OVERRIDES': JSON.stringify(undefined),
       '__VUE_OPTIONS_API__': true,
-      '__VUE_PROD_DEVTOOLS__': false
+      '__VUE_PROD_DEVTOOLS__': false,
+      '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': false
     }),
     // extract css into its own file
     new MiniCssExtractPlugin({
diff --git a/changelog.d/roundup3.skip b/changelog.d/roundup3.skip
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/config/index.js b/config/index.js
index 023d4c9bced973028c1888d8fabca2f777684210..ff89274ad42860c9c49dea4d2957bf3ac4eec8a1 100644
--- a/config/index.js
+++ b/config/index.js
@@ -8,10 +8,10 @@ try {
     // and that's how actual BE reports its url
     settings.target = settings.target.replace(/\/$/, '')
   }
-  console.log('Using local dev server settings (/config/local.json):')
-  console.log(JSON.stringify(settings, null, 2))
+  console.info('Using local dev server settings (/config/local.json):')
+  console.info(JSON.stringify(settings, null, 2))
 } catch (e) {
-  console.log('Local dev server settings not found (/config/local.json)')
+  console.info('Local dev server settings not found (/config/local.json)')
 }
 
 const target = settings.target || 'http://localhost:4000/'
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index cf242092a8a2dc98f642362dfe96f0c274970a3a..e093b78c28e169aa7b5755f79d573510a28f8a16 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -174,8 +174,7 @@ const getTOS = async ({ store }) => {
       throw (res)
     }
   } catch (e) {
-    console.warn("Can't load TOS")
-    console.warn(e)
+    console.warn("Can't load TOS\n", e)
   }
 }
 
@@ -189,8 +188,7 @@ const getInstancePanel = async ({ store }) => {
       throw (res)
     }
   } catch (e) {
-    console.warn("Can't load instance panel")
-    console.warn(e)
+    console.warn("Can't load instance panel\n", e)
   }
 }
 
@@ -220,8 +218,7 @@ const getStickers = async ({ store }) => {
       throw (res)
     }
   } catch (e) {
-    console.warn("Can't load stickers")
-    console.warn(e)
+    console.warn("Can't load stickers\n", e)
   }
 }
 
diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
index d5f82f466c33349c2a403354efe96ed621138407..43db7df323f46bbd11da707f5d6c616710e5d587 100644
--- a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
+++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js
@@ -3,6 +3,9 @@ import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
 import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
 
 export const BookmarkFoldersMenuContent = {
+  props: [
+    'showPin'
+  ],
   components: {
     NavigationEntry
   },
diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue
index d603cd0105d9dff2d8dd58ae85b51b6816782888..197f796543bf5f5fe447e1d6709db78e168b72fb 100644
--- a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue
+++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue
@@ -7,10 +7,12 @@
         label: 'nav.all_bookmarks',
         icon: 'bookmark'
       }"
+      :show-pin="showPin"
     />
     <NavigationEntry
       v-for="item in folders"
       :key="item.id"
+      :show-pin="showPin"
       :item="item"
     />
   </ul>
diff --git a/src/components/confirm_modal/mute_confirm.js b/src/components/confirm_modal/mute_confirm.js
index 62f8c3de3523fff05c70e40e21b750f376f34f31..1bef9f620b229c50e2280e7b8753535eb86ddf2e 100644
--- a/src/components/confirm_modal/mute_confirm.js
+++ b/src/components/confirm_modal/mute_confirm.js
@@ -5,7 +5,7 @@ import ConfirmModal from './confirm_modal.vue'
 import Select from 'src/components/select/select.vue'
 
 export default {
-  props: ['type', 'user'],
+  props: ['type', 'user', 'status'],
   emits: ['hide', 'show', 'muted'],
   data: () => ({
     showing: false,
@@ -61,9 +61,7 @@ export default {
   },
   methods: {
     optionallyPrompt () {
-      console.log('Triggered')
       if (this.shouldConfirm) {
-        console.log('SHAWN!!')
         this.show()
       } else {
         this.doMute()
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index 8f6f1a0d061a64c11155a7fe315dfc55273a59ee..48532d3e7ec62a1daf8260c8c1ef02d9d72fa676 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -162,7 +162,6 @@ const EmojiPicker = {
       } else {
         emojiSizeReal = emojiSizeValue
       }
-      console.log(emojiSizeReal)
 
       const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSizeMultiplier * 14)
       this.emojiSize = fullEmojiSize
@@ -319,7 +318,6 @@ const EmojiPicker = {
       return this.emojiSize
     },
     itemPerRow () {
-      console.log('CALC', this.emojiSize, this.width)
       return this.width ? Math.floor(this.width / this.emojiSize) : 6
     },
     activeGroupView () {
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index 6dc86738e8066f65e6446c9eda2d19fe2d6c8925..fcd25a36e0cdb57c466b4c690d375bb6f4a88bb6 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -32,22 +32,24 @@
             <p>{{ $t("about.mrf.simple.accept_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in acceptInstances"
-                :key="entry.instance + '_accept'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in acceptInstances"
+                  :key="entry.instance + '_accept'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
@@ -57,22 +59,24 @@
             <p>{{ $t("about.mrf.simple.reject_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in rejectInstances"
-                :key="entry.instance + '_reject'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in rejectInstances"
+                  :key="entry.instance + '_reject'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
@@ -82,22 +86,24 @@
             <p>{{ $t("about.mrf.simple.quarantine_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in quarantineInstances"
-                :key="entry.instance + '_quarantine'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in quarantineInstances"
+                  :key="entry.instance + '_quarantine'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
@@ -107,22 +113,24 @@
             <p>{{ $t("about.mrf.simple.ftl_removal_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in ftlRemovalInstances"
-                :key="entry.instance + '_ftl_removal'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in ftlRemovalInstances"
+                  :key="entry.instance + '_ftl_removal'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
@@ -132,22 +140,24 @@
             <p>{{ $t("about.mrf.simple.media_nsfw_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in mediaNsfwInstances"
-                :key="entry.instance + '_media_nsfw'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in mediaNsfwInstances"
+                  :key="entry.instance + '_media_nsfw'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
@@ -157,22 +167,24 @@
             <p>{{ $t("about.mrf.simple.media_removal_desc") }}</p>
 
             <table>
-              <tr>
-                <th>{{ $t("about.mrf.simple.instance") }}</th>
-                <th>{{ $t("about.mrf.simple.reason") }}</th>
-              </tr>
-              <tr
-                v-for="entry in mediaRemovalInstances"
-                :key="entry.instance + '_media_removal'"
-              >
-                <td>{{ entry.instance }}</td>
-                <td v-if="entry.reason === ''">
-                  {{ $t("about.mrf.simple.not_applicable") }}
-                </td>
-                <td v-else>
-                  {{ entry.reason }}
-                </td>
-              </tr>
+              <tbody>
+                <tr>
+                  <th>{{ $t("about.mrf.simple.instance") }}</th>
+                  <th>{{ $t("about.mrf.simple.reason") }}</th>
+                </tr>
+                <tr
+                  v-for="entry in mediaRemovalInstances"
+                  :key="entry.instance + '_media_removal'"
+                >
+                  <td>{{ entry.instance }}</td>
+                  <td v-if="entry.reason === ''">
+                    {{ $t("about.mrf.simple.not_applicable") }}
+                  </td>
+                  <td v-else>
+                    {{ entry.reason }}
+                  </td>
+                </tr>
+              </tbody>
             </table>
           </div>
 
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index f21b43e8b5c4b23594c7b02d4032eda77738afd3..10903bcae729f091d690ae186cb6253b864a6f81 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -113,6 +113,7 @@
           :class="{ '-expanded': showBookmarkFolders }"
         >
           <BookmarkFoldersMenuContent
+            :show-pin="editMode || forceEditMode"
             class="timelines"
           />
         </div>
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
index 86c33d1fbf173d5d37550171bf6ade955cd74b14..fa89b75990ef9b24700bd19beafb167a00c38d9a 100644
--- a/src/components/navigation/navigation_pins.js
+++ b/src/components/navigation/navigation_pins.js
@@ -1,6 +1,8 @@
 import { mapState } from 'vuex'
 import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
-import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
+import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
+
+import StillImage from 'src/components/still-image/still-image.vue'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -34,12 +36,16 @@ const NavPanel = {
       return routeTo(item, this.currentUser)
     }
   },
+  components: {
+    StillImage
+  },
   computed: {
     getters () {
       return this.$store.getters
     },
     ...mapState({
       lists: getListEntries,
+      bookmarks: getBookmarkFolderEntries,
       currentUser: state => state.users.currentUser,
       followRequestCount: state => state.api.followRequests.length,
       privateMode: state => state.instance.private,
@@ -70,6 +76,7 @@ const NavPanel = {
             .filter(([k]) => this.pinnedItems.has(k))
             .map(([k, v]) => ({ ...v, name: k })),
           ...this.lists.filter((k) => this.pinnedItems.has(k.name)),
+          ...this.bookmarks.filter((k) => this.pinnedItems.has(k.name)),
           ...Object
             .entries({ ...ROOT_ITEMS })
             .filter(([k]) => this.pinnedItems.has(k))
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue
index 37351b9182872a8ae735885338cd7c3018509f45..a9d11da872dfd34fd9a937b12761758a4fd6f927 100644
--- a/src/components/navigation/navigation_pins.vue
+++ b/src/components/navigation/navigation_pins.vue
@@ -14,9 +14,14 @@
         :icon="item.icon"
       />
       <span
-        v-if="item.iconLetter"
+        v-if="item.iconLetter && !item.iconEmoji"
         class="iconLetter fa-scale-110 fa-old-padding"
       >{{ item.iconLetter }}</span>
+      <StillImage
+        v-if="item.iconEmoji"
+        class="bookmark-emoji"
+        :src="item.iconEmojiUrl"
+      />
       <div
         v-if="item.badgeGetter && getters[item.badgeGetter]"
         class="badge -dot"
@@ -52,6 +57,13 @@
     box-sizing: border-box;
     height: 100%;
 
+    .bookmark-emoji {
+      height: 100%;
+      box-sizing: border-box;
+      padding: 0.5em;
+    }
+
+    & .bookmark-emoji,
     & .svg-inline--fa,
     & .iconLetter {
       margin: 0;
diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue
index 342354bd0462059b2a3dc868ed4fc89c4a6b17c2..403ccd15923e5196d310779e27fa738d84dc4179 100644
--- a/src/components/quick_view_settings/quick_view_settings.vue
+++ b/src/components/quick_view_settings/quick_view_settings.vue
@@ -3,7 +3,6 @@
     trigger="click"
     class="QuickViewSettings"
     :bound-to="{ x: 'container' }"
-    :trigger-attrs="triggerAttrs"
   >
     <template #content>
       <div
diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss
index 118b6acfca8aeb9940b3abaa0bba82460f6fdd0d..140be2f9813b41549a746b899c7ef57bc8e82e67 100644
--- a/src/components/rich_content/rich_content.scss
+++ b/src/components/rich_content/rich_content.scss
@@ -8,6 +8,10 @@
     --funtextGreentext: var(--funtextGreentextFaint) !important;
     --funtextCyantext: var(--funtextCyantextFaint) !important;
     /* stylelint-enable declaration-no-important */
+
+    a {
+      color: var(--linkFaint);
+    }
   }
 
   blockquote {
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 3ccf025c82280addbf78b3976e07eb3af852eed0..517f54eb19557bf441a7b6df6e328e3a12eebe58 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -30,6 +30,11 @@ const GeneralTab = {
         value: mode,
         label: this.$t(`settings.conversation_display_${mode}`)
       })),
+      absoluteTime12hOptions: ['24h', '12h'].map(mode => ({
+        key: mode,
+        value: mode,
+        label: this.$t(`settings.absolute_time_format_12h_${mode}`)
+      })),
       conversationOtherRepliesButtonOptions: ['below', 'inside'].map(mode => ({
         key: mode,
         value: mode,
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 911e1b1da3868124f01cab05ccffb93d5a994ae2..6d01f208e449be4526422a5b1efb236c7e6b8e3a 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -249,6 +249,16 @@
               {{ $t('settings.absolute_time_format_min_age') }}
             </UnitSetting>
           </li>
+          <li>
+            <ChoiceSetting
+              id="absoluteTime12h"
+              path="absoluteTime12h"
+              :options="absoluteTime12hOptions"
+              :expert="1"
+            >
+              {{ $t('settings.absolute_time_format_12h') }}
+            </ChoiceSetting>
+          </li>
         </ul>
         <h3>{{ $t('settings.attachments') }}</h3>
         <li>
@@ -462,22 +472,6 @@
             {{ $t('settings.minimal_scopes_mode') }}
           </BooleanSetting>
         </li>
-        <li>
-          <BooleanSetting
-            path="alwaysShowNewPostButton"
-            expert="1"
-          >
-            {{ $t('settings.always_show_post_button') }}
-          </BooleanSetting>
-        </li>
-        <li>
-          <BooleanSetting
-            path="autohideFloatingPostButton"
-            expert="1"
-          >
-            {{ $t('settings.autohide_floating_post_button') }}
-          </BooleanSetting>
-        </li>
         <li>
           <BooleanSetting
             path="padEmoji"
@@ -501,7 +495,7 @@
             {{ $t('settings.auto_save_draft') }}
           </BooleanSetting>
         </li>
-        <li v-if="!autoSaveDraft">
+        <li v-if="!mergedConfig.autoSaveDraft">
           <ChoiceSetting
             id="unsavedPostAction"
             path="unsavedPostAction"
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 0657621dd8e98711d8ec88d116dd9375987e3eae..489351b69583e2062ff5c8fe84f30ddddc8bd7e0 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -130,7 +130,7 @@ export default {
         const serialized = this.cValue.map(x => serializeShadow(x)).join(',')
         serialized.split(/,/).map(deserializeShadow) // validate
         const expandedShadow = flattenDeep(findShadow(this.cValue, { dynamicVars: {}, staticVars: this.staticVars }))
-        const fixedShadows = expandedShadow.map(x => ({ ...x, color: console.log(x) || rgb2hex(x.color) }))
+        const fixedShadows = expandedShadow.map(x => ({ ...x, color: rgb2hex(x.color) }))
 
         if (this.separateInset) {
           result = {
diff --git a/src/components/status/status.js b/src/components/status/status.js
index f3a1bbee0bb6ce578142256b7cf2a641c76004cd..4f8ba31e45474a1cae96d1449740b9f0b4c968ba 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -255,7 +255,7 @@ const Status = {
     muteReasons () {
       return [
         this.userIsMuted ? 'user' : null,
-        status.thread_muted ? 'thread' : null,
+        this.status.thread_muted ? 'thread' : null,
         (this.muteWordHits.length > 0) ? 'wordfilter' : null,
         (this.muteBotStatuses && this.botStatus) ? 'bot' : null,
         (this.muteSensitiveStatuses && this.sensitiveStatus) ? 'nsfw' : null
@@ -280,14 +280,18 @@ const Status = {
           case 'nsfw': return this.$t('status.sensitive_muted')
         }
       }
-      return this.$t(
-        'status.multi_reason_mute',
-        {
-          main: mainReason(),
-          numReasonsMore: this.muteReasons.length - 1
-        },
-        this.muteReasons.length - 1
-      )
+      if (this.muteReasons.length > 1) {
+        return this.$t(
+          'status.multi_reason_mute',
+          {
+            main: mainReason(),
+            numReasonsMore: this.muteReasons.length - 1
+          },
+          this.muteReasons.length - 1
+        )
+      } else {
+        return mainReason()
+      }
     },
     muted () {
       if (this.statusoid.user.id === this.currentUser.id) return false
@@ -299,9 +303,9 @@ const Status = {
       const { reblog } = status
       const relationship = this.$store.getters.relationship(status.user.id)
       const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
-      return status.muted ||
+      return (status.muted && !status.thread_muted) ||
         // Reprööt of a muted post according to BE
-        (reblog && reblog.muted) ||
+        (reblog && reblog.muted && !reblog.thread_muted) ||
         // Muted user
         relationship.muting ||
         // Muted user of a reprööt
diff --git a/src/components/status_action_buttons/action_button.js b/src/components/status_action_buttons/action_button.js
index 9a27238fd4f3ca6c8453bf160bdea3c911f6f1dd..32e3bd6e1cf5ab0474f3cefe148e62b2a8c54ae1 100644
--- a/src/components/status_action_buttons/action_button.js
+++ b/src/components/status_action_buttons/action_button.js
@@ -92,6 +92,9 @@ export default {
     threadIsMuted () {
       return this.status.thread_muted
     },
+    hideCustomEmoji () {
+      return !this.$store.state.instance.pleromaCustomEmojiReactionsAvailable
+    },
     buttonInnerClass () {
       return [
         this.button.name + '-button',
diff --git a/src/components/status_action_buttons/action_button.scss b/src/components/status_action_buttons/action_button.scss
index 14bfdfa9d9523fb25aaa9d53bb9fb4b07f5b1942..bbd3f7af446a4ade7d19250d3ef5cce4e37a4a1d 100644
--- a/src/components/status_action_buttons/action_button.scss
+++ b/src/components/status_action_buttons/action_button.scss
@@ -2,9 +2,9 @@
 /* stylelint-disable declaration-no-important */
 
 .quick-action {
-  display: grid;
-  grid-template-columns: minmax(max-content, 1fr);
-  grid-gap: 0.25em;
+  justify-content: space-between;
+  display: flex;
+  align-items: baseline;
   align-items: center;
   height: 1.5em;
 
@@ -12,6 +12,7 @@
     overflow-x: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
+    margin-left: 1em;
   }
 
   .action-button-inner,
@@ -25,6 +26,7 @@
     align-self: stretch;
     width: 1px;
     background-color: var(--icon);
+    margin-left: 1em;
     margin-right: 0.5em;
   }
 
@@ -35,6 +37,7 @@
     border-radius: var(--roundness);
     grid-template-columns: minmax(max-content, 1fr) auto;
 
+    .chevron-icon,
     .extra-button,
     .separator {
       display: none;
@@ -44,10 +47,30 @@
   .action-button-inner {
     display: grid;
     grid-gap: 1em;
-    grid-template-columns: max-content 1fr;
+    grid-template-columns: max-content;
     grid-auto-flow: column;
     grid-auto-columns: max-content;
     align-items: center;
+
+    @include unfocused-style {
+      .focus-marker {
+        visibility: hidden;
+      }
+
+      .active-marker {
+        visibility: visible;
+      }
+    }
+
+    @include focused-style {
+      .focus-marker {
+        visibility: visible;
+      }
+
+      .active-marker {
+        visibility: hidden;
+      }
+    }
   }
 }
 
@@ -79,24 +102,4 @@
       }
     }
   }
-
-  @include unfocused-style {
-    .focus-marker {
-      visibility: hidden;
-    }
-
-    .active-marker {
-      visibility: visible;
-    }
-  }
-
-  @include focused-style {
-    .focus-marker {
-      visibility: visible;
-    }
-
-    .active-marker {
-      visibility: hidden;
-    }
-  }
 }
diff --git a/src/components/status_action_buttons/action_button.vue b/src/components/status_action_buttons/action_button.vue
index b399f392b311b3c76e82488647a886b7367eb4e1..105af5caf106af01f3ccefbfbfb7a71a8ea0c4a3 100644
--- a/src/components/status_action_buttons/action_button.vue
+++ b/src/components/status_action_buttons/action_button.vue
@@ -9,6 +9,7 @@
       :class="buttonInnerClass"
       role="menuitem"
       type="button"
+      :title="$t(button.label(funcArg))"
       target="_blank"
       :tabindex="0"
       :disabled="buttonClass.disabled"
@@ -50,12 +51,6 @@
       >
         {{ $t(button.label(funcArg)) }}
       </span>
-      <span
-        v-if="!extra && button.counter?.(funcArg) > 0"
-        class="action-counter"
-      >
-        {{ button.counter?.(funcArg) }}
-      </span>
       <FAIcon
         v-if="button.dropdown?.()"
         class="chevron-icon"
@@ -64,15 +59,21 @@
         fixed-width
       />
     </component>
+    <span
+      v-if="!extra && button.counter?.(funcArg) > 0"
+      class="action-counter"
+    >
+      {{ button.counter?.(funcArg) }}
+    </span>
     <span
       v-if="!extra && button.name === 'bookmark'"
       class="separator"
     />
     <Popover
       v-if="button.name === 'bookmark'"
-      trigger="hover"
+      :trigger="extra ? 'hover' : 'click'"
       :placement="extra ? 'right' : 'top'"
-      :offset="{ y: 5 }"
+      :offset="extra ? { x: 10 } : { y: 10 }"
       :trigger-attrs="{ class: 'extra-button' }"
     >
       <template #trigger>
diff --git a/src/components/status_action_buttons/action_button_container.js b/src/components/status_action_buttons/action_button_container.js
index 6bd3fde54752b7d688fd83145e21711bb100b0c6..bd91b94047a8d3defd1088814b43090d796e075c 100644
--- a/src/components/status_action_buttons/action_button_container.js
+++ b/src/components/status_action_buttons/action_button_container.js
@@ -58,8 +58,8 @@ export default {
     unmuteUser () {
       return this.$store.dispatch('unmuteUser', this.user.id)
     },
-    unmuteThread () {
-      return this.$store.dispatch('unmuteConversation', this.user.id)
+    unmuteConversation () {
+      return this.$store.dispatch('unmuteConversation', { id: this.status.id })
     },
     unmuteDomain () {
       return this.$store.dispatch('unmuteDomain', this.user.id)
diff --git a/src/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue
index d70f13f9f798853809c044f2e8adb0e6fa09577c..deada02ae706d93befaf90679c88e9b5aafcd4fa 100644
--- a/src/components/status_action_buttons/action_button_container.vue
+++ b/src/components/status_action_buttons/action_button_container.vue
@@ -2,7 +2,7 @@
   <div>
     <Popover
       v-if="button.dropdown?.()"
-      trigger="hover"
+      :trigger="$attrs.extra ? 'hover' : 'click'"
       :offset="{ y: 5 }"
       :placement="$attrs.extra ? 'right' : 'top'"
     >
@@ -40,13 +40,13 @@
           <div class="menu-item dropdown-item extra-action -icon">
             <button
               class="main-button"
-              @click="toggleUserMute"
+              @click="toggleConversationMute"
             >
               <FAIcon
                 icon="folder-tree"
                 fixed-width
               />
-              <template v-if="threadIsMuted">
+              <template v-if="conversationIsMuted">
                 {{ $t('status.unmute_conversation') }}
               </template>
               <template v-else>
@@ -81,19 +81,22 @@
       v-bind="$attrs"
     />
     <teleport to="#modal">
-      <mute-confirm
+      <MuteConfirm
         ref="confirmConversation"
         type="conversation"
         :status="status"
+        :user="user"
       />
-      <mute-confirm
+      <MuteConfirm
         ref="confirmDomain"
         type="domain"
+        :status="status"
         :user="user"
       />
-      <mute-confirm
+      <MuteConfirm
         ref="confirmUser"
         type="user"
+        :status="status"
         :user="user"
       />
     </teleport>
diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js
index f885540033a146ce958cd80fef5c542cd68e08a2..622f21ca586c89d2ce492fc6bd40eed06a4064d9 100644
--- a/src/components/status_action_buttons/status_action_buttons.js
+++ b/src/components/status_action_buttons/status_action_buttons.js
@@ -51,9 +51,6 @@ const StatusActionButtons = {
     currentUser () {
       return this.$store.state.users.currentUser
     },
-    hideCustomEmoji () {
-      return !this.$store.state.instance.pleromaCustomEmojiReactionsAvailable
-    },
     funcArg () {
       return {
         status: this.status,
@@ -71,7 +68,6 @@ const StatusActionButtons = {
       return {
         title: this.$t('status.more_actions'),
         'aria-controls': `popup-menu-${this.randomSeed}`,
-        'aria-expanded': this.expanded,
         'aria-haspopup': 'menu'
       }
     }
diff --git a/src/components/status_action_buttons/status_action_buttons.scss b/src/components/status_action_buttons/status_action_buttons.scss
index 4acf4a072aee3ad82cc1e8e6dff69ce12504d2c5..018ff34d9ca85dd8efdc74b7be52bc2f5ad04792 100644
--- a/src/components/status_action_buttons/status_action_buttons.scss
+++ b/src/components/status_action_buttons/status_action_buttons.scss
@@ -3,12 +3,11 @@
 .StatusActionButtons {
   .quick-action-buttons {
     display: grid;
-    grid-template-columns: repeat(auto-fill, 5em);
+    grid-template-columns: repeat(auto-fill, 4em);
     grid-auto-flow: row dense;
     grid-auto-rows: 1fr;
     grid-gap: 1.25em 1em;
     margin-top: var(--status-margin);
-    align-items: baseline;
   }
 
   .pin-action-button {
diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue
index d0b86e7d406597c7ea69ea1d37ac11c028e83123..bc779e80189aa61ec5456184f4b872ea8f73e2da 100644
--- a/src/components/timeago/timeago.vue
+++ b/src/components/timeago/timeago.vue
@@ -28,6 +28,9 @@ export default {
       }
       return DateUtils.durationStrToMs(this.$store.getters.mergedConfig.absoluteTimeFormatMinAge) <= this.relativeTimeMs
     },
+    time12hFormat () {
+      return this.$store.getters.mergedConfig.absoluteTimeFormat12h === '12h'
+    },
     browserLocale () {
       return localeService.internalToBrowserLocale(this.$i18n.locale)
     },
@@ -57,22 +60,26 @@ export default {
         if (DateUtils.isSameDay(this.timeAsDate, now)) {
           return new Intl.DateTimeFormat(this.browserLocale, {
             minute: 'numeric',
-            hour: 'numeric'
+            hour: 'numeric',
+            hour12: this.time12hFormat
           })
         } else if (DateUtils.isSameMonth(this.timeAsDate, now)) {
           return new Intl.DateTimeFormat(this.browserLocale, {
             month: 'short',
-            day: 'numeric'
+            day: 'numeric',
+            hour12: this.time12hFormat
           })
         } else if (DateUtils.isSameYear(this.timeAsDate, now)) {
           return new Intl.DateTimeFormat(this.browserLocale, {
             month: 'short',
-            day: 'numeric'
+            day: 'numeric',
+            hour12: this.time12hFormat
           })
         } else {
           return new Intl.DateTimeFormat(this.browserLocale, {
             year: 'numeric',
-            month: 'short'
+            month: 'short',
+            hour12: this.time12hFormat
           })
         }
       })()
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index e4018b8a52c3396e51331ef1643e459f7a02bd35..11c3e047ac3d9d4b784ccf46a3cac4f6483d3b3f 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -311,7 +311,7 @@
       />
     </div>
     <teleport to="#modal">
-      <mute-confirm
+      <MuteConfirm
         ref="confirmation"
         type="user"
         :user="user"
diff --git a/src/i18n/compare b/src/i18n/compare
index 4dc1e47dfb141905ad5f125ef03ba9f96f59cff9..94e5aab55585f983df625ac28aa189b06891921a 100755
--- a/src/i18n/compare
+++ b/src/i18n/compare
@@ -2,23 +2,23 @@
 const arg = process.argv[2]
 
 if (typeof arg === 'undefined') {
-  console.log('This is a very simple and tiny tool that checks en.json with any other language and')
-  console.log('outputs all the things present in english but missing in foreign language.')
-  console.log('')
-  console.log('Usage: ./compare.js <lang> ')
-  console.log('       or')
-  console.log('       node ./compare.js <lang>')
-  console.log('')
-  console.log('Where <lang> is name of .json file containing language. For ./fi.json it should be:')
-  console.log('      ./compare.js fi ')
-  console.log('')
-  console.log('Limitations: ')
-  console.log('* This program does not work with languages left over in messages.js')
-  console.log('* This program does not check for extra strings present in foreign language but missing')
-  console.log('  in english.js (for now)')
-  console.log('')
-  console.log('There are no other arguments or options. Make an issue if you encounter a bug or want')
-  console.log('some feature to be implemented. Merge requests are welcome as well.')
+  console.info('This is a very simple and tiny tool that checks en.json with any other language and')
+  console.info('outputs all the things present in english but missing in foreign language.')
+  console.info('')
+  console.info('Usage: ./compare.js <lang> ')
+  console.info('       or')
+  console.info('       node ./compare.js <lang>')
+  console.info('')
+  console.info('Where <lang> is name of .json file containing language. For ./fi.json it should be:')
+  console.info('      ./compare.js fi ')
+  console.info('')
+  console.info('Limitations: ')
+  console.info('* This program does not work with languages left over in messages.js')
+  console.info('* This program does not check for extra strings present in foreign language but missing')
+  console.info('  in english.js (for now)')
+  console.info('')
+  console.info('There are no other arguments or options. Make an issue if you encounter a bug or want')
+  console.info('some feature to be implemented. Merge requests are welcome as well.')
   process.exit()
 }
 
@@ -35,10 +35,10 @@ function walker (a, b, path = []) {
     const article = aType[0] === 'o' ? 'an' : 'a'
 
     if (bType === 'undefined') {
-      console.log(`Foreign language is missing ${article} ${aType} at path ${currentPath.join('.')}`)
+      console.warn(`Foreign language is missing ${article} ${aType} at path ${currentPath.join('.')}`)
     } else if (aType === 'object') {
       if (bType !== 'object') {
-        console.log(`Type mismatch! English has ${aType} while foreign has ${bType} at path ${currentPath.join['.']}`)
+        console.warn(`Type mismatch! English has ${aType} while foreign has ${bType} at path ${currentPath.join['.']}`)
       } else {
         walker(aVal, bVal, currentPath)
       }
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 13872d0e64bd94f6516983c652645a48d95eb2cd..3fad5ca831175e16b828a52734bbc4e58d6856fa 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -532,6 +532,9 @@
     "emoji_reactions_scale": "Reactions scale factor",
     "absolute_time_format": "Use absolute time format",
     "absolute_time_format_min_age": "Only use for time older than this amount of time",
+    "absolute_time_format_12h": "Time format",
+    "absolute_time_format_12h_12h": "12 hour format (i.e. 10:00 PM)",
+    "absolute_time_format_12h_24h": "24 hour format (i.e. 22:00)",
     "export_theme": "Save preset",
     "filtering": "Filtering",
     "wordfilter": "Wordfilter",
@@ -1256,7 +1259,7 @@
     "copy_link": "Copy link to status",
     "external_source": "External source",
     "muted_words": "Wordfiltered: {word} | Wordfiltered: {word} and {numWordsMore} more words",
-    "multi_reason_mute": "{main} | {main} + one more reason | {main} + {numReasonsMore} more reasons",
+    "multi_reason_mute": "{main} + one more reason | {main} + {numReasonsMore} more reasons",
     "muted_user": "User muted",
     "thread_muted": "Thread muted",
     "thread_muted_and_words": ", has words:",
diff --git a/src/modules/config.js b/src/modules/config.js
index c42fc8c12b44cb2e4403732287c9638cab5ab7a2..ebc8b9311ec8e8de6f9e2811d57c55c895822816 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -193,6 +193,7 @@ export const defaultState = {
   autoSaveDraft: undefined, // instance default
   useAbsoluteTimeFormat: undefined, // instance default
   absoluteTimeFormatMinAge: undefined, // instance default
+  absoluteTime12h: undefined, // instance default
   imageCompression: true
 }
 
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 408aaef16b02652d3a7b7751ba5b1c2f616c623f..228eca72a6e92b783f77bd57d1063e1fc28ca2ef 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -131,6 +131,7 @@ const defaultState = {
   autoSaveDraft: false,
   useAbsoluteTimeFormat: false,
   absoluteTimeFormatMinAge: '0d',
+  absoluteTime12h: '24h',
 
   // Nasty stuff
   customEmoji: [],
@@ -314,8 +315,7 @@ const instance = {
         }, {})
         commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
       } catch (e) {
-        console.warn("Can't load static emoji")
-        console.warn(e)
+        console.warn("Can't load static emoji\n", e)
       }
     },
 
@@ -382,8 +382,7 @@ const instance = {
           throw (res)
         }
       } catch (e) {
-        console.warn("Can't load custom emojis")
-        console.warn(e)
+        console.warn("Can't load custom emojis\n", e)
       }
     },
     fetchEmoji ({ dispatch, state }) {
@@ -404,8 +403,7 @@ const instance = {
         })
         commit('setKnownDomains', result)
       } catch (e) {
-        console.warn("Can't load known domains")
-        console.warn(e)
+        console.warn("Can't load known domains\n", e)
       }
     }
   }
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
index ad581fd4ea1e1d33549181bbf09f7c9cf7d98211..fab88ddb17dfe4380549df605b208ca57ee6df3a 100644
--- a/src/modules/serverSideStorage.js
+++ b/src/modules/serverSideStorage.js
@@ -9,7 +9,8 @@ import {
   groupBy,
   findLastIndex,
   takeRight,
-  uniqWith
+  uniqWith,
+  merge as _merge
 } from 'lodash'
 import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
 
@@ -127,14 +128,8 @@ export const _getRecentData = (cache, live, isTest) => {
     _version: a._version ?? b._version,
     _timestamp: a._timestamp ?? b._timestamp,
     needUpload: b.needUpload ?? a.needUpload,
-    prefsStorage: {
-      ...a.prefsStorage,
-      ...b.prefsStorage
-    },
-    flagStorage: {
-      ...a.flagStorage,
-      ...b.flagStorage
-    }
+    prefsStorage: _merge(a.prefsStorage, b.prefsStorage),
+    flagStorage: _merge(a.flagStorage, b.flagStorage)
   })
   result.recent = isTest ? result.recent : (result.recent && merge(defaultState, result.recent))
   result.stale = isTest ? result.stale : (result.stale && merge(defaultState, result.stale))
@@ -315,7 +310,7 @@ export const mutations = {
     state.raw = live
     let cache = state.cache
     if (cache && cache._user !== userData.fqn) {
-      console.warn('cache belongs to another user! reinitializing local cache!')
+      console.warn('Cache belongs to another user! reinitializing local cache!')
       cache = null
     }
 
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 5822c533c314f4025632bd77d311bc13e19905e3..50507fa28c9c2bd8fc0598e718989b9e826e8e5b 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -276,8 +276,8 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
       // NOOP, it is known status but we don't do anything about it for now
     },
     default: (unknown) => {
-      console.log('unknown status type')
-      console.log(unknown)
+      console.warn('unknown status type')
+      console.warn(unknown)
     }
   }
 
@@ -549,11 +549,11 @@ const statuses = {
       rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
-    muteConversation ({ rootState, commit }, statusId) {
+    muteConversation ({ rootState, commit }, { id: statusId }) {
       return rootState.api.backendInteractor.muteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
-    unmuteConversation ({ rootState, commit }, statusId) {
+    unmuteConversation ({ rootState, commit }, { id: statusId }) {
       return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 2a61df1e245098ff051d22571d16749eae784e06..0e78b57afde7dd66e72cefb95155c7096fffd57f 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -1,7 +1,7 @@
 import { invertLightness, contrastRatio, convert } from 'chromatism'
 
 // useful for visualizing color when debugging
-export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
+// const consoleColor = (color) => console.debug('%c##########', 'background: ' + color + '; color: ' + color)
 
 /**
  * Convert r, g, b values into hex notation. All components are [0-255]
@@ -236,8 +236,8 @@ export const getTextColor = function (bg, text, preserve) {
 
   let contrast = getContrastRatio(bg, text)
   const result = convert(rgb2hex(workColor)).hsl
-  const delta = result.l > 50 ? 1 : -1
-  const multiplier = 10
+  const delta = result.l >= 50 ? 1 : -1
+  const multiplier = 1
   while (contrast < 4.5 && result.l > 20 && result.l < 80) {
     result.l += delta * multiplier
     result.l = Math.min(100, Math.max(0, result.l))
diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js
index 348a9d006a927c9fdccce08cec76a7079cb3b9c1..ec53a7434372479664239baf00c2c1b22c59cbe9 100644
--- a/src/services/export_import/export_import.js
+++ b/src/services/export_import/export_import.js
@@ -3,7 +3,7 @@ import utf8 from 'utf8'
 export const newExporter = ({
   filename = 'data',
   mime = 'application/json',
-  extension = '.json',
+  extension = 'json',
   getExportedObject
 }) => ({
   exportData () {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 3791c6f34ab47c6547c2399bc9d782952773d608..5592c527e05e7e68f883806c9f58eae271068bd8 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -95,9 +95,10 @@ export const tryLoadCache = async () => {
   if (!data) return null
   let cache
   try {
-    const decoded = new TextDecoder().decode(pako.inflate(data))
+    const inflated = pako.inflate(data)
+    const decoded = new TextDecoder().decode(inflated)
     cache = JSON.parse(decoded)
-    console.info(`Loaded theme from cache, size=${cache}`)
+    console.info(`Loaded theme from cache, compressed=${Math.ceil(data.length / 1024)}kiB size=${Math.ceil(inflated.length / 1024)}kiB`)
   } catch (e) {
     console.error('Failed to decode theme cache:', e)
     return false
diff --git a/test/unit/specs/components/rich_content.spec.js b/test/unit/specs/components/rich_content.spec.js
index 427eb5ed0e13dc7baec637830cdc7ff081d8e275..37d8bcbfaa3cae958fcf065a4cf4656fc8068b0f 100644
--- a/test/unit/specs/components/rich_content.spec.js
+++ b/test/unit/specs/components/rich_content.spec.js
@@ -539,7 +539,6 @@ describe('RichContent', () => {
       `,
       props: ['handleLinks', 'attentions', 'vhtml']
     }
-    console.log(1)
 
     const ptest = (handleLinks, vhtml) => {
       const t0 = performance.now()
@@ -562,11 +561,11 @@ describe('RichContent', () => {
       return `Mount: ${t1 - t0}ms, destroy: ${t2 - t1}ms, avg ${(t1 - t0) / amount}ms - ${(t2 - t1) / amount}ms per item`
     }
 
-    console.log(`${amount} items with links handling:`)
-    console.log(ptest(true))
-    console.log(`${amount} items without links handling:`)
-    console.log(ptest(false))
-    console.log(`${amount} items plain v-html:`)
-    console.log(ptest(false, true))
+    console.debug(`${amount} items with links handling:`)
+    console.debug(ptest(true))
+    console.debug(`${amount} items without links handling:`)
+    console.debug(ptest(false))
+    console.debug(`${amount} items plain v-html:`)
+    console.debug(ptest(false, true))
   })
 })
diff --git a/test/unit/specs/services/theme_data/iss_deserializer.spec.js b/test/unit/specs/services/theme_data/iss_deserializer.spec.js
index 6eb25dfe91396fa7eb61331381082742e4d30a27..ca97243f723efafb5f48a106b8f61efd6c04a4fd 100644
--- a/test/unit/specs/services/theme_data/iss_deserializer.spec.js
+++ b/test/unit/specs/services/theme_data/iss_deserializer.spec.js
@@ -21,17 +21,17 @@ describe('ISS (de)serialization', () => {
   const onlyComponent = componentsContext('./components/panel_header.style.js').default
   it.only(`(De)serialization of component ${onlyComponent.name} works`, () => {
     const normalized = onlyComponent.defaultRules.map(x => ({ component: onlyComponent.name, ...x }))
-    console.log('BEGIN INPUT ================')
-    console.log(normalized)
-    console.log('END INPUT ==================')
+    console.debug('BEGIN INPUT ================')
+    console.debug(normalized)
+    console.debug('END INPUT ==================')
     const serialized = serialize(normalized)
-    console.log('BEGIN SERIAL ===============')
-    console.log(serialized)
-    console.log('END SERIAL =================')
+    console.debug('BEGIN SERIAL ===============')
+    console.debug(serialized)
+    console.debug('END SERIAL =================')
     const deserialized = deserialize(serialized)
-    console.log('BEGIN DESERIALIZED =========')
-    console.log(serialized)
-    console.log('END DESERIALIZED ===========')
+    console.debug('BEGIN DESERIALIZED =========')
+    console.debug(serialized)
+    console.debug('END DESERIALIZED ===========')
 
     // for some reason comparing objects directly fails the assert
     expect(JSON.stringify(deserialized, null, 2)).to.equal(JSON.stringify(normalized, null, 2))
diff --git a/tools/emoji_merger.js b/tools/emoji_merger.js
index a74e847edf2da28f8cea7e1fc3aec211886cb816..78a798dfe374a71388936e69e65e7e550e1f8324 100644
--- a/tools/emoji_merger.js
+++ b/tools/emoji_merger.js
@@ -60,7 +60,7 @@ const run = () => {
   }, {})
 
   fs.writeFile(outputFilename, JSON.stringify(sorted, null, 2), 'utf8', (err) => {
-    if (err) console.log('Error writing file', err)
+    if (err) console.error('Error writing file', err)
   })
 }