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/_domain_blocks.html.haml12
-rw-r--r--app/views/about/_logged_in.html.haml10
-rw-r--r--app/views/about/_login.html.haml22
-rw-r--r--app/views/about/_registration.html.haml37
-rw-r--r--app/views/about/more.html.haml95
-rw-r--r--app/views/about/show.html.haml79
-rw-r--r--app/views/about/terms.html.haml9
-rw-r--r--app/views/accounts/_bio.html.haml21
-rw-r--r--app/views/accounts/_header.html.haml43
-rw-r--r--app/views/accounts/_moved.html.haml20
-rw-r--r--app/views/accounts/show.html.haml77
-rw-r--r--app/views/admin/accounts/index.html.haml9
-rw-r--r--app/views/admin/announcements/edit.html.haml4
-rw-r--r--app/views/admin/announcements/new.html.haml6
-rw-r--r--app/views/admin/reports/_media_attachments.html.haml8
-rw-r--r--app/views/admin/reports/_status.html.haml11
-rw-r--r--app/views/admin/settings/about/show.html.haml30
-rw-r--r--app/views/admin/settings/appearance/show.html.haml31
-rw-r--r--app/views/admin/settings/branding/show.html.haml36
-rw-r--r--app/views/admin/settings/content_retention/show.html.haml19
-rw-r--r--app/views/admin/settings/discovery/show.html.haml40
-rw-r--r--app/views/admin/settings/edit.html.haml119
-rw-r--r--app/views/admin/settings/registrations/show.html.haml28
-rw-r--r--app/views/admin/settings/shared/_links.html.haml8
-rw-r--r--app/views/admin/status_edits/_status_edit.html.haml20
-rw-r--r--app/views/admin/statuses/show.html.haml61
-rw-r--r--app/views/admin/trends/links/_preview_card.html.haml4
-rw-r--r--app/views/admin/trends/links/index.html.haml2
-rw-r--r--app/views/admin/trends/statuses/_status.html.haml4
-rw-r--r--app/views/admin/trends/statuses/index.html.haml2
-rw-r--r--app/views/admin_mailer/_new_trending_links.text.erb8
-rw-r--r--app/views/admin_mailer/_new_trending_statuses.text.erb8
-rw-r--r--app/views/application/_sidebar.html.haml4
-rw-r--r--app/views/auth/challenges/new.html.haml2
-rw-r--r--app/views/auth/passwords/edit.html.haml4
-rw-r--r--app/views/auth/registrations/edit.html.haml13
-rw-r--r--app/views/auth/registrations/new.html.haml29
-rw-r--r--app/views/auth/registrations/rules.html.haml21
-rw-r--r--app/views/auth/sessions/new.html.haml2
-rw-r--r--app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml2
-rw-r--r--app/views/authorize_interactions/_post_follow_actions.html.haml2
-rw-r--r--app/views/directories/index.html.haml54
-rw-r--r--app/views/disputes/strikes/show.html.haml5
-rw-r--r--app/views/filters/statuses/index.html.haml3
-rw-r--r--app/views/follower_accounts/index.html.haml18
-rw-r--r--app/views/following_accounts/index.html.haml18
-rw-r--r--app/views/home/index.html.haml16
-rw-r--r--app/views/layouts/admin.html.haml14
-rwxr-xr-xapp/views/layouts/application.html.haml4
-rw-r--r--app/views/layouts/public.html.haml61
-rw-r--r--app/views/notification_mailer/_status.html.haml2
-rw-r--r--app/views/notification_mailer/_status.text.erb2
-rw-r--r--app/views/notification_mailer/digest.html.haml44
-rw-r--r--app/views/notification_mailer/digest.text.erb15
-rw-r--r--app/views/notification_mailer/favourite.html.haml2
-rw-r--r--app/views/notification_mailer/follow.html.haml2
-rw-r--r--app/views/notification_mailer/follow.text.erb2
-rw-r--r--app/views/notification_mailer/mention.html.haml2
-rw-r--r--app/views/notification_mailer/reblog.html.haml2
-rw-r--r--app/views/privacy/show.html.haml7
-rw-r--r--app/views/public_timelines/show.html.haml16
-rw-r--r--app/views/remote_follow/new.html.haml20
-rw-r--r--app/views/remote_interaction/new.html.haml24
-rw-r--r--app/views/settings/deletes/show.html.haml4
-rw-r--r--app/views/settings/featured_tags/index.html.haml2
-rw-r--r--app/views/settings/migration/redirects/new.html.haml2
-rw-r--r--app/views/settings/migrations/show.html.haml4
-rw-r--r--app/views/settings/preferences/notifications/show.html.haml4
-rw-r--r--app/views/settings/profiles/show.html.haml9
-rw-r--r--app/views/shared/_og.html.haml6
-rw-r--r--app/views/shared/_web_app.html.haml16
-rw-r--r--app/views/statuses/_detailed_status.html.haml8
-rw-r--r--app/views/statuses/_simple_status.html.haml6
-rw-r--r--app/views/statuses/show.html.haml9
-rw-r--r--app/views/tags/_og.html.haml6
-rw-r--r--app/views/tags/show.html.haml14
-rw-r--r--app/views/user_mailer/confirmation_instructions.html.haml2
-rw-r--r--app/views/user_mailer/confirmation_instructions.text.erb4
-rw-r--r--app/views/user_mailer/welcome.html.haml47
-rw-r--r--app/views/user_mailer/welcome.text.erb13
80 files changed, 439 insertions, 1012 deletions
diff --git a/app/views/about/_domain_blocks.html.haml b/app/views/about/_domain_blocks.html.haml
deleted file mode 100644
index 35a30f16e..000000000
--- a/app/views/about/_domain_blocks.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%table
-  %thead
-    %tr
-      %th= t('about.unavailable_content_description.domain')
-      %th= t('about.unavailable_content_description.reason')
-  %tbody
-    - domain_blocks.each do |domain_block|
-      %tr
-        %td.nowrap
-          %span{ title: "SHA-256: #{domain_block.domain_digest}" }= domain_block.public_domain
-        %td
-          = domain_block.public_comment if display_blocks_rationale?
diff --git a/app/views/about/_logged_in.html.haml b/app/views/about/_logged_in.html.haml
deleted file mode 100644
index e1bcfffb3..000000000
--- a/app/views/about/_logged_in.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-.simple_form
-  %p.lead= t('about.logged_in_as_html', username: content_tag(:strong, current_account.username))
-
-  .actions
-    = link_to t('about.continue_to_web'), root_url, class: 'button button-primary'
-
-.form-footer
-  %ul.no-list
-    %li= link_to t('about.get_apps'), 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer'
-    %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete }
diff --git a/app/views/about/_login.html.haml b/app/views/about/_login.html.haml
deleted file mode 100644
index 0f19e8164..000000000
--- a/app/views/about/_login.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-- unless omniauth_only?
-  = simple_form_for(new_user, url: user_session_path, namespace: 'login') do |f|
-    .fields-group
-      - if use_seamless_external_login?
-        = f.input :email, placeholder: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }, hint: false
-      - else
-        = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
-
-      = f.input :password, placeholder: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }, hint: false
-
-    .actions
-      = f.button :button, t('auth.login'), type: :submit, class: 'button button-primary'
-
-    %p.hint.subtle-hint= link_to t('auth.trouble_logging_in'), new_user_password_path
-
-- if Devise.mappings[:user].omniauthable? and User.omniauth_providers.any?
-  .simple_form.alternative-login
-    %h4= omniauth_only? ? t('auth.log_in_with') : t('auth.or_log_in_with')
-
-    .actions
-      - User.omniauth_providers.each do |provider|
-        = provider_sign_in_link(provider)
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
deleted file mode 100644
index 5db620b2d..000000000
--- a/app/views/about/_registration.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-- disabled = closed_registrations? || omniauth_only? || current_account.present?
-- show_message = disabled && (current_user.present? || @instance_presenter.closed_registrations_message.present?)
-
-.simple_form__overlay-area{ class: show_message ? 'simple_form__overlay-area__blurred' : '' }
-  = simple_form_for(new_user, url: user_registration_path, namespace: 'registration', html: { novalidate: false }) do |f|
-    %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
-
-    .fields-group
-      = f.simple_fields_for :account do |account_fields|
-        = account_fields.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: disabled
-
-      = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: disabled
-      = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: disabled
-      = 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 => 'new-password' }, hint: false, disabled: disabled
-
-      = f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false, disabled: disabled
-      = f.input :website, as: :url, placeholder: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }, hint: false, disabled: disabled
-
-    - if approved_registrations?
-      .fields-group
-        = f.simple_fields_for :invite_request do |invite_request_fields|
-          = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text
-
-    .fields-group
-      = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true, disabled: disabled
-
-    .actions
-      = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: disabled
-
-  - if show_message
-    .simple_form__overlay-area__overlay
-      .simple_form__overlay-area__overlay__content.rich-formatting
-        .block-icon= fa_icon 'warning'
-        - if current_account.present?
-          = t('about.logout_before_registering')
-        - else
-          = @instance_presenter.closed_registrations_message.html_safe
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
deleted file mode 100644
index 0b75f159a..000000000
--- a/app/views/about/more.html.haml
+++ /dev/null
@@ -1,95 +0,0 @@
-- content_for :page_title do
-  = site_hostname
-
-- content_for :header_tags do
-  = render partial: 'shared/og'
-
-.grid-4
-  .column-0
-    .public-account-header.public-account-header--no-bar
-      .public-account-header__image
-        = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.png'), alt: @instance_presenter.site_title, class: 'parallax'
-
-  .column-1
-    .landing-page__call-to-action{ dir: 'ltr' }
-      .row
-        .row__information-board
-          .information-board__section
-            %span= t 'about.user_count_before'
-            %strong= friendly_number_to_human @instance_presenter.user_count
-            %span= t 'about.user_count_after', count: @instance_presenter.user_count
-          .information-board__section
-            %span= t 'about.status_count_before'
-            %strong= friendly_number_to_human @instance_presenter.status_count
-            %span= t 'about.status_count_after', count: @instance_presenter.status_count
-        .row__mascot
-          .landing-page__mascot
-            = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
-
-  .column-2
-    .contact-widget
-      %h4= t 'about.administered_by'
-
-      = account_link_to(@instance_presenter.contact_account)
-
-      - if @instance_presenter.site_contact_email.present?
-        %h4
-          = succeed ':' do
-            = t 'about.contact'
-
-        = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
-
-  .column-3
-    = render 'application/flashes'
-
-    - if @contents.blank? && @rules.empty? && (!display_blocks? || @blocks&.empty?)
-      = nothing_here
-    - else
-      .box-widget
-        .rich-formatting
-          - unless @rules.empty?
-            %h2#rules= t('about.rules')
-
-            %p= t('about.rules_html')
-
-            %ol.rules-list
-              - @rules.each do |rule|
-                %li
-                  .rules-list__text= rule.text
-
-          = @contents.html_safe
-
-          - if display_blocks? && !@blocks.empty?
-            %h2#unavailable-content= t('about.unavailable_content')
-
-            %p= t('about.unavailable_content_html')
-
-            - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty?
-              %h3= t('about.unavailable_content_description.rejecting_media_title')
-              %p= t('about.unavailable_content_description.rejecting_media')
-              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
-            - if (blocks = @blocks.select(&:silence?)) && !blocks.empty?
-              %h3= t('about.unavailable_content_description.silenced_title')
-              %p= t('about.unavailable_content_description.silenced')
-              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
-            - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty?
-              %h3= t('about.unavailable_content_description.suspended_title')
-              %p= t('about.unavailable_content_description.suspended')
-              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
-
-  .column-4
-    %ul.table-of-contents
-      - unless @rules.empty?
-        %li= link_to t('about.rules'), '#rules'
-
-      - @table_of_contents.each do |item|
-        %li
-          = link_to item.title, "##{item.anchor}"
-
-          - unless item.children.empty?
-            %ul
-              - item.children.each do |sub_item|
-                %li= link_to sub_item.title, "##{sub_item.anchor}"
-
-      - if display_blocks? && !@blocks.empty?
-        %li= link_to t('about.unavailable_content'), '#unavailable-content'
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index fb292941b..05d8989ad 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -1,82 +1,7 @@
 - content_for :page_title do
