about summary refs log tree commit diff
path: root/app/views
diff options
context:
space:
mode:
Diffstat (limited to 'app/views')
-rw-r--r--app/views/about/_og.html.haml10
-rw-r--r--app/views/about/_registration.html.haml21
-rw-r--r--app/views/about/more.html.haml15
-rw-r--r--app/views/about/show.html.haml22
-rw-r--r--app/views/accounts/_grid_card.html.haml11
-rw-r--r--app/views/accounts/_header.html.haml44
-rw-r--r--app/views/accounts/_og.html.haml17
-rw-r--r--app/views/accounts/show.html.haml13
-rw-r--r--app/views/admin/accounts/_account.html.haml3
-rw-r--r--app/views/admin/accounts/_card.html.haml31
-rw-r--r--app/views/admin/accounts/index.html.haml22
-rw-r--r--app/views/admin/accounts/show.html.haml192
-rw-r--r--app/views/admin/custom_emojis/_custom_emoji.html.haml7
-rw-r--r--app/views/admin/custom_emojis/index.html.haml14
-rw-r--r--app/views/admin/custom_emojis/new.html.haml12
-rw-r--r--app/views/admin/domain_blocks/index.html.haml19
-rw-r--r--app/views/admin/instances/_instance.html.haml2
-rw-r--r--app/views/admin/instances/index.html.haml25
-rw-r--r--app/views/admin/reports/index.html.haml25
-rw-r--r--app/views/admin/settings/edit.html.haml10
-rw-r--r--app/views/admin/subscriptions/index.html.haml21
-rw-r--r--app/views/auth/registrations/_sessions.html.haml47
-rw-r--r--app/views/auth/registrations/edit.html.haml2
-rw-r--r--app/views/auth/registrations/new.html.haml4
-rw-r--r--app/views/auth/sessions/two_factor.html.haml4
-rw-r--r--app/views/authorize_follows/show.html.haml9
-rw-r--r--app/views/errors/500.html.haml5
-rw-r--r--app/views/home/index.html.haml6
-rwxr-xr-xapp/views/layouts/application.html.haml16
-rw-r--r--app/views/layouts/embedded.html.haml1
-rw-r--r--app/views/layouts/error.html.haml32
-rw-r--r--app/views/layouts/modal.html.haml16
-rw-r--r--app/views/oauth/authorizations/show.html.haml3
-rw-r--r--app/views/oauth/authorized_applications/index.html.haml39
-rw-r--r--app/views/settings/applications/_fields.html.haml21
-rw-r--r--app/views/settings/applications/index.html.haml20
-rw-r--r--app/views/settings/applications/new.html.haml8
-rw-r--r--app/views/settings/applications/show.html.haml31
-rw-r--r--app/views/settings/exports/show.html.haml37
-rw-r--r--app/views/settings/follower_domains/show.html.haml27
-rw-r--r--app/views/settings/preferences/show.html.haml4
-rw-r--r--app/views/settings/profiles/show.html.haml4
-rw-r--r--app/views/shared/_landing_strip.html.haml9
-rw-r--r--app/views/shares/show.html.haml5
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml26
-rw-r--r--app/views/stream_entries/_og_description.html.haml4
-rw-r--r--app/views/stream_entries/_og_image.html.haml25
-rw-r--r--app/views/stream_entries/_simple_status.html.haml23
-rw-r--r--app/views/stream_entries/_status.html.haml7
-rw-r--r--app/views/stream_entries/embed.html.haml5
-rw-r--r--app/views/stream_entries/show.html.haml11
-rw-r--r--app/views/tags/show.html.haml1
-rw-r--r--app/views/user_mailer/confirmation_instructions.es.html.erb12
-rw-r--r--app/views/user_mailer/confirmation_instructions.es.text.erb12
-rw-r--r--app/views/user_mailer/confirmation_instructions.oc.html.erb6
-rw-r--r--app/views/user_mailer/confirmation_instructions.oc.text.erb6
-rw-r--r--app/views/user_mailer/confirmation_instructions.pt-BR.html.erb12
-rw-r--r--app/views/user_mailer/confirmation_instructions.pt-BR.text.erb12
-rw-r--r--app/views/user_mailer/confirmation_instructions.zh-cn.html.erb2
-rw-r--r--app/views/user_mailer/confirmation_instructions.zh-cn.text.erb2
-rw-r--r--app/views/user_mailer/password_change.es.html.erb3
-rw-r--r--app/views/user_mailer/password_change.es.text.erb3
-rw-r--r--app/views/user_mailer/password_change.oc.html.erb4
-rw-r--r--app/views/user_mailer/password_change.oc.text.erb4
-rw-r--r--app/views/user_mailer/password_change.pt-BR.html.erb3
-rw-r--r--app/views/user_mailer/password_change.pt-BR.text.erb3
-rw-r--r--app/views/user_mailer/reset_password_instructions.es.html.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.es.text.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.oc.html.erb6
-rw-r--r--app/views/user_mailer/reset_password_instructions.oc.text.erb6
-rw-r--r--app/views/user_mailer/reset_password_instructions.pt-BR.html.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.pt-BR.text.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.zh-cn.html.erb2
-rw-r--r--app/views/user_mailer/reset_password_instructions.zh-cn.text.erb2
74 files changed, 719 insertions, 401 deletions
diff --git a/app/views/about/_og.html.haml b/app/views/about/_og.html.haml
new file mode 100644
index 000000000..dbd476915
--- /dev/null
+++ b/app/views/about/_og.html.haml
@@ -0,0 +1,10 @@
+- thumbnail = @instance_presenter.thumbnail
+= opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
+= opengraph 'og:url', about_url
+= opengraph 'og:type', 'website'
+= opengraph 'og:title', @instance_presenter.site_title
+= opengraph 'og:description', strip_tags(@instance_presenter.site_description.presence || t('about.about_mastodon_html'))
+= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('preview.jpg', protocol: :request))
+= opengraph 'og:image:width', thumbnail ? thumbnail.meta['width'] : '1200'
+= opengraph 'og:image:height', thumbnail ? thumbnail.meta['height'] : '630'
+= opengraph 'twitter:card', 'summary_large_image'
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index f1c6e6b9d..7a28f9738 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -1,26 +1,13 @@
 = simple_form_for(new_user, url: user_registration_path) do |f|
   = f.simple_fields_for :account do |account_fields|
     .input-with-append
-      = account_fields.input :username,
-        autofocus: true,
-        placeholder: t('simple_form.labels.defaults.username'),
-        required: true,
-        input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+      = account_fields.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off' }
       .append
         = "@#{site_hostname}"
 
-  = f.input :email,
-    placeholder: t('simple_form.labels.defaults.email'),
-    required: true,
-    input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-  = f.input :password,
-    placeholder: t('simple_form.labels.defaults.password'),
-    required: true,
-    input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
-  = f.input :password_confirmation,
-    placeholder: t('simple_form.labels.defaults.confirm_password'),
-    required: true,
-    input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
+  = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
+  = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
+  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.register'), type: :submit, class: 'button button-alternative'
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 6d621ce8b..6e4d0cdd1 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -3,16 +3,7 @@
 
 - content_for :header_tags do
   = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
-
-  %meta{ property: 'og:site_name', content: site_title }/
-  %meta{ property: 'og:url', content: about_url }/
-  %meta{ property: 'og:type', content: 'website' }/
-  %meta{ property: 'og:title', content: site_hostname }/
-  %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.presence || t('about.about_mastodon_html')) }/
-  %meta{ property: 'og:image', content: asset_pack_path('mastodon_small.jpg', protocol: :request) }/
-  %meta{ property: 'og:image:width', content: '400' }/
-  %meta{ property: 'og:image:height', content: '400' }/
-  %meta{ property: 'twitter:card', content: 'summary' }/
+  = render partial: 'og'
 
 .landing-page
   .header-wrapper.compact
@@ -63,9 +54,9 @@
   .footer-links
     .container
       %p
-        = link_to t('about.source_code'), 'https://github.com/glitch-soc/mastodon'
+        = link_to t('about.source_code'), @instance_presenter.source_url
         - if @instance_presenter.commit_hash == ""
-          %strong= @instance_presenter.version_number
+          %strong= " (#{@instance_presenter.version_number})"
         - else
           %strong= "#{@instance_presenter.version_number}, "
           %strong= "#{@instance_presenter.commit_hash}"
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 3e04dd038..c0fa944ae 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -4,16 +4,7 @@
 - content_for :header_tags do
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
-
-  %meta{ property: 'og:site_name', content: site_title }/
-  %meta{ property: 'og:url', content: about_url }/
-  %meta{ property: 'og:type', content: 'website' }/
-  %meta{ property: 'og:title', content: site_hostname }/
-  %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.presence || t('about.about_mastodon_html')) }/
-  %meta{ property: 'og:image', content: asset_pack_path('mastodon_small.jpg', protocol: :request) }/
-  %meta{ property: 'og:image:width', content: '400' }/
-  %meta{ property: 'og:image:height', content: '400' }/
-  %meta{ property: 'twitter:card', content: 'summary' }/
+  = render partial: 'og'
 
 .landing-page
   .header-wrapper
