diff --git a/patched-files/README.md b/patched-files/README.md
new file mode 100644
index 0000000..190d7de
--- /dev/null
+++ b/patched-files/README.md
@@ -0,0 +1,19 @@
+# patched-files
+
+This folder contains some modified files (not patches) that I extracted
+from my dead instance. I was very happy with the recent color changed I made
+so you might want to have a look at the `oe7drt-blue` and `oe7drt-greeny`
+styles.
+
+## additional files/changes
+
+I've also played a bit with some other files. Those were primarily the
+`robots.txt` which I added a few paths to be ignored (haha! funny.
+fucking bots...) by web spiders/crawlers.
+
+I also increased the post character limit to 2000, aswell as the poll
+options to 12 and those options max-chars to 180.
+
+One patch changes the CSP to
+allow the use of Google Fonts (if you use the material theme (not in this
+repo)).
diff --git a/patched-files/app/javascript/mastodon/features/compose/components/compose_form.js b/patched-files/app/javascript/mastodon/features/compose/components/compose_form.js
new file mode 100644
index 0000000..7aab424
--- /dev/null
+++ b/patched-files/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -0,0 +1,301 @@
+import React from 'react';
+import CharacterCounter from './character_counter';
+import Button from '../../../components/button';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import ReplyIndicatorContainer from '../containers/reply_indicator_container';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
+import AutosuggestInput from '../../../components/autosuggest_input';
+import PollButtonContainer from '../containers/poll_button_container';
+import UploadButtonContainer from '../containers/upload_button_container';
+import { defineMessages, injectIntl } from 'react-intl';
+import SpoilerButtonContainer from '../containers/spoiler_button_container';
+import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
+import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
+import PollFormContainer from '../containers/poll_form_container';
+import UploadFormContainer from '../containers/upload_form_container';
+import WarningContainer from '../containers/warning_container';
+import LanguageDropdown from '../containers/language_dropdown_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { length } from 'stringz';
+import { countableText } from '../util/counter';
+import Icon from 'mastodon/components/icon';
+
+const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
+
+const messages = defineMessages({
+ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
+ spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
+ publish: { id: 'compose_form.publish', defaultMessage: 'Publish' },
+ publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
+ saveChanges: { id: 'compose_form.save_changes', defaultMessage: 'Save changes' },
+});
+
+export default @injectIntl
+class ComposeForm extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ text: PropTypes.string.isRequired,
+ suggestions: ImmutablePropTypes.list,
+ spoiler: PropTypes.bool,
+ privacy: PropTypes.string,
+ spoilerText: PropTypes.string,
+ focusDate: PropTypes.instanceOf(Date),
+ caretPosition: PropTypes.number,
+ preselectDate: PropTypes.instanceOf(Date),
+ isSubmitting: PropTypes.bool,
+ isChangingUpload: PropTypes.bool,
+ isEditing: PropTypes.bool,
+ isUploading: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onClearSuggestions: PropTypes.func.isRequired,
+ onFetchSuggestions: PropTypes.func.isRequired,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onChangeSpoilerText: PropTypes.func.isRequired,
+ onPaste: PropTypes.func.isRequired,
+ onPickEmoji: PropTypes.func.isRequired,
+ autoFocus: PropTypes.bool,
+ anyMedia: PropTypes.bool,
+ isInReply: PropTypes.bool,
+ singleColumn: PropTypes.bool,
+ lang: PropTypes.string,
+ };
+
+ static defaultProps = {
+ autoFocus: false,
+ };
+
+ handleChange = (e) => {
+ this.props.onChange(e.target.value);
+ };
+
+ handleKeyDown = (e) => {
+ if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+ this.handleSubmit();
+ }
+ };
+
+ getFulltextForCharacterCounting = () => {
+ return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join('');
+ };
+
+ canSubmit = () => {
+ const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
+ const fulltext = this.getFulltextForCharacterCounting();
+ const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
+
+ return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 2000 || (isOnlyWhitespace && !anyMedia));
+ };
+
+ handleSubmit = (e) => {
+ if (this.props.text !== this.autosuggestTextarea.textarea.value) {
+ // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
+ // Update the state to match the current text
+ this.props.onChange(this.autosuggestTextarea.textarea.value);
+ }
+
+ if (!this.canSubmit()) {
+ return;
+ }
+
+ this.props.onSubmit(this.context.router ? this.context.router.history : null);
+
+ if (e) {
+ e.preventDefault();
+ }
+ };
+
+ onSuggestionsClearRequested = () => {
+ this.props.onClearSuggestions();
+ };
+
+ onSuggestionsFetchRequested = (token) => {
+ this.props.onFetchSuggestions(token);
+ };
+
+ onSuggestionSelected = (tokenStart, token, value) => {
+ this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
+ };
+
+ onSpoilerSuggestionSelected = (tokenStart, token, value) => {
+ this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
+ };
+
+ handleChangeSpoilerText = (e) => {
+ this.props.onChangeSpoilerText(e.target.value);
+ };
+
+ handleFocus = () => {
+ if (this.composeForm && !this.props.singleColumn) {
+ const { left, right } = this.composeForm.getBoundingClientRect();
+ if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
+ this.composeForm.scrollIntoView();
+ }
+ }
+ };
+
+ componentDidMount () {
+ this._updateFocusAndSelection({ });
+ }
+
+ componentDidUpdate (prevProps) {
+ this._updateFocusAndSelection(prevProps);
+ }
+
+ _updateFocusAndSelection = (prevProps) => {
+ // This statement does several things:
+ // - If we're beginning a reply, and,
+ // - Replying to zero or one users, places the cursor at the end of the textbox.
+ // - Replying to more than one user, selects any usernames past the first;
+ // this provides a convenient shortcut to drop everyone else from the conversation.
+ if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) {
+ let selectionEnd, selectionStart;
+
+ if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply) {
+ selectionEnd = this.props.text.length;
+ selectionStart = this.props.text.search(/\s/) + 1;
+ } else if (typeof this.props.caretPosition === 'number') {
+ selectionStart = this.props.caretPosition;
+ selectionEnd = this.props.caretPosition;
+ } else {
+ selectionEnd = this.props.text.length;
+ selectionStart = selectionEnd;
+ }
+
+ // Because of the wicg-inert polyfill, the activeElement may not be
+ // immediately selectable, we have to wait for observers to run, as
+ // described in https://github.com/WICG/inert#performance-and-gotchas
+ Promise.resolve().then(() => {
+ this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
+ this.autosuggestTextarea.textarea.focus();
+ }).catch(console.error);
+ } else if(prevProps.isSubmitting && !this.props.isSubmitting) {
+ this.autosuggestTextarea.textarea.focus();
+ } else if (this.props.spoiler !== prevProps.spoiler) {
+ if (this.props.spoiler) {
+ this.spoilerText.input.focus();
+ } else if (prevProps.spoiler) {
+ this.autosuggestTextarea.textarea.focus();
+ }
+ }
+ };
+
+ setAutosuggestTextarea = (c) => {
+ this.autosuggestTextarea = c;
+ };
+
+ setSpoilerText = (c) => {
+ this.spoilerText = c;
+ };
+
+ setRef = c => {
+ this.composeForm = c;
+ };
+
+ handleEmojiPick = (data) => {
+ const { text } = this.props;
+ const position = this.autosuggestTextarea.textarea.selectionStart;
+ const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]);
+
+ this.props.onPickEmoji(position, data, needsSpace);
+ };
+
+ render () {
+ const { intl, onPaste, autoFocus } = this.props;
+ const disabled = this.props.isSubmitting;
+
+ let publishText = '';
+
+ if (this.props.isEditing) {
+ publishText = intl.formatMessage(messages.saveChanges);
+ } else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
+ publishText = {intl.formatMessage(messages.publish)};
+ } else {
+ publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
+ }
+
+ return (
+
+ );
+ }
+
+}
diff --git a/patched-files/app/javascript/styles/boost.scss b/patched-files/app/javascript/styles/boost.scss
new file mode 100644
index 0000000..67f9a85
--- /dev/null
+++ b/patched-files/app/javascript/styles/boost.scss
@@ -0,0 +1,61 @@
+@function hex-color($color) {
+ @if type-of($color) == 'color' {
+ $color: str-slice(ie-hex-str($color), 4);
+ }
+ @return '%23' + unquote($color)
+}
+
+@mixin boost-svg($color) {
+ background-image: url("data:image/svg+xml;utf8,");
+}
+
+@mixin boost-locked-svg($color) {
+ background-image: url("data:image/svg+xml;utf8,");
+}
+
+@mixin boost-svg-single($color) {
+ background-image: url("data:image/svg+xml;utf8,");
+}
+
+@mixin boost-locked-svg-single($color) {
+ background-image: url("data:image/svg+xml;utf8,");
+}
+
+@mixin envelope($color) {
+ background-image: url("data:image/svg+xml;utf8,");
+}
+
+button.icon-button i.fa-retweet {
+ @include boost-svg-single($ui-base-lighter-color);
+}
+
+.status-private button.icon-button i.fa-retweet {
+ @include boost-locked-svg-single($ui-base-lighter-color);
+}
+
+// Disabled variant
+button.icon-button.disabled i.fa-retweet {
+ @include boost-locked-svg-single(lighten($ui-base-color, 13%));
+}
+
+// Disabled variant for use with DMs
+.status-direct button.icon-button.disabled i.fa-retweet {
+ @include envelope(lighten($ui-base-color, 16%));
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+.no-reduce-motion button.icon-button i.fa-retweet {
+ transition: none;
+ background-position: 0px 684px;
+}
+
+.no-reduce-motion button.icon-button.active i.fa-retweet {
+ @include boost-svg($ui-highlight-color);
+ transition: background-position 0.6s steps(36);
+ background-size: 22px 684px;
+ background-position: 0px 0px;
+}
+.no-reduce-motion .status-private button.icon-button.active i.fa-retweet {
+ @include boost-locked-svg($ui-highlight-color);
+}
diff --git a/patched-files/app/javascript/styles/coffee-dark.scss b/patched-files/app/javascript/styles/coffee-dark.scss
new file mode 100644
index 0000000..6d61398
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-dark.scss
@@ -0,0 +1,11 @@
+@import 'coffee-dark/variables';
+@import 'application';
+@import 'coffee-dark/diff';
+//@import 'boost';
+//@import 'mods/display_browserfont';
+@import 'mods/display_breakname';
+@import 'mods/display_fullname';
+@import 'mods/display_emojizoom';
+//@import 'mods/display_circleavatar';
+@import 'mods/layout_1600px';
+@import 'mods/layout_widercolumns';
diff --git a/patched-files/app/javascript/styles/coffee-dark/diff.scss b/patched-files/app/javascript/styles/coffee-dark/diff.scss
new file mode 100644
index 0000000..b612b87
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-dark/diff.scss
@@ -0,0 +1,77 @@
+// components.scss
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload {
+ &-description {
+ input {
+ &::placeholder {
+ opacity: 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+.rich-formatting a,
+.rich-formatting p a,
+.rich-formatting li a,
+.landing-page__short-description p a,
+.status__content a,
+.reply-indicator__content a {
+ color: lighten($ui-highlight-color, 12%);
+ text-decoration: none;
+
+ &.mention {
+ text-decoration: none;
+ }
+
+ &.mention span {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+
+ &.status__content__spoiler-link {
+ color: $secondary-text-color;
+ text-decoration: none;
+ }
+}
+
+.status__content__read-more-button {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.getting-started__footer a {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.nothing-here {
+ color: $darker-text-color;
+}
+
+.public-layout .public-account-header__tabs__tabs .counter.active::after {
+ border-bottom: 4px solid $ui-highlight-color;
+}
diff --git a/patched-files/app/javascript/styles/coffee-dark/variables.scss b/patched-files/app/javascript/styles/coffee-dark/variables.scss
new file mode 100644
index 0000000..9b7eb2e
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-dark/variables.scss
@@ -0,0 +1,56 @@
+// Commonly used web colors
+$black: #000000; // Black
+$white: #ffffff; // White
+$success-green: #79bd9a !default; // Padua
+$error-red: #df405a !default; // Cerise
+$warning-red: #ff5050 !default; // Sunset Orange
+$gold-star: #ca8f04 !default; // Dark Goldenrod
+
+$red-bookmark: $warning-red;
+
+// Values from the classic Mastodon UI
+$classic-base-color: #282c37; // Midnight Express
+$classic-primary-color: #9baec8; // Echo Blue
+$classic-secondary-color: #d9e1e8; // Pattens Blue
+$classic-highlight-color: #e7b01c; // Summer Sky
+
+// Variables for defaults in UI
+$base-shadow-color: $black !default;
+$base-overlay-background: $black !default;
+$base-border-color: $white !default;
+$simple-background-color: $white !default;
+$valid-value-color: $success-green !default;
+$error-value-color: $error-red !default;
+
+// Tell UI to use selected colors
+$ui-base-color: $classic-base-color !default; // Darkest
+$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
+$ui-primary-color: $classic-primary-color !default; // Lighter
+$ui-secondary-color: $classic-secondary-color !default; // Lightest
+$ui-highlight-color: $classic-highlight-color !default;
+
+// Variables for texts
+$primary-text-color: $white !default;
+$darker-text-color: $ui-primary-color !default;
+$dark-text-color: $ui-base-lighter-color !default;
+$secondary-text-color: $ui-secondary-color !default;
+$highlight-text-color: $ui-highlight-color !default;
+$action-button-color: $ui-base-lighter-color !default;
+// For texts on inverted backgrounds
+$inverted-text-color: $ui-base-color !default;
+$lighter-text-color: $ui-base-lighter-color !default;
+$light-text-color: $ui-primary-color !default;
+
+// Language codes that uses CJK fonts
+$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+
+// Variables for components
+$media-modal-media-max-width: 100%;
+// put margins on top and bottom of image to avoid the screen covered by image.
+$media-modal-media-max-height: 80%;
+
+$no-gap-breakpoint: 415px;
+
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;
diff --git a/patched-files/app/javascript/styles/coffee-light.scss b/patched-files/app/javascript/styles/coffee-light.scss
new file mode 100644
index 0000000..118721f
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-light.scss
@@ -0,0 +1,6 @@
+@import 'coffee-light/variables';
+@import 'application';
+@import 'coffee-light/diff';
+//@import 'boost';
+//@import 'mods/display_fullname';
+@import 'mods/display_circleavatar';
diff --git a/patched-files/app/javascript/styles/coffee-light/diff.scss b/patched-files/app/javascript/styles/coffee-light/diff.scss
new file mode 100644
index 0000000..7a846bc
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-light/diff.scss
@@ -0,0 +1,776 @@
+// Notes!
+// Sass color functions, "darken" and "lighten" are automatically replaced.
+
+html {
+ scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25);
+}
+
+// Change the colors of button texts
+.button {
+ color: $white;
+
+ &.button-alternative-2 {
+ color: $white;
+ }
+}
+
+.status-card__actions button,
+.status-card__actions a {
+ color: rgba($white, 0.8);
+
+ &:hover,
+ &:active,
+ &:focus {
+ color: $white;
+ }
+}
+
+// Change default background colors of columns
+.column > .scrollable,
+.getting-started,
+.column-inline-form,
+.error-column,
+.regeneration-indicator {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.directory__card__img {
+ background: lighten($ui-base-color, 12%);
+}
+
+.filter-form,
+.directory__card__bar {
+ background: $white;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+}
+
+.scrollable .directory__list {
+ width: calc(100% + 2px);
+ margin-left: -1px;
+ margin-right: -1px;
+}
+
+.directory__card,
+.table-of-contents {
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.column-back-button,
+.column-header {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+
+ &--slim-button {
+ top: -50px;
+ right: 0;
+ }
+}
+
+.column-header__back-button,
+.column-header__button,
+.column-header__button.active,
+.account__header__bar,
+.directory__card__extra {
+ background: $white;
+}
+
+.column-header__button.active {
+ color: $ui-highlight-color;
+
+ &:hover,
+ &:active,
+ &:focus {
+ color: $ui-highlight-color;
+ background: $white;
+ }
+}
+
+.account__header__bar .avatar .account__avatar {
+ border-color: $white;
+}
+
+.getting-started__footer a {
+ color: $ui-secondary-color;
+ text-decoration: underline;
+}
+
+.confirmation-modal__secondary-button,
+.confirmation-modal__cancel-button,
+.mute-modal__cancel-button,
+.block-modal__cancel-button {
+ color: lighten($ui-base-color, 26%);
+
+ &:hover,
+ &:focus,
+ &:active {
+ color: $primary-text-color;
+ }
+}
+
+.column-subheading {
+ background: darken($ui-base-color, 4%);
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+}
+
+.getting-started,
+.scrollable {
+ .column-link {
+ background: $white;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+ }
+}
+
+.getting-started .navigation-bar {
+ border-top: 1px solid lighten($ui-base-color, 8%);
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+}
+
+.compose-form__autosuggest-wrapper,
+.poll__option input[type="text"],
+.compose-form .spoiler-input__input,
+.compose-form__poll-wrapper select,
+.search__input,
+.setting-text,
+.box-widget input[type="text"],
+.box-widget input[type="email"],
+.box-widget input[type="password"],
+.box-widget textarea,
+.statuses-grid .detailed-status,
+.audio-player {
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.search__input {
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ border-bottom: 0;
+ }
+}
+
+.list-editor .search .search__input {
+ border-top: 0;
+ border-bottom: 0;
+}
+
+.compose-form__poll-wrapper select {
+ background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px;
+}
+
+.compose-form__poll-wrapper,
+.compose-form__poll-wrapper .poll__footer {
+ border-top-color: lighten($ui-base-color, 8%);
+}
+
+.notification__filter-bar {
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.compose-form .compose-form__buttons-wrapper {
+ background: $ui-base-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.drawer__header,
+.drawer__inner {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.drawer__inner__mastodon {
+ background: $white url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto;
+}
+
+// Change the colors used in compose-form
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload__actions .icon-button {
+ color: lighten($white, 7%);
+
+ &:active,
+ &:focus,
+ &:hover {
+ color: $white;
+ }
+ }
+
+ .compose-form__upload-description input {
+ color: lighten($white, 7%);
+
+ &::placeholder {
+ color: lighten($white, 7%);
+ }
+ }
+ }
+
+ .compose-form__buttons-wrapper {
+ background: darken($ui-base-color, 6%);
+ }
+
+ .autosuggest-textarea__suggestions {
+ background: darken($ui-base-color, 6%);
+ }
+
+ .autosuggest-textarea__suggestions__item {
+ &:hover,
+ &:focus,
+ &:active,
+ &.selected {
+ background: lighten($ui-base-color, 4%);
+ }
+ }
+}
+
+.emoji-mart-bar {
+ border-color: lighten($ui-base-color, 4%);
+
+ &:first-child {
+ background: darken($ui-base-color, 6%);
+ }
+}
+
+.emoji-mart-search input {
+ background: rgba($ui-base-color, 0.3);
+ border-color: $ui-base-color;
+}
+
+// Change the background colors of statuses
+.focusable:focus {
+ background: $ui-base-color;
+}
+
+.status.status-direct {
+ background: lighten($ui-base-color, 4%);
+}
+
+.focusable:focus .status.status-direct {
+ background: lighten($ui-base-color, 8%);
+}
+
+.detailed-status,
+.detailed-status__action-bar {
+ background: $white;
+}
+
+// Change the background colors of status__content__spoiler-link
+.reply-indicator__content .status__content__spoiler-link,
+.status__content .status__content__spoiler-link {
+ background: $ui-base-color;
+
+ &:hover {
+ background: lighten($ui-base-color, 4%);
+ }
+}
+
+// Change the background colors of media and video spoilers
+.media-spoiler,
+.video-player__spoiler {
+ background: $ui-base-color;
+}
+
+.privacy-dropdown.active .privacy-dropdown__value.active .icon-button {
+ color: $white;
+}
+
+.account-gallery__item a {
+ background-color: $ui-base-color;
+}
+
+// Change the colors used in the dropdown menu
+.dropdown-menu {
+ background: $white;
+
+ &__arrow {
+ &.left {
+ border-left-color: $white;
+ }
+
+ &.top {
+ border-top-color: $white;
+ }
+
+ &.bottom {
+ border-bottom-color: $white;
+ }
+
+ &.right {
+ border-right-color: $white;
+ }
+ }
+
+ &__item {
+ a {
+ background: $white;
+ color: $darker-text-color;
+ }
+ }
+}
+
+// Change the text colors on inverted background
+.privacy-dropdown__option.active,
+.privacy-dropdown__option:hover,
+.privacy-dropdown__option.active .privacy-dropdown__option__content,
+.privacy-dropdown__option.active .privacy-dropdown__option__content strong,
+.privacy-dropdown__option:hover .privacy-dropdown__option__content,
+.privacy-dropdown__option:hover .privacy-dropdown__option__content strong,
+.dropdown-menu__item a:active,
+.dropdown-menu__item a:focus,
+.dropdown-menu__item a:hover,
+.actions-modal ul li:not(:empty) a.active,
+.actions-modal ul li:not(:empty) a.active button,
+.actions-modal ul li:not(:empty) a:active,
+.actions-modal ul li:not(:empty) a:active button,
+.actions-modal ul li:not(:empty) a:focus,
+.actions-modal ul li:not(:empty) a:focus button,
+.actions-modal ul li:not(:empty) a:hover,
+.actions-modal ul li:not(:empty) a:hover button,
+.admin-wrapper .sidebar ul .simple-navigation-active-leaf a,
+.simple_form .block-button,
+.simple_form .button,
+.simple_form button {
+ color: $white;
+}
+
+.dropdown-menu__separator {
+ border-bottom-color: lighten($ui-base-color, 4%);
+}
+
+// Change the background colors of modals
+.actions-modal,
+.boost-modal,
+.confirmation-modal,
+.mute-modal,
+.block-modal,
+.report-modal,
+.embed-modal,
+.error-modal,
+.onboarding-modal,
+.report-modal__comment .setting-text__wrapper,
+.report-modal__comment .setting-text {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.report-modal__comment {
+ border-right-color: lighten($ui-base-color, 8%);
+}
+
+.report-modal__container {
+ border-top-color: lighten($ui-base-color, 8%);
+}
+
+.column-header__collapsible-inner {
+ background: darken($ui-base-color, 4%);
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.focal-point__preview strong {
+ color: $white;
+}
+
+.boost-modal__action-bar,
+.confirmation-modal__action-bar,
+.mute-modal__action-bar,
+.block-modal__action-bar,
+.onboarding-modal__paginator,
+.error-modal__footer {
+ background: darken($ui-base-color, 6%);
+
+ .onboarding-modal__nav,
+ .error-modal__nav {
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: darken($ui-base-color, 12%);
+ }
+ }
+}
+
+.display-case__case {
+ background: $white;
+}
+
+.embed-modal .embed-modal__container .embed-modal__html {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ &:focus {
+ border-color: lighten($ui-base-color, 12%);
+ background: $white;
+ }
+}
+
+.react-toggle-track {
+ background: $ui-secondary-color;
+}
+
+.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background: darken($ui-secondary-color, 10%);
+}
+
+.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background: lighten($ui-highlight-color, 10%);
+}
+
+// Change the default color used for the text in an empty column or on the error column
+.empty-column-indicator,
+.error-column {
+ color: $primary-text-color;
+ background: $white;
+}
+
+.tabs-bar {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-bottom: 0;
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+
+ &__link {
+ padding-bottom: 14px;
+ border-bottom-width: 1px;
+ border-bottom-color: lighten($ui-base-color, 8%);
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+
+ &.active {
+ &:hover,
+ &:active,
+ &:focus {
+ background: transparent;
+ border-bottom-color: $ui-highlight-color;
+ }
+ }
+ }
+}
+
+// Change the default colors used on some parts of the profile pages
+.activity-stream-tabs {
+ background: $account-background-color;
+ border-bottom-color: lighten($ui-base-color, 8%);
+}
+
+.box-widget,
+.nothing-here,
+.page-header,
+.directory__tag > a,
+.directory__tag > div,
+.landing-page__call-to-action,
+.contact-widget,
+.landing .hero-widget__text,
+.landing-page__information.contact-widget {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-left: 0;
+ border-right: 0;
+ border-top: 0;
+ }
+}
+
+.landing .hero-widget__text {
+ border-top: 0;
+ border-bottom: 0;
+}
+
+.simple_form {
+ input[type=text],
+ input[type=number],
+ input[type=email],
+ input[type=password],
+ textarea {
+ &:hover {
+ border-color: lighten($ui-base-color, 12%);
+ }
+ }
+}
+
+.landing .hero-widget__footer {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+}
+
+.brand__tagline {
+ color: $ui-secondary-color;
+}
+
+.directory__tag > a {
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+}
+
+.directory__tag.active > a,
+.directory__tag.active > div {
+ border-color: $ui-highlight-color;
+
+ &,
+ h4,
+ h4 small,
+ .fa,
+ .trends__item__current {
+ color: $white;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-highlight-color;
+ }
+}
+
+.batch-table {
+ &__toolbar,
+ &__row,
+ .nothing-here {
+ border-color: lighten($ui-base-color, 8%);
+ }
+}
+
+.activity-stream {
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ &--under-tabs {
+ border-top: 0;
+ }
+
+ .entry {
+ background: $account-background-color;
+
+ .detailed-status.light,
+ .more.light,
+ .status.light {
+ border-bottom-color: lighten($ui-base-color, 8%);
+ }
+ }
+
+ .status.light {
+ .status__content {
+ color: $primary-text-color;
+ }
+
+ .display-name {
+ strong {
+ color: $primary-text-color;
+ }
+ }
+ }
+}
+
+.accounts-grid {
+ .account-grid-card {
+ .controls {
+ .icon-button {
+ color: $darker-text-color;
+ }
+ }
+
+ .name {
+ a {
+ color: $primary-text-color;
+ }
+ }
+
+ .username {
+ color: $darker-text-color;
+ }
+
+ .account__header__content {
+ color: $primary-text-color;
+ }
+ }
+}
+
+.simple_form,
+.table-form {
+ .warning {
+ box-shadow: none;
+ background: rgba($error-red, 0.5);
+ text-shadow: none;
+ }
+
+ .recommended {
+ border-color: $ui-highlight-color;
+ color: $ui-highlight-color;
+ background-color: rgba($ui-highlight-color, 0.1);
+ }
+}
+
+.compose-form .compose-form__warning {
+ border-color: $ui-highlight-color;
+ background-color: rgba($ui-highlight-color, 0.1);
+
+ &,
+ a {
+ color: $ui-highlight-color;
+ }
+}
+
+.status__content,
+.reply-indicator__content {
+ a {
+ color: $highlight-text-color;
+ }
+}
+
+.button.logo-button {
+ color: $white;
+
+ svg {
+ fill: $white;
+ }
+}
+
+.public-layout {
+ .account__section-headline {
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+ }
+
+ .header,
+ .public-account-header,
+ .public-account-bio {
+ box-shadow: none;
+ }
+
+ .public-account-bio,
+ .hero-widget__text {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ }
+
+ .header {
+ background: $ui-base-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+
+ .brand {
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 4%);
+ }
+ }
+ }
+
+ .public-account-header {
+ &__image {
+ background: lighten($ui-base-color, 12%);
+
+ &::after {
+ box-shadow: none;
+ }
+ }
+
+ &__bar {
+ &::before {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+ }
+
+ .avatar img {
+ border-color: $account-background-color;
+ }
+
+ @media screen and (max-width: $no-columns-breakpoint) {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+ }
+ }
+
+ &__tabs {
+ &__name {
+ h1,
+ h1 small {
+ color: $white;
+
+ @media screen and (max-width: $no-columns-breakpoint) {
+ color: $primary-text-color;
+ }
+ }
+ }
+ }
+
+ &__extra {
+ .public-account-bio {
+ border: 0;
+ }
+
+ .public-account-bio .account__header__fields {
+ border-color: lighten($ui-base-color, 8%);
+ }
+ }
+ }
+}
+
+.notification__filter-bar button.active::after,
+.account__section-headline a.active::after {
+ border-color: transparent transparent $white;
+}
+
+.hero-widget,
+.box-widget,
+.contact-widget,
+.landing-page__information.contact-widget,
+.moved-account-widget,
+.memoriam-widget,
+.activity-stream,
+.nothing-here,
+.directory__tag > a,
+.directory__tag > div,
+.card > a,
+.page-header,
+.compose-form .compose-form__warning {
+ box-shadow: none;
+}
+
+.audio-player .video-player__controls button,
+.audio-player .video-player__time-sep,
+.audio-player .video-player__time-current,
+.audio-player .video-player__time-total {
+ color: $primary-text-color;
+}
diff --git a/patched-files/app/javascript/styles/coffee-light/variables.scss b/patched-files/app/javascript/styles/coffee-light/variables.scss
new file mode 100644
index 0000000..5faae83
--- /dev/null
+++ b/patched-files/app/javascript/styles/coffee-light/variables.scss
@@ -0,0 +1,41 @@
+// Dependent colors
+$black: #000000;
+$white: #ffffff;
+
+$classic-base-color: #282c37;
+$classic-primary-color: #9baec8;
+$classic-secondary-color: #d9e1e8;
+$classic-highlight-color: #e7b01c;
+
+// Differences
+$success-green: lighten(#3c754d, 8%);
+
+$base-overlay-background: $white !default;
+$valid-value-color: $success-green !default;
+
+$ui-base-color: $classic-secondary-color !default;
+$ui-base-lighter-color: #b0c0cf;
+$ui-primary-color: #9bcbed;
+$ui-secondary-color: $classic-base-color !default;
+$ui-highlight-color: #e7b01c;
+
+$primary-text-color: $black !default;
+$darker-text-color: $classic-base-color !default;
+$dark-text-color: #444b5d;
+$action-button-color: #606984;
+
+$inverted-text-color: $black !default;
+$lighter-text-color: $classic-base-color !default;
+$light-text-color: #444b5d;
+
+//Newly added colors
+$account-background-color: $white !default;
+
+//Invert darkened and lightened colors
+@function darken($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) + $amount);
+}
+
+@function lighten($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) - $amount);
+}
diff --git a/patched-files/app/javascript/styles/dark-red.scss b/patched-files/app/javascript/styles/dark-red.scss
new file mode 100644
index 0000000..60c35c0
--- /dev/null
+++ b/patched-files/app/javascript/styles/dark-red.scss
@@ -0,0 +1,3 @@
+@import 'dark-red/variables';
+@import 'application';
+@import 'dark-red/diff';
diff --git a/patched-files/app/javascript/styles/dark-red/diff.scss b/patched-files/app/javascript/styles/dark-red/diff.scss
new file mode 100644
index 0000000..b612b87
--- /dev/null
+++ b/patched-files/app/javascript/styles/dark-red/diff.scss
@@ -0,0 +1,77 @@
+// components.scss
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload {
+ &-description {
+ input {
+ &::placeholder {
+ opacity: 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+.rich-formatting a,
+.rich-formatting p a,
+.rich-formatting li a,
+.landing-page__short-description p a,
+.status__content a,
+.reply-indicator__content a {
+ color: lighten($ui-highlight-color, 12%);
+ text-decoration: none;
+
+ &.mention {
+ text-decoration: none;
+ }
+
+ &.mention span {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+
+ &.status__content__spoiler-link {
+ color: $secondary-text-color;
+ text-decoration: none;
+ }
+}
+
+.status__content__read-more-button {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.getting-started__footer a {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.nothing-here {
+ color: $darker-text-color;
+}
+
+.public-layout .public-account-header__tabs__tabs .counter.active::after {
+ border-bottom: 4px solid $ui-highlight-color;
+}
diff --git a/patched-files/app/javascript/styles/dark-red/variables.scss b/patched-files/app/javascript/styles/dark-red/variables.scss
new file mode 100644
index 0000000..d8456fe
--- /dev/null
+++ b/patched-files/app/javascript/styles/dark-red/variables.scss
@@ -0,0 +1,56 @@
+// Commonly used web colors
+$black: #000000; // Black
+$white: #ffffff; // White
+$success-green: #79bd9a !default; // Padua
+$error-red: #df405a !default; // Cerise
+$warning-red: #ff5050 !default; // Sunset Orange
+$gold-star: #ca8f04 !default; // Dark Goldenrod
+
+$red-bookmark: $warning-red;
+
+// Values from the classic Mastodon UI
+$classic-base-color: #282c37; // Midnight Express
+$classic-primary-color: #9baec8; // Echo Blue
+$classic-secondary-color: #d9e1e8; // Pattens Blue
+$classic-highlight-color: #d92b2b; // Summer Sky
+
+// Variables for defaults in UI
+$base-shadow-color: $black !default;
+$base-overlay-background: $black !default;
+$base-border-color: $white !default;
+$simple-background-color: $white !default;
+$valid-value-color: $success-green !default;
+$error-value-color: $error-red !default;
+
+// Tell UI to use selected colors
+$ui-base-color: $classic-base-color !default; // Darkest
+$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
+$ui-primary-color: $classic-primary-color !default; // Lighter
+$ui-secondary-color: $classic-secondary-color !default; // Lightest
+$ui-highlight-color: $classic-highlight-color !default;
+
+// Variables for texts
+$primary-text-color: $white !default;
+$darker-text-color: $ui-primary-color !default;
+$dark-text-color: $ui-base-lighter-color !default;
+$secondary-text-color: $ui-secondary-color !default;
+$highlight-text-color: $ui-highlight-color !default;
+$action-button-color: $ui-base-lighter-color !default;
+// For texts on inverted backgrounds
+$inverted-text-color: $ui-base-color !default;
+$lighter-text-color: $ui-base-lighter-color !default;
+$light-text-color: $ui-primary-color !default;
+
+// Language codes that uses CJK fonts
+$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+
+// Variables for components
+$media-modal-media-max-width: 100%;
+// put margins on top and bottom of image to avoid the screen covered by image.
+$media-modal-media-max-height: 80%;
+
+$no-gap-breakpoint: 415px;
+
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;
diff --git a/patched-files/app/javascript/styles/fullwidth-media.scss b/patched-files/app/javascript/styles/fullwidth-media.scss
new file mode 100644
index 0000000..f6a036d
--- /dev/null
+++ b/patched-files/app/javascript/styles/fullwidth-media.scss
@@ -0,0 +1,48 @@
+
+.detailed-status > .media-spoiler,
+.status > .media-spoiler,
+.status .video-player,
+.media-gallery,
+.status .status-card.interactive {
+ margin-top: 20px;
+ margin-left: -68px;
+ width: calc(100% + 80px);
+}
+
+.detailed-status > .media-spoiler,
+.status > .media-spoiler,
+.video-player {
+ max-width: none;
+}
+
+/* If there's no status text, add an extra margin on top */
+.status .status__info + .media-gallery,
+.status .status__info + .media-spoiler,
+.status .status__info + .video-player,
+.status .status__info + .status-card {
+ margin-top: 40px;
+}
+
+.status__video-player-video {
+ transform: unset;
+ top: unset;
+}
+
+.detailed-status .media-gallery {
+ margin-left: -10px;
+ width: calc(100% + 22px);
+}
+
+.public-layout .status {
+ .status__content {
+ min-height: 15px;
+ }
+ & > .media-spoiler,
+ .video-player,
+ .media-gallery,
+ .status-card {
+ margin-top: 20px;
+ width: calc(100% + 94px);
+ margin-left: -78px;
+ }
+}
diff --git a/patched-files/app/javascript/styles/light-red.scss b/patched-files/app/javascript/styles/light-red.scss
new file mode 100644
index 0000000..6969822
--- /dev/null
+++ b/patched-files/app/javascript/styles/light-red.scss
@@ -0,0 +1,3 @@
+@import 'light-red/variables';
+@import 'application';
+@import 'light-red/diff';
diff --git a/patched-files/app/javascript/styles/light-red/diff.scss b/patched-files/app/javascript/styles/light-red/diff.scss
new file mode 100644
index 0000000..7a846bc
--- /dev/null
+++ b/patched-files/app/javascript/styles/light-red/diff.scss
@@ -0,0 +1,776 @@
+// Notes!
+// Sass color functions, "darken" and "lighten" are automatically replaced.
+
+html {
+ scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25);
+}
+
+// Change the colors of button texts
+.button {
+ color: $white;
+
+ &.button-alternative-2 {
+ color: $white;
+ }
+}
+
+.status-card__actions button,
+.status-card__actions a {
+ color: rgba($white, 0.8);
+
+ &:hover,
+ &:active,
+ &:focus {
+ color: $white;
+ }
+}
+
+// Change default background colors of columns
+.column > .scrollable,
+.getting-started,
+.column-inline-form,
+.error-column,
+.regeneration-indicator {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.directory__card__img {
+ background: lighten($ui-base-color, 12%);
+}
+
+.filter-form,
+.directory__card__bar {
+ background: $white;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+}
+
+.scrollable .directory__list {
+ width: calc(100% + 2px);
+ margin-left: -1px;
+ margin-right: -1px;
+}
+
+.directory__card,
+.table-of-contents {
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.column-back-button,
+.column-header {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+
+ &--slim-button {
+ top: -50px;
+ right: 0;
+ }
+}
+
+.column-header__back-button,
+.column-header__button,
+.column-header__button.active,
+.account__header__bar,
+.directory__card__extra {
+ background: $white;
+}
+
+.column-header__button.active {
+ color: $ui-highlight-color;
+
+ &:hover,
+ &:active,
+ &:focus {
+ color: $ui-highlight-color;
+ background: $white;
+ }
+}
+
+.account__header__bar .avatar .account__avatar {
+ border-color: $white;
+}
+
+.getting-started__footer a {
+ color: $ui-secondary-color;
+ text-decoration: underline;
+}
+
+.confirmation-modal__secondary-button,
+.confirmation-modal__cancel-button,
+.mute-modal__cancel-button,
+.block-modal__cancel-button {
+ color: lighten($ui-base-color, 26%);
+
+ &:hover,
+ &:focus,
+ &:active {
+ color: $primary-text-color;
+ }
+}
+
+.column-subheading {
+ background: darken($ui-base-color, 4%);
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+}
+
+.getting-started,
+.scrollable {
+ .column-link {
+ background: $white;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+ }
+}
+
+.getting-started .navigation-bar {
+ border-top: 1px solid lighten($ui-base-color, 8%);
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+}
+
+.compose-form__autosuggest-wrapper,
+.poll__option input[type="text"],
+.compose-form .spoiler-input__input,
+.compose-form__poll-wrapper select,
+.search__input,
+.setting-text,
+.box-widget input[type="text"],
+.box-widget input[type="email"],
+.box-widget input[type="password"],
+.box-widget textarea,
+.statuses-grid .detailed-status,
+.audio-player {
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.search__input {
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ border-bottom: 0;
+ }
+}
+
+.list-editor .search .search__input {
+ border-top: 0;
+ border-bottom: 0;
+}
+
+.compose-form__poll-wrapper select {
+ background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px;
+}
+
+.compose-form__poll-wrapper,
+.compose-form__poll-wrapper .poll__footer {
+ border-top-color: lighten($ui-base-color, 8%);
+}
+
+.notification__filter-bar {
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.compose-form .compose-form__buttons-wrapper {
+ background: $ui-base-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.drawer__header,
+.drawer__inner {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.drawer__inner__mastodon {
+ background: $white url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto;
+}
+
+// Change the colors used in compose-form
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload__actions .icon-button {
+ color: lighten($white, 7%);
+
+ &:active,
+ &:focus,
+ &:hover {
+ color: $white;
+ }
+ }
+
+ .compose-form__upload-description input {
+ color: lighten($white, 7%);
+
+ &::placeholder {
+ color: lighten($white, 7%);
+ }
+ }
+ }
+
+ .compose-form__buttons-wrapper {
+ background: darken($ui-base-color, 6%);
+ }
+
+ .autosuggest-textarea__suggestions {
+ background: darken($ui-base-color, 6%);
+ }
+
+ .autosuggest-textarea__suggestions__item {
+ &:hover,
+ &:focus,
+ &:active,
+ &.selected {
+ background: lighten($ui-base-color, 4%);
+ }
+ }
+}
+
+.emoji-mart-bar {
+ border-color: lighten($ui-base-color, 4%);
+
+ &:first-child {
+ background: darken($ui-base-color, 6%);
+ }
+}
+
+.emoji-mart-search input {
+ background: rgba($ui-base-color, 0.3);
+ border-color: $ui-base-color;
+}
+
+// Change the background colors of statuses
+.focusable:focus {
+ background: $ui-base-color;
+}
+
+.status.status-direct {
+ background: lighten($ui-base-color, 4%);
+}
+
+.focusable:focus .status.status-direct {
+ background: lighten($ui-base-color, 8%);
+}
+
+.detailed-status,
+.detailed-status__action-bar {
+ background: $white;
+}
+
+// Change the background colors of status__content__spoiler-link
+.reply-indicator__content .status__content__spoiler-link,
+.status__content .status__content__spoiler-link {
+ background: $ui-base-color;
+
+ &:hover {
+ background: lighten($ui-base-color, 4%);
+ }
+}
+
+// Change the background colors of media and video spoilers
+.media-spoiler,
+.video-player__spoiler {
+ background: $ui-base-color;
+}
+
+.privacy-dropdown.active .privacy-dropdown__value.active .icon-button {
+ color: $white;
+}
+
+.account-gallery__item a {
+ background-color: $ui-base-color;
+}
+
+// Change the colors used in the dropdown menu
+.dropdown-menu {
+ background: $white;
+
+ &__arrow {
+ &.left {
+ border-left-color: $white;
+ }
+
+ &.top {
+ border-top-color: $white;
+ }
+
+ &.bottom {
+ border-bottom-color: $white;
+ }
+
+ &.right {
+ border-right-color: $white;
+ }
+ }
+
+ &__item {
+ a {
+ background: $white;
+ color: $darker-text-color;
+ }
+ }
+}
+
+// Change the text colors on inverted background
+.privacy-dropdown__option.active,
+.privacy-dropdown__option:hover,
+.privacy-dropdown__option.active .privacy-dropdown__option__content,
+.privacy-dropdown__option.active .privacy-dropdown__option__content strong,
+.privacy-dropdown__option:hover .privacy-dropdown__option__content,
+.privacy-dropdown__option:hover .privacy-dropdown__option__content strong,
+.dropdown-menu__item a:active,
+.dropdown-menu__item a:focus,
+.dropdown-menu__item a:hover,
+.actions-modal ul li:not(:empty) a.active,
+.actions-modal ul li:not(:empty) a.active button,
+.actions-modal ul li:not(:empty) a:active,
+.actions-modal ul li:not(:empty) a:active button,
+.actions-modal ul li:not(:empty) a:focus,
+.actions-modal ul li:not(:empty) a:focus button,
+.actions-modal ul li:not(:empty) a:hover,
+.actions-modal ul li:not(:empty) a:hover button,
+.admin-wrapper .sidebar ul .simple-navigation-active-leaf a,
+.simple_form .block-button,
+.simple_form .button,
+.simple_form button {
+ color: $white;
+}
+
+.dropdown-menu__separator {
+ border-bottom-color: lighten($ui-base-color, 4%);
+}
+
+// Change the background colors of modals
+.actions-modal,
+.boost-modal,
+.confirmation-modal,
+.mute-modal,
+.block-modal,
+.report-modal,
+.embed-modal,
+.error-modal,
+.onboarding-modal,
+.report-modal__comment .setting-text__wrapper,
+.report-modal__comment .setting-text {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.report-modal__comment {
+ border-right-color: lighten($ui-base-color, 8%);
+}
+
+.report-modal__container {
+ border-top-color: lighten($ui-base-color, 8%);
+}
+
+.column-header__collapsible-inner {
+ background: darken($ui-base-color, 4%);
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+}
+
+.focal-point__preview strong {
+ color: $white;
+}
+
+.boost-modal__action-bar,
+.confirmation-modal__action-bar,
+.mute-modal__action-bar,
+.block-modal__action-bar,
+.onboarding-modal__paginator,
+.error-modal__footer {
+ background: darken($ui-base-color, 6%);
+
+ .onboarding-modal__nav,
+ .error-modal__nav {
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: darken($ui-base-color, 12%);
+ }
+ }
+}
+
+.display-case__case {
+ background: $white;
+}
+
+.embed-modal .embed-modal__container .embed-modal__html {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ &:focus {
+ border-color: lighten($ui-base-color, 12%);
+ background: $white;
+ }
+}
+
+.react-toggle-track {
+ background: $ui-secondary-color;
+}
+
+.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background: darken($ui-secondary-color, 10%);
+}
+
+.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background: lighten($ui-highlight-color, 10%);
+}
+
+// Change the default color used for the text in an empty column or on the error column
+.empty-column-indicator,
+.error-column {
+ color: $primary-text-color;
+ background: $white;
+}
+
+.tabs-bar {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-bottom: 0;
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+
+ &__link {
+ padding-bottom: 14px;
+ border-bottom-width: 1px;
+ border-bottom-color: lighten($ui-base-color, 8%);
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+
+ &.active {
+ &:hover,
+ &:active,
+ &:focus {
+ background: transparent;
+ border-bottom-color: $ui-highlight-color;
+ }
+ }
+ }
+}
+
+// Change the default colors used on some parts of the profile pages
+.activity-stream-tabs {
+ background: $account-background-color;
+ border-bottom-color: lighten($ui-base-color, 8%);
+}
+
+.box-widget,
+.nothing-here,
+.page-header,
+.directory__tag > a,
+.directory__tag > div,
+.landing-page__call-to-action,
+.contact-widget,
+.landing .hero-widget__text,
+.landing-page__information.contact-widget {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-left: 0;
+ border-right: 0;
+ border-top: 0;
+ }
+}
+
+.landing .hero-widget__text {
+ border-top: 0;
+ border-bottom: 0;
+}
+
+.simple_form {
+ input[type=text],
+ input[type=number],
+ input[type=email],
+ input[type=password],
+ textarea {
+ &:hover {
+ border-color: lighten($ui-base-color, 12%);
+ }
+ }
+}
+
+.landing .hero-widget__footer {
+ background: $white;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+}
+
+.brand__tagline {
+ color: $ui-secondary-color;
+}
+
+.directory__tag > a {
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-base-color;
+ }
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+}
+
+.directory__tag.active > a,
+.directory__tag.active > div {
+ border-color: $ui-highlight-color;
+
+ &,
+ h4,
+ h4 small,
+ .fa,
+ .trends__item__current {
+ color: $white;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ background: $ui-highlight-color;
+ }
+}
+
+.batch-table {
+ &__toolbar,
+ &__row,
+ .nothing-here {
+ border-color: lighten($ui-base-color, 8%);
+ }
+}
+
+.activity-stream {
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ &--under-tabs {
+ border-top: 0;
+ }
+
+ .entry {
+ background: $account-background-color;
+
+ .detailed-status.light,
+ .more.light,
+ .status.light {
+ border-bottom-color: lighten($ui-base-color, 8%);
+ }
+ }
+
+ .status.light {
+ .status__content {
+ color: $primary-text-color;
+ }
+
+ .display-name {
+ strong {
+ color: $primary-text-color;
+ }
+ }
+ }
+}
+
+.accounts-grid {
+ .account-grid-card {
+ .controls {
+ .icon-button {
+ color: $darker-text-color;
+ }
+ }
+
+ .name {
+ a {
+ color: $primary-text-color;
+ }
+ }
+
+ .username {
+ color: $darker-text-color;
+ }
+
+ .account__header__content {
+ color: $primary-text-color;
+ }
+ }
+}
+
+.simple_form,
+.table-form {
+ .warning {
+ box-shadow: none;
+ background: rgba($error-red, 0.5);
+ text-shadow: none;
+ }
+
+ .recommended {
+ border-color: $ui-highlight-color;
+ color: $ui-highlight-color;
+ background-color: rgba($ui-highlight-color, 0.1);
+ }
+}
+
+.compose-form .compose-form__warning {
+ border-color: $ui-highlight-color;
+ background-color: rgba($ui-highlight-color, 0.1);
+
+ &,
+ a {
+ color: $ui-highlight-color;
+ }
+}
+
+.status__content,
+.reply-indicator__content {
+ a {
+ color: $highlight-text-color;
+ }
+}
+
+.button.logo-button {
+ color: $white;
+
+ svg {
+ fill: $white;
+ }
+}
+
+.public-layout {
+ .account__section-headline {
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border-top: 0;
+ }
+ }
+
+ .header,
+ .public-account-header,
+ .public-account-bio {
+ box-shadow: none;
+ }
+
+ .public-account-bio,
+ .hero-widget__text {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ }
+
+ .header {
+ background: $ui-base-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ border: 0;
+ }
+
+ .brand {
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 4%);
+ }
+ }
+ }
+
+ .public-account-header {
+ &__image {
+ background: lighten($ui-base-color, 12%);
+
+ &::after {
+ box-shadow: none;
+ }
+ }
+
+ &__bar {
+ &::before {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+ }
+
+ .avatar img {
+ border-color: $account-background-color;
+ }
+
+ @media screen and (max-width: $no-columns-breakpoint) {
+ background: $account-background-color;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-top: 0;
+ }
+ }
+
+ &__tabs {
+ &__name {
+ h1,
+ h1 small {
+ color: $white;
+
+ @media screen and (max-width: $no-columns-breakpoint) {
+ color: $primary-text-color;
+ }
+ }
+ }
+ }
+
+ &__extra {
+ .public-account-bio {
+ border: 0;
+ }
+
+ .public-account-bio .account__header__fields {
+ border-color: lighten($ui-base-color, 8%);
+ }
+ }
+ }
+}
+
+.notification__filter-bar button.active::after,
+.account__section-headline a.active::after {
+ border-color: transparent transparent $white;
+}
+
+.hero-widget,
+.box-widget,
+.contact-widget,
+.landing-page__information.contact-widget,
+.moved-account-widget,
+.memoriam-widget,
+.activity-stream,
+.nothing-here,
+.directory__tag > a,
+.directory__tag > div,
+.card > a,
+.page-header,
+.compose-form .compose-form__warning {
+ box-shadow: none;
+}
+
+.audio-player .video-player__controls button,
+.audio-player .video-player__time-sep,
+.audio-player .video-player__time-current,
+.audio-player .video-player__time-total {
+ color: $primary-text-color;
+}
diff --git a/patched-files/app/javascript/styles/light-red/variables.scss b/patched-files/app/javascript/styles/light-red/variables.scss
new file mode 100644
index 0000000..3bbef4a
--- /dev/null
+++ b/patched-files/app/javascript/styles/light-red/variables.scss
@@ -0,0 +1,41 @@
+// Dependent colors
+$black: #000000;
+$white: #ffffff;
+
+$classic-base-color: #282c37;
+$classic-primary-color: #9baec8;
+$classic-secondary-color: #d9e1e8;
+$classic-highlight-color: #d92b2b;
+
+// Differences
+$success-green: lighten(#3c754d, 8%);
+
+$base-overlay-background: $white !default;
+$valid-value-color: $success-green !default;
+
+$ui-base-color: $classic-secondary-color !default;
+$ui-base-lighter-color: #b0c0cf;
+$ui-primary-color: #9bcbed;
+$ui-secondary-color: $classic-base-color !default;
+$ui-highlight-color: #d92b2b;
+
+$primary-text-color: $black !default;
+$darker-text-color: $classic-base-color !default;
+$dark-text-color: #444b5d;
+$action-button-color: #606984;
+
+$inverted-text-color: $black !default;
+$lighter-text-color: $classic-base-color !default;
+$light-text-color: #444b5d;
+
+//Newly added colors
+$account-background-color: $white !default;
+
+//Invert darkened and lightened colors
+@function darken($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) + $amount);
+}
+
+@function lighten($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) - $amount);
+}
diff --git a/patched-files/app/javascript/styles/mods/deprecated/display_bettersearch.css b/patched-files/app/javascript/styles/mods/deprecated/display_bettersearch.css
new file mode 100644
index 0000000..c9bf885
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/deprecated/display_bettersearch.css
@@ -0,0 +1,10 @@
+/*
+Make search results look better:
+- adds contrast to search icon
+- overlay-style shadowed background
+
+author: trwnh
+license: Public Domain
+*/
+.search__icon .fa.active {opacity: 1}
+.drawer__inner.darker {background: rgba(0,0,0,0.5)}
diff --git a/patched-files/app/javascript/styles/mods/display_breakname.css b/patched-files/app/javascript/styles/mods/display_breakname.css
new file mode 100644
index 0000000..fc3936e
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_breakname.css
@@ -0,0 +1,9 @@
+/*
+Add a line break between display name and account handle:
+- this allows user/display names to expand more by default.
+- it also makes names look better in general.
+
+author: trwnh
+license: Public Domain
+*/
+.display-name__html {display: block;}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_browserfont.css b/patched-files/app/javascript/styles/mods/display_browserfont.css
new file mode 100644
index 0000000..6389f20
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_browserfont.css
@@ -0,0 +1,20 @@
+/*
+Use browser default font:
+- override mastodon-font-sans-serif with sans-serif
+- note: this is not the same as "use system default font"
+ in mastodon's preferences! that option uses a font that
+ would be *expected to load on that system*, and ignores
+ your browser's settings entirely. for example, if you
+ are running ms windows, you will see segoe ui, even if
+ your browser's default font is something else!
+
+author: trwnh
+license: Public Domain
+*/
+body,
+.landing-page #mastodon-timeline,
+.landing-page li,
+.landing-page p
+{
+ font-family: sans-serif
+}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_circleavatar.css b/patched-files/app/javascript/styles/mods/display_circleavatar.css
new file mode 100644
index 0000000..b7d5d49
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_circleavatar.css
@@ -0,0 +1,15 @@
+/*
+* Rounded avatars:
+* - adjust the border radius around all avatar elements.
+* - default override is 50% (i.e. turn squares into circles),
+* but you can set it to whatever you want.
+*
+* author: trwnh
+* license: Public Domain
+*/
+.card .avatar img,
+.activity-stream .status.light .status__avatar img,
+.account__avatar,
+ .account__avatar-overlay-base,
+ .account__avatar-overlay-overlay
+{border-radius: 50%}
diff --git a/patched-files/app/javascript/styles/mods/display_collapsedinteractions.css b/patched-files/app/javascript/styles/mods/display_collapsedinteractions.css
new file mode 100644
index 0000000..7cb1f7c
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_collapsedinteractions.css
@@ -0,0 +1,37 @@
+/*
+Collapse fave/boost/poll notifications
+- limits display to just a few lines (~3), so you can at least identify it
+- hides the display name on fave/boost, because you already know you posted it
+- tighter margins, remove space between CW and content
+- hides the buttons, but you can expand a status to interact with it
+
+author: trwnh
+license: Public Domain
+*/
+
+.notification-favourite .status,
+.notification-reblog .status,
+.notification-poll .status{
+ max-height: 4em;
+ overflow: hidden;
+}
+
+.notification-favourite .display-name,
+.notification-reblog .display-name {
+ display: none;
+}
+
+.notification-favourite .status__content,
+.notification-reblog .status__content {
+ margin-top: -4px;
+}
+
+.notification-favourite .status__content p,
+.notification-reblog .status__content p {
+ margin-bottom: 0px;
+}
+
+.notification-favourite .status__action-bar,
+.notification-reblog .status__action-bar {
+ display: none;
+}
diff --git a/patched-files/app/javascript/styles/mods/display_emojizoom.css b/patched-files/app/javascript/styles/mods/display_emojizoom.css
new file mode 100644
index 0000000..32ba536
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_emojizoom.css
@@ -0,0 +1,23 @@
+ /*
+ Emoji hover zoom:
+ - makes emoji grow in size when moused over
+
+ author: noiob
+ license: CC0 - Public Domain
+ source: https://userstyles.org/styles/150165
+ */
+
+ .emojione:hover
+ {
+ width: 50px !important;
+ /* set the width and height of the expanded emojo here */
+ height: 50px !important;
+ transition: all 0.3s ease-in-out !important;
+ /* the 0.3s is the animation time for growing the emojo, it can be set to 0 */;
+ }
+
+ .emojione
+ {
+ transition: all 0.2s ease-in-out;
+ /* the 0.2s is the animation time for shrinking the emojo, it can be set to 0 */;
+ }
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_fadedinteractions.css b/patched-files/app/javascript/styles/mods/display_fadedinteractions.css
new file mode 100644
index 0000000..8945d26
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_fadedinteractions.css
@@ -0,0 +1,9 @@
+/*
+Fade out faved/boosted toots in notifications:
+- for "x favourited your toot" / "x boosted your toot",
+ make the faved/boosted toot half-transparent.
+
+author: trwnh
+license: Public Domain
+*/
+.status.muted {opacity: 0.5}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_fullmedia.css b/patched-files/app/javascript/styles/mods/display_fullmedia.css
new file mode 100644
index 0000000..04cdbf5
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_fullmedia.css
@@ -0,0 +1,31 @@
+/*
+Full-height media previews:
+- normal media previews are forced to be 16:9 for consistency
+- use this if you prefer to see the aspect ratio unchanged
+
+author: Kevin
+license: CC0 - Public Domain
+source: https://userstyles.org/styles/167207 [in part]
+*/
+
+.media-gallery {
+ max-height: 100% !important;
+ height: 100% !important;
+}
+
+.media-gallery__item-gifv-thumbnail, .media-gallery__item-gifv-thumbnail img {
+ transform: translateY(0%) !important;
+ max-height: 100% !important;
+}
+
+.media-gallery__item-thumbnail, .media-gallery__item-thumbnail img, .media-gallery__gifv {
+ max-height: 100% !important;
+}
+
+.media-gallery__item {
+ width: 100% !important;
+ height: 100% !important;
+ max-height: 100% !important;
+ inset: 0 !important;
+ margin-bottom: 4px;
+}
diff --git a/patched-files/app/javascript/styles/mods/display_fullname.css b/patched-files/app/javascript/styles/mods/display_fullname.css
new file mode 100644
index 0000000..1f2e541
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_fullname.css
@@ -0,0 +1,11 @@
+/*
+Always show full name and handle:
+- this removes the `...` and allows text to overflow past the column.
+- this can look worse, but it can also prevent having to mouse over
+ to see the full name or handle.
+- by default, it will also break long names onto a new line.
+
+author: trwnh
+license: Public Domain
+*/
+.display-name {overflow: visible; white-space: normal; word-wrap: break-word}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_hidefollowcounts.css b/patched-files/app/javascript/styles/mods/display_hidefollowcounts.css
new file mode 100644
index 0000000..e9ac9ed
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_hidefollowcounts.css
@@ -0,0 +1,10 @@
+/*
+Hide the following and follower counters on profiles.
+- full counts are still available by hovering over the text, though
+author: trwnh
+license: Public Domain
+*/
+.account__header__extra__links a:not(:first-child) strong
+{display: none}
+.details-counters .counter:not(:first-child) .counter-number
+{visibility: hidden}
diff --git a/patched-files/app/javascript/styles/mods/display_hidereplycounts.css b/patched-files/app/javascript/styles/mods/display_hidereplycounts.css
new file mode 100644
index 0000000..513251c
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_hidereplycounts.css
@@ -0,0 +1,7 @@
+/*
+Hide the 0/1/1+ counters of replies.
+
+author: trwnh
+license: Public Domain
+*/
+.status__action-bar__counter__label {display: none}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/display_starstohearts.css b/patched-files/app/javascript/styles/mods/display_starstohearts.css
new file mode 100644
index 0000000..53efb55
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_starstohearts.css
@@ -0,0 +1,16 @@
+/*
+Turn stars into hearts:
+- similar to twitter's change
+
+author: numimyon
+license: CC0 - Public Domain
+source: https://userstyles.org/styles/151233
+*/
+
+.notification__favourite-icon-wrapper .star-icon,
+.star-icon.active,
+.star-icon:hover,
+.star-icon:active
+{color: crimson !important;}
+
+.fa-star:before {content: "";}
diff --git a/patched-files/app/javascript/styles/mods/display_transparentmedia.css b/patched-files/app/javascript/styles/mods/display_transparentmedia.css
new file mode 100644
index 0000000..afa18a1
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/display_transparentmedia.css
@@ -0,0 +1,10 @@
+/*
+Remove the checker-board background from the media modal:
+- this makes transparent images actually transparent
+
+author: trwnh
+license: Public Domain
+*/
+.media-modal canvas,
+.media-modal img
+{background: none}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_1600px.css b/patched-files/app/javascript/styles/mods/layout_1600px.css
new file mode 100644
index 0000000..f81af00
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_1600px.css
@@ -0,0 +1,12 @@
+/*
+Allow for wider layout on bigger screens
+- vanilla max-width is 1200px
+- there is no penalty to slightly expanding flexbox on bigger screens
+- only applies on landing pages (webapp will expand as you add columns)
+
+author: trwnh
+license: Public Domain
+*/
+@media (min-width: 1600px) {
+ .landing-page .container {max-width: 1600px}
+}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_elefriend.css b/patched-files/app/javascript/styles/mods/layout_elefriend.css
new file mode 100644
index 0000000..3d79a2c
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_elefriend.css
@@ -0,0 +1,20 @@
+/*
+Release elephant friend from their confines:
+- elephant friend will now hang out in the corner of your browser,
+ instead of being trapped in the drawer.
+
+author: trwnh
+license: Public Domain
+*/
+.drawer__inner, .drawer__inner__mastodon {
+ background: none; z-index: 0
+}
+.drawer__inner__mastodon > img {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ height: 180px;
+ z-index: -1
+}
+.compose-form {z-index: 1}
+.drawer__inner {height: 100%} /* firefox bug highlights drawer text on click? */
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_gettingstartedheight.css b/patched-files/app/javascript/styles/mods/layout_gettingstartedheight.css
new file mode 100644
index 0000000..536a707
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_gettingstartedheight.css
@@ -0,0 +1,13 @@
+/*
+Make "getting started" column height consistent with all other columns:
+- puts the footer back at the bottom of the page, instead of floating.
+
+author: trwnh
+license: Public Domain
+*/
+.getting-started {
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ justify-content: space-between
+}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_hidedisabled.css b/patched-files/app/javascript/styles/mods/layout_hidedisabled.css
new file mode 100644
index 0000000..3196db9
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_hidedisabled.css
@@ -0,0 +1,17 @@
+/*
+Hide buttons that can't be clicked
+- columns on /about and tag pages have buttons that don't work.
+- so, this snippet hides those nonworking buttons to save space
+- and to avoid confusion.
+- unboostable buttons are made transparent on hover instead.
+
+this is fixed in https://github.com/tootsuite/mastodon/pull/10054
+
+author: trwnh
+license: Public Domain
+*/
+.status__action-bar .icon-button.disabled:hover,
+.notification-favourite .status.status-direct .icon-button.disabled:hover
+{color: transparent !important}
+
+#mastodon-timeline .status__action-bar {display: none}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_hidefiltered.css b/patched-files/app/javascript/styles/mods/layout_hidefiltered.css
new file mode 100644
index 0000000..f701e5f
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_hidefiltered.css
@@ -0,0 +1,9 @@
+/*
+Remove the "Filtered" tombstone from timelines.
+- WARNING: this breaks keyboard scrolling with j/k!
+
+author: trwnh
+license: Public Domain
+*/
+
+.status__wrapper--filtered {display: none}
diff --git a/patched-files/app/javascript/styles/mods/layout_mobile_bottombar.css b/patched-files/app/javascript/styles/mods/layout_mobile_bottombar.css
new file mode 100644
index 0000000..e5ddaf4
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_mobile_bottombar.css
@@ -0,0 +1,29 @@
+/*
+Bottom tabs on mobile:
+- Places the tab bar at the bottom instead of the top.
+- Fixes layout errors that are a result of this change.
+
+author: trwnh
+license: Public Domain
+*/
+@media (max-width: 630px) {
+
+.tabs-bar {
+position: fixed;
+bottom: 0;
+z-index: 1;
+width: 100%;
+margin: 0 !important;
+}
+
+.getting-started {overflow: auto} /* can be removed after PR #10075 is merged */
+
+.columns-area {padding: 0}
+.getting-started__trends, .getting-started__wrapper, .search {margin: 0}
+.columns-area__panels__main, .tabs-bar__wrapper {padding: 0}
+
+.floating-action-button, .column .scrollable > div:last-child {margin-bottom: 50px}
+.react-swipeable-view-container {height: calc(100% - 50px)}
+.react-swipeable-view-container .columns-area {height: 100% !important}
+
+}
diff --git a/patched-files/app/javascript/styles/mods/layout_singlecolumn.css b/patched-files/app/javascript/styles/mods/layout_singlecolumn.css
new file mode 100644
index 0000000..88bf19e
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_singlecolumn.css
@@ -0,0 +1,25 @@
+/*
+Single column layout:
+- re-uses tab bar from mobile layout
+- hides search from drawer (redundant with search tab)
+
+author: trwnh
+license: Public Domain
+*/
+
+@media (min-width: 1024px) {
+ /* place constraints on app layout */
+ .ui {max-width: 960px; max-height: 100vh;}
+ .drawer {width: 300px}
+ .column:last-child, .drawer:last-child
+ {display: flex; flex: 1 1 100%;}
+ /* show tabs bar (from mobile layout) as header */
+ .tabs-bar {display: flex;}
+ /* hide redundant ui elements */
+ .column,
+ .drawer__header,
+ .drawer:first-child .search,
+ .drawer:first-child .search-results
+ {display: none;}
+ .drawer:first-child .drawer__inner.darker {z-index: -1}
+}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/mods/layout_widercolumns.css b/patched-files/app/javascript/styles/mods/layout_widercolumns.css
new file mode 100644
index 0000000..557297c
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/layout_widercolumns.css
@@ -0,0 +1,10 @@
+/*
+* Wider columns:
+* - Make the multi-column layout use wider columns by default.
+*
+* author: trwnh
+* license: Public Domain
+*/
+@media (min-width: 580px) {
+ .column, #mastodon-timeline {min-width: 55ch;}
+}
diff --git a/patched-files/app/javascript/styles/mods/test_colorizedlogo.css b/patched-files/app/javascript/styles/mods/test_colorizedlogo.css
new file mode 100644
index 0000000..634ae5f
--- /dev/null
+++ b/patched-files/app/javascript/styles/mods/test_colorizedlogo.css
@@ -0,0 +1,12 @@
+/*
+Colorize logo on landing page:
+- DO NOT IMPORT. It works as standalone CSS, but it makes Sass choke.
+
+author: trwnh
+license: Public Domain
+*/
+
+.landing-page__logo img {
+ filter: sepia(100%) hue-rotate(160deg) saturate(400%) brightness(40%);
+ mix-blend-mode: normal
+}
\ No newline at end of file
diff --git a/patched-files/app/javascript/styles/oe7drt-blue.scss b/patched-files/app/javascript/styles/oe7drt-blue.scss
new file mode 100644
index 0000000..efde786
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-blue.scss
@@ -0,0 +1,11 @@
+@import 'oe7drt-blue/variables';
+@import 'application';
+@import 'oe7drt-blue/diff';
+//@import 'boost';
+//@import 'mods/display_browserfont';
+@import 'mods/display_breakname';
+@import 'mods/display_fullname';
+@import 'mods/display_emojizoom';
+//@import 'mods/display_circleavatar';
+@import 'mods/layout_1600px';
+@import 'mods/layout_widercolumns';
diff --git a/patched-files/app/javascript/styles/oe7drt-blue/diff.scss b/patched-files/app/javascript/styles/oe7drt-blue/diff.scss
new file mode 100644
index 0000000..b612b87
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-blue/diff.scss
@@ -0,0 +1,77 @@
+// components.scss
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload {
+ &-description {
+ input {
+ &::placeholder {
+ opacity: 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+.rich-formatting a,
+.rich-formatting p a,
+.rich-formatting li a,
+.landing-page__short-description p a,
+.status__content a,
+.reply-indicator__content a {
+ color: lighten($ui-highlight-color, 12%);
+ text-decoration: none;
+
+ &.mention {
+ text-decoration: none;
+ }
+
+ &.mention span {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+
+ &.status__content__spoiler-link {
+ color: $secondary-text-color;
+ text-decoration: none;
+ }
+}
+
+.status__content__read-more-button {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.getting-started__footer a {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.nothing-here {
+ color: $darker-text-color;
+}
+
+.public-layout .public-account-header__tabs__tabs .counter.active::after {
+ border-bottom: 4px solid $ui-highlight-color;
+}
diff --git a/patched-files/app/javascript/styles/oe7drt-blue/variables.scss b/patched-files/app/javascript/styles/oe7drt-blue/variables.scss
new file mode 100644
index 0000000..d3f858a
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-blue/variables.scss
@@ -0,0 +1,66 @@
+// Commonly used web colors
+$black: #000000; // Black
+$white: #ffffff; // White
+$success-green: #79bd9a !default; // Padua
+$error-red: #df405a !default; // Cerise
+$warning-red: #ff5050 !default; // Sunset Orange
+$gold-star: #ca8f04 !default; // Dark Goldenrod
+
+$red-bookmark: $warning-red;
+
+// Values from the classic Mastodon UI
+$classic-base-color: #282c37; // Midnight Express
+$classic-primary-color: #9baec8; // Echo Blue
+$classic-secondary-color: #d9e1e8; // Pattens Blue
+//$classic-highlight-color: #e7b01c; // Summer Sky
+//$classic-highlight-color: #4285f4; // OE7DRT (new) Blue
+//$classic-highlight-color: #1e57b6; // OE7DRT (new) Blue (darker) (this is a bit too dark)
+//$classic-highlight-color: #3a74d5; // OE7DRT (new) Blue (darker) (use this)
+//$classic-highlight-color: #7612cc; // OE7DRT violet (too dark)
+//$classic-highlight-color: #8737cc; // OE7DRT violet (still a bit too dark)
+//$classic-highlight-color: #9d59d8; // OE7DRT violet (damn white)
+
+//$classic-highlight-color: #4c7899; // OE7DRT /\rch (not bad) bit too high
+//$classic-highlight-color: #00bc8c; // OE7DRT /\rch (greenish like cloudlog)
+$classic-highlight-color: #1b83c8; // OE7DRT /\dark (blue like chaos.social)
+
+// Variables for defaults in UI
+$base-shadow-color: $black !default;
+$base-overlay-background: $black !default;
+$base-border-color: $white !default;
+$simple-background-color: $white !default;
+$valid-value-color: $success-green !default;
+$error-value-color: $error-red !default;
+
+// Tell UI to use selected colors
+$ui-base-color: $classic-base-color !default; // Darkest
+$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
+$ui-primary-color: $classic-primary-color !default; // Lighter
+$ui-secondary-color: $classic-secondary-color !default; // Lightest
+$ui-highlight-color: $classic-highlight-color !default;
+
+// Variables for texts
+$primary-text-color: $white !default;
+$darker-text-color: $ui-primary-color !default;
+$dark-text-color: $ui-base-lighter-color !default;
+$secondary-text-color: $ui-secondary-color !default;
+$highlight-text-color: $ui-highlight-color !default;
+$action-button-color: $ui-base-lighter-color !default;
+// For texts on inverted backgrounds
+$inverted-text-color: $ui-base-color !default;
+$lighter-text-color: $ui-base-lighter-color !default;
+$light-text-color: $ui-primary-color !default;
+
+// Language codes that uses CJK fonts
+$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+
+// Variables for components
+$media-modal-media-max-width: 100%;
+// put margins on top and bottom of image to avoid the screen covered by image.
+$media-modal-media-max-height: 80%;
+
+$no-gap-breakpoint: 415px;
+
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;
diff --git a/patched-files/app/javascript/styles/oe7drt-greeny.scss b/patched-files/app/javascript/styles/oe7drt-greeny.scss
new file mode 100644
index 0000000..993c3bb
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-greeny.scss
@@ -0,0 +1,11 @@
+@import 'oe7drt-greeny/variables';
+@import 'application';
+@import 'oe7drt-greeny/diff';
+//@import 'boost';
+//@import 'mods/display_browserfont';
+@import 'mods/display_breakname';
+@import 'mods/display_fullname';
+@import 'mods/display_emojizoom';
+//@import 'mods/display_circleavatar';
+@import 'mods/layout_1600px';
+@import 'mods/layout_widercolumns';
diff --git a/patched-files/app/javascript/styles/oe7drt-greeny/diff.scss b/patched-files/app/javascript/styles/oe7drt-greeny/diff.scss
new file mode 100644
index 0000000..b612b87
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-greeny/diff.scss
@@ -0,0 +1,77 @@
+// components.scss
+.compose-form {
+ .compose-form__modifiers {
+ .compose-form__upload {
+ &-description {
+ input {
+ &::placeholder {
+ opacity: 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+.rich-formatting a,
+.rich-formatting p a,
+.rich-formatting li a,
+.landing-page__short-description p a,
+.status__content a,
+.reply-indicator__content a {
+ color: lighten($ui-highlight-color, 12%);
+ text-decoration: none;
+
+ &.mention {
+ text-decoration: none;
+ }
+
+ &.mention span {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+
+ &.status__content__spoiler-link {
+ color: $secondary-text-color;
+ text-decoration: none;
+ }
+}
+
+.status__content__read-more-button {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.getting-started__footer a {
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+}
+
+.nothing-here {
+ color: $darker-text-color;
+}
+
+.public-layout .public-account-header__tabs__tabs .counter.active::after {
+ border-bottom: 4px solid $ui-highlight-color;
+}
diff --git a/patched-files/app/javascript/styles/oe7drt-greeny/variables.scss b/patched-files/app/javascript/styles/oe7drt-greeny/variables.scss
new file mode 100644
index 0000000..34db656
--- /dev/null
+++ b/patched-files/app/javascript/styles/oe7drt-greeny/variables.scss
@@ -0,0 +1,66 @@
+// Commonly used web colors
+$black: #000000; // Black
+$white: #ffffff; // White
+$success-green: #79bd9a !default; // Padua
+$error-red: #df405a !default; // Cerise
+$warning-red: #ff5050 !default; // Sunset Orange
+$gold-star: #ca8f04 !default; // Dark Goldenrod
+
+$red-bookmark: $warning-red;
+
+// Values from the classic Mastodon UI
+$classic-base-color: #282c37; // Midnight Express
+$classic-primary-color: #9baec8; // Echo Blue
+$classic-secondary-color: #d9e1e8; // Pattens Blue
+//$classic-highlight-color: #e7b01c; // Summer Sky
+//$classic-highlight-color: #4285f4; // OE7DRT (new) Blue
+//$classic-highlight-color: #1e57b6; // OE7DRT (new) Blue (darker) (this is a bit too dark)
+//$classic-highlight-color: #3a74d5; // OE7DRT (new) Blue (darker) (use this)
+//$classic-highlight-color: #7612cc; // OE7DRT violet (too dark)
+//$classic-highlight-color: #8737cc; // OE7DRT violet (still a bit too dark)
+//$classic-highlight-color: #9d59d8; // OE7DRT violet (damn white)
+
+//$classic-highlight-color: #4c7899; // OE7DRT /\rch (not bad) bit too high
+$classic-highlight-color: #00bc8c; // OE7DRT /\rch (greenish like cloudlog)
+//$classic-highlight-color: #1b83c8; // OE7DRT /\dark (blue like chaos.social)
+
+// Variables for defaults in UI
+$base-shadow-color: $black !default;
+$base-overlay-background: $black !default;
+$base-border-color: $white !default;
+$simple-background-color: $white !default;
+$valid-value-color: $success-green !default;
+$error-value-color: $error-red !default;
+
+// Tell UI to use selected colors
+$ui-base-color: $classic-base-color !default; // Darkest
+$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
+$ui-primary-color: $classic-primary-color !default; // Lighter
+$ui-secondary-color: $classic-secondary-color !default; // Lightest
+$ui-highlight-color: $classic-highlight-color !default;
+
+// Variables for texts
+$primary-text-color: $white !default;
+$darker-text-color: $ui-primary-color !default;
+$dark-text-color: $ui-base-lighter-color !default;
+$secondary-text-color: $ui-secondary-color !default;
+$highlight-text-color: $ui-highlight-color !default;
+$action-button-color: $ui-base-lighter-color !default;
+// For texts on inverted backgrounds
+$inverted-text-color: $ui-base-color !default;
+$lighter-text-color: $ui-base-lighter-color !default;
+$light-text-color: $ui-primary-color !default;
+
+// Language codes that uses CJK fonts
+$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+
+// Variables for components
+$media-modal-media-max-width: 100%;
+// put margins on top and bottom of image to avoid the screen covered by image.
+$media-modal-media-max-height: 80%;
+
+$no-gap-breakpoint: 415px;
+
+$font-sans-serif: 'mastodon-font-sans-serif' !default;
+$font-display: 'mastodon-font-display' !default;
+$font-monospace: 'mastodon-font-monospace' !default;
diff --git a/patched-files/app/models/account.rb b/patched-files/app/models/account.rb
new file mode 100644
index 0000000..d144ef5
--- /dev/null
+++ b/patched-files/app/models/account.rb
@@ -0,0 +1,597 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: accounts
+#
+# id :bigint(8) not null, primary key
+# username :string default(""), not null
+# domain :string
+# private_key :text
+# public_key :text default(""), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# note :text default(""), not null
+# display_name :string default(""), not null
+# uri :string default(""), not null
+# url :string
+# avatar_file_name :string
+# avatar_content_type :string
+# avatar_file_size :integer
+# avatar_updated_at :datetime
+# header_file_name :string
+# header_content_type :string
+# header_file_size :integer
+# header_updated_at :datetime
+# avatar_remote_url :string
+# locked :boolean default(FALSE), not null
+# header_remote_url :string default(""), not null
+# last_webfingered_at :datetime
+# inbox_url :string default(""), not null
+# outbox_url :string default(""), not null
+# shared_inbox_url :string default(""), not null
+# followers_url :string default(""), not null
+# protocol :integer default("ostatus"), not null
+# memorial :boolean default(FALSE), not null
+# moved_to_account_id :bigint(8)
+# featured_collection_url :string
+# fields :jsonb
+# actor_type :string
+# discoverable :boolean
+# also_known_as :string is an Array
+# silenced_at :datetime
+# suspended_at :datetime
+# hide_collections :boolean
+# avatar_storage_schema_version :integer
+# header_storage_schema_version :integer
+# devices_url :string
+# suspension_origin :integer
+# sensitized_at :datetime
+# trendable :boolean
+# reviewed_at :datetime
+# requested_review_at :datetime
+#
+
+class Account < ApplicationRecord
+ self.ignored_columns = %w(
+ subscription_expires_at
+ secret
+ remote_url
+ salmon_url
+ hub_url
+ trust_level
+ )
+
+ USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
+ MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i
+ URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/
+ USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
+
+ include Attachmentable
+ include AccountAssociations
+ include AccountAvatar
+ include AccountFinderConcern
+ include AccountHeader
+ include AccountInteractions
+ include Paginable
+ include AccountCounters
+ include DomainNormalizable
+ include DomainMaterializable
+ include AccountMerging
+
+ enum protocol: [:ostatus, :activitypub]
+ enum suspension_origin: [:local, :remote], _prefix: true
+
+ validates :username, presence: true
+ validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
+
+ # Remote user validations, also applies to internal actors
+ validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? }
+
+ # Local user validations
+ validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
+ validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
+ validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
+ validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
+ validates :fields, length: { maximum: 12 }, if: -> { local? && will_save_change_to_fields? }
+
+ scope :remote, -> { where.not(domain: nil) }
+ scope :local, -> { where(domain: nil) }
+ scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
+ scope :silenced, -> { where.not(silenced_at: nil) }
+ scope :suspended, -> { where.not(suspended_at: nil) }
+ scope :sensitized, -> { where.not(sensitized_at: nil) }
+ scope :without_suspended, -> { where(suspended_at: nil) }
+ scope :without_silenced, -> { where(silenced_at: nil) }
+ scope :without_instance_actor, -> { where.not(id: -99) }
+ scope :recent, -> { reorder(id: :desc) }
+ scope :bots, -> { where(actor_type: %w(Application Service)) }
+ scope :groups, -> { where(actor_type: 'Group') }
+ scope :alphabetic, -> { order(domain: :asc, username: :asc) }
+ scope :matches_username, ->(value) { where('lower((username)::text) LIKE lower(?)', "#{value}%") }
+ scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
+ scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
+ scope :without_unapproved, -> { left_outer_joins(:user).remote.or(left_outer_joins(:user).merge(User.approved.confirmed)) }
+ scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
+ scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
+ scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
+ scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
+ scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
+ scope :popular, -> { order('account_stats.followers_count desc') }
+ scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
+ scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
+ scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
+
+ delegate :email,
+ :unconfirmed_email,
+ :current_sign_in_at,
+ :created_at,
+ :sign_up_ip,
+ :confirmed?,
+ :approved?,
+ :pending?,
+ :disabled?,
+ :unconfirmed?,
+ :unconfirmed_or_pending?,
+ :role,
+ :locale,
+ :shows_application?,
+ :prefers_noindex?,
+ to: :user,
+ prefix: true,
+ allow_nil: true
+
+ delegate :chosen_languages, to: :user, prefix: false, allow_nil: true
+
+ update_index('accounts', :self)
+
+ def local?
+ domain.nil?
+ end
+
+ def moved?
+ moved_to_account_id.present?
+ end
+
+ def bot?
+ %w(Application Service).include? actor_type
+ end
+
+ def instance_actor?
+ id == -99
+ end
+
+ alias bot bot?
+
+ def bot=(val)
+ self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
+ end
+
+ def group?
+ actor_type == 'Group'
+ end
+
+ alias group group?
+
+ def acct
+ local? ? username : "#{username}@#{domain}"
+ end
+
+ def pretty_acct
+ local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
+ end
+
+ def local_username_and_domain
+ "#{username}@#{Rails.configuration.x.local_domain}"
+ end
+
+ def local_followers_count
+ Follow.where(target_account_id: id).count
+ end
+
+ def to_webfinger_s
+ "acct:#{local_username_and_domain}"
+ end
+
+ def possibly_stale?
+ last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
+ end
+
+ def refresh!
+ ResolveAccountService.new.call(acct) unless local?
+ end
+
+ def silenced?
+ silenced_at.present?
+ end
+
+ def silence!(date = Time.now.utc)
+ update!(silenced_at: date)
+ end
+
+ def unsilence!
+ update!(silenced_at: nil)
+ end
+
+ def suspended?
+ suspended_at.present? && !instance_actor?
+ end
+
+ def suspended_permanently?
+ suspended? && deletion_request.nil?
+ end
+
+ def suspended_temporarily?
+ suspended? && deletion_request.present?
+ end
+
+ def suspend!(date: Time.now.utc, origin: :local, block_email: true)
+ transaction do
+ create_deletion_request!
+ update!(suspended_at: date, suspension_origin: origin)
+ create_canonical_email_block! if block_email
+ end
+ end
+
+ def unsuspend!
+ transaction do
+ deletion_request&.destroy!
+ update!(suspended_at: nil, suspension_origin: nil)
+ destroy_canonical_email_block!
+ end
+ end
+
+ def sensitized?
+ sensitized_at.present?
+ end
+
+ def sensitize!(date = Time.now.utc)
+ update!(sensitized_at: date)
+ end
+
+ def unsensitize!
+ update!(sensitized_at: nil)
+ end
+
+ def memorialize!
+ update!(memorial: true)
+ end
+
+ def trendable?
+ boolean_with_default('trendable', Setting.trendable_by_default)
+ end
+
+ def sign?
+ true
+ end
+
+ def previous_strikes_count
+ strikes.where(overruled_at: nil).count
+ end
+
+ def keypair
+ @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
+ end
+
+ def tags_as_strings=(tag_names)
+ hashtags_map = Tag.find_or_create_by_names(tag_names).index_by(&:name)
+
+ # Remove hashtags that are to be deleted
+ tags.each do |tag|
+ if hashtags_map.key?(tag.name)
+ hashtags_map.delete(tag.name)
+ else
+ tags.delete(tag)
+ end
+ end
+
+ # Add hashtags that were so far missing
+ hashtags_map.each_value do |tag|
+ tags << tag
+ end
+ end
+
+ def also_known_as
+ self[:also_known_as] || []
+ end
+
+ def fields
+ (self[:fields] || []).map do |f|
+ Account::Field.new(self, f)
+ rescue
+ nil
+ end.compact
+ end
+
+ def fields_attributes=(attributes)
+ fields = []
+ old_fields = self[:fields] || []
+ old_fields = [] if old_fields.is_a?(Hash)
+
+ if attributes.is_a?(Hash)
+ attributes.each_value do |attr|
+ next if attr[:name].blank?
+
+ previous = old_fields.find { |item| item['value'] == attr[:value] }
+
+ if previous && previous['verified_at'].present?
+ attr[:verified_at] = previous['verified_at']
+ end
+
+ fields << attr
+ end
+ end
+
+ self[:fields] = fields
+ end
+
+ DEFAULT_FIELDS_SIZE = 4
+
+ def build_fields
+ return if fields.size >= DEFAULT_FIELDS_SIZE
+
+ tmp = self[:fields] || []
+ tmp = [] if tmp.is_a?(Hash)
+
+ (DEFAULT_FIELDS_SIZE - tmp.size).times do
+ tmp << { name: '', value: '' }
+ end
+
+ self.fields = tmp
+ end
+
+ def save_with_optional_media!
+ save!
+ rescue ActiveRecord::RecordInvalid => e
+ errors = e.record.errors.errors
+ errors.each do |err|
+ if err.attribute == :avatar
+ self.avatar = nil
+ elsif err.attribute == :header
+ self.header = nil
+ end
+ end
+
+ save!
+ end
+
+ def hides_followers?
+ hide_collections?
+ end
+
+ def hides_following?
+ hide_collections?
+ end
+
+ def object_type
+ :person
+ end
+
+ def to_param
+ username
+ end
+
+ def to_log_human_identifier
+ acct
+ end
+
+ def excluded_from_timeline_account_ids
+ Rails.cache.fetch("exclude_account_ids_for:#{id}") { block_relationships.pluck(:target_account_id) + blocked_by_relationships.pluck(:account_id) + mute_relationships.pluck(:target_account_id) }
+ end
+
+ def excluded_from_timeline_domains
+ Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
+ end
+
+ def preferred_inbox_url
+ shared_inbox_url.presence || inbox_url
+ end
+
+ def synchronization_uri_prefix
+ return 'local' if local?
+
+ @synchronization_uri_prefix ||= "#{uri[URL_PREFIX_RE]}/"
+ end
+
+ def requires_review?
+ reviewed_at.nil?
+ end
+
+ def reviewed?
+ reviewed_at.present?
+ end
+
+ def requested_review?
+ requested_review_at.present?
+ end
+
+ def requires_review_notification?
+ requires_review? && !requested_review?
+ end
+
+ class << self
+ DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
+ TEXTSEARCH = "(setweight(to_tsvector('simple', accounts.display_name), 'A') || setweight(to_tsvector('simple', accounts.username), 'B') || setweight(to_tsvector('simple', coalesce(accounts.domain, '')), 'C'))"
+
+ REPUTATION_SCORE_FUNCTION = '(greatest(0, coalesce(s.followers_count, 0)) / (greatest(0, coalesce(s.following_count, 0)) + 1.0))'
+ FOLLOWERS_SCORE_FUNCTION = 'log(greatest(0, coalesce(s.followers_count, 0)) + 2)'
+ TIME_DISTANCE_FUNCTION = '(case when s.last_status_at is null then 0 else exp(-1.0 * ((greatest(0, abs(extract(DAY FROM age(s.last_status_at))) - 30.0)^2) / (2.0 * ((-1.0 * 30^2) / (2.0 * ln(0.3)))))) end)'
+ BOOST = "((#{REPUTATION_SCORE_FUNCTION} + #{FOLLOWERS_SCORE_FUNCTION} + #{TIME_DISTANCE_FUNCTION}) / 3.0)"
+
+ def readonly_attributes
+ super - %w(statuses_count following_count followers_count)
+ end
+
+ def inboxes
+ urls = reorder(nil).where(protocol: :activitypub).group(:preferred_inbox_url).pluck(Arel.sql("coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url) AS preferred_inbox_url"))
+ DeliveryFailureTracker.without_unavailable(urls)
+ end
+
+ def search_for(terms, limit: 10, offset: 0)
+ tsquery = generate_query_for_search(terms)
+
+ sql = <<-SQL.squish
+ SELECT
+ accounts.*,
+ #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
+ FROM accounts
+ LEFT JOIN users ON accounts.id = users.account_id
+ LEFT JOIN account_stats AS s ON accounts.id = s.account_id
+ WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
+ AND accounts.suspended_at IS NULL
+ AND accounts.moved_to_account_id IS NULL
+ AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
+ ORDER BY rank DESC
+ LIMIT :limit OFFSET :offset
+ SQL
+
+ records = find_by_sql([sql, limit: limit, offset: offset, tsquery: tsquery])
+ ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+ records
+ end
+
+ def advanced_search_for(terms, account, limit: 10, following: false, offset: 0)
+ tsquery = generate_query_for_search(terms)
+ sql = advanced_search_for_sql_template(following)
+ records = find_by_sql([sql, id: account.id, limit: limit, offset: offset, tsquery: tsquery])
+ ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+ records
+ end
+
+ def from_text(text)
+ return [] if text.blank?
+
+ text.scan(MENTION_RE).map { |match| match.first.split('@', 2) }.uniq.filter_map do |(username, domain)|
+ domain = begin
+ if TagManager.instance.local_domain?(domain)
+ nil
+ else
+ TagManager.instance.normalize_domain(domain)
+ end
+ end
+ EntityCache.instance.mention(username, domain)
+ end
+ end
+
+ private
+
+ def generate_query_for_search(unsanitized_terms)
+ terms = unsanitized_terms.gsub(DISALLOWED_TSQUERY_CHARACTERS, ' ')
+
+ # The final ":*" is for prefix search.
+ # The trailing space does not seem to fit any purpose, but `to_tsquery`
+ # behaves differently with and without a leading space if the terms start
+ # with `./`, `../`, or `.. `. I don't understand why, so, in doubt, keep
+ # the same query.
+ "' #{terms} ':*"
+ end
+
+ def advanced_search_for_sql_template(following)
+ if following
+ <<-SQL.squish
+ WITH first_degree AS (
+ SELECT target_account_id
+ FROM follows
+ WHERE account_id = :id
+ UNION ALL
+ SELECT :id
+ )
+ SELECT
+ accounts.*,
+ (count(f.id) + 1) * #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
+ FROM accounts
+ LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id)
+ LEFT JOIN account_stats AS s ON accounts.id = s.account_id
+ WHERE accounts.id IN (SELECT * FROM first_degree)
+ AND to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
+ AND accounts.suspended_at IS NULL
+ AND accounts.moved_to_account_id IS NULL
+ GROUP BY accounts.id, s.id
+ ORDER BY rank DESC
+ LIMIT :limit OFFSET :offset
+ SQL
+ else
+ <<-SQL.squish
+ SELECT
+ accounts.*,
+ #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank,
+ count(f.id) AS followed
+ FROM accounts
+ LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id) OR (accounts.id = f.target_account_id AND f.account_id = :id)
+ LEFT JOIN users ON accounts.id = users.account_id
+ LEFT JOIN account_stats AS s ON accounts.id = s.account_id
+ WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
+ AND accounts.suspended_at IS NULL
+ AND accounts.moved_to_account_id IS NULL
+ AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
+ GROUP BY accounts.id, s.id
+ ORDER BY followed DESC, rank DESC
+ LIMIT :limit OFFSET :offset
+ SQL
+ end
+ end
+ end
+
+ def emojis
+ @emojis ||= CustomEmoji.from_text(emojifiable_text, domain)
+ end
+
+ before_create :generate_keys
+ before_validation :prepare_contents, if: :local?
+ before_validation :prepare_username, on: :create
+ before_destroy :clean_feed_manager
+
+ def ensure_keys!
+ return unless local? && private_key.blank? && public_key.blank?
+ generate_keys
+ save!
+ end
+
+ private
+
+ def prepare_contents
+ display_name&.strip!
+ note&.strip!
+ end
+
+ def prepare_username
+ username&.squish!
+ end
+
+ def generate_keys
+ return unless local? && private_key.blank? && public_key.blank?
+
+ keypair = OpenSSL::PKey::RSA.new(2048)
+ self.private_key = keypair.to_pem
+ self.public_key = keypair.public_key.to_pem
+ end
+
+ def normalize_domain
+ return if local?
+
+ super
+ end
+
+ def emojifiable_text
+ [note, display_name, fields.map(&:name), fields.map(&:value)].join(' ')
+ end
+
+ def clean_feed_manager
+ FeedManager.instance.clean_feeds!(:home, [id])
+ end
+
+ def create_canonical_email_block!
+ return unless local? && user_email.present?
+
+ begin
+ CanonicalEmailBlock.create(reference_account: self, email: user_email)
+ rescue ActiveRecord::RecordNotUnique
+ # A canonical e-mail block may already exist for the same e-mail
+ end
+ end
+
+ def destroy_canonical_email_block!
+ return unless local?
+
+ CanonicalEmailBlock.where(reference_account: self).delete_all
+ end
+end
diff --git a/patched-files/app/validators/poll_validator.rb b/patched-files/app/validators/poll_validator.rb
new file mode 100644
index 0000000..b5e380c
--- /dev/null
+++ b/patched-files/app/validators/poll_validator.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class PollValidator < ActiveModel::Validator
+ MAX_OPTIONS = 12
+ MAX_OPTION_CHARS = 180
+ MAX_EXPIRATION = 1.month.freeze
+ MIN_EXPIRATION = 5.minutes.freeze
+
+ def validate(poll)
+ current_time = Time.now.utc
+
+ poll.errors.add(:options, I18n.t('polls.errors.too_few_options')) unless poll.options.size > 1
+ poll.errors.add(:options, I18n.t('polls.errors.too_many_options', max: MAX_OPTIONS)) if poll.options.size > MAX_OPTIONS
+ poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS }
+ poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size
+ poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION
+ poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION
+ end
+end
diff --git a/patched-files/app/validators/status_length_validator.rb b/patched-files/app/validators/status_length_validator.rb
new file mode 100644
index 0000000..5e2641d
--- /dev/null
+++ b/patched-files/app/validators/status_length_validator.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+class StatusLengthValidator < ActiveModel::Validator
+ MAX_CHARS = 2000
+ URL_PLACEHOLDER_CHARS = 23
+ URL_PLACEHOLDER = 'x' * 23
+
+ def validate(status)
+ return unless status.local? && !status.reblog?
+
+ status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
+ end
+
+ private
+
+ def too_long?(status)
+ countable_length(combined_text(status)) > MAX_CHARS
+ end
+
+ def countable_length(str)
+ str.mb_chars.grapheme_length
+ end
+
+ def combined_text(status)
+ [status.spoiler_text, countable_text(status.text)].join
+ end
+
+ def countable_text(str)
+ return '' if str.blank?
+
+ # To ensure that we only give length concessions to entities that
+ # will be correctly parsed during formatting, we go through full
+ # entity extraction
+
+ entities = Extractor.remove_overlapping_entities(Extractor.extract_urls_with_indices(str, extract_url_without_protocol: false) + Extractor.extract_mentions_or_lists_with_indices(str))
+
+ rewrite_entities(str, entities) do |entity|
+ if entity[:url]
+ URL_PLACEHOLDER
+ elsif entity[:screen_name]
+ "@#{entity[:screen_name].split('@').first}"
+ end
+ end
+ end
+
+ def rewrite_entities(str, entities)
+ entities.sort_by! { |entity| entity[:indices].first }
+ result = ''.dup
+
+ last_index = entities.reduce(0) do |index, entity|
+ result << str[index...entity[:indices].first]
+ result << yield(entity)
+ entity[:indices].last
+ end
+
+ result << str[last_index..-1]
+ result
+ end
+end
diff --git a/patched-files/config/initializers/content_security_policy.rb b/patched-files/config/initializers/content_security_policy.rb
new file mode 100644
index 0000000..bf7b7ed
--- /dev/null
+++ b/patched-files/config/initializers/content_security_policy.rb
@@ -0,0 +1,82 @@
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+def host_to_url(str)
+ "http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}" unless str.blank?
+end
+
+base_host = Rails.configuration.x.web_domain
+
+assets_host = Rails.configuration.action_controller.asset_host
+assets_host ||= host_to_url(base_host)
+
+media_host = host_to_url(ENV['S3_ALIAS_HOST'])
+media_host ||= host_to_url(ENV['S3_CLOUDFRONT_HOST'])
+media_host ||= host_to_url(ENV['S3_HOSTNAME']) if ENV['S3_ENABLED'] == 'true'
+media_host ||= assets_host
+
+google_fonts_host = "https://fonts.gstatic.com" # Google Fonts
+
+Rails.application.config.content_security_policy do |p|
+ p.base_uri :none
+ p.default_src :none
+ p.frame_ancestors :none
+ p.font_src :self, assets_host, google_fonts_host
+ p.img_src :self, :https, :data, :blob, assets_host
+ p.style_src :self, assets_host
+ p.media_src :self, :https, :data, assets_host
+ p.frame_src :self, :https
+ p.manifest_src :self, assets_host
+ p.form_action :self
+
+ if Rails.env.development?
+ webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" }
+
+ p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
+ p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
+ p.child_src :self, :blob, assets_host
+ p.worker_src :self, :blob, assets_host
+ else
+ p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url
+ p.script_src :self, assets_host, "'wasm-unsafe-eval'"
+ p.child_src :self, :blob, assets_host
+ p.worker_src :self, :blob, assets_host
+ end
+end
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
+
+Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
+
+Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
+
+Rails.application.reloader.to_prepare do
+ PgHero::HomeController.content_security_policy do |p|
+ p.script_src :self, :unsafe_inline, assets_host
+ p.style_src :self, :unsafe_inline, assets_host
+ end
+
+ PgHero::HomeController.after_action do
+ request.content_security_policy_nonce_generator = nil
+ end
+
+ if Rails.env.development?
+ LetterOpenerWeb::LettersController.content_security_policy do |p|
+ p.child_src :self
+ p.connect_src :none
+ p.frame_ancestors :self
+ p.frame_src :self
+ p.script_src :unsafe_inline
+ p.style_src :unsafe_inline
+ p.worker_src :none
+ end
+
+ LetterOpenerWeb::LettersController.after_action do |p|
+ request.content_security_policy_nonce_directives = %w(script-src)
+ end
+ end
+end
diff --git a/patched-files/config/themes.yml b/patched-files/config/themes.yml
new file mode 100644
index 0000000..77a8a55
--- /dev/null
+++ b/patched-files/config/themes.yml
@@ -0,0 +1,11 @@
+default: styles/application.scss
+contrast: styles/contrast.scss
+mastodon-light: styles/mastodon-light.scss
+coffee-dark: styles/coffee-dark.scss
+coffee-light: styles/coffee-light.scss
+oe7drt-blue: styles/oe7drt-blue.scss
+oe7drt-greeny: styles/oe7drt-greeny.scss
+dark-red: styles/dark-red.scss
+light-red: styles/light-red.scss
+#cute: styles/cute.scss
+#droid: styles/droid.scss
diff --git a/patched-files/public/robots.txt b/patched-files/public/robots.txt
new file mode 100644
index 0000000..e5cbdc3
--- /dev/null
+++ b/patched-files/public/robots.txt
@@ -0,0 +1,19 @@
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
+
+User-agent: *
+Disallow: /media_proxy/
+Disallow: /interact/
+Allow: /about
+Disallow: /
+Disallow: /users/*/followers
+Disallow: /users/*/following
+Disallow: /@*/media
+Disallow: /@*/with_replies
+Disallow: /@*/tagged/*
+Disallow: /media_proxy/*
+Disallow: /emoji/*
+Disallow: /packs/*
+Disallow: /sounds/*
+Disallow: /system/*
+Disallow: /avatars/*
+Disallow: /headers/*