-  = site_hostname
+  = t('about.title')
 
 - content_for :header_tags do
-  %link{ rel: 'canonical', href: about_url }/
   = render partial: 'shared/og'
 
-.landing
-  .landing__brand
-    = link_to root_url, class: 'brand' do
-      = logo_as_symbol(:wordmark)
-      %span.brand__tagline=t 'about.tagline'
-
-  .landing__grid
-    .landing__grid__column.landing__grid__column-registration
-      .box-widget
-        = render 'registration'
-
-      .directory
-        - if Setting.profile_directory
-          .directory__tag
-            = optional_link_to Setting.profile_directory, explore_path do
-              %h4
-                = fa_icon 'address-book fw'
-                = t('about.discover_users')
-                %small= t('about.browse_directory')
-
-              .avatar-stack
-                - @instance_presenter.sample_accounts.each do |account|
-                  = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, alt: '', class: 'account__avatar'
-
-        - if Setting.timeline_preview
-          .directory__tag
-            = optional_link_to Setting.timeline_preview, public_timeline_path do
-              %h4
-                = fa_icon 'globe fw'
-                = t('about.see_whats_happening')
-                %small= t('about.browse_public_posts')
-
-        .directory__tag
-          = link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
-            %h4
-              = fa_icon 'tablet fw'
-              = t('about.get_apps')
-              %small= t('about.apps_platforms')
-
-    .landing__grid__column.landing__grid__column-login
-      .box-widget
-        - if current_user.present?
-          = render 'logged_in'
-        - else
-          = render 'login'
-
-      .hero-widget
-        .hero-widget__img
-          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.png'), alt: @instance_presenter.site_title
-
-        .hero-widget__text
-          %p
-            = @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
-            = link_to about_more_path do
-              = t('about.learn_more')
-              = fa_icon 'angle-double-right'
-
-        .hero-widget__footer
-          .hero-widget__footer__column
-            %h4= t 'about.administered_by'
-
-            = account_link_to @instance_presenter.contact_account
-
-          .hero-widget__footer__column
-            %h4= t 'about.server_stats'
-
-            .hero-widget__counters__wrapper
-              .hero-widget__counter
-                %strong= friendly_number_to_human @instance_presenter.user_count
-                %span= t 'about.user_count_after', count: @instance_presenter.user_count
-              .hero-widget__counter
-                %strong= friendly_number_to_human @instance_presenter.active_user_count
-                %span
-                  = t 'about.active_count_after'
-                  %abbr{ title: t('about.active_footnote') } *
+= render partial: 'shared/web_app'
diff --git a/app/views/about/terms.html.haml b/app/views/about/terms.html.haml
deleted file mode 100644
index 9d076a91b..000000000
--- a/app/views/about/terms.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- content_for :page_title do
-  = t('terms.title', instance: site_hostname)
-
-.grid
-  .column-0
-    .box-widget
-      .rich-formatting= @instance_presenter.site_terms.html_safe.presence || t('terms.body_html')
-  .column-1
-    = render 'application/sidebar'
diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml
deleted file mode 100644
index e2539b1d4..000000000
--- a/app/views/accounts/_bio.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-- fields = account.fields
-
-.public-account-bio
-  - unless fields.empty?
-    .account__header__fields
-      - fields.each do |field|
-        %dl
-          %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
-          %dd{ title: field.value, class: custom_field_classes(field) }
-            - if field.verified?
-              %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
-                = fa_icon 'check'
-            = prerender_custom_emojis(account_field_value_format(field), account.emojis)
-
-  = account_badge(account)
-
-  - if account.note.present?
-    .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
-
-  .public-account-bio__extra
-    = t 'accounts.joined', date: l(account.created_at, format: :month)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
deleted file mode 100644
index d583edbd2..000000000
--- a/app/views/accounts/_header.html.haml
+++ /dev/null
@@ -1,43 +0,0 @@
-.public-account-header{:class => ("inactive" if account.moved?)}
-  .public-account-header__image
-    = image_tag (prefers_autoplay? ? account.header_original_url : account.header_static_url), class: 'parallax'
-  .public-account-header__bar
-    = link_to short_account_url(account), class: 'avatar' do
-      = image_tag (prefers_autoplay? ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: { original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: prefers_autoplay? }
-    .public-account-header__tabs
-      .public-account-header__tabs__name
-        %h1
-          = display_name(account, custom_emojify: true)
-          %small
-            = acct(account)
-            = fa_icon('lock') if account.locked?
-      .public-account-header__tabs__tabs
-        .details-counters
-          .counter{ class: active_nav_class(short_account_url(account), short_account_with_replies_url(account), short_account_media_url(account)) }
-            = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do
-              %span.counter-number= friendly_number_to_human account.statuses_count
-              %span.counter-label= t('accounts.posts', count: account.statuses_count)
-
-          .counter{ class: active_nav_class(account_following_index_url(account)) }
-            = link_to account_following_index_url(account), title: number_with_delimiter(account.following_count) do
-              %span.counter-number= friendly_number_to_human account.following_count
-              %span.counter-label= t('accounts.following', count: account.following_count)
-
-          .counter{ class: active_nav_class(account_followers_url(account)) }
-            = link_to account_followers_url(account), title: hide_followers_count?(account) ? nil : number_with_delimiter(account.followers_count) do
-              %span.counter-number= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
-              %span.counter-label= t('accounts.followers', count: account.followers_count)
-        .spacer
-        .public-account-header__tabs__tabs__buttons
-          = account_action_button(account)
-
-    .public-account-header__extra
-      = render 'accounts/bio', account: account
-
-      .public-account-header__extra__links
-        = link_to account_following_index_url(account) do
-          %strong= friendly_number_to_human account.following_count
-          = t('accounts.following', count: account.following_count)
-        = link_to account_followers_url(account) do
-          %strong= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
-          = t('accounts.followers', count: account.followers_count)
diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml
deleted file mode 100644
index 2f46e0dd0..000000000
--- a/app/views/accounts/_moved.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- moved_to_account = account.moved_to_account
-
-.moved-account-widget
-  .moved-account-widget__message
-    = fa_icon 'suitcase'
-    = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.pretty_acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
-
-  .moved-account-widget__card
-    = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
-      .detailed-status__display-avatar
-        .account__avatar-overlay
-          .account__avatar-overlay-base
-            = image_tag moved_to_account.avatar_static_url
-          .account__avatar-overlay-overlay
-            = image_tag account.avatar_static_url
-
-      %span.display-name
-        %bdi
-          %strong.emojify= display_name(moved_to_account, custom_emojify: true)
-        %span @#{moved_to_account.pretty_acct}
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 7fa688bd3..e8fd27e10 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -2,85 +2,16 @@
   = "#{display_name(@account)} (#{acct(@account)})"
 
 - content_for :header_tags do
-  - if @account.user&.setting_noindex
+  - if @account.user_prefers_noindex?
     %meta{ name: 'robots', content: 'noindex, noarchive' }/
 
   %link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/
   %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
 
-  - if @older_url
-    %link{ rel: 'next', href: @older_url }/
-  - if @newer_url
-    %link{ rel: 'prev', href: @newer_url }/
+  - @account.fields.select(&:verifiable?).each do |field|
+    %link{ rel: 'me', type: 'text/html', href: field.value }/
 
   = opengraph 'og:type', 'profile'
   = render 'og', account: @account, url: short_account_url(@account, only_path: false)
 
-
-= render 'header', account: @account, with_bio: true
-
-.grid
-  .column-0
-    .h-feed
-      %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
-
-      .account__section-headline
-        = active_link_to t('accounts.posts_tab_heading'), 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 user_signed_in? && @account.blocking?(current_account)
-        .nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
-      - elsif @statuses.empty?
-        = nothing_here 'nothing-here--under-tabs'
-      - else
-        .activity-stream.activity-stream--under-tabs
-          - if params[:page].to_i.zero?
-            = render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
-
-          - if @newer_url
-            .entry= link_to_newer @newer_url
-
-          = render partial: 'statuses/status', collection: @statuses, as: :status
-
-          - if @older_url
-            .entry= link_to_older @older_url
-
-  .column-1
-    - if @account.memorial?
-      .memoriam-widget= t('in_memoriam_html')
-    - elsif @account.moved?
-      = render 'moved', account: @account
-
-    = render 'bio', account: @account
-
-    - if @endorsed_accounts.empty? && @account.id == current_account&.id
-      .placeholder-widget= t('accounts.endorsements_hint')
-    - elsif !@endorsed_accounts.empty?
-      .endorsements-widget
-        %h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true))
-
-        - @endorsed_accounts.each do |account|
-          = account_link_to account
-
-    - if @featured_hashtags.empty? && @account.id == current_account&.id
-      .placeholder-widget
-        = t('accounts.featured_tags_hint')
-        = link_to settings_featured_tags_path do
-          = t('featured_tags.add_new')
-          = fa_icon 'chevron-right fw'
-    - else
-      - @featured_hashtags.each do |featured_tag|
-        .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
-          = link_to short_account_tag_path(@account, featured_tag.tag) do
-            %h4
-              = fa_icon 'hashtag'
-              = featured_tag.display_name
-              %small
-                - if featured_tag.last_status_at.nil?
-                  = t('accounts.nothing_here')
-                - else
-                  %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
-            .trends__item__current= friendly_number_to_human featured_tag.statuses_count
-
-    = render 'application/sidebar'
+= render partial: 'shared/web_app'
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index e0879fcb6..9571f27b4 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -34,6 +34,7 @@
 
 = form_for(@form, url: batch_admin_accounts_path) do |f|
   = hidden_field_tag :page, params[:page] || 1