@@ -56,9 +47,16 @@
                 %p= t('about.closed_registrations')
               - else
                 = @instance_presenter.closed_registrations_message.html_safe
+
+            = simple_form_for(:user, html: { style: 'margin-left: -20px' }, url: session_path(:user)) do |f|
+              = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+              = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
+
+              .actions
+                = f.button :button, t('auth.login'), type: :submit
             = link_to t('about.find_another_instance'), 'https://joinmastodon.org/', class: 'button button-alternative button--block'
 
-  .learn-more-cta
+  .about-short
     .container
       %h3= t('about.description_headline', domain: site_hostname)
       %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
@@ -76,7 +74,7 @@
   .footer-links
     .container
       %p
-        = link_to t('about.source_code'), 'https://github.com/glitch-soc/mastodon'
+        = link_to t('about.source_code'), @instance_presenter.source_url
         - if @instance_presenter.commit_hash == ""
           %strong= " (#{@instance_presenter.version_number})"
         - else
diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml
index 0571d1d5e..305eb2c44 100644
--- a/app/views/accounts/_grid_card.html.haml
+++ b/app/views/accounts/_grid_card.html.haml
@@ -1,8 +1,9 @@
 .account-grid-card
-  .account-grid-card__header
+  .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
+  .account-grid-card__avatar
     .avatar= image_tag account.avatar.url(:original)
-    .name
-      = link_to TagManager.instance.url_for(account) do
-        %span.display_name.emojify= display_name(account)
-        %span.username @#{account.acct}
+  .name
+    = link_to TagManager.instance.url_for(account) do
+      %span.display_name.emojify= display_name(account)
+      %span.username @#{account.acct}
   %p.note.emojify= truncate(strip_tags(account.note), length: 150)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index ed8a6f091..dcc6661ba 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -1,41 +1,57 @@
 - processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account
 .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
-  .details
+  .card__illustration
     - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
       .controls
         - if current_account.following?(account)
-          = link_to t('accounts.unfollow'), account_unfollow_path(account), data: { method: :post }, class: 'button'
+          = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
+            = fa_icon 'user-times'
+            = t('accounts.unfollow')
         - else
-          = link_to t('accounts.follow'), account_follow_path(account), data: { method: :post }, class: 'button'
+          = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
+            = fa_icon 'user-plus'
+            = t('accounts.follow')
     - elsif !user_signed_in?
       .controls
         .remote-follow
-          = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button'
+          = link_to account_remote_follow_path(account), class: 'icon-button' do
+            = fa_icon 'user-plus'
+            = t('accounts.remote_follow')
+
     .avatar= image_tag account.avatar.url(:original), class: 'u-photo'
+
+  .card__bio
     %h1.name
       %span.p-name.emojify= display_name(account)
       %small
-        %span @#{account.username}
+        %span @#{account.local_username_and_domain}
         = fa_icon('lock') if account.locked?
+
+    - if account.user_admin?
+      .roles
+        .account-role
+          = t 'accounts.roles.admin'
     .bio
       .account__header__content.p-note.emojify!=processed_bio[:text]
+      - if processed_bio[:metadata].length > 0
+        .metadata<
+          - processed_bio[:metadata].each do |i|
+            .metadata-item><
+              %b.emojify>!=i[0]
+              %span.emojify>!=i[1]
 
     .details-counters
       .counter{ class: active_nav_class(short_account_url(account)) }
         = link_to short_account_url(account), class: 'u-url u-uid' do
+          %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
           %span.counter-label= t('accounts.posts')
-          %span.counter-number= number_with_delimiter account.statuses_count
+
       .counter{ class: active_nav_class(account_following_index_url(account)) }
         = link_to account_following_index_url(account) do
+          %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
           %span.counter-label= t('accounts.following')
-          %span.counter-number= number_with_delimiter account.following_count
+
       .counter{ class: active_nav_class(account_followers_url(account)) }
         = link_to account_followers_url(account) do
+          %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
           %span.counter-label= t('accounts.followers')
-          %span.counter-number= number_with_delimiter account.followers_count
-  - if processed_bio[:metadata].length > 0
-    .metadata<
-      - processed_bio[:metadata].each do |i|
-        .metadata-item><
-          %b.emojify>!=i[0]
-          %span.emojify>!=i[1]
diff --git a/app/views/accounts/_og.html.haml b/app/views/accounts/_og.html.haml
index 3ad39f391..1d16be590 100644
--- a/app/views/accounts/_og.html.haml
+++ b/app/views/accounts/_og.html.haml
@@ -1,8 +1,9 @@
-%meta{ property: 'og:url', content: url }/
-%meta{ property: 'og:site_name', content: site_title }/
-%meta{ property: 'og:title', content: [yield(:page_title).strip.presence, site_title].compact.join(' - ') }/
-%meta{ property: 'og:description', content: account.note }/
-%meta{ property: 'og:image', content: full_asset_url(account.avatar.url(:original)) }/
-%meta{ property: 'og:image:width', content: '120' }/
-%meta{ property: 'og:image:height', content: '120' }/
-%meta{ property: 'twitter:card', content: 'summary' }/
+= opengraph 'og:url', url
+= opengraph 'og:site_name', site_title
+= opengraph 'og:title', [yield(:page_title).strip.presence, site_title].compact.join(' - ')
+= opengraph 'og:description', account.note
+= opengraph 'og:image', full_asset_url(account.avatar.url(:original))
+= opengraph 'og:image:width', '120'
+= opengraph 'og:image:height', '120'
+= opengraph 'twitter:card', 'summary'
+= opengraph 'profile:username', account.local_username_and_domain
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 150c14791..6c90b2c04 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -7,8 +7,9 @@
 
   %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/
   %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/
+  %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
 
-  %meta{ property: 'og:type', content: 'profile' }/
+  = opengraph 'og:type', 'profile'
   = render 'og', account: @account, url: short_account_url(@account, only_path: false)
 
 - if show_landing_strip?
@@ -19,13 +20,21 @@
 
   = render 'header', account: @account
 
+  .activity-stream-tabs
+    = active_link_to t('accounts.posts'), short_account_url(@account)
+    = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
+    = active_link_to t('accounts.media'), short_account_media_url(@account)
+
   - if @statuses.empty?
     .accounts-grid
       = render 'nothing_here'
   - else
     .activity-stream.with-header
+      - if params[:page].to_i.zero?
+        = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
+
       = render partial: 'stream_entries/status', collection: @statuses, as: :status
 
   - if @statuses.size == 20
     .pagination
-      = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), short_account_url(@account, max_id: @statuses.last.id), class: 'next', rel: 'next'
+      = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next'
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index c513776b7..5265d77f6 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -4,6 +4,9 @@
   %td.domain
     - unless account.local?
       = link_to account.domain, admin_accounts_path(by_domain: account.domain)
+  %td.protocol
+    - unless account.local?
+      = account.protocol.humanize
   %td.confirmed
     - if account.local?
       - if account.user_confirmed?
diff --git a/app/views/admin/accounts/_card.html.haml b/app/views/admin/accounts/_card.html.haml
index bb33582eb..2f5955011 100644
--- a/app/views/admin/accounts/_card.html.haml
+++ b/app/views/admin/accounts/_card.html.haml
@@ -1,16 +1,17 @@
-%table.table
-  %tbody
-    %tr
-      %td= t('admin.accounts.show.created_reports')
-      %td= link_to pluralize(account.reports.count, t('admin.accounts.show.report')), admin_reports_path(account_id: account.id)
-    %tr
-      %td= t('admin.accounts.show.targeted_reports')
-      %td= link_to pluralize(account.targeted_reports.count, t('admin.accounts.show.report')), admin_reports_path(target_account_id: account.id)
-    - if account.silenced? || account.suspended?
+.table-wrapper
+  %table.table
+    %tbody
       %tr
-        %td= t('admin.accounts.moderation.title')
-        %td
-          - if account.silenced?
-            %p= t('admin.accounts.moderation.silenced')
-          - if account.suspended?
-            %p= t('admin.accounts.moderation.suspended')
+        %td= t('admin.accounts.show.created_reports')
+        %td= link_to pluralize(account.reports.count, t('admin.accounts.show.report')), admin_reports_path(account_id: account.id)
+      %tr
+        %td= t('admin.accounts.show.targeted_reports')
+        %td= link_to pluralize(account.targeted_reports.count, t('admin.accounts.show.report')), admin_reports_path(target_account_id: account.id)
+      - if account.silenced? || account.suspended?
+        %tr
+          %td= t('admin.accounts.moderation.title')
+          %td
+            - if account.silenced?
+              %p= t('admin.accounts.moderation.silenced')
+            - if account.suspended?
+              %p= t('admin.accounts.moderation.suspended')
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 07c8d1632..1b56a3a31 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -50,15 +50,17 @@
       %button= t('admin.accounts.search')
       = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
 
