diff --git a/changes/changelog.d/872.feature b/changes/changelog.d/872.feature
new file mode 100644
index 0000000000000000000000000000000000000000..083601ecfb05ad68debd48ba4ba6fe9d6dee8b7e
--- /dev/null
+++ b/changes/changelog.d/872.feature
@@ -0,0 +1 @@
+Redesign of the landing and about pages (#872)
diff --git a/changes/notes.rst b/changes/notes.rst
index 442fc0528f7f6c5d8a022d16457c850efefe9b03..51c53e85792877782e4649cf96601e2bd3856aba 100644
--- a/changes/notes.rst
+++ b/changes/notes.rst
@@ -62,6 +62,39 @@ For more information about this feature, please check out our documentation:
 
 -  `User documentation <https://docs.funkwhale.audio/users/account.html>`_
 
+Landing and about page redesign [Manual action suggested]
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In this release, we've completely redesigned the landing and about page, by making it more useful and adapted to your pod
+configuration. Among other things, the landing page will now include:
+
+- your pod and an excerpt from your pod's description
+- your pod banner image, if any
+- your contact email, if any
+- the login form
+- the signup form (if registrations are open on your pod)
+- some basic statistics about your pod
+- a widget including recently uploaded albums, if anonymous access is enabled
+
+The landing page will still include some information about Funkwhale, but in a less intrusive and proeminent way than before.
+
+Additionally, the about page now includes:
+
+- your pod name, description, rules and terms
+- your pod banner image, if any
+- your contact email, if any
+- comprehensive statistics about your pod
+- some info about your pod configuration, such as registration and federation status or the default upload quota for new users
+
+With this redesign, we've added a handful of additional pod settings:
+
+- Pod banner image
+- Contact email
+- Rules
+- Terms of service
+
+We recommend taking a few moments to fill these accordingly to your needs, by visiting ``/manage/settings``.
+
 Allow-list to restrict federation to trusted domains
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/front/src/App.vue b/front/src/App.vue
index 9881554f9f102a6b5507de2c40e2f9f8279d0de3..5be97dfb5655f43f5093353fcfffd55e034aa310 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -62,13 +62,12 @@ export default {
   data () {
     return {
       bridge: null,
-      nodeinfo: null,
       instanceUrl: null,
       showShortcutsModal: false,
       showSetInstanceModal: false,
     }
   },
-  created () {
+  async created () {
     this.openWebsocket()
     let self = this
     if (!this.$store.state.ui.selectedLanguage) {
@@ -78,7 +77,12 @@ export default {
       // used to redraw ago dates every minute
       self.$store.commit('ui/computeLastDate')
     }, 1000 * 60)
-    if (!this.$store.state.instance.instanceUrl) {
+    const urlParams = new URLSearchParams(window.location.search);
+    const serverUrl = urlParams.get('_server')
+    if (serverUrl) {
+      this.$store.commit('instance/instanceUrl', serverUrl)
+    }
+    else if (!this.$store.state.instance.instanceUrl) {
       // we have several way to guess the API server url. By order of precedence:
       // 1. use the url provided in settings.json, if any
       // 2. use the url specified when building via VUE_APP_INSTANCE_URL
@@ -89,9 +93,9 @@ export default {
       // needed to trigger initialization of axios
       this.$store.commit('instance/instanceUrl', this.$store.state.instance.instanceUrl)
     }
+    await this.fetchNodeInfo()
     this.$store.dispatch('auth/check')
     this.$store.dispatch('instance/fetchSettings')
-    this.fetchNodeInfo()
     this.$store.commit('ui/addWebsocketEventHandler', {
       eventName: 'inbox.item_added',
       id: 'sidebarCount',
@@ -152,14 +156,11 @@ export default {
       this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewEdits', value: event.pending_review_count})
     },
     incrementPendingReviewReportsCountInSidebar (event) {
-      console.log('HELLO', event)
       this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewReports', value: event.unresolved_count})
     },
-    fetchNodeInfo () {
-      let self = this
-      axios.get('instance/nodeinfo/2.0/').then(response => {
-        self.nodeinfo = response.data
-      })
+    async fetchNodeInfo () {
+      let response = await axios.get('instance/nodeinfo/2.0/')
+      this.$store.commit('instance/nodeinfo', response.data)
     },
     autodetectLanguage () {
       let userLanguage = navigator.language || navigator.userLanguage
@@ -235,7 +236,8 @@ export default {
   },
   computed: {
     ...mapState({
-      messages: state => state.ui.messages
+      messages: state => state.ui.messages,
+      nodeinfo: state => state.instance.nodeinfo,
     }),
     ...mapGetters({
       currentTrack: 'queue/currentTrack'
diff --git a/front/src/assets/network.png b/front/src/assets/network.png
new file mode 100644
index 0000000000000000000000000000000000000000..e9d5f4ddd465afcb18acd5d7359c4b788b706d9a
Binary files /dev/null and b/front/src/assets/network.png differ
diff --git a/front/src/components/About.vue b/front/src/components/About.vue
index 87f741aa819f38ba84c856522979f25e6f6016d1..3c84b48f9642bc132b54cd16a8b0508fb669711e 100644
--- a/front/src/components/About.vue
+++ b/front/src/components/About.vue
@@ -1,38 +1,197 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
-    <section class="ui vertical center aligned stripe segment">
-      <div class="ui text container">
-        <h1 class="ui huge header">
-            <span v-translate="{instance: instance.name.value}" translate-context="Content/About/Title/Short, Noun" v-if="instance.name.value" :translate-params="{instance: instance.name.value}">
-             About %{ instance }
-            </span>
-            <translate translate-context="Content/About/Title" v-else>About this instance</translate>
+  <main class="main pusher">
+    <section :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle">
+      <div class="segment-content">
+        <h1 class="ui center aligned large header">
+          <translate translate-context="Content/Home/Header"
+            :translate-params="{podName: podName}">
+            About %{ podName }
+          </translate>
+          <div v-if="shortDescription" class="sub header">
+            {{ shortDescription }}
+          </div>
         </h1>
-        <stats></stats>
       </div>
     </section>
     <section class="ui vertical stripe segment">
-      <div
-        class="ui middle aligned stackable text container">
-        <p
-        v-if="!instance.short_description.value && !instance.long_description.value"><translate translate-context="Content/About/Paragraph">Unfortunately, the owners of this instance did not yet take the time to complete this page.</translate></p>
-        <router-link
-          class="ui button"
-          v-if="$store.state.auth.availablePermissions['settings']"
-          :to="{path: '/manage/settings', hash: 'instance'}">
-          <i class="pencil icon"></i><translate translate-context="Content/Settings/Button.Label/Verb">Edit instance info</translate>
-        </router-link>
-        <div class="ui hidden divider"></div>
-      </div>
-      <div
-        v-if="instance.short_description.value"
-        class="ui middle aligned stackable text container">
-        <p>{{ instance.short_description.value }}</p>
-      </div>
-      <div
-        v-if="markdown && instance.long_description.value"
-        class="ui middle aligned stackable text container"
-        v-html="markdown.makeHtml(instance.long_description.value)">
+      <div class="ui container">
+        <div class="ui mobile reversed stackable grid">
+          <div class="ten wide column">
+            <div class="ui text container">
+              <h3 class="ui header" id="description">
+                <translate translate-context="Content/About/Header">About this pod</translate>
+              </h3>
+              <div v-html="markdown.makeHtml(longDescription)" v-if="longDescription"></div>
+              <p v-else>
+                <translate translate-context="Content/Home/Paragraph">No description available.</translate>
+              </p>
+              <h3 class="ui header" id="rules">
+                <translate translate-context="Content/About/Header">Rules</translate>
+              </h3>
+              <div v-html="markdown.makeHtml(rules)" v-if="rules"></div>
+              <p v-else>
+                <translate translate-context="Content/Home/Paragraph">No rules available.</translate>
+              </p>
+              <h3 class="ui header" id="terms">
+                <translate translate-context="Content/About/Header">Terms and privacy policy</translate>
+              </h3>
+              <div v-html="markdown.makeHtml(terms)" v-if="terms"></div>
+              <p v-else>
+                <translate translate-context="Content/Home/Paragraph">No terms available.</translate>
+              </p>
+            </div>
+          </div>
+          <div class="six wide column">
+            <div class="ui raised segment">
+              <h3 class="ui header">
+                <translate translate-context="Content/About/Header">Contents</translate>
+              </h3>
+              <div class="ui list">
+                <div class="ui item">
+                  <a href="#description">
+                    <translate translate-context="Content/About/Header">About this pod</translate>
+                  </a>
+                </div>
+                <div class="ui item">
+                  <a href="#rules">
+                    <translate translate-context="Content/About/Header">Rules</translate>
+                  </a>
+                </div>
+                <div class="ui item">
+                  <a href="#terms">
+                    <translate translate-context="Content/About/Header">Terms and privacy policy</translate>
+                  </a>
+                </div>
+              </div>
+              <template v-if="contactEmail">
+                <h3 class="header">
+                  <translate translate-context="Content/Home/Header/Name">Contact</translate>
+                </h3>
+                <a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
+              </template>
+              <h3 class="header">
+                <translate translate-context="Content/About/Header/Name">Pod configuration</translate>
+              </h3>
+              <table class="ui very basic table">
+                <tbody>
+                  <tr v-if="version">
+                    <td>
+                      <translate translate-context="*/*/*">Funkwhale version</translate>
+                    </td>
+                    <td>
+                      {{ version }}
+                    </td>
+                  </tr>
+                  <tr>
+                    <td>
+                      <translate translate-context="*/*/*">Registrations</translate>
+                    </td>
+                    <td v-if="openRegistrations">
+                      <i class="check icon"></i>
+                      <translate translate-context="*/*/*/State of registrations">Open</translate>
+                    </td>
+                    <td v-else>
+                      <i class="x icon"></i>
+                      <translate translate-context="*/*/*/State of registrations">Closed</translate>
+                    </td>
+                  </tr>
+                  <tr>
+                    <td>
+                      <translate translate-context="*/*/*">Upload quota</translate>
+                    </td>
+                    <td v-if="defaultUploadQuota">
+                      {{ defaultUploadQuota * 1000 * 1000 | humanSize }}
+                    </td>
+                    <td v-else>
+                      <translate translate-context="*/*/*">N/A</translate>
+                    </td>
+                  </tr>
+                  <tr>
+                    <td>
+                      <translate translate-context="*/*/*">Federation</translate>
+                    </td>
+                    <td v-if="federationEnabled">
+                      <i class="check icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Enabled</translate>
+                    </td>
+                    <td v-else>
+                      <i class="x icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Disabled</translate>
+                    </td>
+                  </tr>
+                  <tr>
+                    <td>
+                      <translate translate-context="*/*/*">Anonymous access</translate>
+                    </td>
+                    <td v-if="anonymousCanListen">
+                      <i class="check icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Enabled</translate>
+                    </td>
+                    <td v-else>
+                      <i class="x icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Disabled</translate>
+                    </td>
+                  </tr>
+                  <tr>
+                    <td>
+                      <translate translate-context="*/*/*">Allow-list</translate>
+                    </td>
+                    <td v-if="allowListEnabled">
+                      <i class="check icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Enabled</translate>
+                    </td>
+                    <td v-else>
+                      <i class="x icon"></i>
+                      <translate translate-context="*/*/*/State of feature">Disabled</translate>
+                    </td>
+                  </tr>
+                  <tr v-if="allowListDomains">
+                    <td>
+                      <translate translate-context="*/*/*">Allowed domains</translate>
+                    </td>
+                    <td>
+                      <translate :translate-n="allowListDomains.length"  translate-plural="%{ count } allowed domains" :translate-params="{count: allowListDomains.length}" translate-context="*/*/*">%{ count } allowed domains</translate>
+                      <br>
+                      <a @click.prevent="showAllowedDomains = !showAllowedDomains">
+                        <translate v-if="showAllowedDomains" key="1" translate-context="*/*/*/Verb">Hide</translate>
+                        <translate v-else key="2" translate-context="*/*/*/Verb">Show</translate>
+                      </a>
+                      <ul class="ui list" v-if="showAllowedDomains">
+                        <li v-for="domain in allowListDomains" :key="domain">
+                          <a :href="`https://${domain}`" target="_blank" rel="noopener">{{ domain }}</a>
+                        </li>
+                      </ul>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+
+              <template v-if="stats">
+                <h3 class="header">
+                  <translate translate-context="Content/Home/Header">Statistics</translate>
+                </h3>
+                <p>
+                  <i class="user grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.users" translate-plural="%{ count } active users">%{ count } active user</translate>
+                </p>
+                <p>
+                  <i class="music grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
+                </p>
+                <p v-if="stats.artists">
+                  <i class="users grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.artists.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.artists" translate-plural="%{ count } artists">%{ count } artists</translate>
+                </p>
+                <p v-if="stats.albums">
+                  <i class="headphones grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.albums.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.albums" translate-plural="%{ count } albums">%{ count } albums</translate>
+                </p>
+                <p v-if="stats.tracks">
+                  <i class="file grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.tracks.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.tracks" translate-plural="%{ count } tracks">%{ count } tracks</translate>
+                </p>
+                <p v-if="stats.listenings">
+                  <i class="play grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.listenings.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.listenings" translate-plural="%{ count } listenings">%{ count } listenings</translate>
+                </p>
+              </template>
+            </div>
+          </div>
+        </div>
       </div>
     </section>
   </main>
@@ -40,37 +199,122 @@
 
 <script>
 import { mapState } from "vuex"
-import Stats from "@/components/instance/Stats"
+import _ from '@/lodash'
+import showdown from 'showdown'
 
 export default {
-  components: {
-    Stats
-  },
   data () {
     return {
-      markdown: null
+      markdown: new showdown.Converter(),
+      showAllowedDomains: false,
     }
   },
-  created () {
-    this.$store.dispatch("instance/fetchSettings")
-    let self = this
-    import('showdown').then(module => {
-      self.markdown = new module.default.Converter()
-    })
-  },
   computed: {
-    ...mapState({
-      instance: state => state.instance.settings.instance
+
+  ...mapState({
+      nodeinfo: state => state.instance.nodeinfo,
     }),
-    labels() {
-      return {
-        title: this.$pgettext('Content/About/Title', "About this instance")
+    podName() {
+      return _.get(this.nodeinfo, 'metadata.nodeName') || "Funkwhale"
+    },
+    banner () {
+      return _.get(this.nodeinfo, 'metadata.banner')
+    },
+    shortDescription () {
+      return _.get(this.nodeinfo, 'metadata.shortDescription')
+    },
+    longDescription () {
+      return _.get(this.nodeinfo, 'metadata.longDescription')
+    },
+    rules () {
+      return _.get(this.nodeinfo, 'metadata.rules')
+    },
+    terms () {
+      return _.get(this.nodeinfo, 'metadata.terms')
+    },
+    stats () {
+      let data = {
+        users: _.get(this.nodeinfo, 'usage.users.activeMonth', null),
+        hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null),
+        artists: _.get(this.nodeinfo, 'metadata.library.artists.total', null),
+        albums: _.get(this.nodeinfo, 'metadata.library.albums.total', null),
+        tracks: _.get(this.nodeinfo, 'metadata.library.tracks.total', null),
+        listenings: _.get(this.nodeinfo, 'metadata.usage.listenings.total', null),
       }
-    }
+      if (data.users === null || data.artists === null) {
+        return
+      }
+      return data
+    },
+    contactEmail () {
+      return _.get(this.nodeinfo, 'metadata.contactEmail')
+    },
+    anonymousCanListen () {
+      return _.get(this.nodeinfo, 'metadata.library.anonymousCanListen')
+    },
+    allowListEnabled () {
+      return _.get(this.nodeinfo, 'metadata.allowList.enabled')
+    },
+    allowListDomains () {
+      return _.get(this.nodeinfo, 'metadata.allowList.domains')
+    },
+    version () {
+      return _.get(this.nodeinfo, 'software.version')
+    },
+    openRegistrations () {
+      return _.get(this.nodeinfo, 'openRegistrations')
+    },
+    defaultUploadQuota () {
+      return _.get(this.nodeinfo, 'metadata.defaultUploadQuota')
+    },
+    federationEnabled () {
+      return _.get(this.nodeinfo, 'metadata.library.federationEnabled')
+    },
+    headerStyle() {
+      if (!this.banner) {
+        return ""
+      }
+      return (
+        "background-image: url(" +
+        this.$store.getters["instance/absoluteUrl"](this.banner) +
+        ")"
+      )
+    },
   }
 }
 </script>
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+
+.ui.list .list.icon {
+  padding: 0;
+}
+
+h1.header, h1 .sub.header {
+  text-shadow: 0 2px 0 rgba(0,0,0,.8);
+  color: #fff !important;
+}
+h1.ui.header {
+  font-size: 3em;
+}
+h1.ui.header .sub.header {
+  font-size: 0.8em;
+}
+.main.pusher {
+  margin-top: 0;
+  min-height: 10em;
+}
+section.segment.head {
+  padding: 8em 3em;
+  background: linear-gradient(90deg, rgba(40,88,125,1) 0%, rgba(64,130,180,1) 100%);
+  background-repeat: no-repeat;
+  background-size: cover;
+}
+#pod {
+  font-size: 110%;
+  display: block;
+}
 </style>
diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue
index 976262cfa751bba2dbd4cacb84c5d0bb75141071..4b291d839f6e89b4a13898aa58b24de4d88a786d 100644
--- a/front/src/components/Home.vue
+++ b/front/src/components/Home.vue
@@ -1,152 +1,276 @@
 <template>
   <main class="main pusher" v-title="labels.title">
-    <section class="ui vertical center aligned stripe segment">
-      <div class="ui text container">
-        <h1 class="ui huge header">
-          <translate translate-context="Content/Home/Title/Verb">Welcome to Funkwhale</translate>
+    <section :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle">
+      <div class="segment-content">
+        <h1 class="ui center aligned large header">
+          <translate translate-context="Content/Home/Header"
+            :translate-params="{podName: podName}">
+            Welcome to %{ podName }!
+          </translate>
+          <div v-if="shortDescription" class="sub header">
+            {{ shortDescription }}
+          </div>
         </h1>
-        <p><translate translate-context="Content/Home/Title">We think listening to music should be simple.</translate></p>
-        <router-link class="ui icon button" to="/about">
-          <i class="info icon"></i>
-          <translate translate-context="Content/Home/Button.Label/Verb">Learn more about this instance</translate>
-        </router-link>
-        <router-link class="ui icon teal button" to="/library">
-          <translate translate-context="Content/Home/Button.Label/Verb">Get me to the library</translate>
-          <i class="right arrow icon"></i>
-        </router-link>
       </div>
     </section>
     <section class="ui vertical stripe segment">
-      <div class="ui middle aligned stackable text container">
-        <div class="ui grid">
-          <div class="row">
-            <div class="eight wide left floated column">
-              <h2 class="ui header">
-                <translate translate-context="Content/Home/Title">Why funkwhale?</translate>
-              </h2>
-              <p><translate translate-context="Content/Home/Paragraph">That's simple: we loved Grooveshark and we want to build something even better.</translate></p>
-            </div>
-            <div class="four wide left floated column">
-              <img class="ui medium image" src="../assets/logo/logo.png" />
+      <div class="ui stackable grid">
+        <div class="ten wide column">
+          <h3 class="header">
+            <translate translate-context="Content/Home/Header">About this Funkwhale pod</translate>
+          </h3>
+          <div class="ui raised segment" id="pod">
+            <div class="ui stackable grid">
+              <div class="eight wide column">
+                <p v-if="!truncatedDescription">
+                  <translate translate-context="Content/Home/Paragraph">No description available.</translate>
+                </p>
+                <template v-if="truncatedDescription || rules">
+                  <div v-if="truncatedDescription" v-html="truncatedDescription"></div>
+                  <div v-if="truncatedDescription" class="ui hidden divider"></div>
+                  <div class="ui relaxed list">
+                    <div class="item" v-if="truncatedDescription">
+                      <i class="arrow right grey icon"></i>
+                      <div class="content">
+                        <router-link class="ui link" :to="{name: 'about'}">
+                          <translate translate-context="Content/Home/Link">Learn more</translate>
+                        </router-link>
+                      </div>
+                    </div>
+                    <div class="item" v-if="rules">
+                      <i class="book open grey icon"></i>
+                      <div class="content">
+                        <router-link class="ui link" v-if="rules" :to="{name: 'about', hash: '#rules'}">
+                          <translate translate-context="Content/Home/Link">Server rules</translate>
+                        </router-link>
+                      </div>
+                    </div>
+                  </div>
+                </template>
+              </div>
+              <div class="eight wide column">
+                <template v-if="stats">
+                  <h3 class="sub header">
+                    <translate translate-context="Content/Home/Header">Statistics</translate>
+                  </h3>
+                  <p>
+                    <i class="user grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.users" translate-plural="%{ count } active users">%{ count } active user</translate>
+                  </p>
+                  <p>
+                    <i class="music grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
+                  </p>
+
+                </template>
+                <template v-if="contactEmail">
+                  <h3 class="sub header">
+                    <translate translate-context="Content/Home/Header/Name">Contact</translate>
+                  </h3>
+                  <i class="at grey icon"></i>
+                  <a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
+                </template>
+
+              </div>
             </div>
           </div>
         </div>
-      </div>
-      <div class="ui middle aligned stackable text container">
-        <div class="ui hidden divider"></div>
-        <h2 class="ui header">
-          <translate translate-context="Content/Home/Title">Unlimited music</translate>
-        </h2>
-        <p><translate translate-context="Content/Home/Paragraph">Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.</translate></p>
-        <div class="ui list">
-          <div class="item">
-            <i class="sound icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item/Verb">Click once, listen for hours using built-in radios</translate>
-            </div>
-          </div>
-          <div class="item">
-            <i class="heart icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item/Verb">Keep track of your favorite songs</translate>
-            </div>
-          </div>
-          <div class="item">
-            <i class="list icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">Playlists? We've got them</translate>
-            </div>
-          </div>
+
+        <div class="six wide column">
+          <img class="ui image" src="../assets/network.png" />
         </div>
       </div>
-      <div class="ui middle aligned stackable text container">
-        <div class="ui hidden divider"></div>
-        <h2 class="ui header">
-          <translate translate-context="Content/Home/Title">A clean library</translate>
-        </h2>
-        <p><translate translate-context="Content/Home/Paragraph">Funkwhale takes care of handling your music</translate>.</p>
-        <div class="ui list">
-          <div class="item">
-            <i class="tag icon"></i>
-            <div class="content" v-html="musicbrainzItem"></div>
-          </div>
-          <div class="item">
-            <i class="plus icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">Covers, lyrics... our goal is to have them all ;)</translate>
-            </div>
-          </div>
+      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider"></div>
+      <div class="ui stackable grid">
+        <div class="four wide column">
+          <h3 class="header">
+            <translate translate-context="Content/Home/Header">About Funkwhale</translate>
+          </h3>
+          <p v-translate translate-context="Content/Home/Paragraph">This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.</p>
+          <p v-translate translate-context="Content/Home/Paragraph">Funkwhale is free and developped by a friendly community of volunteers.</p>
+          <a target="_blank" rel="noopener" href="https://funkwhale.audio">
+            <i class="external alternate icon"></i>
+            <translate translate-context="Content/Home/Link">Visit funkwhale.audio</translate>
+          </a>
         </div>
-      </div>
-      <div class="ui middle aligned stackable text container">
-        <div class="ui hidden divider"></div>
-        <h2 class="ui header">
-          <translate translate-context="Content/Home/Title">Easy to use</translate>
-        </h2>
-        <p><translate translate-context="Content/Home/Paragraph">Funkwhale is dead simple to use.</translate></p>
-        <div class="ui list">
-          <div class="item">
-            <i class="book icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">No add-ons, no plugins... you only need a web library</translate>
-            </div>
-          </div>
-          <div class="item">
-            <i class="wizard icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">Access your music from a clean interface that focuses on what really matters</translate>
-            </div>
+        <div class="four wide column">
+          <h3 class="header">
+            <translate translate-context="Head/Login/Title">Log In</translate>
+          </h3>
+          <login-form button-classes="basic green" :show-signup="false"></login-form>
+          <div class="ui hidden clearing divider"></div>
+        </div>
+        <div class="four wide column">
+          <h3 class="header">
+            <translate translate-context="*/Signup/Title">Sign up</translate>
+          </h3>
+          <template v-if="openRegistrations">
+            <p>
+              <translate translate-context="Content/Home/Paragraph">Sign up now to keep a track of your favorites, create playlists, discover new content and much more!</translate>
+            </p>
+            <p v-if="defaultUploadQuota">
+              <translate translate-context="Content/Home/Paragraph" :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}">Users on this pod also get %{ quota } of free storage to upload their own content!</translate>
+            </p>
+            <signup-form button-classes="basic green" :show-login="false"></signup-form>
+          </template>
+          <div v-else>
+            <p translate-context="Content/Home/Paragraph">Registrations are closed on this pod. You can signup on another pod using the link below.</p>
+            <a target="_blank" rel="noopener" href="https://funkwhale.audio/#get-started">
+              <i class="external alternate icon"></i>
+              <translate translate-context="Content/Home/Link">Find another pod</translate>
+            </a>
           </div>
         </div>
-      </div>
-      <div class="ui middle aligned stackable text container">
-        <div class="ui hidden divider"></div>
-        <h2 class="ui header">
-          <translate translate-context="Content/Home/Title">Your music, your way</translate>
-        </h2>
-        <p><translate translate-context="Content/Home/Paragraph">Funkwhale is free and gives you control over your music.</translate></p>
-        <div class="ui list">
-          <div class="item">
-            <i class="smile icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">The platform is free and open-source, you can install it and modify it without worries</translate>
+
+        <div class="four wide column">
+          <h3 class="header">
+            <translate translate-context="Content/Home/Header">Useful links</translate>
+          </h3>
+          <div class="ui relaxed list">
+            <div class="item">
+              <i class="headphones icon"></i>
+              <div class="content">
+                <router-link v-if="anonymousCanListen" class="header" to="/library">
+                  <translate translate-context="Content/Home/Link">Browse public content</translate>
+                </router-link>
+                <div class="description">
+                  <translate translate-context="Content/Home/Link">Listen to public albums and playlists shared on this pod</translate>
+                </div>
+              </div>
             </div>
-          </div>
-          <div class="item">
-            <i class="protect icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">We do not track you or bother you with ads</translate>
+            <div class="item">
+              <i class="mobile alternate icon"></i>
+              <div class="content">
+                <a class="header" href="https://funkwhale.audio/apps" target="_blank" rel="noopener">
+                  <translate translate-context="Content/Home/Link">Mobile apps</translate>
+                </a>
+                <div class="description">
+                  <translate translate-context="Content/Home/Link">Use Funkwhale on other devices with our apps</translate>
+                </div>
+              </div>
             </div>
-          </div>
-          <div class="item">
-            <i class="users icon"></i>
-            <div class="content">
-              <translate translate-context="Content/Home/List item">You can invite friends and family to your instance so they can enjoy your music</translate>
+            <div class="item">
+              <i class="book icon"></i>
+              <div class="content">
+                <a class="header" href="https://docs.funkwhale.audio/users/index.html" target="_blank" rel="noopener">
+                  <translate translate-context="Content/Home/Link">User guides</translate>
+                </a>
+                <div class="description">
+                  <translate translate-context="Content/Home/Link">Discover everything you need to know about Funkwhale and its features</translate>
+                </div>
+              </div>
             </div>
           </div>
         </div>
       </div>
     </section>
+    <section v-if="anonymousCanListen" class="ui vertical stripe segment">
+      <album-widget :filters="{playable: true, ordering: '-creation_date'}" :limit="10">
+        <template slot="title"><translate translate-context="Content/Home/Title">Recently added albums</translate></template>
+        <router-link to="/library">
+          <translate translate-context="Content/Home/Link">View more…</translate>
+          <div class="ui hidden divider"></div>
+        </router-link>
+      </album-widget>
+    </section>
   </main>
 </template>
 
 <script>
+import $ from 'jquery'
+import _ from '@/lodash'
+import {mapState} from 'vuex'
+import showdown from 'showdown'
+import AlbumWidget from "@/components/audio/album/Widget"
+import LoginForm from "@/components/auth/LoginForm"
+import SignupForm from "@/components/auth/SignupForm"
+import {humanSize } from '@/filters'
+
 export default {
-  data() {
+  components: {
+    AlbumWidget,
+    LoginForm,
+    SignupForm,
+  },
+  data () {
     return {
-      musicbrainzUrl: "https://musicbrainz.org/"
+      markdown: new showdown.Converter(),
+      excerptLength: 2, // html nodes,
+      humanSize
     }
   },
   computed: {
+    ...mapState({
+      nodeinfo: state => state.instance.nodeinfo,
+    }),
     labels() {
       return {
         title: this.$pgettext('Head/Home/Title', "Welcome")
       }
     },
-    musicbrainzItem () {
-      let msg = this.$pgettext('Content/Home/List item/Verb', 'Get quality metadata about your music thanks to <a href="%{ url }" target="_blank">MusicBrainz</a>')
-      return this.$gettextInterpolate(msg, {url: this.musicbrainzUrl})
-    }
+    podName() {
+      return _.get(this.nodeinfo, 'metadata.nodeName') || "Funkwhale"
+    },
+    banner () {
+      return _.get(this.nodeinfo, 'metadata.banner')
+    },
+    shortDescription () {
+      return _.get(this.nodeinfo, 'metadata.shortDescription')
+    },
+    longDescription () {
+      return _.get(this.nodeinfo, 'metadata.longDescription')
+    },
+    rules () {
+      return _.get(this.nodeinfo, 'metadata.rules')
+    },
+    truncatedDescription () {
+      if (!this.longDescription) {
+        return
+      }
+      let doc = this.markdown.makeHtml(this.longDescription)
+      let nodes = $.parseHTML(doc)
+      let excerptParts = []
+      let handled = 0
+      nodes.forEach((n) => {
+        let content = n.innerHTML || n.nodeValue
+        if (handled < this.excerptLength && content.trim()) {
+          excerptParts.push(n)
+          handled += 1
+        }
+      })
+      return excerptParts.map((p) => { return p.outerHTML }).join('')
+    },
+    stats () {
+      let data = {
+        users: _.get(this.nodeinfo, 'usage.users.activeMonth', null),
+        hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null),
+      }
+      if (data.users === null || data.artists === null) {
+        return
+      }
+      return data
+    },
+    contactEmail () {
+      return _.get(this.nodeinfo, 'metadata.contactEmail')
+    },
+    defaultUploadQuota () {
+      return _.get(this.nodeinfo, 'metadata.defaultUploadQuota')
+    },
+    anonymousCanListen () {
+      return _.get(this.nodeinfo, 'metadata.library.anonymousCanListen')
+    },
+    openRegistrations () {
+      return _.get(this.nodeinfo, 'openRegistrations')
+    },
+    headerStyle() {
+      if (!this.banner) {
+        return ""
+      }
+      return (
+        "background-image: url(" +
+        this.$store.getters["instance/absoluteUrl"](this.banner) +
+        ")"
+      )
+    },
   },
   watch: {
     '$store.state.auth.authenticated': {
@@ -164,11 +288,34 @@ export default {
 </script>
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-.stripe p {
-  font-size: 120%;
-}
+<style scoped lang="scss">
+
 .ui.list .list.icon {
   padding: 0;
 }
+
+h1.header, h1 .sub.header {
+  text-shadow: 0 2px 0 rgba(0,0,0,.8);
+  color: #fff !important;
+}
+h1.ui.header {
+  font-size: 3em;
+}
+h1.ui.header .sub.header {
+  font-size: 0.8em;
+}
+.main.pusher {
+  margin-top: 0;
+  min-height: 10em;
+}
+section.segment.head {
+  padding: 8em 3em;
+  background: linear-gradient(90deg, rgba(40,88,125,1) 0%, rgba(64,130,180,1) 100%);
+  background-repeat: no-repeat;
+  background-size: cover;
+}
+#pod {
+  font-size: 110%;
+  display: block;
+}
 </style>
diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue
index 609ef6ebcc2cf5d114f32f27732cc4ed9a75e88b..c9e395f3c7022d9741f4f894ebc18212efcc4382 100644
--- a/front/src/components/audio/album/Widget.vue
+++ b/front/src/components/audio/album/Widget.vue
@@ -4,6 +4,7 @@
       <slot name="title"></slot>
       <span v-if="showCount" class="ui tiny circular label">{{ count }}</span>
     </h3>
+    <slot></slot>
     <button v-if="controls" :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button>
     <button v-if="controls" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
     <button v-if="controls" @click="fetchData('albums/')" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button>
@@ -48,6 +49,7 @@ export default {
     filters: {type: Object, required: true},
     controls: {type: Boolean, default: true},
     showCount: {type: Boolean, default: false},
+    limit: {type: Number, default: 12},
   },
   components: {
     PlayButton
@@ -55,7 +57,6 @@ export default {
   data () {
     return {
       albums: [],
-      limit: 12,
       count: 0,
       isLoading: false,
       errors: null,
diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue
deleted file mode 100644
index 0fb39165469e4c8a4a98db0a62848b6dbb836ec0..0000000000000000000000000000000000000000
--- a/front/src/components/auth/Login.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-<template>
-  <main class="main pusher" v-title="labels.title">
-    <section class="ui vertical stripe segment">
-      <div class="ui small text container">
-        <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
-        <form class="ui form" @submit.prevent="submit()">
-          <div v-if="error" class="ui negative message">
-            <div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div>
-            <ul class="list">
-              <li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li>
-              <li v-else>{{ error }}</li>
-            </ul>
-          </div>
-          <div class="field">
-            <label>
-              <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate> |
-              <router-link :to="{path: '/signup'}">
-                <translate translate-context="*/Signup/Link/Verb">Create an account</translate>
-              </router-link>
-            </label>
-            <input
-            ref="username"
-            tabindex="1"
-            required
-            name="username"
-            type="text"
-            autofocus
-            :placeholder="labels.usernamePlaceholder"
-            v-model="credentials.username"
-            >
-          </div>
-          <div class="field">
-            <label>
-              <translate translate-context="Content/*/Input.Label">Password</translate> |
-              <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}">
-                <translate translate-context="*/Login/*/Verb">Reset your password</translate>
-              </router-link>
-            </label>
-            <password-input :index="2" required v-model="credentials.password" />
-
-          </div>
-          <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
-             <translate translate-context="*/Login/*/Verb">Login</translate>
-          </button>
-        </form>
-      </div>
-    </section>
-  </main>
-</template>
-
-<script>
-import PasswordInput from "@/components/forms/PasswordInput"
-
-export default {
-  props: {
-    next: { type: String, default: "/library" }
-  },
-  components: {
-    PasswordInput
-  },
-  data() {
-    return {
-      // We need to initialize the component with any
-      // properties that will be used in it
-      credentials: {
-        username: "",
-        password: ""
-      },
-      error: "",
-      isLoading: false
-    }
-  },
-  created () {
-    if (this.$store.state.auth.authenticated) {
-      this.$router.push(this.next)
-    }
-  },
-  mounted() {
-    this.$refs.username.focus()
-  },
-  computed: {
-    labels() {
-      let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email")
-      let title = this.$pgettext('Head/Login/Title', "Log In")
-      return {
-        usernamePlaceholder,
-        title
-      }
-    }
-  },
-  methods: {
-    submit() {
-      var self = this
-      self.isLoading = true
-      this.error = ""
-      var credentials = {
-        username: this.credentials.username,
-        password: this.credentials.password
-      }
-      this.$store
-        .dispatch("auth/login", {
-          credentials,
-          next: this.next,
-          onError: error => {
-            if (error.response.status === 400) {
-              self.error = "invalid_credentials"
-            } else {
-              self.error = error.backendErrors[0]
-            }
-          }
-        })
-        .then(e => {
-          self.isLoading = false
-        })
-    }
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..75ecf96f140940dde93d4d386ce2fc11db813d13
--- /dev/null
+++ b/front/src/components/auth/LoginForm.vue
@@ -0,0 +1,118 @@
+<template>
+  <form class="ui form" @submit.prevent="submit()">
+    <div v-if="error" class="ui negative message">
+      <div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div>
+      <ul class="list">
+        <li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li>
+        <li v-else>{{ error }}</li>
+      </ul>
+    </div>
+    <div class="field">
+      <label>
+        <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate>
+        <template v-if="showSignup">
+          |
+          <router-link :to="{path: '/signup'}">
+            <translate translate-context="*/Signup/Link/Verb">Create an account</translate>
+          </router-link>
+        </template>
+      </label>
+      <input
+      ref="username"
+      tabindex="1"
+      required
+      name="username"
+      type="text"
+      autofocus
+      :placeholder="labels.usernamePlaceholder"
+      v-model="credentials.username"
+      >
+    </div>
+    <div class="field">
+      <label>
+        <translate translate-context="Content/*/Input.Label">Password</translate> |
+        <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}">
+          <translate translate-context="*/Login/*/Verb">Reset your password</translate>
+        </router-link>
+      </label>
+      <password-input :index="2" required v-model="credentials.password" />
+
+    </div>
+    <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']" type="submit">
+        <translate translate-context="*/Login/*/Verb">Login</translate>
+    </button>
+  </form>
+</template>
+
+<script>
+import PasswordInput from "@/components/forms/PasswordInput"
+
+export default {
+  props: {
+    next: { type: String, default: "/library" },
+    buttonClasses: { type: String, default: "green" },
+    showSignup: { type: Boolean, default: true},
+  },
+  components: {
+    PasswordInput
+  },
+  data() {
+    return {
+      // We need to initialize the component with any
+      // properties that will be used in it
+      credentials: {
+        username: "",
+        password: ""
+      },
+      error: "",
+      isLoading: false
+    }
+  },
+  created () {
+    if (this.$store.state.auth.authenticated) {
+      this.$router.push(this.next)
+    }
+  },
+  mounted() {
+    this.$refs.username.focus()
+  },
+  computed: {
+    labels() {
+      let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email")
+      return {
+        usernamePlaceholder,
+      }
+    }
+  },
+  methods: {
+    submit() {
+      var self = this
+      self.isLoading = true
+      this.error = ""
+      var credentials = {
+        username: this.credentials.username,
+        password: this.credentials.password
+      }
+      this.$store
+        .dispatch("auth/login", {
+          credentials,
+          next: this.next,
+          onError: error => {
+            if (error.response.status === 400) {
+              self.error = "invalid_credentials"
+            } else {
+              self.error = error.backendErrors[0]
+            }
+          }
+        })
+        .then(e => {
+          self.isLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>
diff --git a/front/src/components/auth/Signup.vue b/front/src/components/auth/Signup.vue
deleted file mode 100644
index c84a5095b0502cb56d2b7c4f47734b7fd7da6e47..0000000000000000000000000000000000000000
--- a/front/src/components/auth/Signup.vue
+++ /dev/null
@@ -1,146 +0,0 @@
-<template>
-  <main class="main pusher" v-title="labels.title">
-    <section class="ui vertical stripe segment">
-      <div class="ui small text container">
-        <h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2>
-        <form
-          :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
-          @submit.prevent="submit()">
-          <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
-            <translate translate-context="Content/Signup/Form/Paragraph">Public registrations are not possible on this instance. You will need an invitation code to sign up.</translate>
-          </p>
-
-          <div v-if="errors.length > 0" class="ui negative message">
-            <div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div>
-            <ul class="list">
-              <li v-for="error in errors">{{ error }}</li>
-            </ul>
-          </div>
-          <div class="field">
-            <label><translate translate-context="Content/*/*">Username</translate></label>
-            <input
-            ref="username"
-            name="username"
-            required
-            type="text"
-            autofocus
-            :placeholder="labels.usernamePlaceholder"
-            v-model="username">
-          </div>
-          <div class="field">
-            <label><translate translate-context="Content/*/*/Noun">Email</translate></label>
-            <input
-            ref="email"
-            name="email"
-            required
-            type="email"
-            :placeholder="labels.emailPlaceholder"
-            v-model="email">
-          </div>
-          <div class="field">
-            <label><translate translate-context="Content/*/Input.Label">Password</translate></label>
-            <password-input v-model="password" />
-          </div>
-          <div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value">
-            <label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
-            <input
-            required
-            type="text"
-            name="invitation"
-            :placeholder="labels.placeholder"
-            v-model="invitation">
-          </div>
-          <button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit">
-            <translate translate-context="Content/Signup/Button.Label">Create my account</translate>
-          </button>
-        </form>
-      </div>
-    </section>
-  </main>
-</template>
-
-<script>
-import axios from "axios"
-import logger from "@/logging"
-
-import PasswordInput from "@/components/forms/PasswordInput"
-
-export default {
-  props: {
-    defaultInvitation: { type: String, required: false, default: null },
-    next: { type: String, default: "/" }
-  },
-  components: {
-    PasswordInput
-  },
-  data() {
-    return {
-      username: "",
-      email: "",
-      password: "",
-      isLoadingInstanceSetting: true,
-      errors: [],
-      isLoading: false,
-      invitation: this.defaultInvitation
-    }
-  },
-  created() {
-    let self = this
-    this.$store.dispatch("instance/fetchSettings", {
-      callback: function() {
-        self.isLoadingInstanceSetting = false
-      }
-    })
-  },
-  computed: {
-    labels() {
-      let title = this.$pgettext("*/Signup/Title", "Sign Up")
-      let placeholder = this.$pgettext(
-        "Content/Signup/Form/Placeholder",
-        "Enter your invitation code (case insensitive)"
-      )
-      let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
-      let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email")
-      return {
-        title,
-        usernamePlaceholder,
-        emailPlaceholder,
-        placeholder
-      }
-    }
-  },
-  methods: {
-    submit() {
-      var self = this
-      self.isLoading = true
-      this.errors = []
-      var payload = {
-        username: this.username,
-        password1: this.password,
-        password2: this.password,
-        email: this.email,
-        invitation: this.invitation
-      }
-      return axios.post("auth/registration/", payload).then(
-        response => {
-          logger.default.info("Successfully created account")
-          self.$router.push({
-            name: "profile",
-            params: {
-              username: this.username
-            }
-          })
-        },
-        error => {
-          self.errors = error.backendErrors
-          self.isLoading = false
-        }
-      )
-    }
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/front/src/components/auth/SignupForm.vue b/front/src/components/auth/SignupForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..46752f8408147e731e0edddf8d67e70627c3b4fa
--- /dev/null
+++ b/front/src/components/auth/SignupForm.vue
@@ -0,0 +1,138 @@
+<template>
+  <form
+    :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
+    @submit.prevent="submit()">
+    <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
+      <translate translate-context="Content/Signup/Form/Paragraph">Public registrations are not possible on this instance. You will need an invitation code to sign up.</translate>
+    </p>
+
+    <div v-if="errors.length > 0" class="ui negative message">
+      <div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div>
+      <ul class="list">
+        <li v-for="error in errors">{{ error }}</li>
+      </ul>
+    </div>
+    <div class="field">
+      <label><translate translate-context="Content/*/*">Username</translate></label>
+      <input
+      ref="username"
+      name="username"
+      required
+      type="text"
+      autofocus
+      :placeholder="labels.usernamePlaceholder"
+      v-model="username">
+    </div>
+    <div class="field">
+      <label><translate translate-context="Content/*/*/Noun">Email</translate></label>
+      <input
+      ref="email"
+      name="email"
+      required
+      type="email"
+      :placeholder="labels.emailPlaceholder"
+      v-model="email">
+    </div>
+    <div class="field">
+      <label><translate translate-context="Content/*/Input.Label">Password</translate></label>
+      <password-input v-model="password" />
+    </div>
+    <div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value">
+      <label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
+      <input
+      required
+      type="text"
+      name="invitation"
+      :placeholder="labels.placeholder"
+      v-model="invitation">
+    </div>
+    <button :class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']" type="submit">
+      <translate translate-context="Content/Signup/Button.Label">Create my account</translate>
+    </button>
+  </form>
+</template>
+
+<script>
+import axios from "axios"
+import logger from "@/logging"
+
+import PasswordInput from "@/components/forms/PasswordInput"
+
+export default {
+  props: {
+    defaultInvitation: { type: String, required: false, default: null },
+    next: { type: String, default: "/" },
+    buttonClasses: { type: String, default: "green" },
+  },
+  components: {
+    PasswordInput
+  },
+  data() {
+    return {
+      username: "",
+      email: "",
+      password: "",
+      isLoadingInstanceSetting: true,
+      errors: [],
+      isLoading: false,
+      invitation: this.defaultInvitation
+    }
+  },
+  created() {
+    let self = this
+    this.$store.dispatch("instance/fetchSettings", {
+      callback: function() {
+        self.isLoadingInstanceSetting = false
+      }
+    })
+  },
+  computed: {
+    labels() {
+      let placeholder = this.$pgettext(
+        "Content/Signup/Form/Placeholder",
+        "Enter your invitation code (case insensitive)"
+      )
+      let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
+      let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email")
+      return {
+        usernamePlaceholder,
+        emailPlaceholder,
+        placeholder
+      }
+    }
+  },
+  methods: {
+    submit() {
+      var self = this
+      self.isLoading = true
+      this.errors = []
+      var payload = {
+        username: this.username,
+        password1: this.password,
+        password2: this.password,
+        email: this.email,
+        invitation: this.invitation
+      }
+      return axios.post("auth/registration/", payload).then(
+        response => {
+          logger.default.info("Successfully created account")
+          self.$router.push({
+            name: "profile",
+            params: {
+              username: this.username
+            }
+          })
+        },
+        error => {
+          self.errors = error.backendErrors
+          self.isLoading = false
+        }
+      )
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>
diff --git a/front/src/components/instance/Stats.vue b/front/src/components/instance/Stats.vue
deleted file mode 100644
index 85ce14fa07bfc67c79f7627da92525506dff093c..0000000000000000000000000000000000000000
--- a/front/src/components/instance/Stats.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<template>
-  <div>
-    <div v-if="stats && stats.trackFavorites !== undefined" class="ui stackable two column grid">
-      <div class="column">
-        <h3 class="ui left aligned header">
-          <translate translate-context="Content/About/Title/Noun">User activity</translate>
-        </h3>
-        <div v-if="stats" class="ui mini horizontal statistics">
-          <div class="statistic">
-            <div class="value">
-              <i class="green user icon"></i>
-              {{ stats.users.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="Content/About/Paragraph/Unit">users</translate></div>
-          </div>
-          <div class="statistic">
-            <div class="value">
-              <i class="orange sound icon"></i> {{ stats.listenings.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="Content/About/Paragraph/Unit">tracks listened</translate></div>
-          </div>
-          <div class="statistic">
-            <div class="value">
-              <i class="pink heart icon"></i> {{ stats.trackFavorites.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="Content/About/Paragraph/Unit">Tracks favorited</translate></div>
-          </div>
-        </div>
-      </div>
-      <div class="column">
-        <h3 class="ui left aligned header"><translate translate-context="*/*/*">Library</translate></h3>
-        <div class="ui mini horizontal statistics">
-          <div class="statistic">
-            <div class="value">
-              {{ stats.musicDuration.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="Content/About/Paragraph/Unit">Hours of music</translate></div>
-          </div>
-          <div class="statistic">
-            <div class="value">
-              {{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="*/*/*/Noun">Artists</translate></div>
-          </div>
-          <div class="statistic">
-            <div class="value">
-              {{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="*/*/*">Albums</translate></div>
-          </div>
-          <div class="statistic">
-            <div class="value">
-              {{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}
-            </div>
-            <div class="label"><translate translate-context="*/*/*/Noun">Tracks</translate></div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import _ from '@/lodash'
-import axios from 'axios'
-import logger from '@/logging'
-
-export default {
-  data () {
-    return {
-      stats: null
-    }
-  },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      var self = this
-      this.isLoading = true
-      logger.default.debug('Fetching instance stats...')
-      axios.get('instance/nodeinfo/2.0/').then((response) => {
-        let d = response.data
-        self.stats = {}
-        self.stats.users = _.get(d, 'usage.users.total')
-        self.stats.listenings = _.get(d, 'metadata.usage.listenings.total')
-        self.stats.trackFavorites = _.get(d, 'metadata.usage.favorites.tracks.total')
-        self.stats.musicDuration = Math.round(_.get(d, 'metadata.library.music.hours'))
-        self.stats.artists = _.get(d, 'metadata.library.artists.total')
-        self.stats.albums = _.get(d, 'metadata.library.albums.total')
-        self.stats.tracks = _.get(d, 'metadata.library.tracks.total')
-        self.isLoading = false
-      })
-    },
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 0aedb20e3feaf2c02bf3cd90b28bd2f8e3ffba8a..d8bd0001f497e8835a6972ff68d0ba0b0d904eee 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -8,6 +8,17 @@ export default new Router({
   mode: "history",
   linkActiveClass: "active",
   base: process.env.VUE_APP_ROUTER_BASE_URL || "/",
+  scrollBehavior(to, from, savedPosition) {
+    return new Promise(resolve => {
+      setTimeout(() => {
+        if (to.hash) {
+          resolve({ selector: to.hash });
+        }
+        let pos = savedPosition || { x: 0, y: 0 };
+        resolve(pos);
+      }, 100);
+    });
+  },
   routes: [
     {
       path: "/",
@@ -18,7 +29,10 @@ export default new Router({
     {
       path: "/front",
       name: "front",
-      redirect: "/"
+      redirect: to => {
+        const { hash, params, query } = to
+        return { name: 'index', hash, query }
+      }
     },
     {
       path: "/about",
@@ -30,7 +44,7 @@ export default new Router({
       path: "/login",
       name: "login",
       component: () =>
-        import(/* webpackChunkName: "core" */ "@/components/auth/Login"),
+        import(/* webpackChunkName: "core" */ "@/views/auth/Login"),
       props: route => ({ next: route.query.next || "/library" })
     },
     {
@@ -87,7 +101,7 @@ export default new Router({
       path: "/signup",
       name: "signup",
       component: () =>
-        import(/* webpackChunkName: "core" */ "@/components/auth/Signup"),
+        import(/* webpackChunkName: "core" */ "@/views/auth/Signup"),
       props: route => ({
         defaultInvitation: route.query.invitation
       })
diff --git a/front/src/store/instance.js b/front/src/store/instance.js
index 6af36e8090e3ba276c7fabaf870ebd251f086a15..efe40736493df800ac5b8aeb9f43c000356899de 100644
--- a/front/src/store/instance.js
+++ b/front/src/store/instance.js
@@ -17,6 +17,7 @@ export default {
     instanceUrl: process.env.VUE_APP_INSTANCE_URL,
     events: [],
     knownInstances: [],
+    nodeinfo: null,
     settings: {
       instance: {
         name: {
@@ -41,7 +42,7 @@ export default {
         enabled: {
           value: true
         }
-      }
+      },
     }
   },
   mutations: {
@@ -57,6 +58,9 @@ export default {
     events: (state, value) => {
       state.events = value
     },
+    nodeinfo: (state, value) => {
+      state.nodeinfo = value
+    },
     frontSettings: (state, value) => {
       state.frontSettings = value
     },
diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss
index 76a4b2357fc7bcf7c6f79023dac813ca97f7c3e0..b98584f7d21e510a8ec9476fc1d25facbef2ab26 100644
--- a/front/src/style/_main.scss
+++ b/front/src/style/_main.scss
@@ -388,6 +388,9 @@ input + .help {
   padding-left: 0;
   padding-right: 0;
 }
+.column .ui.text.container {
+  max-width: 100% !important;
+}
 
 @import "./themes/_light.scss";
 @import "./themes/_dark.scss";
diff --git a/front/src/style/themes/_dark.scss b/front/src/style/themes/_dark.scss
index 56bd244ee7f6980d9764c71097043056bd8b70a0..a34b60cb5399b72ef5e55518d92609ef2b23f13f 100644
--- a/front/src/style/themes/_dark.scss
+++ b/front/src/style/themes/_dark.scss
@@ -123,6 +123,9 @@ $link-color: rgb(255, 144, 0);
     .ui.segment:not(.basic) {
       background-color: $light-background-color;
     }
+    .link {
+      color: $link-color;
+    }
     .ui.list,
     .ui.dropdown {
       .item,
@@ -136,6 +139,9 @@ $link-color: rgb(255, 144, 0);
         color: $background-color;
       }
     }
+    .segment .ui.list .item {
+      background-color: transparent;
+    }
     .ui.divided.items > .item:not(:first-child) {
       border-top: 1px solid $border-color;
     }
@@ -251,9 +257,12 @@ $link-color: rgb(255, 144, 0);
       }
     }
   }
+  .ui.list > .item .description {
+    color: $text-color;
+  }
   .ui.link.list.list a.item:hover,
-  .ui.link.list.list .item a:not(.ui):not(.button):hover {
-    color: $link-color;
+  .ui.link.list.list .item a:not(.ui):not(.button):hover, .ui.list > .item a.header {
+    color: $link-color !important;
   }
   [data-tooltip]::after {
     background-color: $light-background-color;
diff --git a/front/src/views/auth/Login.vue b/front/src/views/auth/Login.vue
new file mode 100644
index 0000000000000000000000000000000000000000..22285bc9cc7952428e6b5d3354d7da766e8f4212
--- /dev/null
+++ b/front/src/views/auth/Login.vue
@@ -0,0 +1,40 @@
+<template>
+  <main class="main pusher" v-title="labels.title">
+    <section class="ui vertical stripe segment">
+      <div class="ui small text container">
+        <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
+        <login-form :next="next"></login-form>
+      </div>
+    </section>
+  </main>
+</template>
+
+<script>
+import LoginForm from "@/components/auth/LoginForm"
+
+export default {
+  props: {
+    next: { type: String, default: "/library" }
+  },
+  components: {
+    LoginForm
+  },
+  created () {
+    if (this.$store.state.auth.authenticated) {
+      this.$router.push(this.next)
+    }
+  },
+  computed: {
+    labels() {
+      let title = this.$pgettext('Head/Login/Title', "Log In")
+      return {
+        title
+      }
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>
diff --git a/front/src/views/auth/Signup.vue b/front/src/views/auth/Signup.vue
new file mode 100644
index 0000000000000000000000000000000000000000..37b918c72bd4c08b3949c3698f74055211152008
--- /dev/null
+++ b/front/src/views/auth/Signup.vue
@@ -0,0 +1,48 @@
+<template>
+  <main class="main pusher" v-title="labels.title">
+    <section class="ui vertical stripe segment">
+      <div class="ui small text container">
+        <h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2>
+        <signup-form :default-invitation="defaultInvitation" :next="next"></signup-form>
+      </div>
+    </section>
+  </main>
+</template>
+
+<script>
+
+import SignupForm from "@/components/auth/SignupForm"
+
+export default {
+  props: {
+    defaultInvitation: { type: String, required: false, default: null },
+    next: { type: String, default: "/" }
+  },
+  components: {
+    SignupForm
+  },
+  data() {
+    return {
+      username: "",
+      email: "",
+      password: "",
+      isLoadingInstanceSetting: true,
+      errors: [],
+      isLoading: false,
+      invitation: this.defaultInvitation
+    }
+  },
+  computed: {
+    labels() {
+      let title = this.$pgettext("*/Signup/Title", "Sign Up")
+      return {
+        title
+      }
+    }
+  },
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>