+  = hidden_field_tag :select_all_matching, '0'
 
   - AccountFilter::KEYS.each do |key|
     = hidden_field_tag key, params[key] if params[key].present?
@@ -49,6 +50,14 @@
           = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
         = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), name: :suspend, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+    - if @accounts.total_count > @accounts.size
+      .batch-table__select-all
+        .not-selected.active
+          %span= t('generic.all_items_on_page_selected_html', count: @accounts.size)
+          %button{ type: 'button' }= t('generic.select_all_matching_items', count: @accounts.total_count)
+        .selected
+          %span= t('generic.all_matching_items_selected_html', count: @accounts.total_count)
+          %button{ type: 'button' }= t('generic.deselect')
     .batch-table__body
       - if @accounts.empty?
         = nothing_here 'nothing-here--under-tabs'
diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml
index 5f56db5e7..0f9727014 100644
--- a/app/views/admin/announcements/edit.html.haml
+++ b/app/views/admin/announcements/edit.html.haml
@@ -5,8 +5,8 @@
   = render 'shared/error_messages', object: @announcement
 
   .fields-group
-    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
-    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .fields-group
     = f.input :all_day, as: :boolean, wrapper: :with_label
diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml
index a5298c5f6..b2f0c01ec 100644
--- a/app/views/admin/announcements/new.html.haml
+++ b/app/views/admin/announcements/new.html.haml
@@ -5,8 +5,8 @@
   = render 'shared/error_messages', object: @announcement
 
   .fields-group
-    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
-    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .fields-group
     = f.input :all_day, as: :boolean, wrapper: :with_label
@@ -15,7 +15,7 @@
     = f.input :text, wrapper: :with_block_label
 
   .fields-group
-    = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label
+    = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .actions
     = f.button :button, t('.create'), type: :submit
diff --git a/app/views/admin/reports/_media_attachments.html.haml b/app/views/admin/reports/_media_attachments.html.haml
new file mode 100644
index 000000000..d0b7d52c3
--- /dev/null
+++ b/app/views/admin/reports/_media_attachments.html.haml
@@ -0,0 +1,8 @@
+- if status.ordered_media_attachments.first.video?
+  - video = status.ordered_media_attachments.first
+  = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json
+- elsif status.ordered_media_attachments.first.audio?
+  - audio = status.ordered_media_attachments.first
+  = react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, duration: audio.file.meta.dig(:original, :duration)
+- else
+  = react_component :media_gallery, height: 343, sensitive: status.sensitive?, visible: false, media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index 392fc8f81..b2982a42b 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -12,14 +12,7 @@
           = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
 
     - unless status.proper.ordered_media_attachments.empty?
-      - if status.proper.ordered_media_attachments.first.video?
-        - video = status.proper.ordered_media_attachments.first
-        = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.proper.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json
-      - elsif status.proper.ordered_media_attachments.first.audio?
-        - audio = status.proper.ordered_media_attachments.first
-        = react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, duration: audio.file.meta.dig(:original, :duration)
-      - else
-        = react_component :media_gallery, height: 343, sensitive: status.proper.sensitive?, visible: false, media: status.proper.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
+      = render partial: 'admin/reports/media_attachments', locals: { status: status.proper }
 
     .detailed-status__meta
       - if status.application
@@ -29,7 +22,7 @@
         %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
       - if status.edited?
         ·
-        = t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted'))
+        = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')), admin_account_status_path(status.account_id, status), class: 'detailed-status__datetime'
       - if status.discarded?
         ·
         %span.negative-hint= t('admin.statuses.deleted')
diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml
new file mode 100644
index 000000000..6ee719e36
--- /dev/null
+++ b/app/views/admin/settings/about/show.html.haml
@@ -0,0 +1,30 @@
+- content_for :page_title do
+  = t('admin.settings.about.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_about_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.about.preamble')
+
+  .fields-group
+    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+    %p.hint
+      = t 'admin.settings.about.rules_hint'
+      = link_to t('admin.settings.about.manage_rules'), admin_rules_path
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
+  .fields-group
+    = f.input :site_terms, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml
new file mode 100644
index 000000000..f02ecc105
--- /dev/null
+++ b/app/views/admin/settings/appearance/show.html.haml
@@ -0,0 +1,31 @@
+- content_for :page_title do
+  = t('admin.settings.appearance.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_appearance_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.appearance.preamble')
+
+  .fields-group
+    = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, label: t('admin.settings.flavour_and_skin.title'), include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last
+
+  .fields-group
+    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :mascot, as: :file, wrapper: :with_block_label
+
+    .fields-row__column.fields-row__column-6.fields-group
+      - if @admin_settings.mascot.persisted?
+        = image_tag @admin_settings.mascot.file.url, class: 'fields-group__thumbnail'
+        = link_to admin_site_upload_path(@admin_settings.mascot), data: { method: :delete }, class: 'link-button link-button--destructive' do
+          = fa_icon 'trash fw'
+          = t('admin.site_uploads.delete')
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml
new file mode 100644
index 000000000..aee730689
--- /dev/null
+++ b/app/views/admin/settings/branding/show.html.haml
@@ -0,0 +1,36 @@
+- content_for :page_title do
+  = t('admin.settings.branding.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.branding.preamble')
+
+  .fields-group
+    = f.input :site_title, wrapper: :with_label
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :site_contact_username, wrapper: :with_label
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :site_contact_email, wrapper: :with_label
+
+  .fields-group
+    = f.input :site_short_description, wrapper: :with_block_label, as: :text, input_html: { rows: 2, maxlength: 200 }
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :thumbnail, as: :file, wrapper: :with_block_label
+    .fields-row__column.fields-row__column-6.fields-group
+      - if @admin_settings.thumbnail.persisted?
+        = image_tag @admin_settings.thumbnail.file.url(:'@1x'), class: 'fields-group__thumbnail'
+        = link_to admin_site_upload_path(@admin_settings.thumbnail), data: { method: :delete }, class: 'link-button link-button--destructive' do
+          = fa_icon 'trash fw'
+          = t('admin.site_uploads.delete')
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml
new file mode 100644
index 000000000..b9467572a
--- /dev/null
+++ b/app/views/admin/settings/content_retention/show.html.haml
@@ -0,0 +1,19 @@
+- content_for :page_title do
+  = t('admin.settings.content_retention.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_content_retention_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.content_retention.preamble')
+
+  .fields-group
+    = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+    = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+    = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml
new file mode 100644
index 000000000..9b6424c79
--- /dev/null
+++ b/app/views/admin/settings/discovery/show.html.haml
@@ -0,0 +1,40 @@
+- content_for :page_title do
+  = t('admin.settings.discovery.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_discovery_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.discovery.preamble')
+
+  %h4= t('admin.settings.discovery.trends')
+
+  .fields-group
+    = f.input :trends, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, recommended: :not_recommended
+
+  .fields-group
+    = f.input :trending_status_cw, as: :boolean, wrapper: :with_label, label: t('admin.settings.trending_status_cw.title'), hint: t('admin.settings.trending_status_cw.desc_html')
+
+  %h4= t('admin.settings.discovery.public_timelines')
+
+  .fields-group
+    = f.input :timeline_preview, as: :boolean, wrapper: :with_label
+
+  %h4= t('admin.settings.discovery.follow_recommendations')
+
+  .fields-group
+    = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label
+
+  %h4= t('admin.settings.discovery.profile_directory')
+
+  .fields-group
+    = f.input :profile_directory, as: :boolean, wrapper: :with_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
deleted file mode 100644
index 98af7e718..000000000
--- a/app/views/admin/settings/edit.html.haml
+++ /dev/null
@@ -1,119 +0,0 @@
-- content_for :page_title do
-  = t('admin.settings.title')
-
-  - content_for :heading_actions do
-    = button_tag t('generic.save_changes'), class: 'button', form: 'edit_admin'
-
-= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch, id: 'edit_admin' } do |f|
-  = render 'shared/error_messages', object: @admin_settings
-
-  .fields-group
-    = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, label: t('admin.settings.flavour_and_skin.title'), include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :site_contact_username, wrapper: :with_label, label: t('admin.settings.contact_information.username')
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
-
-  .fields-group
-    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
-
-  .fields-group
-    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: site_upload_delete_hint(t('admin.settings.thumbnail.desc_html'), :thumbnail)
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :hero, as: :file, wrapper: :with_block_label, label: t('admin.settings.hero.title'), hint: site_upload_delete_hint(t('admin.settings.hero.desc_html'), :hero)
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :mascot, as: :file, wrapper: :with_block_label, label: t('admin.settings.mascot.title'), hint: site_upload_delete_hint(t('admin.settings.mascot.desc_html'), :mascot)
-
-  %hr.spacer/
-
-  .fields-group
-    = f.input :require_invite_text, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.require_invite_text.title'), hint: t('admin.settings.registrations.require_invite_text.desc_html'), disabled: !approved_registrations?
-
-  - if captcha_available?
-    .fields-group
-      = f.input :captcha_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.captcha_enabled.title'), hint: t('admin.settings.captcha_enabled.desc_html')
-
-  %hr.spacer/
-
-  .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')
-
-  %hr.spacer/
-
-  - unless whitelist_mode?
-    .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
-      = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
-
-  .fields-group
-    = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
-
-  - unless whitelist_mode?
-    .fields-group
-      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true
-
-    .fields-group
-      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html'), recommended: true
-
-    .fields-group
-      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
-
-    .fields-group
-      = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
-
-    .fields-group
-      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
-
-    .fields-group
-      = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
-
-    .fields-group
-      = f.input :trending_status_cw, as: :boolean, wrapper: :with_label, label: t('admin.settings.trending_status_cw.title'), hint: t('admin.settings.trending_status_cw.desc_html')
-
-    .fields-group
-      = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
-
-  .fields-group
-    = f.input :hide_followers_count, as: :boolean, wrapper: :with_label, label: t('admin.settings.hide_followers_count.title'), hint: t('admin.settings.hide_followers_count.desc_html')
-
-  .fields-group
-    = f.input :show_reblogs_in_public_timelines, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_reblogs_in_public_timelines.title'), hint: t('admin.settings.show_reblogs_in_public_timelines.desc_html')
-
-  .fields-group
-    = f.input :show_replies_in_public_timelines, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_replies_in_public_timelines.title'), hint: t('admin.settings.show_replies_in_public_timelines.desc_html')
-
-  %hr.spacer/
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-
-  .fields-group
-    = f.input :outgoing_spoilers, wrapper: :with_label, label: t('admin.settings.outgoing_spoilers.title'), hint: t('admin.settings.outgoing_spoilers.desc_html')
-
-  .fields-group
-    = 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 } unless whitelist_mode?
-    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.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 }
-    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
-
-  .actions
-    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml
new file mode 100644
index 000000000..f5e448125
--- /dev/null
+++ b/app/views/admin/settings/registrations/show.html.haml
@@ -0,0 +1,28 @@
+- content_for :page_title do
+  = t('admin.settings.registrations.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.registrations.preamble')
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
+
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations?
+
+  - if captcha_available?
+    .fields-group
+      = f.input :captcha_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.captcha_enabled.title'), hint: t('admin.settings.captcha_enabled.desc_html')
+
+  .fields-group
+    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml
new file mode 100644
index 000000000..1294c26ce
--- /dev/null
+++ b/app/views/admin/settings/shared/_links.html.haml
@@ -0,0 +1,8 @@
+.content__heading__tabs
+  = render_navigation renderer: :links do |primary|
+    - primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path
+    - primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path
+    - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path
+    - primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path
+    - primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path
+    - primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path
diff --git a/app/views/admin/status_edits/_status_edit.html.haml b/app/views/admin/status_edits/_status_edit.html.haml
new file mode 100644
index 000000000..19a0e063d
--- /dev/null
+++ b/app/views/admin/status_edits/_status_edit.html.haml
@@ -0,0 +1,20 @@
+.status
+  .status__content><
+    - if status_edit.spoiler_text.blank?
+      = prerender_custom_emojis(status_content_format(status_edit), status_edit.emojis)
+    - else
+      %details<
+        %summary><
+          %strong> Content warning: #{prerender_custom_emojis(h(status_edit.spoiler_text), status_edit.emojis)}
+        = prerender_custom_emojis(status_content_format(status_edit), status_edit.emojis)
+
+  - unless status_edit.ordered_media_attachments.empty?
+    = render partial: 'admin/reports/media_attachments', locals: { status: status_edit }
+
+  .detailed-status__meta
+    %time.formatted{ datetime: status_edit.created_at.iso8601, title: l(status_edit.created_at) }= l(status_edit.created_at)
+
+    - if status_edit.sensitive?
+      ·
+      = fa_icon('eye-slash fw')
+      = t('stream_entries.sensitive_content')
diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml
new file mode 100644
index 000000000..1e1e63f37
--- /dev/null
+++ b/app/views/admin/statuses/show.html.haml
@@ -0,0 +1,61 @@
+- content_for :page_title do
+  = t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false))
+
+- content_for :heading_actions do
+  = link_to t('admin.statuses.open'), ActivityPub::TagManager.instance.url_for(@status), class: 'button', target: '_blank'
+
+%h3= t('admin.statuses.metadata')
+
+.table-wrapper
+  %table.table.horizontal-table
+    %tbody
+      %tr
+        %th= t('admin.statuses.account')
+        %td= admin_account_link_to @status.account
+      - if @status.reply?
+        %tr
+          %th= t('admin.statuses.in_reply_to')
+          %td= admin_account_link_to @status.in_reply_to_account, path: admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id)
+      %tr
+        %th= t('admin.statuses.application')
+        %td= @status.application&.name
+      %tr
+        %th= t('admin.statuses.language')
+        %td= standard_locale_name(@status.language)
+      %tr
+        %th= t('admin.statuses.visibility')
+        %td= t("statuses.visibilities.#{@status.visibility}")
+      - if @status.trend
+        %tr
+          %th= t('admin.statuses.trending')
+          %td
+            - if @status.trend.allowed?
+              %abbr{ title: t('admin.trends.tags.current_score', score: @status.trend.score) }= t('admin.trends.tags.trending_rank', rank: @status.trend.rank)
+            - elsif @status.trend.requires_review?
+              = t('admin.trends.pending_review')
+            - else
+              = t('admin.trends.not_allowed_to_trend')
+      %tr
+        %th= t('admin.statuses.reblogs')
+        %td= friendly_number_to_human @status.reblogs_count
+      %tr
+        %th= t('admin.statuses.favourites')
+        %td= friendly_number_to_human @status.favourites_count
+
+%hr.spacer/
+
+%h3= t('admin.statuses.history')
+
+%ol.history
+  - @status.edits.includes(:account, status: [:account]).each.with_index do |status_edit, i|
+    %li
+      .history__entry
+        %h5
+          - if i.zero?
+            = t('admin.statuses.original_status')
+          - else
+            = t('admin.statuses.status_changed')
+          ·
+          %time.formatted{ datetime: status_edit.created_at.iso8601, title: l(status_edit.created_at) }= l(status_edit.created_at)
+
+        = render status_edit
diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml
index 7d4897c7e..8812feb31 100644
--- a/app/views/admin/trends/links/_preview_card.html.haml
+++ b/app/views/admin/trends/links/_preview_card.html.haml
@@ -18,9 +18,9 @@
 
       = t('admin.trends.links.shared_by_over_week', count: preview_card.history.reduce(0) { |sum, day| sum + day.accounts })
 
-      - if preview_card.trendable? && (rank = Trends.links.rank(preview_card.id, locale: params[:locale].presence))
+      - if preview_card.trend.allowed?

-        %abbr{ title: t('admin.trends.tags.current_score', score: Trends.links.score(preview_card.id, locale: params[:locale].presence)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
+        %abbr{ title: t('admin.trends.tags.current_score', score: preview_card.trend.score) }= t('admin.trends.tags.trending_rank', rank: preview_card.trend.rank)
 
         - if preview_card.decaying?

diff --git a/app/views/admin/trends/links/index.html.haml b/app/views/admin/trends/links/index.html.haml
index 6f090df7b..e6ed9d95f 100644
--- a/app/views/admin/trends/links/index.html.haml
+++ b/app/views/admin/trends/links/index.html.haml
@@ -13,7 +13,7 @@
     .filter-subset.filter-subset--with-select
       %strong= t('admin.follow_recommendations.language')
       .input.select.optional
-        = select_tag :locale, options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
+        = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
     .filter-subset
       %strong= t('admin.trends.trending')
       %ul
diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml
index e4d75bbb9..f35e13d12 100644
--- a/app/views/admin/trends/statuses/_status.html.haml
+++ b/app/views/admin/trends/statuses/_status.html.haml
@@ -25,9 +25,9 @@
     - if status.trendable? && !status.account.discoverable?

       = t('admin.trends.statuses.not_discoverable')
-    - if status.trendable? && (rank = Trends.statuses.rank(status.id, locale: params[:locale].presence))
+    - if status.trend.allowed?

-      %abbr{ title: t('admin.trends.tags.current_score', score: Trends.statuses.score(status.id, locale: params[:locale].presence)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
+      %abbr{ title: t('admin.trends.tags.current_score', score: status.trend.score) }= t('admin.trends.tags.trending_rank', rank: status.trend.rank)
     - elsif status.requires_review?

       = t('admin.trends.pending_review')
diff --git a/app/views/admin/trends/statuses/index.html.haml b/app/views/admin/trends/statuses/index.html.haml
index c96f4323a..bf04772f2 100644
--- a/app/views/admin/trends/statuses/index.html.haml
+++ b/app/views/admin/trends/statuses/index.html.haml
@@ -13,7 +13,7 @@
     .filter-subset.filter-subset--with-select
       %strong= t('admin.follow_recommendations.language')
       .input.select.optional
-        = select_tag :locale, options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key]}, params[:locale]), include_blank: true
+        = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
     .filter-subset
       %strong= t('admin.trends.trending')
       %ul
diff --git a/app/views/admin_mailer/_new_trending_links.text.erb b/app/views/admin_mailer/_new_trending_links.text.erb
index 405926fdd..602e12793 100644
--- a/app/views/admin_mailer/_new_trending_links.text.erb
+++ b/app/views/admin_mailer/_new_trending_links.text.erb
@@ -2,13 +2,7 @@
 
 <% @links.each do |link| %>
 - <%= link.title %> • <%= link.url %>
-  <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: Trends.links.score(link.id).round(2)) %>
-<% end %>
-
-<% if @lowest_trending_link %>
-<%= raw t('admin_mailer.new_trends.new_trending_links.requirements', lowest_link_title: @lowest_trending_link.title, lowest_link_score: Trends.links.score(@lowest_trending_link.id).round(2), rank: Trends.links.options[:review_threshold]) %>
-<% else %>
-<%= raw t('admin_mailer.new_trends.new_trending_links.no_approved_links') %>
+  <%= standard_locale_name(link.language) %> • <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %>
 <% end %>
 
 <%= raw t('application_mailer.view')%> <%= admin_trends_links_url %>