-%table.table
-  %thead
-    %tr
-      %th= t('admin.accounts.username')
-      %th= t('admin.accounts.domain')
-      %th= t('admin.accounts.confirmed')
-      %th= fa_icon 'paper-plane-o'
-      %th
-  %tbody
-    = render @accounts
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.accounts.username')
+        %th= t('admin.accounts.domain')
+        %th= t('admin.accounts.protocol')
+        %th= t('admin.accounts.confirmed')
+        %th= fa_icon 'paper-plane-o'
+        %th
+    %tbody
+      = render @accounts
 
 = paginate @accounts
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 5ad1fd6ee..3775b6721 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -1,95 +1,131 @@
 - content_for :page_title do
   = @account.acct
 
-%table.table
-  %tbody
-    %tr
-      %th= t('admin.accounts.username')
-      %td= @account.username
-    %tr
-      %th= t('admin.accounts.domain')
-      %td= @account.domain
-    %tr
-      %th= t('admin.accounts.display_name')
-      %td= @account.display_name
-
-    - if @account.local?
+.table-wrapper
+  %table.table
+    %tbody
       %tr
-        %th= t('admin.accounts.email')
-        %td= @account.user_email
+        %th= t('admin.accounts.username')
+        %td= @account.username
       %tr
-        %th= t('admin.accounts.most_recent_ip')
-        %td= @account.user_current_sign_in_ip
+        %th= t('admin.accounts.domain')
+        %td= @account.domain
       %tr
-        %th= t('admin.accounts.most_recent_activity')
-        %td
-          - if @account.user_current_sign_in_at
-            = l @account.user_current_sign_in_at
-          - else
-            Never
-    - else
+        %th= t('admin.accounts.display_name')
+        %td= @account.display_name
+
+      - if @account.local?
+        %tr
+          %th= t('admin.accounts.email')
+          %td= @account.user_email
+        %tr
+          %th= t('admin.accounts.most_recent_ip')
+          %td= @account.user_current_sign_in_ip
+        %tr
+          %th= t('admin.accounts.most_recent_activity')
+          %td
+            - if @account.user_current_sign_in_at
+              %time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }
+                = l @account.user_current_sign_in_at
+            - else
+              Never
+      - else
+        %tr
+          %th= t('admin.accounts.profile_url')
+          %td= link_to @account.url, @account.url
+        %tr
+          %th= t('admin.accounts.protocol')
+          %td= @account.protocol.humanize
+
       %tr
-        %th= t('admin.accounts.profile_url')
-        %td= link_to @account.url, @account.url
+        %th= t('admin.accounts.follows')
+        %td= @account.following_count
       %tr
-        %th= t('admin.accounts.feed_url')
-        %td= link_to @account.remote_url, @account.remote_url
+        %th= t('admin.accounts.followers')
+        %td= @account.followers_count
       %tr
-        %th= t('admin.accounts.push_subscription_expires')
+        %th= t('admin.accounts.statuses')
+        %td= link_to @account.statuses_count, admin_account_statuses_path(@account.id)
+      %tr
+        %th= t('admin.accounts.media_attachments')
         %td
-          - if @account.subscribed?
-            = l @account.subscription_expires_at
-          - else
-            = t('admin.accounts.not_subscribed')
+          = link_to @account.media_attachments.count, admin_account_statuses_path(@account.id, { media: true })
+          = surround '(', ')' do
+            = number_to_human_size @account.media_attachments.sum('file_file_size')
+      %tr
+        %th= t('.created_reports')
+        %td= link_to pluralize(@account.reports.count, t('.report')), admin_reports_path(account_id: @account.id)
       %tr
-        %th= t('admin.accounts.salmon_url')
-        %td= link_to @account.salmon_url, @account.salmon_url
+        %th= t('.targeted_reports')
+        %td= link_to pluralize(@account.targeted_reports.count, t('.report')), admin_reports_path(target_account_id: @account.id)
 
-    %tr
-      %th= t('admin.accounts.follows')
-      %td= @account.following_count
-    %tr
-      %th= t('admin.accounts.followers')
-      %td= @account.followers_count
-    %tr
-      %th= t('admin.accounts.statuses')
-      %td= link_to @account.statuses_count, admin_account_statuses_path(@account.id)
-    %tr
-      %th= t('admin.accounts.media_attachments')
-      %td
-        = link_to @account.media_attachments.count, admin_account_statuses_path(@account.id, { media: true })
-        = surround '(', ')' do
-          = number_to_human_size @account.media_attachments.sum('file_file_size')
-    %tr
-      %th= t('.created_reports')
-      %td= link_to pluralize(@account.reports.count, t('.report')), admin_reports_path(account_id: @account.id)
-    %tr
-      %th= t('.targeted_reports')
-      %td= link_to pluralize(@account.targeted_reports.count, t('.report')), admin_reports_path(target_account_id: @account.id)
+%div{ style: 'overflow: hidden' }
+  %div{ style: 'float: right' }
+    - if @account.local?
+      = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
+      - if @account.user&.otp_required_for_login?
+        = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button'
+    - else
+      = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button'
+
+  %div{ style: 'float: left' }
+    - if @account.silenced?
+      = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
+    - else
+      = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
+
+    - if @account.local?
+      - unless @account.user_confirmed?
+        = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button'
+
+    - if @account.suspended?
+      = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
+    - else
+      = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
 
+- unless @account.local?
+  %hr
+  %h3 OStatus
 
-%div{ style: 'float: right' }
-  - if @account.local?
-    = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
-    - if @account.user&.otp_required_for_login?
-      = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button'
-  - else
-    = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button'
-    - if @account.subscribed?
-      = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative'
-    = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button'
+  .table-wrapper
+    %table.table
+      %tbody
+        %tr
+          %th= t('admin.accounts.feed_url')
+          %td= link_to @account.remote_url, @account.remote_url
+        %tr
+          %th= t('admin.accounts.push_subscription_expires')
+          %td
+            - if @account.subscribed?
+              %time.formatted{ datetime: @account.subscription_expires_at.iso8601, title: l(@account.subscription_expires_at) }
+                = l @account.subscription_expires_at
+            - else
+              = t('admin.accounts.not_subscribed')
+        %tr
+          %th= t('admin.accounts.salmon_url')
+          %td= link_to @account.salmon_url, @account.salmon_url
 
-%div{ style: 'float: left' }
-  - if @account.silenced?
-    = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
-  - else
-    = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
+  %div{ style: 'overflow: hidden' }
+    %div{ style: 'float: right' }
+      = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button'
+      - if @account.subscribed?
+        = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative'
 
-  - if @account.local?
-    - unless @account.user_confirmed?
-      = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button'
+  %hr
+  %h3 ActivityPub
 
-  - if @account.suspended?
-    = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
-  - else
-    = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+  .table-wrapper
+    %table.table
+      %tbody
+        %tr
+          %th= t('admin.accounts.inbox_url')
+          %td= link_to @account.inbox_url, @account.inbox_url
+        %tr
+          %th= t('admin.accounts.outbox_url')
+          %td= link_to @account.outbox_url, @account.outbox_url
+        %tr
+          %th= t('admin.accounts.shared_inbox_url')
+          %td= link_to @account.shared_inbox_url, @account.shared_inbox_url
+        %tr
+          %th= t('admin.accounts.followers_url')
+          %td= link_to @account.followers_url, @account.followers_url
diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml
new file mode 100644
index 000000000..ff1aa9925
--- /dev/null
+++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml
@@ -0,0 +1,7 @@
+%tr
+  %td
+    = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:"
+  %td
+    %samp= ":#{custom_emoji.shortcode}:"
+  %td
+    = table_link_to 'times', t('admin.custom_emojis.delete'), admin_custom_emoji_path(custom_emoji), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml
new file mode 100644
index 000000000..d5f32e84b
--- /dev/null
+++ b/app/views/admin/custom_emojis/index.html.haml
@@ -0,0 +1,14 @@
+- content_for :page_title do
+  = t('admin.custom_emojis.title')
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.custom_emojis.emoji')
+        %th= t('admin.custom_emojis.shortcode')
+        %th
+    %tbody
+      = render @custom_emojis
+
+= link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button'
diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/admin/custom_emojis/new.html.haml
new file mode 100644
index 000000000..672afe435
--- /dev/null
+++ b/app/views/admin/custom_emojis/new.html.haml
@@ -0,0 +1,12 @@
+- content_for :page_title do
+  = t('.title')
+
+= simple_form_for @custom_emoji, url: admin_custom_emojis_path do |f|
+  = render 'shared/error_messages', object: @custom_emoji
+
+  .fields-group
+    = f.input :shortcode, placeholder: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint')
+    = f.input :image, input_html: { accept: 'image/png' }, hint: t('admin.custom_emojis.image_hint')
+
+  .actions
+    = f.button :button, t('admin.custom_emojis.upload'), type: :submit
diff --git a/app/views/admin/domain_blocks/index.html.haml b/app/views/admin/domain_blocks/index.html.haml
index 5ae9fec53..42b8ca53f 100644
--- a/app/views/admin/domain_blocks/index.html.haml
+++ b/app/views/admin/domain_blocks/index.html.haml
@@ -1,15 +1,16 @@
 - content_for :page_title do
   = t('admin.domain_blocks.title')
 
