about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-12 17:34:00 +0100
committerGitHub <noreply@github.com>2019-03-12 17:34:00 +0100
commit65fffeac3f960f9c74d693525a73ac14b201bf2b (patch)
tree41d5eaa2a446e161dc26d39960cde870135ee06f /app/javascript
parent6a8dc59eb8187b49aa3cbf3e4bf80565d8aa15d3 (diff)
Redesign landing page (#10232)
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/containers/timeline_container.js12
-rw-r--r--app/javascript/mastodon/features/standalone/community_timeline/index.js71
-rw-r--r--app/javascript/mastodon/features/standalone/hashtag_timeline/index.js8
-rw-r--r--app/javascript/mastodon/features/standalone/public_timeline/index.js116
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/styles/mastodon/about.scss816
-rw-r--r--app/javascript/styles/mastodon/forms.scss21
-rw-r--r--app/javascript/styles/mastodon/widgets.scss5
8 files changed, 288 insertions, 763 deletions
diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js
index a1a4bd024..54f8eb310 100644
--- a/app/javascript/mastodon/containers/timeline_container.js
+++ b/app/javascript/mastodon/containers/timeline_container.js
@@ -7,7 +7,6 @@ import { hydrateStore } from '../actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import PublicTimeline from '../features/standalone/public_timeline';
-import CommunityTimeline from '../features/standalone/community_timeline';
 import HashtagTimeline from '../features/standalone/hashtag_timeline';
 import ModalContainer from '../features/ui/containers/modal_container';
 import initialState from '../initial_state';
@@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent {
   static propTypes = {
     locale: PropTypes.string.isRequired,
     hashtag: PropTypes.string,
-    showPublicTimeline: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
   static defaultProps = {
-    showPublicTimeline: initialState.settings.known_fediverse,
+    local: !initialState.settings.known_fediverse,
   };
 
   render () {
-    const { locale, hashtag, showPublicTimeline } = this.props;
+    const { locale, hashtag, local } = this.props;
 
     let timeline;
 
     if (hashtag) {
       timeline = <HashtagTimeline hashtag={hashtag} />;
-    } else if (showPublicTimeline) {
-      timeline = <PublicTimeline />;
     } else {
-      timeline = <CommunityTimeline />;
+      timeline = <PublicTimeline local={local} />;
     }
 
     return (
@@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent {
         <Provider store={store}>
           <Fragment>
             {timeline}
+
             {ReactDOM.createPortal(
               <ModalContainer />,
               document.getElementById('modal-container'),
diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js
deleted file mode 100644
index f917f41c9..000000000
--- a/app/javascript/mastodon/features/standalone/community_timeline/index.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
-import { expandCommunityTimeline } from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectCommunityStream } from '../../../actions/streaming';
-
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
-
-export default @connect()
-@injectIntl
-class CommunityTimeline extends React.PureComponent {
-
-  static propTypes = {
-    dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleHeaderClick = () => {
-    this.column.scrollTop();
-  }
-
-  setRef = c => {
-    this.column = c;
-  }
-
-  componentDidMount () {
-    const { dispatch } = this.props;
-
-    dispatch(expandCommunityTimeline());
-    this.disconnect = dispatch(connectCommunityStream());
-  }
-
-  componentWillUnmount () {
-    if (this.disconnect) {
-      this.disconnect();
-      this.disconnect = null;
-    }
-  }
-
-  handleLoadMore = maxId => {
-    this.props.dispatch(expandCommunityTimeline({ maxId }));
-  }
-
-  render () {
-    const { intl } = this.props;
-
-    return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='users'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='community'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
-    );
-  }
-
-}
diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
index 333726f94..0880d98c8 100644
--- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
@@ -2,13 +2,13 @@ import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { expandHashtagTimeline } from '../../../actions/timelines';
-import { connectHashtagStream } from '../../../actions/streaming';
+import { expandHashtagTimeline } from 'mastodon/actions/timelines';
+import { connectHashtagStream } from 'mastodon/actions/streaming';
 import Masonry from 'react-masonry-infinite';
 import { List as ImmutableList } from 'immutable';
-import DetailedStatusContainer from '../../status/containers/detailed_status_container';
+import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
 import { debounce } from 'lodash';
-import LoadingIndicator from '../../../components/loading_indicator';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
 
 const mapStateToProps = (state, { hashtag }) => ({
   statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js
index 618696eb1..5a67492ac 100644
--- a/app/javascript/mastodon/features/standalone/public_timeline/index.js
+++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js
@@ -1,42 +1,59 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
-import StatusListContainer from '../../ui/containers/status_list_container';
-import { expandPublicTimeline } from '../../../actions/timelines';
-import Column from '../../../components/column';
-import ColumnHeader from '../../../components/column_header';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connectPublicStream } from '../../../actions/streaming';
-
-const messages = defineMessages({
-  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
-});
-
-export default @connect()
-@injectIntl
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
+import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
+import Masonry from 'react-masonry-infinite';
+import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
+import { debounce } from 'lodash';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
+
+const mapStateToProps = (state, { local }) => {
+  const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
+
+  return {
+    statusIds: timeline.get('items', ImmutableList()),
+    isLoading: timeline.get('isLoading', false),
+    hasMore: timeline.get('hasMore', false),
+  };
+};
+
+export default @connect(mapStateToProps)
 class PublicTimeline extends React.PureComponent {
 
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
+    statusIds: ImmutablePropTypes.list.isRequired,
+    isLoading: PropTypes.bool.isRequired,
+    hasMore: PropTypes.bool.isRequired,
+    local: PropTypes.bool,
   };
 
-  handleHeaderClick = () => {
-    this.column.scrollTop();
+  componentDidMount () {
+    this._connect();
   }
 
-  setRef = c => {
-    this.column = c;
+  componentDidUpdate (prevProps) {
+    if (prevProps.local !== this.props.local) {
+      this._disconnect();
+      this._connect();
+    }
   }
 
-  componentDidMount () {
-    const { dispatch } = this.props;
+  componentWillUnmount () {
+    this._disconnect();
+  }
 
-    dispatch(expandPublicTimeline());
-    this.disconnect = dispatch(connectPublicStream());
+  _connect () {
+    const { dispatch, local } = this.props;
+
+    dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
+    this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream());
   }
 
-  componentWillUnmount () {
+  _disconnect () {
     if (this.disconnect) {
       this.disconnect();
       this.disconnect = null;
@@ -44,27 +61,48 @@ class PublicTimeline extends React.PureComponent {
   }
 
   handleLoadMore = maxId => {
-    this.props.dispatch(expandPublicTimeline({ maxId }));
+    const { dispatch, local } = this.props;
+    dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
+  }
+
+  setRef = c => {
+    this.masonry = c;
   }
 
+  handleHeightChange = debounce(() => {
+    if (!this.masonry) {
+      return;
+    }
+
+    this.masonry.forcePack();
+  }, 50)
+
   render () {
-    const { intl } = this.props;
+    const { statusIds, hasMore, isLoading } = this.props;
+
+    const sizes = [
+      { columns: 1, gutter: 0 },
+      { mq: '415px', columns: 1, gutter: 10 },
+      { mq: '640px', columns: 2, gutter: 10 },
+      { mq: '960px', columns: 3, gutter: 10 },
+      { mq: '1255px', columns: 3, gutter: 10 },
+    ];
+
+    const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
-        <ColumnHeader
-          icon='globe'
-          title={intl.formatMessage(messages.title)}
-          onClick={this.handleHeaderClick}
-        />
-
-        <StatusListContainer
-          timelineId='public'
-          onLoadMore={this.handleLoadMore}
-          scrollKey='standalone_public_timeline'
-          trackScroll={false}
-        />
-      </Column>
+      <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
+        {statusIds.map(statusId => (
+          <div className='statuses-grid__item' key={statusId}>
+            <DetailedStatusContainer
+              id={statusId}
+              compact
+              measureHeight
+              onHeightChange={this.handleHeightChange}
+            />
+          </div>
+        )).toArray()}
+      </Masonry>
     );
   }
 
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 5cd50f055..5c79f9f19 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -23,7 +23,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
   };
 
   static propTypes = {
-    status: ImmutablePropTypes.map.isRequired,
+    status: ImmutablePropTypes.map,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
     onToggleHidden: PropTypes.func.isRequired,
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index b078d4d24..465ef2c11 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -193,6 +193,7 @@ $small-breakpoint: 960px;
     }
 
     strong {
+      font-family: $font-display, sans-serif;
       font-weight: 500;
       font-size: 32px;
       line-height: 48px;
@@ -280,168 +281,6 @@ $small-breakpoint: 960px;
 }
 
 .landing-page {
-  .grid {
-    display: grid;
-    grid-gap: 10px;
-    grid-template-columns: 1fr 2fr;
-    grid-auto-columns: 25%;
-    grid-auto-rows: max-content;
-
-    .column-0 {
-      display: none;
-    }
-
-    .column-1 {
-      grid-column: 1;
-      grid-row: 1;
-    }
-
-    .column-2 {
-      grid-column: 2;
-      grid-row: 1;
-    }
-
-    .column-3 {
-      grid-column: 3;
-      grid-row: 1 / 3;
-    }
-
-    .column-4 {
-      grid-column: 1 / 3;
-      grid-row: 2;
-    }
-  }
-
-  @media screen and (max-width: $small-breakpoint) {
-    .grid {
-      grid-template-columns: 40% 60%;
-
-      .column-0 {
-        display: none;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 1;
-
-        &.non-preview .landing-page__forms {
-          height: 100%;
-        }
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1 / 3;
-
-        &.non-preview {
-          grid-column: 2;
-          grid-row: 1;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 2 / 4;
-      }
-
-      .column-4 {
-        grid-column: 2;
-        grid-row: 3;
-
-        &.non-preview {
-          grid-column: 1 / 3;
-          grid-row: 2;
-        }
-      }
-    }
-  }
-
-  @media screen and (max-width: $column-breakpoint) {
-    .grid {
-      grid-template-columns: 100%;
-
-      .column-0 {
-        display: block;
-        grid-column: 1;
-        grid-row: 1;
-      }
-
-      .column-1 {
-        grid-column: 1;
-        grid-row: 3;
-
-        .brand {
-          display: none;
-        }
-      }
-
-      .column-2 {
-        grid-column: 1;
-        grid-row: 2;
-
-        .landing-page__logo,
-        .landing-page__call-to-action {
-          display: none;
-        }
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 2;
-        }
-      }
-
-      .column-3 {
-        grid-column: 1;
-        grid-row: 5;
-      }
-
-      .column-4 {
-        grid-column: 1;
-        grid-row: 4;
-
-        &.non-preview {
-          grid-column: 1;
-          grid-row: 4;
-        }
-      }
-    }
-  }
-
-  .column-flex {
-    display: flex;
-    flex-direction: column;
-  }
-
-  .separator-or {
-    position: relative;
-    margin: 40px 0;
-    text-align: center;
-
-    &::before {
-      content: "";
-      display: block;
-      width: 100%;
-      height: 0;
-      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-      position: absolute;
-      top: 50%;
-      left: 0;
-    }
-
-    span {
-      display: inline-block;
-      background: $ui-base-color;
-      font-size: 12px;
-      font-weight: 500;
-      color: $darker-text-color;
-      text-transform: uppercase;
-      position: relative;
-      z-index: 1;
-      padding: 0 8px;
-      cursor: default;
-    }
-  }
-
   p,
   li {
     font-family: $font-sans-serif, sans-serif;
@@ -458,28 +297,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .closed-registrations-message {
-    margin-top: 20px;
-
-    &,
-    p {
-      text-align: center;
-      font-size: 12px;
-      line-height: 18px;
-      color: $darker-text-color;
-      margin-bottom: 0;
-
-      a {
-        color: $highlight-text-color;
-        text-decoration: underline;
-      }
-    }
-
-    p:last-child {
-      margin-bottom: 0;
-    }
-  }
-
   em {
     display: inline;
     margin: 0;
@@ -593,187 +410,6 @@ $small-breakpoint: 960px;
     }
   }
 
-  .container-alt {
-    width: 100%;
-    box-sizing: border-box;
-    max-width: 800px;
-    margin: 0 auto;
-    word-wrap: break-word;
-  }
-
-  .header-wrapper {
-    padding-top: 15px;
-    background: $ui-base-color;
-    background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color);
-    position: relative;
-
-    &.compact {
-      background: $ui-base-color;
-      padding-bottom: 15px;
-
-      .hero .heading {
-        padding-bottom: 20px;
-        font-family: $font-sans-serif, sans-serif;
-        font-size: 16px;
-        font-weight: 400;
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
-
-        a {
-          color: $highlight-text-color;
-          text-decoration: underline;
-        }
-      }
-    }
-  }
-
-  .brand {
-    a {
-      padding-left: 0;
-      padding-right: 0;
-      color: $white;
-    }
-
-    img {
-      height: 32px;
-      position: relative;
-      top: 4px;
-      left: -10px;
-    }
-  }
-
-  .header {
-    line-height: 30px;
-    overflow: hidden;
-
-    .container-alt {
-      display: flex;
-      justify-content: space-between;
-    }
-
-    .links {
-      position: relative;
-      z-index: 4;
-
-      a {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        color: $darker-text-color;
-        text-decoration: none;
-        padding: 12px 16px;
-        line-height: 32px;
-        font-family: $font-display, sans-serif;
-        font-weight: 500;
-        font-size: 14px;
-
-        &:hover {
-          color: $secondary-text-color;
-        }
-      }
-
-      ul {
-        list-style: none;
-        margin: 0;
-
-        li {
-          display: inline-block;
-          vertical-align: bottom;
-          margin: 0;
-
-          &:first-child a {
-            padding-left: 0;
-          }
-
-          &:last-child a {
-            padding-right: 0;
-          }
-        }
-      }
-    }
-
-    .hero {
-      margin-top: 50px;
-      align-items: center;
-      position: relative;
-
-      .heading {
-        position: relative;
-        z-index: 4;
-        padding-bottom: 150px;
-      }
-
-      .simple_form,
-      .closed-registrations-message {
-        background: darken($ui-base-color, 4%);
-        width: 280px;
-        padding: 15px 20px;
-        border-radius: 4px 4px 0 0;
-        line-height: initial;
-        position: relative;
-        z-index: 4;
-
-        .actions {
-          margin-bottom: 0;
-
-          button,
-          .button,
-          .block-button {
-            margin-bottom: 0;
-          }
-        }
-      }
-
-      .closed-registrations-message {
-        min-height: 330px;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-      }
-    }
-  }
-
-  .about-short {
-    background: darken($ui-base-color, 4%);
-    padding: 50px 0 30px;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 16px;
-    font-weight: 400;
-    font-size: 16px;
-    line-height: 30px;
-    color: $darker-text-color;
-
-    a {
-      color: $highlight-text-color;
-      text-decoration: underline;
-    }
-  }
-
-  &.alternative {
-    padding: 10px 0;
-
-    .brand {
-      text-align: center;
-      padding: 30px 0;
-      margin-bottom: 10px;
-
-      img {
-        position: static;
-        padding: 10px 0;
-      }
-
-      @media screen and (max-width: $small-breakpoint) {
-        padding: 15px 0;
-      }
-
-      @media screen and (max-width: $column-breakpoint) {
-        padding: 0;
-        margin-bottom: -10px;
-      }
-    }
-  }
-
   &__information,
   &__forms {
     padding: 20px;
@@ -967,353 +603,253 @@ $small-breakpoint: 960px;
     }
   }
 
-  &__forms {
-    height: 100%;
-
-    @media screen and (max-width: $small-breakpoint) {
-      height: auto;
-    }
-
-    @media screen and (max-width: $column-breakpoint) {
-      background: transparent;
-      box-shadow: none;
-      padding: 0 20px;
-      margin-top: 30px;
-      margin-bottom: 40px;
-
-      .separator-or {
-        span {
-          background: darken($ui-base-color, 8%);
-        }
+  @media screen and (max-width: 840px) {
+    .information-board {
+      .container-alt {
+        padding-right: 20px;
       }
-    }
-
-    hr {
-      margin: 40px 0;
-    }
 
-    .button {
-      display: block;
-    }
-
-    .subtle-hint a {
-      text-decoration: none;
+      .panel {
+        position: static;
+        margin-top: 20px;
+        width: 100%;
+        border-radius: 4px;
 
-      &:hover,
-      &:focus,
-      &:active {
-        text-decoration: underline;
+        .panel-header {
+          text-align: center;
+        }
       }
     }
   }
 
-  #mastodon-timeline {
-    display: flex;
-    -webkit-overflow-scrolling: touch;
-    -ms-overflow-style: -ms-autohiding-scrollbar;
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 13px;
-    line-height: 18px;
-    font-weight: 400;
-    color: $primary-text-color;
-    width: 100%;
-    flex: 1 1 auto;
-    overflow: hidden;
-    height: 100%;
-
-    .column-header {
-      color: inherit;
-      font-family: inherit;
-      font-size: 16px;
-      line-height: inherit;
-      font-weight: inherit;
-      margin: 0;
-      padding: 0;
-    }
-
-    .column {
-      padding: 0;
-      border-radius: 4px;
-      overflow: hidden;
-      width: 100%;
-    }
-
-    .scrollable {
-      height: 400px;
-    }
-
-    p {
-      font-size: inherit;
-      line-height: inherit;
-      font-weight: inherit;
-      color: $primary-text-color;
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
+  @media screen and (max-width: 675px) {
+    .header-wrapper {
+      padding-top: 0;
 
-      a {
-        color: $secondary-text-color;
-        text-decoration: none;
+      &.compact {
+        padding-bottom: 0;
       }
-    }
-
-    .attachment-list__list {
-      margin-left: 0;
-      list-style: none;
-
-      li {
-        font-size: inherit;
-        line-height: inherit;
-        font-weight: inherit;
-        margin-bottom: 0;
-
-        a {
-          color: $dark-text-color;
-          text-decoration: none;
 
-          &:hover {
-            text-decoration: underline;
-          }
-        }
+      &.compact .hero .heading {
+        text-align: initial;
       }
     }
 
-    @media screen and (max-width: $column-breakpoint) {
-      display: none;
+    .header .container-alt,
+    .features .container-alt {
+      display: block;
     }
   }
 
-  &__features {
-    & > p {
-      padding-right: 60px;
-    }
-
-    .features-list {
-      margin: 40px 0;
-      margin-top: 30px;
-    }
-
-    &__action {
-      text-align: center;
-    }
+  .cta {
+    margin: 20px;
   }
+}
 
-  .features-list {
-    .features-list__row {
-      display: flex;
-      padding: 10px 0;
-      justify-content: space-between;
-
-      .visual {
-        flex: 0 0 auto;
-        display: flex;
-        align-items: center;
-        margin-left: 15px;
+.landing {
+  margin-bottom: 100px;
 
-        .fa {
-          display: block;
-          color: $darker-text-color;
-          font-size: 48px;
-        }
-      }
+  @media screen and (max-width: 738px) {
+    margin-bottom: 0;
+  }
 
-      .text {
-        font-size: 16px;
-        line-height: 30px;
-        color: $darker-text-color;
+  &__brand {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 100px;
 
-        h6 {
-          font-size: inherit;
-          line-height: inherit;
-          margin-bottom: 0;
-        }
-      }
+    img {
+      height: 52px;
     }
 
-    @media screen and (min-width: $small-breakpoint) {
-      display: grid;
-      grid-gap: 30px;
-      grid-template-columns: 1fr 1fr;
-      grid-auto-columns: 50%;
-      grid-auto-rows: max-content;
+    @media screen and (max-width: $no-gap-breakpoint) {
+      padding: 0;
+      margin-bottom: 30px;
     }
   }
 
-  .footer-links {
-    padding-bottom: 50px;
-    text-align: right;
-    color: $dark-text-color;
+  .directory {
+    margin-top: 30px;
+    background: transparent;
+    box-shadow: none;
+    border-radius: 0;
+  }
 
-    p {
-      font-size: 14px;
-    }
+  .hero-widget {
+    margin-top: 30px;
+    margin-bottom: 0;
 
-    a {
-      color: inherit;
-      text-decoration: underline;
+    h4 {
+      padding: 10px;
+      text-transform: uppercase;
+      font-weight: 700;
+      font-size: 13px;
+      color: $darker-text-color;
     }
-  }
 
-  &__footer {
-    margin-top: 10px;
-    text-align: center;
-    color: $dark-text-color;
+    &__text {
+      border-radius: 0;
+      padding-bottom: 0;
+    }
 
-    p {
-      font-size: 14px;
+    &__footer {
+      background: $ui-base-color;
+      padding: 10px;
+      border-radius: 0 0 4px 4px;
+      display: flex;
 
-      a {
-        color: inherit;
-        text-decoration: underline;
+      &__column {
+        flex: 1 1 50%;
       }
     }
-  }
 
-  @media screen and (max-width: 840px) {
-    .container-alt {
-      padding: 0 20px;
-    }
+    .account {
+      padding: 10px 0;
+      border-bottom: 0;
 
-    .information-board {
-      .container-alt {
-        padding-right: 20px;
+      .account__display-name {
+        display: flex;
+        align-items: center;
       }
 
-      .panel {
-        position: static;
-        margin-top: 20px;
-        width: 100%;
-        border-radius: 4px;
-
-        .panel-header {
-          text-align: center;
-        }
+      .account__avatar {
+        width: 44px;
+        height: 44px;
+        background-size: 44px 44px;
       }
     }
-  }
 
-  @media screen and (max-width: 675px) {
-    .header-wrapper {
-      padding-top: 0;
+    &__counter {
+      padding: 10px;
 
-      &.compact {
-        padding-bottom: 0;
+      strong {
+        font-family: $font-display, sans-serif;
+        font-size: 15px;
+        font-weight: 700;
+        display: block;
       }
 
-      &.compact .hero .heading {
-        text-align: initial;
+      span {
+        font-size: 14px;
+        color: $darker-text-color;
       }
     }
+  }
 
-    .header .container-alt,
-    .features .container-alt {
-      display: block;
-    }
-
-    .header {
-      .links {
-        padding-top: 15px;
-        background: darken($ui-base-color, 4%);
+  .simple_form .user_agreement .label_input > label {
+    font-weight: 400;
+    color: $darker-text-color;
+  }
 
-        a {
-          padding: 12px 8px;
-        }
+  .simple_form p.lead {
+    color: $darker-text-color;
+    font-size: 15px;
+    line-height: 20px;
+    font-weight: 400;
+    margin-bottom: 25px;
+  }
 
-        .nav {
-          display: flex;
-          flex-flow: row wrap;
-          justify-content: space-around;
-        }
+  &__grid {
+    max-width: 960px;
+    margin: 0 auto;
+    display: grid;
+    grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+    grid-gap: 30px;
 
-        .brand img {
-          left: 0;
-          top: 0;
-        }
-      }
+    @media screen and (max-width: 738px) {
+      grid-template-columns: minmax(0, 100%);
+      grid-gap: 10px;
 
-      .hero {
-        margin-top: 30px;
-        padding: 0;
+      &__column-login {
+        grid-row: 1;
+        display: flex;
+        flex-direction: column;
 
-        .heading {
-          padding: 30px 20px;
-          text-align: center;
+        .box-widget {
+          order: 2;
+          flex: 0 0 auto;
         }
 
-        .simple_form,
-        .closed-registrations-message {
-          background: darken($ui-base-color, 8%);
-          width: 100%;
-          border-radius: 0;
-          box-sizing: border-box;
+        .hero-widget {
+          margin-top: 0;
+          margin-bottom: 10px;
+          order: 1;
+          flex: 0 0 auto;
         }
       }
-    }
-  }
-
-  .cta {
-    margin: 20px;
-  }
 
-  &.tag-page {
-    @media screen and (max-width: $column-breakpoint) {
-      padding: 0;
-
-      .container {
-        padding: 0;
+      &__column-registration {
+        grid-row: 2;
       }
 
-      #mastodon-timeline {
-        display: flex;
-        height: 100vh;
-        border-radius: 0;
+      .directory {
+        margin-top: 10px;
       }
     }
 
-    .grid {
-      @media screen and (min-width: $small-breakpoint) {
-        grid-template-columns: 33% 67%;
-      }
-
-      .column-2 {
-        grid-column: 2;
-        grid-row: 1;
-      }
-    }
+    @media screen and (max-width: $no-gap-breakpoint) {
+      grid-gap: 0;
 
-    .brand {
-      text-align: unset;
-      padding: 0;
+      .hero-widget {
+        display: block;
+        margin-bottom: 0;
+        box-shadow: none;
 
-      img {
-        height: 48px;
-        width: auto;
+        &__img,
+        &__img img,
+        &__footer {
+          border-radius: 0;
+        }
       }
-    }
 
-    .cta {
-      margin: 0;
-
-      .button {
-        margin-right: 4px;
+      .hero-widget,
+      .box-widget,
+      .directory__tag {
+        border-bottom: 1px solid lighten($ui-base-color, 8%);
       }
-    }
 
-    @media screen and (max-width: $column-breakpoint) {
-      .grid {
-        grid-gap: 0;
+      .directory {
+        margin-top: 0;
 
-        .column-1 {
-          grid-column: 1;
-          grid-row: 1;
-        }
+        &__tag {
+          margin-bottom: 0;
 
-        .column-2 {
-          display: none;
+          & > a,
+          & > div {
+            border-radius: 0;
+            box-shadow: none;
+          }
+
+          &:last-child {
+            border-bottom: 0;
+          }
         }
       }
     }
   }
 }
+
+.brand {
+  position: relative;
+  text-decoration: none;
+}
+
+.brand__tagline {
+  display: block;
+  position: absolute;
+  bottom: -10px;
+  left: 50px;
+  width: 300px;
+  color: $ui-primary-color;
+  text-decoration: none;
+  font-size: 14px;
+
+  @media screen and (max-width: $no-gap-breakpoint) {
+    position: static;
+    width: auto;
+    margin-top: 20px;
+    color: $dark-text-color;
+  }
+}
+
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index bab982706..6051c1d00 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -68,6 +68,17 @@ code {
         top: 2px;
         left: 0;
       }
+
+      label a {
+        color: $highlight-text-color;
+        text-decoration: underline;
+
+        &:hover,
+        &:active,
+        &:focus {
+          text-decoration: none;
+        }
+      }
     }
   }
 
@@ -305,7 +316,7 @@ code {
       box-shadow: none;
     }
 
-    &:focus:invalid {
+    &:focus:invalid:not(:placeholder-shown) {
       border-color: lighten($error-red, 12%);
     }
 
@@ -346,6 +357,10 @@ code {
     }
   }
 
+  .input.disabled {
+    opacity: 0.5;
+  }
+
   .actions {
     margin-top: 30px;
     display: flex;
@@ -392,6 +407,10 @@ code {
       background-color: darken($ui-highlight-color, 5%);
     }
 
+    &:disabled:hover {
+      background-color: $ui-primary-color;
+    }
+
     &.negative {
       background: $error-value-color;
 
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 1eaf30c5b..645192ea4 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -295,6 +295,11 @@
       cursor: default;
     }
 
+    &.disabled > div {
+      opacity: 0.5;
+      cursor: default;
+    }
+
     h4 {
       flex: 1 1 auto;
       font-size: 18px;