diff --git a/app/views/admin_mailer/_new_trending_statuses.text.erb b/app/views/admin_mailer/_new_trending_statuses.text.erb
index 8d11a80c2..1ed3ae857 100644
--- a/app/views/admin_mailer/_new_trending_statuses.text.erb
+++ b/app/views/admin_mailer/_new_trending_statuses.text.erb
@@ -2,13 +2,7 @@
 
 <% @statuses.each do |status| %>
 - <%= ActivityPub::TagManager.instance.url_for(status) %>
-  <%= raw t('admin.trends.tags.current_score', score: Trends.statuses.score(status.id).round(2)) %>
-<% end %>
-
-<% if @lowest_trending_status %>
-<%= raw t('admin_mailer.new_trends.new_trending_statuses.requirements', lowest_status_url: ActivityPub::TagManager.instance.url_for(@lowest_trending_status), lowest_status_score: Trends.statuses.score(@lowest_trending_status.id).round(2), rank: Trends.statuses.options[:review_threshold]) %>
-<% else %>
-<%= raw t('admin_mailer.new_trends.new_trending_statuses.no_approved_statuses') %>
+  <%= standard_locale_name(status.language) %> • <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %>
 <% end %>
 
 <%= raw t('application_mailer.view')%> <%= admin_trends_statuses_url %>
diff --git a/app/views/application/_sidebar.html.haml b/app/views/application/_sidebar.html.haml
index cc157bf47..6d18668b0 100644
--- a/app/views/application/_sidebar.html.haml
+++ b/app/views/application/_sidebar.html.haml
@@ -1,9 +1,9 @@
 .hero-widget
   .hero-widget__img
-    = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.png'), alt: @instance_presenter.site_title
+    = image_tag @instance_presenter.thumbnail&.file&.url(:'@1x') || asset_pack_path('media/images/preview.png'), alt: @instance_presenter.title
 
   .hero-widget__text
-    %p= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
+    %p= @instance_presenter.description.html_safe.presence || t('about.about_mastodon_html')
 
 - if Setting.trends && !(user_signed_in? && !current_user.setting_trends)
   - trends = Trends.tags.query.allowed.limit(3)
diff --git a/app/views/auth/challenges/new.html.haml b/app/views/auth/challenges/new.html.haml
index 9aef2c35d..ff4b7a506 100644
--- a/app/views/auth/challenges/new.html.haml
+++ b/app/views/auth/challenges/new.html.haml
@@ -5,7 +5,7 @@
   = f.input :return_to, as: :hidden
 
   .field-group
-    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off', :autofocus => true }, label: t('challenge.prompt'), required: true
+    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password', :autofocus => true }, label: t('challenge.prompt'), required: true
 
   .actions
     = f.button :button, t('challenge.confirm'), type: :submit
diff --git a/app/views/auth/passwords/edit.html.haml b/app/views/auth/passwords/edit.html.haml
index 114a74454..c7dbebe75 100644
--- a/app/views/auth/passwords/edit.html.haml
+++ b/app/views/auth/passwords/edit.html.haml
@@ -8,9 +8,9 @@
     = f.input :reset_password_token, as: :hidden
 
     .fields-group
-      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
+      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
     .fields-group
-      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, required: true
+      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, required: true
 
     .actions
       = f.button :button, t('auth.set_new_password'), type: :submit
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index a3445b421..c642c2293 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -13,13 +13,13 @@
       .fields-row__column.fields-group.fields-row__column-6
         = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?, hint: false
+        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
 
     .fields-row
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
+        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
+        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, disabled: current_account.suspended?
 
     .actions
       = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
@@ -41,8 +41,7 @@
   %h3= t('migrations.incoming_migrations')
   %p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
 
-  - if open_deletion?
-    %hr.spacer/
+  %hr.spacer/
 
-    %h3= t('auth.delete_account')
-    %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
+  %h3= t('auth.delete_account')
+  %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index 6981195ed..b1d52dd0c 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -5,6 +5,9 @@
   = render partial: 'shared/og', locals: { description: description_for_sign_up }
 
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f|
+  %h1.title= t('auth.sign_up.title', domain: site_hostname)
+  %p.lead= t('auth.sign_up.preamble')
+
   = render 'shared/error_messages', object: resource
 
   - if @invite.present? && @invite.autofollow?
@@ -12,31 +15,27 @@
       %p.hint= t('invites.invited_by')
       = render 'application/card', account: @invite.user.account
 
-  = f.simple_fields_for :account do |ff|
-    .fields-group
-      = ff.input :username, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: t('simple_form.hints.defaults.username', domain: site_hostname)
-
-  .fields-group
-    = f.input :email, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
-
   .fields-group
-    = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }
-
-  .fields-group
-    = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
-    = f.input :confirm_password, as: :string, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }
-
-  = f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }
+    = f.simple_fields_for :account do |ff|
+      = ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.display_name'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.display_name') }
+      = ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false
+    = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'username' }, hint: false
+    = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false
+    = 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 => 'new-password' }, hint: false
+    = f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false
+    = f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }
 
   - if approved_registrations? && !@invite.present?
     .fields-group
       = f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
         = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text
 
+
+  = hidden_field_tag :accept, params[:accept]
   = f.input :invite_code, as: :hidden
 
   .fields-group
-    = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true
+    = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.privacy_policy_agreement_html', rules_path: about_more_path, privacy_policy_path: privacy_policy_path), required: true
 
   .actions
     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml
new file mode 100644
index 000000000..8e7a90cbe
--- /dev/null
+++ b/app/views/auth/registrations/rules.html.haml
@@ -0,0 +1,21 @@
+- content_for :page_title do
+  = t('auth.register')
+
+- content_for :header_tags do
+  = render partial: 'shared/og', locals: { description: description_for_sign_up }
+
+.simple_form
+  %h1.title= t('auth.rules.title')
+  %p.lead= t('auth.rules.preamble', domain: site_hostname)
+
+  %ol.rules-list
+    - @rules.each do |rule|
+      %li
+        .rules-list__text= rule.text
+
+  .stacked-actions
+    - accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token)
+    = link_to t('auth.rules.accept'), accept_path, class: 'button'
+    = link_to t('auth.rules.back'), root_path, class: 'button button-tertiary'
+
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index a4323d1d9..943618e39 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -12,7 +12,7 @@
       - else
         = f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
     .fields-group
-      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false
+      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'current-password' }, hint: false
 
     .actions
       = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml b/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml
index ab2d48c0a..82f957527 100644
--- a/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml
+++ b/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml
@@ -5,7 +5,7 @@
   %p.hint.authentication-hint= t('simple_form.hints.sessions.otp')
 
   .fields-group
-    = f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, autofocus: true
+    = f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'one-time-code' }, autofocus: true
 
   .actions
     = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/authorize_interactions/_post_follow_actions.html.haml b/app/views/authorize_interactions/_post_follow_actions.html.haml
index dd71160e2..e30097964 100644
--- a/app/views/authorize_interactions/_post_follow_actions.html.haml
+++ b/app/views/authorize_interactions/_post_follow_actions.html.haml
@@ -1,4 +1,4 @@
 .post-follow-actions
-  %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
+  %div= link_to t('authorize_follow.post_follow.web'), web_url("@#{@resource.pretty_acct}"), class: 'button button--block'
   %div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@resource), class: 'button button--block'
   %div= t('authorize_follow.post_follow.close')
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
deleted file mode 100644
index 4872432d4..000000000
--- a/app/views/directories/index.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-- content_for :page_title do
-  = t('directories.explore_mastodon', title: site_title)
-
-- content_for :header_tags do
-  %meta{ name: 'description', content: t('directories.explanation') }
-
-  = opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
-  = opengraph 'og:type', 'website'
-  = opengraph 'og:title', t('directories.explore_mastodon', title: site_title)
-  = opengraph 'og:description', t('directories.explanation')
-  = opengraph 'og:image', File.join(root_url, 'android-chrome-192x192.png')
-
-.page-header
-  %h1= t('directories.explore_mastodon', title: site_title)
-  %p= t('directories.explanation')
-
-- if @accounts.empty?
-  = nothing_here
-- else
-  .directory__list
-    - @accounts.each do |account|
-      .account-card
-        = link_to TagManager.instance.url_for(account), class: 'account-card__permalink' do
-          .account-card__header
-            = image_tag account.header.url, alt: ''
-          .account-card__title
-            .account-card__title__avatar
-              = image_tag account.avatar.url, alt: ''
-            .display-name
-              %bdi
-                %strong.emojify.p-name= display_name(account, custom_emojify: true)
-              %span
-                = acct(account)
-                = fa_icon('lock') if account.locked?
-        - if account.note.present?
-          .account-card__bio.emojify
-            = prerender_custom_emojis(account_bio_format(account), account.emojis)
-        - else
-          .flex-spacer
-        .account-card__actions
-          .account-card__counters
-            .account-card__counters__item
-              = friendly_number_to_human account.statuses_count
-              %small= t('accounts.posts', count: account.statuses_count).downcase
-            .account-card__counters__item
-              = hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
-              %small= t('accounts.followers', count: account.followers_count).downcase
-            .account-card__counters__item
-              = friendly_number_to_human account.following_count
-              %small= t('accounts.following', count: account.following_count).downcase
-          .account-card__actions__button
-            = account_action_button(account)
-
-  = paginate @accounts
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 1be50331a..4a3005f72 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -59,8 +59,9 @@
                         = media_attachment.file_file_name
                 .strike-card__statuses-list__item__meta
                   %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