-%table.table
-  %thead
-    %tr
-      %th= t('admin.domain_blocks.domain')
-      %th= t('admin.domain_blocks.severity')
-      %th= t('admin.domain_blocks.reject_media')
-      %th
-  %tbody
-    = render @domain_blocks
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.domain_blocks.domain')
+        %th= t('admin.domain_blocks.severity')
+        %th= t('admin.domain_blocks.reject_media')
+        %th
+    %tbody
+      = render @domain_blocks
 
 = paginate @domain_blocks
 = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml
index 435cd8f64..6efbbbe60 100644
--- a/app/views/admin/instances/_instance.html.haml
+++ b/app/views/admin/instances/_instance.html.haml
@@ -1,6 +1,6 @@
 %tr
   %td.domain
-    = instance.domain
+    = link_to instance.domain, admin_accounts_path(by_domain: instance.domain)
   %td.count
     = instance.accounts_count
   %td
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index be21d6bf7..3314ce077 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -1,12 +1,23 @@
 - content_for :page_title do
   = t('admin.instances.title')
 
-%table.table
-  %thead
-    %tr
-      %th= t('admin.instances.domain_name')
-      %th= t('admin.instances.account_count')
-  %tbody
-    = render @instances
+= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
+  .fields-group
+    - %i(domain_name).each do |key|
+      .input.string.optional
+        = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}")
+
+    .actions
+      %button= t('admin.instances.search')
+      = link_to t('admin.instances.reset'), admin_instances_path, class: 'button negative'
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.instances.domain_name')
+        %th= t('admin.instances.account_count')
+    %tbody
+      = render @instances
 
 = paginate paginated_instances
diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml
index f1c4a93c4..577c68a86 100644
--- a/app/views/admin/reports/index.html.haml
+++ b/app/views/admin/reports/index.html.haml
@@ -10,17 +10,18 @@
 
 = form_tag do
 
-  %table.table
-    %thead
-      %tr
-        -# %th
-        %th= t('admin.reports.id')
-        %th= t('admin.reports.target')
-        %th= t('admin.reports.reported_by')
-        %th= t('admin.reports.comment.label')
-        %th= t('admin.reports.report_contents')
-        %th
-    %tbody
-      = render @reports
+  .table-wrapper
+    %table.table
+      %thead
+        %tr
+          -# %th
+          %th= t('admin.reports.id')
+          %th= t('admin.reports.target')
+          %th= t('admin.reports.reported_by')
+          %th= t('admin.reports.comment.label')
+          %th= t('admin.reports.report_contents')
+          %th
+      %tbody
+        = render @reports
 
 = paginate @reports
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 9f8a6640b..468166035 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -11,6 +11,11 @@
   %hr/
 
   .fields-group
+    = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: t('admin.settings.thumbnail.desc_html')
+
+  %hr/
+
+  .fields-group
     = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
 
   .fields-group
@@ -28,5 +33,10 @@
     = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 }
     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
 
+  %hr/
+
+  .fields-group
+    = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html')
+
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/subscriptions/index.html.haml b/app/views/admin/subscriptions/index.html.haml
index 21b3238a6..83704c8ee 100644
--- a/app/views/admin/subscriptions/index.html.haml
+++ b/app/views/admin/subscriptions/index.html.haml
@@ -1,15 +1,16 @@
 - content_for :page_title do
   = t('admin.subscriptions.title')
 
-%table.table
-  %thead
-    %tr
-      %th= t('admin.subscriptions.topic')
-      %th= t('admin.subscriptions.callback_url')
-      %th= t('admin.subscriptions.confirmed')
-      %th= t('admin.subscriptions.expires_in')
-      %th= t('admin.subscriptions.last_delivery')
-  %tbody
-    = render @subscriptions
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('admin.subscriptions.topic')
+        %th= t('admin.subscriptions.callback_url')
+        %th= t('admin.subscriptions.confirmed')
+        %th= t('admin.subscriptions.expires_in')
+        %th= t('admin.subscriptions.last_delivery')
+    %tbody
+      = render @subscriptions
 
 = paginate @subscriptions
diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml
index 7ac578bb1..c1e9764b3 100644
--- a/app/views/auth/registrations/_sessions.html.haml
+++ b/app/views/auth/registrations/_sessions.html.haml
@@ -1,28 +1,29 @@
 %h6= t 'sessions.title'
 %p.muted-hint= t 'sessions.explanation'
 
-%table.table.inline-table
-  %thead
-    %tr
-      %th= t 'sessions.browser'
-      %th= t 'sessions.ip'
-      %th= t 'sessions.activity'
-      %td
-  %tbody
-    - @sessions.each do |session|
+.table-wrapper
+  %table.table.inline-table
+    %thead
       %tr
+        %th= t 'sessions.browser'
+        %th= t 'sessions.ip'
+        %th= t 'sessions.activity'
         %td
-          %span{ title: session.user_agent }<
-            = fa_icon "#{session_device_icon(session)} fw", 'aria-label' => session_device_icon(session)
-            = ' '
-            = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}")
-        %td
-          %samp= session.ip
-        %td
-          - if current_session.session_id == session.session_id
-            = t 'sessions.current_session'
-          - else
-            %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
-        %td
-          - if current_session.session_id != session.session_id
-            = table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete
+    %tbody
+      - @sessions.each do |session|
+        %tr
+          %td
+            %span{ title: session.user_agent }<
+              = fa_icon "#{session_device_icon(session)} fw", 'aria-label' => session_device_icon(session)
+              = ' '
+              = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}")
+          %td
+            %samp= session.ip
+          %td
+            - if current_session.session_id == session.session_id
+              = t 'sessions.current_session'
+            - else
+              %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
+          %td
+            - if current_session.session_id != session.session_id
+              = table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index f016a4883..145f5cd9e 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -1,7 +1,7 @@
 - content_for :page_title do
   = t('auth.change_password')
 
-= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
+= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
   = render 'shared/error_messages', object: resource
 
   = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index d0529a20c..807020310 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -6,11 +6,11 @@
 
   = f.simple_fields_for :account do |ff|
     .input-with-append
-      = ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+      = ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off' }
       .append
         = "@#{site_hostname}"
 
-  = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+  = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
   = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
   = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
 
diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml
index cb5e32f3e..2b07c923b 100644
--- a/app/views/auth/sessions/two_factor.html.haml
+++ b/app/views/auth/sessions/two_factor.html.haml
@@ -2,9 +2,7 @@
   = t('auth.login')
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
-  = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'),
-      input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true,
-      hint: t('simple_form.hints.sessions.otp')
+  = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true, hint: t('simple_form.hints.sessions.otp')
 
   .actions
     = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/authorize_follows/show.html.haml b/app/views/authorize_follows/show.html.haml
index 3b60df058..f7a8f72d2 100644
--- a/app/views/authorize_follows/show.html.haml
+++ b/app/views/authorize_follows/show.html.haml
@@ -3,10 +3,9 @@
 
 .form-container
   .follow-prompt
-    %h2= t('authorize_follow.prompt_html', self: current_account.username)
-
     = render 'card', account: @account
 
-  = form_tag authorize_follow_path, method: :post, class: 'simple_form' do
-    = hidden_field_tag :acct, @account.acct
-    = button_tag t('authorize_follow.follow'), type: :submit
+  - unless current_account.following?(@account)
+    = form_tag authorize_follow_path, method: :post, class: 'simple_form' do
+      = hidden_field_tag :acct, @account.acct
+      = button_tag t('authorize_follow.follow'), type: :submit
diff --git a/app/views/errors/500.html.haml b/app/views/errors/500.html.haml
new file mode 100644
index 000000000..6244ff209
--- /dev/null
+++ b/app/views/errors/500.html.haml
@@ -0,0 +1,5 @@
+- content_for :page_title do
+  = t('errors.500.title')
+
+- content_for :content do
+  = t('errors.500.content')
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index ec6e53461..3b4219c56 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -2,12 +2,12 @@
   %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
 