-                  ·
-                  = status.application.name
+                  - unless status.application.nil?
+                    ·
+                    = status.application.name
               - else
                 .one-liner= t('disputes.strikes.status', id: status_id)
                 .strike-card__statuses-list__item__meta
diff --git a/app/views/filters/statuses/index.html.haml b/app/views/filters/statuses/index.html.haml
index 886de58fa..eaa39e170 100644
--- a/app/views/filters/statuses/index.html.haml
+++ b/app/views/filters/statuses/index.html.haml
@@ -1,6 +1,3 @@
-- content_for :header_tags do
-  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-
 - content_for :page_title do
   = t('filters.statuses.index.title')
   \-
diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml
index 92de35a9f..d93540c02 100644
--- a/app/views/follower_accounts/index.html.haml
+++ b/app/views/follower_accounts/index.html.haml
@@ -1,20 +1,6 @@
-- content_for :page_title do
-  = t('accounts.people_who_follow', name: display_name(@account))
-
 - content_for :header_tags do
   %meta{ name: 'robots', content: 'noindex' }/
-  = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
-= render 'accounts/header', account: @account
-
-- if @account.hide_collections?
-  .nothing-here= t('accounts.network_hidden')
-- elsif user_signed_in? && @account.blocking?(current_account)
-  .nothing-here= t('accounts.unavailable')
-- elsif @follows.empty?
-  = nothing_here
-- else
-  .card-grid
-    = render partial: 'application/card', collection: @follows.map(&:account), as: :account
+  = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
-  = paginate @follows
+= render 'shared/web_app'
diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml
index 9bb1a9edd..d93540c02 100644
--- a/app/views/following_accounts/index.html.haml
+++ b/app/views/following_accounts/index.html.haml
@@ -1,20 +1,6 @@
-- content_for :page_title do
-  = t('accounts.people_followed_by', name: display_name(@account))
-
 - content_for :header_tags do
   %meta{ name: 'robots', content: 'noindex' }/
-  = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
-= render 'accounts/header', account: @account
-
-- if @account.hide_collections?
-  .nothing-here= t('accounts.network_hidden')
-- elsif user_signed_in? && @account.blocking?(current_account)
-  .nothing-here= t('accounts.unavailable')
-- elsif @follows.empty?
-  = nothing_here
-- else
-  .card-grid
-    = render partial: 'application/card', collection: @follows.map(&:target_account), as: :account
+  = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
-  = paginate @follows
+= render 'shared/web_app'
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 568b23eff..45990cd10 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1,15 +1,7 @@
 - content_for :header_tags do
-  = preload_pack_asset 'features/getting_started.js', crossorigin: 'anonymous'
-  = preload_pack_asset 'features/compose.js', crossorigin: 'anonymous'
-  = preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous'
-  = preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous'
+  - unless request.path == '/'
+    %meta{ name: 'robots', content: 'noindex' }/
 
-  %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
-  = render_initial_state
+  = render partial: 'shared/og'
 
-.notranslate.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
-  %noscript
-    = image_pack_tag 'logo.svg', alt: 'Mastodon'
-
-    %div
-      = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
+= render 'shared/web_app'
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index aa66815cc..3048e0e6a 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -14,22 +14,24 @@
               = link_to root_path do
                 = logo_as_symbol(:wordmark)
 
-            = link_to '#', class: 'sidebar__toggle__icon' do
+            = link_to '#', class: 'sidebar__toggle__icon', 'aria-label': t('navigation.toggle_menu'), 'aria-expanded': 'false' do
               = fa_icon 'bars'
+              = fa_icon 'times'
 
           = render_navigation
 
     .content-wrapper
       .content
-        .content-heading
+        .content__heading
           - if content_for?(:heading)
             = yield :heading
           - else
-            %h2= yield :page_title
+            .content__heading__row
+              %h2= yield :page_title
 
-          - if :heading_actions
-            .content-heading-actions
-              = yield :heading_actions
+              - if content_for?(:heading_actions)
+                .content__heading__actions
+                  = yield :heading_actions
 
         = render 'application/flashes'
 
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 40c38cecb..d19ea1390 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -21,7 +21,7 @@
 
     %link{ rel: 'mask-icon', href: asset_pack_path('media/images/logo-symbol-icon.svg'), color: '#6364FF' }/
     %link{ rel: 'manifest', href: manifest_path(format: :json) }/
-    %meta{ name: 'theme-color', content: '#6364FF' }/
+    %meta{ name: 'theme-color', content: '#191b22' }/
     %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/
 
     %title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title
@@ -43,7 +43,7 @@
     = render partial: 'layouts/theme', object: @core
     = render partial: 'layouts/theme', object: @theme
 
-    = stylesheet_link_tag custom_css_path, host: request.host, media: 'all'
+    = stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'
 
   %body{ class: body_classes }
     = content_for?(:content) ? yield(:content) : yield
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
deleted file mode 100644
index f4ef199e6..000000000
--- a/app/views/layouts/public.html.haml
+++ /dev/null
@@ -1,61 +0,0 @@
-- content_for :header_tags do
-  = render_initial_state
-
-- content_for :content do
-  .public-layout
-    - unless @hide_navbar
-      .container
-        %nav.header
-          .nav-left
-            = link_to root_url, class: 'brand' do
-              = logo_as_symbol(:wordmark)
-
-            - unless whitelist_mode?
-              = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
-              = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
-              = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
-
-          .nav-center
-
-          .nav-right
-            - if user_signed_in?
-              = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'
-            - else
-              = link_to_login t('auth.login'), class: 'webapp-btn nav-link nav-button'
-              = link_to t('auth.register'), available_sign_up_path, class: 'webapp-btn nav-link nav-button'
-
-    .container= yield
-
-    .container
-      .footer
-        .grid
-          .column-0
-            %h4= t 'footer.resources'
-            %ul
-              %li= link_to t('about.terms'), terms_path
-              %li= link_to t('about.privacy_policy'), terms_path
-          .column-1
-            %h4= t 'footer.developers'
-            %ul
-              %li= link_to t('about.documentation'), 'https://docs.joinmastodon.org/'
-              %li= link_to t('about.api'), 'https://docs.joinmastodon.org/client/intro/'
-          .column-2
-            %h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/'
-            = link_to logo_as_symbol, root_url, class: 'brand'
-          .column-3
-            %h4= site_hostname
-            %ul
-              - unless whitelist_mode?
-                %li= link_to t('about.about_this'), about_more_path
-              %li= "v#{Mastodon::Version.to_s}"
-          .column-4
-            %h4= t 'footer.more'
-            %ul
-              %li= link_to t('about.source_code'), Mastodon::Version.source_url
-              %li= link_to t('about.apps'), 'https://joinmastodon.org/apps'
-        .legal-xs
-          = link_to "v#{Mastodon::Version.to_s}", Mastodon::Version.source_url
-          ·
-          = link_to t('about.privacy_policy'), terms_path
-
-= render template: 'layouts/application'
diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml
index 444b06fe6..e7cd5ba3e 100644
--- a/app/views/notification_mailer/_status.html.haml
+++ b/app/views/notification_mailer/_status.html.haml
@@ -42,4 +42,4 @@
                                         = link_to a.remote_url, a.remote_url
 
                               %p.status-footer
-                                = link_to l(status.created_at), web_url("statuses/#{status.id}")
+                                = link_to l(status.created_at), web_url("@#{status.account.pretty_acct}/#{status.id}")
diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb
index 1dc8de739..e03e8346c 100644
--- a/app/views/notification_mailer/_status.text.erb
+++ b/app/views/notification_mailer/_status.text.erb
@@ -5,4 +5,4 @@
 <% end %>
 > <%= raw word_wrap(extract_status_plain_text(status), break_sequence: "\n> ") %>
 