-  = javascript_pack_tag "frontends/#{@frontend}", integrity: true, crossorigin: 'anonymous'
-  = stylesheet_pack_tag "frontends/#{@frontend}", integrity: true, media: 'all'
+  = javascript_pack_tag "themes/#{current_theme}", integrity: true, crossorigin: 'anonymous'
+  = stylesheet_pack_tag "themes/#{current_theme}", integrity: true, media: 'all'
 
 .app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
   %noscript
     = image_tag asset_pack_path('logo.svg'), alt: 'Mastodon'
 
     %div
-      = t('errors.noscript')
+      = t('errors.noscript_html')
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 399d70bc0..e6190f7e2 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -21,13 +21,12 @@
     = stylesheet_pack_tag 'common', media: 'all'
     = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
 
-    = javascript_pack_tag 'features/getting_started', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'features/compose', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'features/home_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'features/notifications', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'features/community_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'features/public_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
-    = javascript_pack_tag 'emojione_picker', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
+    %link{ href: asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+    %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+    %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+    %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+    %link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+    %link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
 
     = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
     = csrf_meta_tags
@@ -37,7 +36,8 @@
 
     = yield :header_tags
 
-  - body_classes ||= @body_classes
+  - body_classes ||= @body_classes || ''
+  - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui
 
   %body{ class: add_rtl_body_class(body_classes) }
     = content_for?(:content) ? yield(:content) : yield
diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml
index 46dab2d0f..5fc60be17 100644
--- a/app/views/layouts/embedded.html.haml
+++ b/app/views/layouts/embedded.html.haml
@@ -6,6 +6,7 @@
 
     = stylesheet_pack_tag 'common', media: 'all'
     = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
+    = stylesheet_pack_tag 'application', integrity: true, media: 'all'
     = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
     = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
   %body.embed
diff --git a/app/views/layouts/error.html.haml b/app/views/layouts/error.html.haml
index 08b94af54..8b260c619 100644
--- a/app/views/layouts/error.html.haml
+++ b/app/views/layouts/error.html.haml
@@ -3,34 +3,12 @@
   %head
     %meta{ content: 'text/html; charset=UTF-8', 'http-equiv' => 'Content-Type' }/
     %meta{ charset: 'utf-8' }/
-    %title= yield :page_title
+    %title= safe_join([yield(:page_title), title], ' - ')
     %meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/
-    %link{ href: 'https://fonts.googleapis.com/css?family=Roboto:400', rel: 'stylesheet' }/
-    :css
-      body {
-        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-        background: #282c37;
-        color: #9baec8;
-        text-align: center;
-        margin: 0;
-        padding: 20px;
-      }
-
-      .dialog img {
-        display: block;
-        margin: 20px auto;
-        margin-top: 50px;
-        max-width: 600px;
-        width: 100%;
-        height: auto;
-      }
-
-      .dialog h1 {
-        font: 20px/28px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-        font-weight: 400;
-      }
-  %body
+    = stylesheet_pack_tag 'common', media: 'all'
+    = stylesheet_pack_tag 'application', integrity: true, media: 'all'
+  %body.error
     .dialog
-      %img{ alt: 'Mastodon', src: '/oops.png' }/
+      %img{ alt: title, src: '/oops.gif' }/
       %div
         %h1= yield :content
diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml
new file mode 100644
index 000000000..a819e098d
--- /dev/null
+++ b/app/views/layouts/modal.html.haml
@@ -0,0 +1,16 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
+
+- content_for :content do
+  - if user_signed_in?
+    .account-header
+      .avatar= image_tag current_account.avatar.url(:original)
+      .name
+        = t 'users.signed_in_as'
+        %span.username @#{current_account.local_username_and_domain}
+      = link_to destroy_user_session_path, method: :delete, class: 'logout-link icon-button' do
+        = fa_icon 'sign-out'
+
+  .container= yield
+
+= render template: 'layouts/application'
diff --git a/app/views/oauth/authorizations/show.html.haml b/app/views/oauth/authorizations/show.html.haml
index b56667f35..ad5236007 100644
--- a/app/views/oauth/authorizations/show.html.haml
+++ b/app/views/oauth/authorizations/show.html.haml
@@ -1,3 +1,4 @@
 .form-container
   .flash-message
-    %code= params[:code]
+    %p= t('doorkeeper.authorizations.show.title')
+    %input{ type: 'text', class: 'oauth-code', readonly: true, value: params[:code], onClick: 'select()' }
diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml
index ba0c08495..19af5f55d 100644
--- a/app/views/oauth/authorized_applications/index.html.haml
+++ b/app/views/oauth/authorized_applications/index.html.haml
@@ -1,23 +1,24 @@
 - content_for :page_title do
   = t('doorkeeper.authorized_applications.index.title')
 
-%table.table
-  %thead
-    %tr
-      %th= t('doorkeeper.authorized_applications.index.application')
-      %th= t('doorkeeper.authorized_applications.index.scopes')
-      %th= t('doorkeeper.authorized_applications.index.created_at')
-      %th
-  %tbody
-    - @applications.each do |application|
+.table-wrapper
+  %table.table
+    %thead
       %tr
-        %td
-          - if application.website.blank?
-            = application.name
-          - else
-            = link_to application.name, application.website, target: '_blank', rel: 'noopener'
-        %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />')
-        %td= l application.created_at
-        %td
-          - unless application.superapp?
-            = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') }
+        %th= t('doorkeeper.authorized_applications.index.application')
+        %th= t('doorkeeper.authorized_applications.index.scopes')
+        %th= t('doorkeeper.authorized_applications.index.created_at')
+        %th
+    %tbody
+      - @applications.each do |application|
+        %tr
+          %td
+            - if application.website.blank?
+              = application.name
+            - else
+              = link_to application.name, application.website, target: '_blank', rel: 'noopener'
+          %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />')
+          %td= l application.created_at
+          %td
+            - unless application.superapp?
+              = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') }
diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml
new file mode 100644
index 000000000..b21f3cca6
--- /dev/null
+++ b/app/views/settings/applications/_fields.html.haml
@@ -0,0 +1,21 @@
+.fields-group
+  = f.input :name, placeholder: t('activerecord.attributes.doorkeeper/application.name')
+  = f.input :website, placeholder: t('activerecord.attributes.doorkeeper/application.website')
+
+.fields-group
+  = f.input :redirect_uri, wrapper: :with_block_label, label: t('activerecord.attributes.doorkeeper/application.redirect_uri'), hint: t('doorkeeper.applications.help.redirect_uri')
+
+  %p.hint= t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: Doorkeeper.configuration.native_redirect_uri)
+
+.field-group
+  = f.input :scopes,
+    label: t('activerecord.attributes.doorkeeper/application.scopes'),
+    collection: Doorkeeper.configuration.scopes,
+    wrapper: :with_label,
+    include_blank: false,
+    label_method: lambda { |scope| safe_join([scope, content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) },
+    selected: f.object.scopes.all,
+    required: false,
+    as: :check_boxes,
+    collection_wrapper_tag: 'ul',
+    item_wrapper_tag: 'li'
diff --git a/app/views/settings/applications/index.html.haml b/app/views/settings/applications/index.html.haml
new file mode 100644
index 000000000..919472c2e
--- /dev/null
+++ b/app/views/settings/applications/index.html.haml
@@ -0,0 +1,20 @@
+- content_for :page_title do
+  = t('doorkeeper.applications.index.title')
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('doorkeeper.applications.index.application')
+        %th= t('doorkeeper.applications.index.scopes')
+        %th
+    %tbody
+      - @applications.each do |application|
+        %tr
+          %td= link_to application.name, settings_application_path(application)
+          %th= application.scopes
+          %td
+            = table_link_to 'times', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') }
+
+= paginate @applications
+= link_to t('doorkeeper.applications.index.new'), new_settings_application_path, class: 'button'
diff --git a/app/views/settings/applications/new.html.haml b/app/views/settings/applications/new.html.haml
new file mode 100644
index 000000000..5274a430c
--- /dev/null
+++ b/app/views/settings/applications/new.html.haml
@@ -0,0 +1,8 @@
+- content_for :page_title do
+  = t('doorkeeper.applications.new.title')
+
+= simple_form_for @application, url: settings_applications_path do |f|
+  = render 'fields', f: f
+  
+  .actions
+    = f.button :button, t('doorkeeper.applications.buttons.submit'), type: :submit
diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml
new file mode 100644
index 000000000..12baed088
--- /dev/null
+++ b/app/views/settings/applications/show.html.haml
@@ -0,0 +1,31 @@
+- content_for :page_title do
+  = t('doorkeeper.applications.show.title', name: @application.name)
+
+%p.hint= t('applications.warning')
+
+.table-wrapper
+  %table.table
+    %tbody
+      %tr  
+        %th= t('doorkeeper.applications.show.application_id')
+        %td
+          %code= @application.uid
+      %tr
+        %th= t('doorkeeper.applications.show.secret')
+        %td
+          %code= @application.secret
+      %tr
+        %th{ rowspan: 2}= t('applications.your_token')
+        %td
+          %code= current_user.token_for_app(@application).token
+      %tr
+        %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post
+
+%hr/
+
+= simple_form_for @application, url: settings_application_path(@application), method: :put do |f|
+  = render 'fields', f: f
+    
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
+
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index f2f6f9556..e0df1c480 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -1,21 +1,22 @@
 - content_for :page_title do
   = t('settings.export')
 
-%table.table
-  %tbody
-    %tr
-      %th= t('exports.storage')
-      %td= number_to_human_size @export.total_storage
-      %td
-    %tr
-      %th= t('exports.follows')
-      %td= @export.total_follows
-      %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
-    %tr
-      %th= t('exports.blocks')
-      %td= @export.total_blocks
-      %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
-    %tr
-      %th= t('exports.mutes')
-      %td= @export.total_mutes
-      %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
+.table-wrapper
+  %table.table
+    %tbody
+      %tr
+        %th= t('exports.storage')
+        %td= number_to_human_size @export.total_storage
+        %td
+      %tr
+        %th= t('exports.follows')
+        %td= @export.total_follows
+        %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
+      %tr
+        %th= t('exports.blocks')
+        %td= @export.total_blocks
+        %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
+      %tr
+        %th= t('exports.mutes')
+        %td= @export.total_mutes
+        %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
diff --git a/app/views/settings/follower_domains/show.html.haml b/app/views/settings/follower_domains/show.html.haml
index dad2770f1..f1687d4d2 100644
--- a/app/views/settings/follower_domains/show.html.haml
+++ b/app/views/settings/follower_domains/show.html.haml
@@ -12,20 +12,21 @@
   %p= t('followers.explanation_html')
   %p= t('followers.true_privacy_html')
 
-  %table.table
-    %thead
-      %tr
-        %th
-        %th= t('followers.domain')
-        %th= t('followers.followers_count')
-    %tbody
-      - @domains.each do |domain|
+  .table-wrapper
+    %table.table
+      %thead
         %tr
-          %td
-            = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil?
-          %td
-            %samp= domain.domain.presence || Rails.configuration.x.local_domain
-          %td= number_with_delimiter domain.accounts_from_domain
+          %th
+          %th= t('followers.domain')
+          %th= t('followers.followers_count')
+      %tbody
+        - @domains.each do |domain|
+          %tr
+            %td
+              = check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil?
+            %td
+              %samp= domain.domain.presence || Rails.configuration.x.local_domain
+            %td= number_with_delimiter domain.accounts_from_domain
 
   .action-pagination
     .actions
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index fae6090c8..5efd538e4 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -5,6 +5,8 @@
   = render 'shared/error_messages', object: current_user
 
   .fields-group
+    = f.input :setting_theme, collection: Themes.instance.names, label_method: lambda { |theme| safe_join([I18n.t("themes.#{theme}", default: theme)])}, wrapper: :with_label, include_blank: false
+
     = f.input :locale,
       collection: I18n.available_locales,
       wrapper: :with_label,
@@ -13,7 +15,7 @@
       selected: I18n.locale
 
     = f.input :filtered_languages,
-      collection: I18n.available_locales,
+      collection: filterable_languages,
       wrapper: :with_block_label,
       include_blank: false,
       label_method: lambda { |locale| human_locale(locale) },
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 3fa540bba..551a7ca49 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -8,8 +8,8 @@
     = f.input :display_name, placeholder: t('simple_form.labels.defaults.display_name'), hint: t('simple_form.hints.defaults.display_name', count: 30 - @account.display_name.size).html_safe
     = f.input :note, placeholder: t('simple_form.labels.defaults.note'), hint: t('simple_form.hints.defaults.note', count: 500 - @account.note.size).html_safe
 
-  .card.compact{ style: "background-image: url(#{@account.header.url(:original)})" }
-    .avatar= image_tag @account.avatar.url(:original)
+  .card.compact{ style: "background-image: url(#{@account.header.url(:original)})", data: { original_src: @account.header.url(:original) } }
+    .avatar= image_tag @account.avatar.url(:original), data: { original_src: @account.avatar.url(:original) }
 
   .fields-group
     = f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar')
diff --git a/app/views/shared/_landing_strip.html.haml b/app/views/shared/_landing_strip.html.haml
index 35461a8cb..ae26fc1ff 100644
--- a/app/views/shared/_landing_strip.html.haml
+++ b/app/views/shared/_landing_strip.html.haml
@@ -1,5 +1,8 @@
 .landing-strip
-  = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path))
+  = image_tag asset_pack_path('logo.svg'), class: 'logo'
 
-  - if open_registrations?
-    = t('landing_strip_signup_html', sign_up_path: new_user_registration_path)
+  %div
+    = t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path))
+
+    - if open_registrations?
+      = t('landing_strip_signup_html', sign_up_path: new_user_registration_path)
diff --git a/app/views/shares/show.html.haml b/app/views/shares/show.html.haml
new file mode 100644
index 000000000..44b6f145f
--- /dev/null
+++ b/app/views/shares/show.html.haml
@@ -0,0 +1,5 @@
+- content_for :header_tags do
+  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
+  = javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous'
+
+#mastodon-compose{ data: { props: Oj.dump(default_props) } }
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 157a7e7fb..9a26d2c0b 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -7,25 +7,27 @@
       %strong.p-name.emojify= display_name(status.account)
       %span= acct(status.account)
 
+  - if embedded_view?
+    = link_to "web+mastodon://follow?uri=#{status.account.local_username_and_domain}", class: 'button button-secondary logo-button', target: '_new' do
+      = render file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')
+      = t('accounts.follow')
+
   .status__content.p-name.emojify<
     - if status.spoiler_text?
       %p{ style: 'margin-bottom: 0' }<
-        %span.p-summary> #{status.spoiler_text}&nbsp;
+        %span.p-summary> #{Formatter.instance.format_spoiler(status)}&nbsp;
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
-      = Formatter.instance.format(status)
+    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
+      = Formatter.instance.format(status, custom_emojify: true)
 
-      - unless status.media_attachments.empty?
+      - if !status.media_attachments.empty?
         - if status.media_attachments.first.video?
-          .video-player><
-            = render partial: 'stream_entries/content_spoiler', locals: { sensitive: status.sensitive? }
-            %video.u-video{ src: status.media_attachments.first.file.url(:original), loop: true }
+          - video = status.media_attachments.first
+          %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}><
         - else
-          .detailed-status__attachments><
-            = render partial: 'stream_entries/content_spoiler', locals: { sensitive: status.sensitive? }
-            .status__attachments__inner<
-              - status.media_attachments.each do |media|
-                = render partial: 'stream_entries/media', locals: { media: media }
+          %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}><
+      - elsif status.preview_cards.first
+        %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}><
 
   .detailed-status__meta
     %data.dt-published{ value: status.created_at.to_time.iso8601 }
diff --git a/app/views/stream_entries/_og_description.html.haml b/app/views/stream_entries/_og_description.html.haml
index 5762aca04..d2fa99e63 100644
--- a/app/views/stream_entries/_og_description.html.haml
+++ b/app/views/stream_entries/_og_description.html.haml
@@ -1,4 +1,4 @@
 - if activity.is_a?(Status) && activity.spoiler_text?
-  %meta{ property: 'og:description', content: activity.spoiler_text }/
+  = opengraph 'og:description', activity.spoiler_text
 - else
-  %meta{ property: 'og:description', content: activity.content }/
+  = opengraph 'og:description', activity.content
diff --git a/app/views/stream_entries/_og_image.html.haml b/app/views/stream_entries/_og_image.html.haml
index f725209d8..b5058583b 100644
--- a/app/views/stream_entries/_og_image.html.haml
+++ b/app/views/stream_entries/_og_image.html.haml
@@ -1,6 +1,23 @@
 - if activity.is_a?(Status) && activity.non_sensitive_with_media?
-  %meta{ property: 'og:image', content: full_asset_url(activity.media_attachments.first.file.url(:small)) }/
+  - activity.media_attachments.each do |media|
+    - if media.image?
+      = opengraph 'og:image', full_asset_url(media.file.url(:original))
+      = opengraph 'og:image:type', media.file_content_type
+      - unless media.file.meta.nil?
+        = opengraph 'og:image:width', media.file.meta['original']['width']
+        = opengraph 'og:image:height', media.file.meta['original']['height']
+    - elsif media.video?
+      = opengraph 'og:image', full_asset_url(media.file.url(:small))
+      = opengraph 'og:image:type', 'image/png'
+      - unless media.file.meta.nil?
+        = opengraph 'og:image:width', media.file.meta['small']['width']
+        = opengraph 'og:image:height', media.file.meta['small']['height']
+      = opengraph 'og:video', full_asset_url(media.file.url(:original))
+      = opengraph 'og:video:type', media.file_content_type
+      - unless media.file.meta.nil?
+        = opengraph 'og:video:width', media.file.meta['small']['width']
+        = opengraph 'og:video:height', media.file.meta['small']['height']
 - else