-<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>
+<%= raw t('application_mailer.view')%> <%= web_url("@#{status.account.pretty_acct}/#{status.id}") %>
diff --git a/app/views/notification_mailer/digest.html.haml b/app/views/notification_mailer/digest.html.haml
deleted file mode 100644
index a94ace228..000000000
--- a/app/views/notification_mailer/digest.html.haml
+++ /dev/null
@@ -1,44 +0,0 @@
-%table.email-table{ cellspacing: 0, cellpadding: 0 }
-  %tbody
-    %tr
-      %td.email-body
-        .email-container
-          %table.content-section{ cellspacing: 0, cellpadding: 0 }
-            %tbody
-              %tr
-                %td.content-cell.darker.hero-with-button
-                  .email-row
-                    .col-6
-                      %table.column{ cellspacing: 0, cellpadding: 0 }
-                        %tbody
-                          %tr
-                            %td.column-cell.text-center.padded
-                              %h1= t 'notification_mailer.digest.title'
-                              %p.lead= t('notification_mailer.digest.body', since: l((@me.user_current_sign_in_at || @since).to_date, format: :short), instance: site_hostname)
-                              %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
-                                %tbody
-                                  %tr
-                                    %td.button-primary
-                                      = link_to web_url do
-                                        %span= t 'notification_mailer.digest.action'
-
-- @notifications.each_with_index do |n, i|
-  = render 'status', status: n.target_status, i: i
-
-- unless @follows_since.zero?
-  %table.email-table{ cellspacing: 0, cellpadding: 0 }
-    %tbody
-      %tr
-        %td.email-body
-          .email-container
-            %table.content-section{ cellspacing: 0, cellpadding: 0 }
-              %tbody
-                %tr
-                  %td.content-cell.content-start.border-top
-                    .email-row
-                      .col-6
-                        %table.column{ cellspacing: 0, cellpadding: 0 }
-                          %tbody
-                            %tr
-                              %td.column-cell.text-center
-                                %p= t('notification_mailer.digest.new_followers_summary', count: @follows_since)
diff --git a/app/views/notification_mailer/digest.text.erb b/app/views/notification_mailer/digest.text.erb
deleted file mode 100644
index 0f84a4ef0..000000000
--- a/app/views/notification_mailer/digest.text.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
-
-<%= raw t('notification_mailer.digest.body', since: l(@me.user_current_sign_in_at || @since), instance: root_url) %>
-<% @notifications.each do |notification| %>
-
-* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %>
-
-  <%= raw extract_status_plain_text(notification.target_status) %>
-
-  <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %>
-<% end %>
-<% if @follows_since > 0 %>
-
-<%= raw t('notification_mailer.digest.new_followers_summary', count: @follows_since) %>
-<% end %>
diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml
index ebc5c29c7..5d9be3f57 100644
--- a/app/views/notification_mailer/favourite.html.haml
+++ b/app/views/notification_mailer/favourite.html.haml
@@ -41,5 +41,5 @@
                             %tbody
                               %tr
                                 %td.button-primary
-                                  = link_to web_url("statuses/#{@status.id}") do
+                                  = link_to web_url("@#{@status.account.pretty_acct}/#{@status.id}") do
                                     %span= t 'application_mailer.view_status'
diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml
index a59ef8835..f250cbbd2 100644
--- a/app/views/notification_mailer/follow.html.haml
+++ b/app/views/notification_mailer/follow.html.haml
@@ -39,5 +39,5 @@
                             %tbody
                               %tr
                                 %td.button-primary
-                                  = link_to web_url("accounts/#{@account.id}") do
+                                  = link_to web_url("@#{@account.pretty_acct}") do
                                     %span= t 'application_mailer.view_profile'
diff --git a/app/views/notification_mailer/follow.text.erb b/app/views/notification_mailer/follow.text.erb
index 016a0a4cf..e498df093 100644
--- a/app/views/notification_mailer/follow.text.erb
+++ b/app/views/notification_mailer/follow.text.erb
@@ -2,4 +2,4 @@
 
 <%= raw t('notification_mailer.follow.body', name: @account.pretty_acct) %>
 
-<%= raw t('application_mailer.view')%> <%= web_url("accounts/#{@account.id}") %>
+<%= raw t('application_mailer.view')%> <%= web_url("@#{@account.pretty_acct}") %>
diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml
index cfb7465c1..4ae9bb7b0 100644
--- a/app/views/notification_mailer/mention.html.haml
+++ b/app/views/notification_mailer/mention.html.haml
@@ -41,5 +41,5 @@
                             %tbody
                               %tr
                                 %td.button-primary
-                                  = link_to web_url("statuses/#{@status.id}") do
+                                  = link_to web_url("@#{@status.account.pretty_acct}/#{@status.id}") do
                                     %span= t 'notification_mailer.mention.action'
diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml
index c528536ec..f805c79f0 100644
--- a/app/views/notification_mailer/reblog.html.haml
+++ b/app/views/notification_mailer/reblog.html.haml
@@ -41,5 +41,5 @@
                             %tbody
                               %tr
                                 %td.button-primary
-                                  = link_to web_url("statuses/#{@status.id}") do
+                                  = link_to web_url("@#{@status.account.pretty_acct}/#{@status.id}") do
                                     %span= t 'application_mailer.view_status'
diff --git a/app/views/privacy/show.html.haml b/app/views/privacy/show.html.haml
new file mode 100644
index 000000000..95e506641
--- /dev/null
+++ b/app/views/privacy/show.html.haml
@@ -0,0 +1,7 @@
+- content_for :page_title do
+  = t('privacy_policy.title')
+
+- content_for :header_tags do
+  = render partial: 'shared/og'
+
+= render 'shared/web_app'
diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml
deleted file mode 100644
index 71a3d289b..000000000
--- a/app/views/public_timelines/show.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- content_for :page_title do
-  = t('about.see_whats_happening')
-
-- content_for :header_tags do
-  %meta{ name: 'robots', content: 'noindex' }/
-
-.page-header
-  %h1= t('about.see_whats_happening')
-
-  - if Setting.show_known_fediverse_at_about_page
-    %p= t('about.browse_public_posts')
-  - else
-    %p= t('about.browse_local_posts')
-
-#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }}
-.notranslate#modal-container
diff --git a/app/views/remote_follow/new.html.haml b/app/views/remote_follow/new.html.haml
deleted file mode 100644
index 4e9601f6a..000000000
--- a/app/views/remote_follow/new.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for :header_tags do
-  %meta{ name: 'robots', content: 'noindex' }/
-
-.form-container
-  .follow-prompt
-    %h2= t('remote_follow.prompt')
-
-    = render partial: 'application/card', locals: { account: @account }
-
-  = simple_form_for @remote_follow, as: :remote_follow, url: account_remote_follow_path(@account) do |f|
-    = render 'shared/error_messages', object: @remote_follow
-
-    = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
-
-    .actions
-      = f.button :button, t('remote_follow.proceed'), type: :submit
-
-    %p.hint.subtle-hint
-      = t('remote_follow.reason_html', instance: site_hostname)
-      = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml
deleted file mode 100644
index 2cc0fcb93..000000000
--- a/app/views/remote_interaction/new.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- content_for :header_tags do
-  %meta{ name: 'robots', content: 'noindex' }/
-
-.form-container
-  .follow-prompt
-    %h2= t("remote_interaction.#{@interaction_type}.prompt")
-
-    .public-layout
-      .activity-stream.activity-stream--highlighted
-        = render 'statuses/status', status: @status
-
-  = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f|
-    = render 'shared/error_messages', object: @remote_follow
-
-    = hidden_field_tag :type, @interaction_type
-
-    = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
-
-    .actions
-      = f.button :button, t("remote_interaction.#{@interaction_type}.proceed"), type: :submit
-
-    %p.hint.subtle-hint
-      = t('remote_follow.reason_html', instance: site_hostname)
-      = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/settings/deletes/show.html.haml b/app/views/settings/deletes/show.html.haml
index 08792e0af..c08ee85b0 100644
--- a/app/views/settings/deletes/show.html.haml
+++ b/app/views/settings/deletes/show.html.haml
@@ -16,12 +16,12 @@
       %li.positive-hint= t('deletes.warning.email_contact_html', email: Setting.site_contact_email)
       %li.positive-hint= t('deletes.warning.username_available')
 
-  %p.hint= t('deletes.warning.more_details_html', terms_path: terms_path)
+  %p.hint= t('deletes.warning.more_details_html', terms_path: privacy_policy_path)
 
   %hr.spacer/
 
   - if current_user.encrypted_password.present?
-    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_password')
+    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, hint: t('deletes.confirm_password')
   - else
     = f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
 
diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml
index 5d87e2862..595094fc7 100644
--- a/app/views/settings/featured_tags/index.html.haml
+++ b/app/views/settings/featured_tags/index.html.haml
@@ -21,7 +21,7 @@
     %div
       %h4
         = fa_icon 'hashtag'
-        = featured_tag.name
+        = featured_tag.display_name
         %small
           - if featured_tag.last_status_at.nil?
             = t('accounts.nothing_here')
diff --git a/app/views/settings/migration/redirects/new.html.haml b/app/views/settings/migration/redirects/new.html.haml
index 017450f4b..d7868e900 100644
--- a/app/views/settings/migration/redirects/new.html.haml
+++ b/app/views/settings/migration/redirects/new.html.haml
@@ -19,7 +19,7 @@
 
     .fields-row__column.fields-group.fields-row__column-6
       - if current_user.encrypted_password.present?
-        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true
       - else
         = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
 
diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml
index 492f6fe12..1ecf7302a 100644
--- a/app/views/settings/migrations/show.html.haml
+++ b/app/views/settings/migrations/show.html.haml
@@ -48,7 +48,7 @@
 
     .fields-row__column.fields-group.fields-row__column-6
       - if current_user.encrypted_password.present?
-        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true, disabled: on_cooldown?
       - else
         = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 
@@ -76,7 +76,7 @@
               - if migration.target_account.present?
                 = compact_account_link_to migration.target_account
               - else
-                = migration.pretty_acct
+                = migration.acct
 
             %td= number_with_delimiter migration.followers_count
 
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index 943e21b50..a03faa145 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -28,10 +28,6 @@
   .fields-group
     = f.input :setting_always_send_emails, as: :boolean, wrapper: :with_label
 
-  .fields-group
-    = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
-      = ff.input :digest, as: :boolean, wrapper: :with_label
-
   %h4= t 'notifications.other_settings'
 
   .fields-group
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index b84d06c27..430d1f339 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -41,7 +41,7 @@
     .fields-row__column.fields-group.fields-row__column-6
       .input.with_block_label
         %label= t('simple_form.labels.defaults.fields')
-        %span.hint= t('simple_form.hints.defaults.fields')
+        %span.hint= t('simple_form.hints.defaults.fields', count: Account::DEFAULT_FIELDS_SIZE)
 
         = f.simple_fields_for :fields do |fields_f|
           .row
@@ -70,8 +70,7 @@
 %h6= t 'migrations.incoming_migrations'
 %p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
 
-- if open_deletion?
-  %hr.spacer/
+%hr.spacer/
 
-  %h6= t('auth.delete_account')
-  %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
+%h6= t('auth.delete_account')
+%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/app/views/shared/_og.html.haml b/app/views/shared/_og.html.haml
index 7feae1b8b..2941b566e 100644
--- a/app/views/shared/_og.html.haml
+++ b/app/views/shared/_og.html.haml
@@ -1,14 +1,14 @@
 - thumbnail     = @instance_presenter.thumbnail
-- description ||= strip_tags(@instance_presenter.site_short_description.presence || t('about.about_mastodon_html'))
+- description ||= strip_tags(@instance_presenter.description.presence || t('about.about_mastodon_html'))
 
 %meta{ name: 'description', content: description }/
 
 = opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
 = opengraph 'og:url', url_for(only_path: false)
 = opengraph 'og:type', 'website'
-= opengraph 'og:title', @instance_presenter.site_title
+= opengraph 'og:title', @instance_presenter.title
 = opengraph 'og:description', description
-= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('media/images/preview.png', protocol: :request))
+= opengraph 'og:image', full_asset_url(thumbnail&.file&.url(:'@1x') || asset_pack_path('media/images/preview.png', 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/shared/_web_app.html.haml b/app/views/shared/_web_app.html.haml
new file mode 100644
index 000000000..b9a0ce1fc
--- /dev/null
+++ b/app/views/shared/_web_app.html.haml
@@ -0,0 +1,16 @@
+- content_for :header_tags do
+  - if user_signed_in?
+    = preload_pack_asset 'features/compose.js', crossorigin: 'anonymous'
+    = preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous'
+    = preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous'
+
+  %meta{ name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key }
+
+  = render_initial_state
+
+.notranslate.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
+  %noscript
+    = image_pack_tag 'logo.svg', alt: 'Mastodon'
+
+    %div
+      = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index c67f0e4d9..619406d89 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -56,7 +56,7 @@
       - else
         = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer'
       ·
-    = link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
+    %span.detailed-status__link
       - if status.in_reply_to_id.nil?
         = fa_icon('reply')
       - else
@@ -65,16 +65,16 @@
       = " "
     ·
     - if status.public_visibility? || status.unlisted_visibility?
-      = link_to remote_interaction_path(status, type: :reblog), class: 'modal-button detailed-status__link' do
+      %span.detailed-status__link
         = fa_icon('retweet')
         %span.detailed-status__reblogs>= friendly_number_to_human status.reblogs_count
         = " "
       ·
-    = link_to remote_interaction_path(status, type: :favourite), class: 'modal-button detailed-status__link' do
+    %span.detailed-status__link
       = fa_icon('star')
       %span.detailed-status__favorites>= friendly_number_to_human status.favourites_count
       = " "
 
     - if user_signed_in?
       ·
-      = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank'
+      = link_to t('statuses.open_in_web'), web_url("@#{status.account.pretty_acct}/#{status.id}"), class: 'detailed-status__application', target: '_blank'
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 936ecd27e..1e37b6cf3 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -53,18 +53,18 @@
       = t 'statuses.show_thread'
 
   .status__action-bar
-    = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button icon-button--with-counter modal-button' do
+    %span.status__action-bar-button.icon-button.icon-button--with-counter
       - if status.in_reply_to_id.nil?
         = fa_icon 'reply fw'
       - else
         = fa_icon 'reply-all fw'
       %span.icon-button__counter= obscured_counter status.replies_count
-    = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
+    %span.status__action-bar-button.icon-button
       - if status.distributable?
         = fa_icon 'retweet fw'
       - elsif status.private_visibility? || status.limited_visibility?
         = fa_icon 'lock fw'
       - else
         = fa_icon 'at fw'
-    = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do
+    %span.status__action-bar-button.icon-button
       = fa_icon 'star fw'
diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml
index 7ef7b09a2..106c41725 100644
--- a/app/views/statuses/show.html.haml
+++ b/app/views/statuses/show.html.haml
@@ -2,7 +2,7 @@
   = t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false))
 
 - content_for :header_tags do
-  - if @account.user&.setting_noindex
+  - if @account.user_prefers_noindex?
     %meta{ name: 'robots', content: 'noindex, noarchive' }/
 
   %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: short_account_status_url(@account, @status), format: 'json') }/
@@ -17,9 +17,4 @@
   = render 'og_description', activity: @status
   = render 'og_image', activity: @status, account: @account
 
-.grid
-  .column-0
-    .activity-stream.h-entry
-      = render partial: 'status', locals: { status: @status, include_threads: true }
-  .column-1
-    = render 'application/sidebar'
+= render 'shared/web_app'
diff --git a/app/views/tags/_og.html.haml b/app/views/tags/_og.html.haml
deleted file mode 100644
index 37f644cf2..000000000
--- a/app/views/tags/_og.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-= opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
-= opengraph 'og:url', tag_url(@tag)
-= opengraph 'og:type', 'website'
-= opengraph 'og:title', "##{@tag.display_name}"
-= opengraph 'og:description', strip_tags(t('about.about_hashtag_html', hashtag: @tag.display_name))
-= opengraph 'twitter:card', 'summary'
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 608989a2b..4b4967a8f 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -1,15 +1,5 @@
-- content_for :page_title do
-  = "##{@tag.display_name}"
-
 - content_for :header_tags do
   %meta{ name: 'robots', content: 'noindex' }/
-  %link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
-
-  = render 'og'
-
-.page-header
-  %h1= "##{@tag.display_name}"
-  %p= t('about.about_hashtag_html', hashtag: @tag.display_name)
+  = render partial: 'shared/og'
 
-#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name, local: @local)) }}
-.notranslate#modal-container
+= render partial: 'shared/web_app'
diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml
index 39a83faff..447e689b4 100644
--- a/app/views/user_mailer/confirmation_instructions.html.haml
+++ b/app/views/user_mailer/confirmation_instructions.html.haml
@@ -77,4 +77,4 @@
                         %tbody
                           %tr
                             %td.column-cell.text-center
-                              %p= t 'devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: terms_url
+                              %p= t 'devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: privacy_policy_url
diff --git a/app/views/user_mailer/confirmation_instructions.text.erb b/app/views/user_mailer/confirmation_instructions.text.erb
index aad91cd9d..a1b2ba7d2 100644
--- a/app/views/user_mailer/confirmation_instructions.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.text.erb
@@ -6,7 +6,7 @@
 
 => <%= confirmation_url(@resource, confirmation_token: @token, redirect_to_app: @resource.created_by_application ? 'true' : nil) %>
 
-<%= strip_tags(t('devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: terms_url)) %>
+<%= strip_tags(t('devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: privacy_policy_url)) %>
 
 => <%= about_more_url %>
-=> <%= terms_url %>
+=> <%= privacy_policy_url %>
diff --git a/app/views/user_mailer/welcome.html.haml b/app/views/user_mailer/welcome.html.haml
index 1f75ff48a..3ab994ad3 100644
--- a/app/views/user_mailer/welcome.html.haml
+++ b/app/views/user_mailer/welcome.html.haml
@@ -76,26 +76,7 @@
                                     %td.button-primary
                                       = link_to settings_profile_url do
                                         %span= t 'user_mailer.welcome.edit_profile_action'
-              %tr
-                %td.content-cell
-                  .email-row
-                    .col-4
-                      %table.column{ cellspacing: 0, cellpadding: 0 }
-                        %tbody
-                          %tr
-                            %td.column-cell.padded
-                              = t 'user_mailer.welcome.review_preferences_step'
-                    .col-2
-                      %table.column{ cellspacing: 0, cellpadding: 0 }
-                        %tbody
-                          %tr
-                            %td.column-cell.padded
-                              %table.button.button-small{ align: 'left', cellspacing: 0, cellpadding: 0 }
-                                %tbody
-                                  %tr
-                                    %td.button-primary
-                                      = link_to settings_preferences_url do
-                                        %span= t 'user_mailer.welcome.review_preferences_action'
+
               %tr
                 %td.content-cell.padded-bottom
                   .email-row
@@ -116,29 +97,3 @@
                                     %td.button-primary
                                       = link_to web_url do
                                         %span= t 'user_mailer.welcome.final_action'
-
-%table.email-table{ cellspacing: 0, cellpadding: 0 }
-  %tbody
-    %tr
-      %td.email-body
-        .email-container
-          %table.content-section{ cellspacing: 0, cellpadding: 0 }
-            %tbody
-              %tr
-                %td.content-cell.border-top
-                  .email-row
-                    .col-6
-                      %table.column{ cellspacing: 0, cellpadding: 0 }
-                        %tbody
-                          %tr
-                            %td.column-cell.padded
-                              %h5= t 'user_mailer.welcome.tips'
-                              %ul
-                                %li
-                                  %span= t 'user_mailer.welcome.tip_mobile_webapp'
-                                %li
-                                  %span= t 'user_mailer.welcome.tip_following'
-                                %li
-                                  %span= t 'user_mailer.welcome.tip_local_timeline', instance: @instance
-                                %li
-                                  %span= t 'user_mailer.welcome.tip_federated_timeline'
diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb
index e310d7ca6..d78cdb938 100644
--- a/app/views/user_mailer/welcome.text.erb
+++ b/app/views/user_mailer/welcome.text.erb
@@ -11,19 +11,6 @@
 
 => <%= settings_profile_url %>
 
-<%= t 'user_mailer.welcome.review_preferences_step' %>
-
-=> <%= settings_preferences_url %>
-
 <%= t 'user_mailer.welcome.final_step' %>
 
 => <%= web_url %>
-
----
-
-<%= t 'user_mailer.welcome.tips' %>
-
-* <%= t 'user_mailer.welcome.tip_mobile_webapp' %>
-* <%= t 'user_mailer.welcome.tip_following' %>
-* <%= t 'user_mailer.welcome.tip_local_timeline', instance: @instance %>
-* <%= t 'user_mailer.welcome.tip_federated_timeline' %>