-  %meta{ property: 'og:image', content: full_asset_url(account.avatar.url(:original)) }/
-  %meta{ property: 'og:image:width', content: '120' }/
-  %meta{ property: 'og:image:height', content: '120' }/
+  = opengraph 'og:image', full_asset_url(account.avatar.url(:original))
+  = opengraph 'og:image:width', '120'
+  = opengraph 'og:image:height','120'
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index b44f9820f..9a2524219 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -16,21 +16,14 @@
   .status__content.p-name.emojify<
     - if status.spoiler_text?
       %p{ style: 'margin-bottom: 0' }<
-        %span.p-summary> #{status.spoiler_text}&nbsp;
+        %span.p-summary> #{Formatter.instance.format_spoiler(status)}&nbsp;
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
-      = Formatter.instance.format(status)
+    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
+      = Formatter.instance.format(status, custom_emojify: true)
 
       - unless status.media_attachments.empty?
-        .status__attachments><
-          = render partial: 'stream_entries/content_spoiler', locals: { sensitive: status.sensitive? }
-          - if status.media_attachments.first.video?
-            .status__attachments__inner<
-              .video-item<
-                = link_to (status.media_attachments.first.remote_url.blank? ? status.media_attachments.first.file.url(:original) : status.media_attachments.first.remote_url), style: "background-image: url(#{status.media_attachments.first.file.url(:small)})", target: '_blank', rel: 'noopener', class: 'u-video' do
-                  .video-item__play
-                    = fa_icon('play')
-          - else
-            .status__attachments__inner<
-              - status.media_attachments.each do |media|
-                = render partial: 'stream_entries/media', locals: { media: media }
+        - if status.media_attachments.first.video?
+          - video = status.media_attachments.first
+          %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 610, height: 343) }}><
+        - else
+          %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 343, sensitive: status.sensitive?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}><
diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml
index 50a373743..e2e1fdd12 100644
--- a/app/views/stream_entries/_status.html.haml
+++ b/app/views/stream_entries/_status.html.haml
@@ -1,4 +1,5 @@
 :ruby
+  pinned          ||= false
   include_threads ||= false
   is_predecessor  ||= false
   is_successor    ||= false
@@ -25,6 +26,12 @@
         = link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
           %strong.emojify= display_name(status.account)
         = t('stream_entries.reblogged')
+  - elsif pinned
+    .pre-header
+      .pre-header__icon
+        = fa_icon('thumb-tack fw')
+      %span
+        = t('stream_entries.pinned')
 
   = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper
 
diff --git a/app/views/stream_entries/embed.html.haml b/app/views/stream_entries/embed.html.haml
index 5df82528b..b703c15d2 100644
--- a/app/views/stream_entries/embed.html.haml
+++ b/app/views/stream_entries/embed.html.haml
@@ -1,2 +1,3 @@
-.activity-stream.activity-stream-headless
-  = render @type, @type.to_sym => @stream_entry.activity, centered: true
+- cache @stream_entry.activity do
+  .activity-stream.activity-stream-headless
+    = render "stream_entries/#{@type}", @type.to_sym => @stream_entry.activity, centered: true
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index 80ea30eb1..1bb8a32b2 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -4,16 +4,17 @@
 
   %link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/
   %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/
+  %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@stream_entry.activity) }/
 
-  %meta{ property: 'og:site_name', content: site_title }/
-  %meta{ property: 'og:type', content: 'article' }/
-  %meta{ property: 'og:title', content: "#{@account.username} on #{site_hostname}" }/
-  %meta{ property: 'og:url', content: account_stream_entry_url(@account, @stream_entry) }/
+  = opengraph 'og:site_name', site_title
+  = opengraph 'og:type', 'article'
+  = opengraph 'og:title', "#{@account.username} on #{site_hostname}"
+  = opengraph 'og:url', account_stream_entry_url(@account, @stream_entry)
 
   = render 'stream_entries/og_description', activity: @stream_entry.activity
   = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
 
-  %meta{ property: 'twitter:card', content: 'summary' }/
+  = opengraph 'twitter:card', 'summary_large_image'
 
 - if show_landing_strip?
   = render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 15bf714c2..8cd2f1825 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -4,6 +4,7 @@
 .compact-header
   %h1<
     = link_to site_title, root_path
+    %br
     %small ##{@tag.name}
 
 - if @statuses.empty?
diff --git a/app/views/user_mailer/confirmation_instructions.es.html.erb b/app/views/user_mailer/confirmation_instructions.es.html.erb
new file mode 100644
index 000000000..1d46a12c0
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.es.html.erb
@@ -0,0 +1,12 @@
+<p>¡Bienvenido, <%= @resource.email %>!</p>
+
+<p>Acabas de crear una cuenta en <%= @instance %>.</p>
+
+<p>Para confirmar tu registro, por favor ingresa al siguiente enlace:<br>
+<%= link_to 'Confirmar mi cuenta', confirmation_url(@resource, confirmation_token: @token) %>
+
+<p>También revisa nuestros <%= link_to 'términos y condiciones', terms_url %>.</p>
+
+<p>Sinceramente,<p>
+
+<p>El equipo de <%= @instance %></p>
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.es.text.erb b/app/views/user_mailer/confirmation_instructions.es.text.erb
new file mode 100644
index 000000000..e9d83b3f8
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.es.text.erb
@@ -0,0 +1,12 @@
+¡Bienvenido, <%= @resource.email %>!
+
+Acabas de crear una cuenta en <%= @instance %>.
+
+Para confirmar tu registro, por favor ingresa al siguiente enlace:
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Por favor, también revisa nuestros términos y condiciones <%= terms_url %>
+
+Sinceramente,
+
+El equipo de <%= @instance %>
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.oc.html.erb b/app/views/user_mailer/confirmation_instructions.oc.html.erb
index 9e9732bd8..7a16db67a 100644
--- a/app/views/user_mailer/confirmation_instructions.oc.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.oc.html.erb
@@ -1,13 +1,13 @@
-<p>Bonjorn <%= @resource.email %>&nbsp;!<p>
+<p>Bonjorn <%= @resource.email %> !<p>
 
 <p>Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :)</p>
 
-<p>Per confirmar vòstre inscripcion, mercés de clicar sul ligam seguent : <br>
+<p>Per confirmar vòstra inscripcion, mercés de clicar sul ligam seguent : <br>
 <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %></p>
 
 <p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.</p>
 
-<p>Pensatz tanben a gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.</p>
+<p>Pensatz tanben de gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.</p>
 
 <p>Amistosament,</p>
 
diff --git a/app/views/user_mailer/confirmation_instructions.oc.text.erb b/app/views/user_mailer/confirmation_instructions.oc.text.erb
index ff1abf62d..bf2acfec1 100644
--- a/app/views/user_mailer/confirmation_instructions.oc.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.oc.text.erb
@@ -1,13 +1,13 @@
-Bonjorn <%= @resource.email %>&nbsp;!
+Bonjorn <%= @resource.email %> !
 
 Venètz de vos crear un compte sus <%= @instance %> e vos mercegem :)
 
-er confirmar vòstre inscripcion, mercés de clicar sul ligam seguent :
+er confirmar vòstra inscripcion, mercés de clicar sul ligam seguent : 
 <%= link_to 'Confirmar mon compte', confirmation_url(@resource, confirmation_token: @token) %>
 
 Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.
 
-Pensatz tanben a gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.
+Pensatz tanben de gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.
 
 Amistosament,
 
diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb
new file mode 100644
index 000000000..80edcfda7
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb
@@ -0,0 +1,12 @@
+<p>Boas vindas, <%= @resource.email %>!</p>
+
+<p>Você acabou de criar uma conta no <%= @instance %>.</p>
+
+<p>Para confirmar o seu cadastro, por favor clique no link a seguir: <br>
+<%= link_to 'Confirmar cadastro', confirmation_url(@resource, confirmation_token: @token) %>
+
+<p>Por favor, leia também os nossos <%= link_to 'termos de serviços', terms_url %>.</p>
+
+<p>Atenciosamente,<p>
+
+<p>A equipe do <%= @instance %></p>
diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb
new file mode 100644
index 000000000..95efb3436
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb
@@ -0,0 +1,12 @@
+Boas vindas, <%= @resource.email %>!
+
+Você acabou de criar uma conta no <%= @instance %>.
+
+Para confirmar o seu cadastro, por favor clique no link a seguir:
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Por favor, leia também os nossos termos e condições de uso <%= terms_url %>
+
+Atenciosamente,
+
+A equipe do <%= @instance %>
diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
index 575b2ff9e..de2f8b6e0 100644
--- a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
@@ -3,7 +3,7 @@
 <p>你刚刚在 <%= @instance %> 创建了帐号。</p>
 
 <p>点击下面的链接来完成注册啦 : <br>
-<%= link_to '确认账户', confirmation_url(@resource, confirmation_token: @token) %>
+<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %>
 
 <p>别忘了看看 <%= link_to '使用条款', terms_url %>。</p>
 
diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
index ce237a32d..d7d4b4b23 100644
--- a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
@@ -3,7 +3,7 @@
 你刚刚在 <%= @instance %> 创建了帐号。
 
 点击下面的链接来完成注册啦 : <br>
-<%= link_to '确认账户', confirmation_url(@resource, confirmation_token: @token) %>
+<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %>
 
 别忘了看看 <%= link_to 'terms and conditions', terms_url %>。
 
diff --git a/app/views/user_mailer/password_change.es.html.erb b/app/views/user_mailer/password_change.es.html.erb
new file mode 100644
index 000000000..0a9eb4c4c
--- /dev/null
+++ b/app/views/user_mailer/password_change.es.html.erb
@@ -0,0 +1,3 @@
+<p>¡Hola, <%= @resource.email %>!</p>
+
+<p>Te contactamos para notificarte que tu contraseña en <%= @instance %> ha sido modificada.</p>
\ No newline at end of file
diff --git a/app/views/user_mailer/password_change.es.text.erb b/app/views/user_mailer/password_change.es.text.erb
new file mode 100644
index 000000000..192faf9ad
--- /dev/null
+++ b/app/views/user_mailer/password_change.es.text.erb
@@ -0,0 +1,3 @@
+¡Hola, <%= @resource.email %>!
+
+Te contactamos para notificarte que tu contraseña en <%= @instance %> ha sido modificada.
\ No newline at end of file
diff --git a/app/views/user_mailer/password_change.oc.html.erb b/app/views/user_mailer/password_change.oc.html.erb
index a6b506f5e..094c221a8 100644
--- a/app/views/user_mailer/password_change.oc.html.erb
+++ b/app/views/user_mailer/password_change.oc.html.erb
@@ -1,3 +1,3 @@
-<p>Bonjorn <%= @resource.email %>&nbsp;!</p>
+<p>Bonjorn <%= @resource.email %> !</p>
 
-<p>Vos contactem per vos avisar que vòstre senhal per Mastodon es ben estat cambiat.</p>
+<p>Vos contactem per vos avisar qu’avèm ben cambiat vòstre senhal Mastodon.</p>
diff --git a/app/views/user_mailer/password_change.oc.text.erb b/app/views/user_mailer/password_change.oc.text.erb
index 1caebde69..9fe9116d9 100644
--- a/app/views/user_mailer/password_change.oc.text.erb
+++ b/app/views/user_mailer/password_change.oc.text.erb
@@ -1,3 +1,3 @@
-Bonjorn <%= @resource.email %>&nbsp;!
+Bonjorn <%= @resource.email %> !
 
-Vos contactem per vos avisar que vòstre senhal per Mastodon es ben estat cambiat.
+Vos contactem per vos avisar qu’avèm ben cambiat vòstre senhal Mastodon.
diff --git a/app/views/user_mailer/password_change.pt-BR.html.erb b/app/views/user_mailer/password_change.pt-BR.html.erb
new file mode 100644
index 000000000..5f707ba09
--- /dev/null
+++ b/app/views/user_mailer/password_change.pt-BR.html.erb
@@ -0,0 +1,3 @@
+<p>Olá, <%= @resource.email %>!</p>
+
+<p>Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.</p>
diff --git a/app/views/user_mailer/password_change.pt-BR.text.erb b/app/views/user_mailer/password_change.pt-BR.text.erb
new file mode 100644
index 000000000..d8b76648c
--- /dev/null
+++ b/app/views/user_mailer/password_change.pt-BR.text.erb
@@ -0,0 +1,3 @@
+Olá, <%= @resource.email %>!
+
+Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.
diff --git a/app/views/user_mailer/reset_password_instructions.es.html.erb b/app/views/user_mailer/reset_password_instructions.es.html.erb
new file mode 100644
index 000000000..4eeb6601d
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.es.html.erb
@@ -0,0 +1,8 @@
+<p>¡Hola, <%= @resource.email %>!</p>
+
+<p>Alguien pidió un enlace para cambiar tu contraseña en <%= @instance %>. Puedes hacer esto con el siguiente enlace.</p>
+
+<p><%= link_to 'Cambiar mi contraseña', edit_password_url(@resource, reset_password_token: @token) %></p>
+
+<p>Si no fuiste tú, por favor ignora este mensaje.</p>
+<p>Tu contraseña no cambiará hasta que ingreses al enlace y crees una nueva.</p>
diff --git a/app/views/user_mailer/reset_password_instructions.es.text.erb b/app/views/user_mailer/reset_password_instructions.es.text.erb
new file mode 100644
index 000000000..8abafcc99
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.es.text.erb
@@ -0,0 +1,8 @@
+¡Hola, <%= @resource.email %>!
+
+Alguien pidió un enlace para cambiar tu contraseña en <%= @instance %>. Puedes hacer esto con el siguiente enlace.
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+Si no fuiste tú, por favor ignora este mensaje.
+Tu contraseña no cambiará hasta que ingreses al enlace y crees una nueva.
diff --git a/app/views/user_mailer/reset_password_instructions.oc.html.erb b/app/views/user_mailer/reset_password_instructions.oc.html.erb
index acf2c0abd..6c775b3a1 100644
--- a/app/views/user_mailer/reset_password_instructions.oc.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.oc.html.erb
@@ -1,8 +1,8 @@
-<p>Bonjorn <%= @resource.email %>&nbsp;!</p>
+<p>Bonjorn <%= @resource.email %> !</p>
 
-<p>Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
+<p>Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
 
 <p><%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %></p>
 
 <p>S’avètz pas res demandat, fasquètz pas cas a aqueste corrièl.</p>
-<p>Vòstre senhal cambiarà pas se clicatz pas sul ligam e que ne causissètz pas un nòu.</p>
+<p>Vòstre senhal cambiarà pas se clicatz pas sul ligam e que ne causissètz pas un novèl.</p>
diff --git a/app/views/user_mailer/reset_password_instructions.oc.text.erb b/app/views/user_mailer/reset_password_instructions.oc.text.erb
index 211974cc2..26432d2df 100644
--- a/app/views/user_mailer/reset_password_instructions.oc.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.oc.text.erb
@@ -1,8 +1,8 @@
-Bonjorn <%= @resource.email %>&nbsp;!
+Bonjorn <%= @resource.email %> !
 
-Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
+Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
 
 <%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %>
 
 S’avètz pas res demandat, fasquètz pas cas a aqueste corrièl.
-Vòstre senhal cambiarà pas se clicatz pas sul ligam e que ne causissètz pas un nòu.
+Vòstre senhal cambiarà pas se clicatz pas sul ligam e que ne causissètz pas un novèl.
diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb
new file mode 100644
index 000000000..940438b7c
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb
@@ -0,0 +1,8 @@
+<p>Olá, <%= @resource.email %>!</p>
+
+<p>Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:</p>
+
+<p><%= link_to 'Mudar a minha senha', edit_password_url(@resource, reset_password_token: @token) %></p>
+
+<p>Se você não solicitou isso, por favor ignore este e-mail.</p>
+<p>A senha senha não será modificada até que você acesse o link acima e crie uma nova.</p>
diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb
new file mode 100644
index 000000000..f574fe08f
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb
@@ -0,0 +1,8 @@
+Olá, <%= @resource.email %>!
+
+Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+Se você não solicitou isso, por favor ignore este e-mail.
+A senha senha não será modificada até que você acesse o link acima e crie uma nova.
diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
index 245382b2c..51e3073f1 100644
--- a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
@@ -1,6 +1,6 @@
 <p><%= @resource.email %> ,嗨呀!!</p>
 
-<p>有人(但愿是你)请求更改你Mastodon账户的密码。如果是你的话,请点击下面的链接:</p>
+<p>有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:</p>
 
 <p><%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %></p>
 
diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
index 574a0bb2e..7df590f78 100644
--- a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
@@ -1,6 +1,6 @@
 <%= @resource.email %> ,嗨呀!!
 
-有人(但愿是你)请求更改你Mastodon账户的密码。如果是你的话,请点击下面的链接:
+有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:
 
 <%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %>