Implement CSS theme tokens behind feature flag (#36861)

This commit is contained in:
diondiondion 2025-11-17 10:44:55 +01:00 committed by GitHub
parent 489bee8f4e
commit 284b46fee7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 19690 additions and 87 deletions

View File

@ -68,6 +68,7 @@ docker-compose.override.yml
# Ignore vendored CSS reset # Ignore vendored CSS reset
app/javascript/styles/mastodon/reset.scss app/javascript/styles/mastodon/reset.scss
app/javascript/styles_new/mastodon/reset.scss
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631 # Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
*.js *.js

View File

@ -4,11 +4,11 @@ module ThemeHelper
def theme_style_tags(theme) def theme_style_tags(theme)
if theme == 'system' if theme == 'system'
''.html_safe.tap do |tags| ''.html_safe.tap do |tags|
tags << vite_stylesheet_tag('themes/mastodon-light', type: :virtual, media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') tags << vite_stylesheet_tag(theme_path_for('mastodon-light'), type: :virtual, media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
tags << vite_stylesheet_tag('themes/default', type: :virtual, media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') tags << vite_stylesheet_tag(theme_path_for('default'), type: :virtual, media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
end end
else else
vite_stylesheet_tag "themes/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous' vite_stylesheet_tag theme_path_for(theme), type: :virtual, media: 'all', crossorigin: 'anonymous'
end end
end end
@ -53,4 +53,8 @@ module ThemeHelper
def theme_color_for(theme) def theme_color_for(theme)
theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark] theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark]
end end
def theme_path_for(theme)
Mastodon::Feature.theme_tokens_enabled? ? "themes/#{theme}_theme_tokens" : "themes/#{theme}"
end
end end

View File

@ -553,7 +553,6 @@ class Status extends ImmutablePureComponent {
} }
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
return ( return (
<Hotkeys handlers={handlers} focusable={!unfocusable}> <Hotkeys handlers={handlers} focusable={!unfocusable}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader({intl, status, rebloggedByText, isQuote: isQuotedPost})} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}> <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader({intl, status, rebloggedByText, isQuote: isQuotedPost})} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>

View File

@ -64,6 +64,7 @@ const StandaloneBoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
title={intl.formatMessage(meta ?? title)} title={intl.formatMessage(meta ?? title)}
icon='retweet' icon='retweet'
iconComponent={iconComponent} iconComponent={iconComponent}
className='status__action-bar__button'
onClick={!disabled ? handleClick : undefined} onClick={!disabled ? handleClick : undefined}
counter={ counter={
counters counters
@ -195,6 +196,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
isMenuDisabled ? messages.all_disabled : messages.reblog_or_quote, isMenuDisabled ? messages.all_disabled : messages.reblog_or_quote,
)} )}
icon='retweet' icon='retweet'
className='status__action-bar__button'
iconComponent={boostIcon} iconComponent={boostIcon}
counter={ counter={
counters counters

View File

@ -406,15 +406,19 @@ class StatusActionBar extends ImmutablePureComponent {
status={status} status={status}
needsStatusRefresh={quickBoosting && status.get('quote_approval') === null} needsStatusRefresh={quickBoosting && status.get('quote_approval') === null}
items={menu} items={menu}
icon='ellipsis-h'
iconComponent={MoreHorizIcon}
direction='right' direction='right'
title={intl.formatMessage(messages.more)}
onOpen={() => { onOpen={() => {
dismissQuoteHint(); dismissQuoteHint();
return true; return true;
}} }}
/> >
<IconButton
className='status__action-bar__button'
icon='ellipsis-h'
iconComponent={MoreHorizIcon}
title={intl.formatMessage(messages.more)}
/>
</Dropdown>
)} )}
</RemoveQuoteHint> </RemoveQuoteHint>
</div> </div>

View File

@ -104,17 +104,19 @@ export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
defaultMessage='Language' defaultMessage='Language'
/> />
</label> </label>
<select onChange={handleLocaleChange} id='language-select'> <div className='select-wrapper'>
{localeOptions.map((option) => ( <select onChange={handleLocaleChange} id='language-select'>
<option {localeOptions.map((option) => (
key={option.value} <option
value={option.value} key={option.value}
selected={option.value === selectedLocale} value={option.value}
> selected={option.value === selectedLocale}
{option.text} >
</option> {option.text}
))} </option>
</select> ))}
</select>
</div>
</div> </div>
)} )}
</Section> </Section>

View File

@ -24,12 +24,12 @@ export default class FollowRequestNote extends ImmutablePureComponent {
</div> </div>
<div className='follow-request-banner__action'> <div className='follow-request-banner__action'>
<button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}> <button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
<Icon id='check' icon={CheckIcon} /> <Icon id='check' icon={CheckIcon} />
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' /> <FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
</button> </button>
<button type='button' className='button button-tertiary button--destructive' onClick={onReject}> <button type='button' className='button button-secondary button--destructive' onClick={onReject}>
<Icon id='times' icon={CloseIcon} /> <Icon id='times' icon={CloseIcon} />
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' /> <FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
</button> </button>

View File

@ -1,38 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
const iconStyle = {
height: null,
lineHeight: '27px',
minWidth: `${18 * 1.28571429}px`,
};
export default class TextIconButton extends PureComponent {
static propTypes = {
label: PropTypes.string.isRequired,
title: PropTypes.string,
active: PropTypes.bool,
onClick: PropTypes.func.isRequired,
ariaControls: PropTypes.string,
};
render () {
const { label, title, active, ariaControls } = this.props;
return (
<button
type='button'
title={title}
aria-label={title}
className={`text-icon-button ${active ? 'active' : ''}`}
aria-expanded={active}
onClick={this.props.onClick}
aria-controls={ariaControls} style={iconStyle}
>
{label}
</button>
);
}
}

View File

@ -166,7 +166,7 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
<div className='drawer__inner'> <div className='drawer__inner'>
<ComposeFormContainer /> <ComposeFormContainer />
<div className='drawer__inner__mastodon'> <div className='drawer__inner__mastodon with-zig-zag-decoration'>
<img alt='' draggable='false' src={mascot ?? elephantUIPlane} /> <img alt='' draggable='false' src={mascot ?? elephantUIPlane} />
</div> </div>
</div> </div>

View File

@ -75,7 +75,7 @@ export const DisabledAccountBanner: React.FC = () => {
</a> </a>
<button <button
type='button' type='button'
className='button button--block button-tertiary' className='button button--block button-secondary'
onClick={handleLogOutClick} onClick={handleLogOutClick}
> >
<FormattedMessage <FormattedMessage

View File

@ -46,7 +46,7 @@ export const SignInBanner: React.FC = () => {
<a <a
href={sso_redirect} href={sso_redirect}
data-method='post' data-method='post'
className='button button--block button-tertiary' className='button button--block button-secondary'
> >
<FormattedMessage <FormattedMessage
id='sign_in_banner.sso_redirect' id='sign_in_banner.sso_redirect'
@ -98,7 +98,7 @@ export const SignInBanner: React.FC = () => {
/> />
</p> </p>
{signupButton} {signupButton}
<a href='/auth/sign_in' className='button button--block button-tertiary'> <a href='/auth/sign_in' className='button button--block button-secondary'>
<FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /> <FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' />
</a> </a>
</div> </div>

View File

@ -98,7 +98,7 @@ class BundleColumnError extends PureComponent {
<div className='error-column__message__actions'> <div className='error-column__message__actions'>
{errorType === 'network' && <Button onClick={this.handleRetry}><FormattedMessage id='bundle_column_error.retry' defaultMessage='Try again' /></Button>} {errorType === 'network' && <Button onClick={this.handleRetry}><FormattedMessage id='bundle_column_error.retry' defaultMessage='Try again' /></Button>}
{errorType === 'error' && <CopyButton value={stacktrace}><FormattedMessage id='bundle_column_error.copy_stacktrace' defaultMessage='Copy error report' /></CopyButton>} {errorType === 'error' && <CopyButton value={stacktrace}><FormattedMessage id='bundle_column_error.copy_stacktrace' defaultMessage='Copy error report' /></CopyButton>}
<Link to='/' className={classNames('button', { 'button-tertiary': errorType !== 'routing' })}><FormattedMessage id='bundle_column_error.return' defaultMessage='Go back home' /></Link> <Link to='/' className={classNames('button', { 'button-secondary': errorType !== 'routing' })}><FormattedMessage id='bundle_column_error.return' defaultMessage='Go back home' /></Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -46,7 +46,7 @@ export const ModalPlaceholder: React.FC<{
defaultMessage='Try again' defaultMessage='Try again'
/> />
</Button> </Button>
<Button onClick={handleClose} className='button button-tertiary'> <Button onClick={handleClose} className='button button-secondary'>
<FormattedMessage <FormattedMessage
id='bundle_modal_error.close' id='bundle_modal_error.close'
defaultMessage='Close' defaultMessage='Close'

View File

@ -104,7 +104,7 @@ const LoginOrSignUp: React.FC = () => {
<a <a
href={sso_redirect} href={sso_redirect}
data-method='post' data-method='post'
className='button button--block button-tertiary' className='button button--block button-secondary'
> >
<FormattedMessage <FormattedMessage
id='sign_in_banner.sso_redirect' id='sign_in_banner.sso_redirect'
@ -143,7 +143,7 @@ const LoginOrSignUp: React.FC = () => {
return ( return (
<div className='ui__navigation-bar__sign-up'> <div className='ui__navigation-bar__sign-up'>
{signupButton} {signupButton}
<a href='/auth/sign_in' className='button button-tertiary'> <a href='/auth/sign_in' className='button button-secondary'>
<FormattedMessage <FormattedMessage
id='sign_in_banner.sign_in' id='sign_in_banner.sign_in'
defaultMessage='Login' defaultMessage='Login'

View File

@ -1024,10 +1024,6 @@ a.name-tag,
margin-top: 15px; margin-top: 15px;
} }
.user-role {
color: var(--user-role-accent);
}
.applications-list { .applications-list {
.icon { .icon {
vertical-align: middle; vertical-align: middle;

View File

@ -0,0 +1,7 @@
@use 'mastodon/css_variables';
@use 'mastodon/variables';
@use 'common';
html {
color-scheme: dark;
}

View File

@ -0,0 +1,24 @@
@use 'mastodon/mixins';
@use 'fonts/roboto';
@use 'fonts/roboto-mono';
@use 'mastodon/reset';
@use 'mastodon/basics';
@use 'mastodon/branding';
@use 'mastodon/containers';
@use 'mastodon/lists';
@use 'mastodon/widgets';
@use 'mastodon/forms';
@use 'mastodon/accounts';
@use 'mastodon/components';
@use 'mastodon/polls';
@use 'mastodon/modal';
@use 'mastodon/emoji_picker';
@use 'mastodon/annual_reports';
@use 'mastodon/about';
@use 'mastodon/tables';
@use 'mastodon/admin';
@use 'mastodon/dashboard';
@use 'mastodon/rtl';
@use 'mastodon/accessibility';
@use 'mastodon/rich_text';

View File

@ -0,0 +1,8 @@
@use 'mastodon/css_variables';
@use 'mastodon/variables';
@use 'common';
@use 'contrast/diff';
html {
color-scheme: dark;
}

View File

@ -0,0 +1,54 @@
:root {
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-50);
--color-text-secondary: var(--color-grey-300);
--color-text-tertiary: var(--color-grey-400);
--color-text-brand: var(--color-indigo-300);
--color-text-status-links: var(--color-text-brand);
/* BORDER TOKENS */
--border-strength-primary: 18%;
}
.status__content a,
.reply-indicator__content a,
.edit-indicator__content a,
.link-footer a,
.status__content__read-more-button,
.status__content__translate-button {
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
&.mention {
text-decoration: none;
span {
text-decoration: underline;
}
&:hover,
&:focus,
&:active {
span {
text-decoration: none;
}
}
}
}
.link-button:disabled {
cursor: not-allowed;
&:hover,
&:focus,
&:active {
text-decoration: none !important;
}
}

View File

@ -0,0 +1,14 @@
/* This is needed for the wicg-inert polyfill */
[inert] {
pointer-events: none;
cursor: default;
}
[inert],
[inert] * {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
@font-face {
font-family: Inter;
src: url('../../fonts/inter/inter-variable-font-slnt-wght.woff2')
format('woff2-variations');
font-weight: 100 900;
font-style: normal;
mso-generic-font-family: swiss;
}

View File

@ -0,0 +1,13 @@
@font-face {
font-family: mastodon-font-monospace;
src:
local('Roboto Mono'),
url('@/fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'),
url('@/fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'),
url('@/fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'),
url('@/fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular')
format('svg');
font-weight: 400;
font-display: swap;
font-style: normal;
}

View File

@ -0,0 +1,55 @@
@font-face {
font-family: mastodon-font-sans-serif;
src:
local('Roboto Italic'),
url('@/fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
url('@/fonts/roboto/roboto-italic-webfont.woff') format('woff'),
url('@/fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
url('@/fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont')
format('svg');
font-weight: normal;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: mastodon-font-sans-serif;
src:
local('Roboto Bold'),
url('@/fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
url('@/fonts/roboto/roboto-bold-webfont.woff') format('woff'),
url('@/fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
url('@/fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont')
format('svg');
font-weight: bold;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: mastodon-font-sans-serif;
src:
local('Roboto Medium'),
url('@/fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
url('@/fonts/roboto/roboto-medium-webfont.woff') format('woff'),
url('@/fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
url('@/fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont')
format('svg');
font-weight: 500;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: mastodon-font-sans-serif;
src:
local('Roboto'),
url('@/fonts/roboto/roboto-regular-webfont.woff2') format('woff2'),
url('@/fonts/roboto/roboto-regular-webfont.woff') format('woff'),
url('@/fonts/roboto/roboto-regular-webfont.ttf') format('truetype'),
url('@/fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont')
format('svg');
font-weight: normal;
font-display: swap;
font-style: normal;
}

View File

@ -0,0 +1,9 @@
@use 'mastodon-light/css_variables';
@use 'mastodon/variables' with (
$emojis-requiring-inversion: 'chains'
);
@use 'common';
html {
color-scheme: light;
}

View File

@ -0,0 +1,214 @@
@use '../mastodon/theme_utils' as utils;
:root {
--color-black: #000;
--color-grey-950: #181821;
--color-grey-800: #292938;
--color-grey-700: #444664;
--color-grey-600: #545778;
--color-grey-500: #696d91;
--color-grey-400: #8b8dac;
--color-grey-300: #b4b6cb;
--color-grey-200: #d8d9e3;
--color-grey-100: #f0f0f5;
--color-grey-50: #f0f1ff;
--color-white: #fff;
--color-indigo-600: #6147e6;
--color-indigo-400: #8886ff;
--color-indigo-300: #a5abfd;
--color-indigo-200: #c8cdfe;
--color-indigo-100: #e0e3ff;
--color-indigo-50: #f0f1ff;
--color-red-500: #ff637e;
--color-red-600: #ec003f;
--color-yellow-400: #ffb900;
--color-yellow-600: #e17100;
--color-green-400: #05df72;
--color-green-600: #00a63e;
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-950);
--color-text-secondary: var(--color-grey-600);
--color-text-tertiary: var(--color-grey-500);
--color-text-on-inverted: var(--color-white);
--color-text-brand: var(--color-indigo-600);
--color-text-brand-soft: color-mix(
in oklab,
var(--color-text-primary),
var(--color-text-brand)
);
--color-text-on-brand-base: var(--color-white);
--color-text-error: var(--color-red-600);
--color-text-on-error-base: var(--color-white);
--color-text-warning: var(--color-yellow-600);
--color-text-on-warning-base: var(--color-white);
--color-text-success: var(--color-green-600);
--color-text-on-success-base: var(--color-white);
--color-text-disabled: var(--color-grey-300);
--color-text-on-disabled: var(--color-grey-200);
--color-text-bookmark-highlight: var(--color-text-error);
--color-text-favourite-highlight: var(--color-text-warning);
--color-text-on-media: var(--color-white);
--color-text-status-links: var(--color-text-brand);
/* BACKGROUND TOKENS */
// Neutrals
--color-bg-primary: var(--color-white);
--overlay-strength-secondary: 5%;
--color-bg-secondary-base: var(--color-grey-600);
--color-bg-secondary: #{color-mix(
in oklab,
var(--color-bg-primary),
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
)};
--color-bg-secondary-solid: #{color-mix(
in srgb,
var(--color-bg-primary),
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
)};
--color-bg-tertiary: #{color-mix(
in oklab,
var(--color-bg-primary),
var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary))
)};
// Utility
--color-bg-ambient: var(--color-bg-primary);
--color-bg-elevated: var(--color-bg-primary);
--color-bg-inverted: var(--color-grey-950);
--color-bg-media-base: var(--color-black);
--color-bg-media-strength: 65%;
--color-bg-media: #{utils.css-alpha(
var(--color-bg-media-base),
var(--color-bg-media-strength)
)};
--color-bg-overlay: var(--color-bg-primary);
--color-bg-disabled: var(--color-grey-400);
// Brand
--overlay-strength-brand: 8%;
--color-bg-brand-base: var(--color-indigo-600);
--color-bg-brand-base-hover: color-mix(
in oklab,
var(--color-bg-brand-base),
black var(--overlay-strength-brand)
);
--color-bg-brand-soft: #{utils.css-alpha(
var(--color-bg-brand-base),
calc(var(--overlay-strength-brand) * 1.5)
)};
--color-bg-brand-softer: #{utils.css-alpha(
var(--color-bg-brand-base),
var(--overlay-strength-brand)
)};
// Error
--overlay-strength-error: 12%;
--color-bg-error-base: var(--color-red-600);
--color-bg-error-base-hover: color-mix(
in oklab,
var(--color-bg-error-base),
black var(--overlay-strength-error)
);
--color-bg-error-soft: #{utils.css-alpha(
var(--color-bg-error-base),
calc(var(--overlay-strength-error) * 1.5)
)};
--color-bg-error-softer: #{utils.css-alpha(
var(--color-bg-error-base),
var(--overlay-strength-error)
)};
// Warning
--overlay-strength-warning: 10%;
--color-bg-warning-base: var(--color-yellow-600);
--color-bg-warning-base-hover: color-mix(
in oklab,
var(--color-bg-warning-base),
black var(--overlay-strength-warning)
);
--color-bg-warning-soft: #{utils.css-alpha(
var(--color-bg-warning-base),
calc(var(--overlay-strength-warning) * 1.5)
)};
--color-bg-warning-softer: #{utils.css-alpha(
var(--color-bg-warning-base),
var(--overlay-strength-warning)
)};
// Success
--overlay-strength-success: 15%;
--color-bg-success-base: var(--color-green-600);
--color-bg-success-base-hover: color-mix(
in oklab,
var(--color-bg-success-base),
black var(--overlay-strength-success)
);
--color-bg-success-soft: #{utils.css-alpha(
var(--color-bg-success-base),
calc(var(--overlay-strength-success) * 1.5)
)};
--color-bg-success-softer: #{utils.css-alpha(
var(--color-bg-success-base),
var(--overlay-strength-success)
)};
/* BORDER TOKENS */
--border-strength-primary: 15%;
--color-border-primary: color-mix(
in oklab,
var(--color-bg-primary),
var(--color-grey-950) var(--border-strength-primary)
);
--color-border-media: rgb(252 248 255 / 15%);
--color-border-on-bg-secondary: var(--color-grey-200);
--color-border-on-bg-brand-softer: var(--color-indigo-200);
--color-border-on-bg-error-softer: #{utils.css-alpha(
var(--color-text-error),
50%
)};
--color-border-on-bg-warning-softer: #{utils.css-alpha(
var(--color-text-warning),
50%
)};
--color-border-on-bg-success-softer: #{utils.css-alpha(
var(--color-text-success),
50%
)};
--color-border-on-bg-inverted: var(--color-border-primary);
/* SHADOW TOKENS */
--shadow-strength-primary: 30%;
--color-shadow-primary: #{utils.css-alpha(
var(--color-black),
var(--shadow-strength-primary)
)};
--dropdown-shadow:
0 20px 25px -5px var(--color-shadow-primary),
0 8px 10px -6px var(--color-shadow-primary);
--overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary));
/* GRAPHS/CHARTS TOKENS */
--color-graph-primary-stroke: var(--color-text-brand);
--color-graph-primary-fill: var(--color-bg-brand-softer);
--color-graph-warning-stroke: var(--color-text-warning);
--color-graph-warning-fill: var(--color-bg-warning-softer);
--color-graph-disabled-stroke: var(--color-text-disabled);
--color-graph-disabled-fill: var(--color-bg-disabled);
/* LEGACY TOKENS */
--rich-text-container-color: rgb(255 216 231 / 100%);
--rich-text-text-color: rgb(114 47 83 / 100%);
--rich-text-decorations-color: rgb(255 175 212 / 100%);
/* MISCELLANEOUS */
--outline-focus-default: 2px solid var(--color-text-brand);
--avatar-border-radius: 8px;
}

View File

@ -0,0 +1,45 @@
@mixin search-input {
outline: 0;
box-sizing: border-box;
width: 100%;
box-shadow: none;
font-family: inherit;
background: var(--color-bg-secondary);
color: var(--color-text-primary);
border-radius: 4px;
border: 1px solid var(--color-border-on-bg-secondary);
font-size: 17px;
line-height: normal;
margin: 0;
}
@mixin search-popout {
background: var(--color-bg-elevated);
border-radius: 4px;
padding: 10px 14px;
padding-bottom: 14px;
margin-top: 10px;
color: var(--color-text-secondary);
box-shadow: 2px 4px 15px var(--color-shadow-primary);
h4 {
text-transform: uppercase;
color: var(--color-text-secondary);
font-size: 13px;
font-weight: 500;
margin-bottom: 10px;
}
li {
padding: 4px 0;
}
ul {
margin-bottom: 10px;
}
em {
font-weight: 500;
color: var(--color-text-primary);
}
}

View File

@ -0,0 +1,3 @@
@function css-alpha($base-color, $amount) {
@return #{rgb(from $base-color r g b / $amount)};
}

View File

@ -0,0 +1,27 @@
// Keep this filter a SCSS variable rather than
// a CSS Custom Property due to this Safari bug:
// https://github.com/mdn/browser-compat-data/issues/25914#issuecomment-2676190245
$backdrop-blur-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
// 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: 1175px;
$mobile-menu-breakpoint: 760px;
$mobile-breakpoint: 630px;
$no-columns-breakpoint: 600px;
$font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default;
$font-monospace: 'mastodon-font-monospace' !default;
$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange'
'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign'
'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on'
'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default;

View File

@ -0,0 +1,130 @@
@use 'variables' as *;
$maximum-width: 1235px;
$fluid-breakpoint: $maximum-width + 20px;
.container {
box-sizing: border-box;
max-width: $maximum-width;
margin: 0 auto;
position: relative;
@media screen and (max-width: $fluid-breakpoint) {
width: 100%;
padding: 0 10px;
}
}
.brand {
position: relative;
text-decoration: none;
}
.rules-list {
font-size: 15px;
line-height: 22px;
counter-reset: list-counter;
li {
position: relative;
border-bottom: 1px solid var(--color-border-primary);
padding: 1em 1.75em;
padding-inline-start: 3em;
font-weight: 500;
counter-increment: list-counter;
min-height: 4ch;
button {
background: transparent;
border: 0;
padding: 0;
margin: 0;
text-align: start;
font: inherit;
&:hover,
&:focus,
&:active {
background: transparent;
}
&[aria-expanded='false'] .rules-list__hint {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@supports (-webkit-line-clamp: 2) {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: normal;
}
}
}
&::before {
content: counter(list-counter);
position: absolute;
inset-inline-start: 0;
top: 1em;
background: var(--color-bg-brand-base);
color: var(--color-text-on-brand-base);
border-radius: 50%;
width: 4ch;
height: 4ch;
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
}
&:last-child {
border-bottom: 0;
}
}
&__text {
color: var(--color-text-primary);
}
&__hint {
font-size: 14px;
font-weight: 400;
color: var(--color-text-secondary);
}
}
.rules-languages {
display: flex;
gap: 1rem;
align-items: center;
position: relative;
> label {
font-size: 14px;
font-weight: 600;
color: var(--color-text-primary);
}
select {
appearance: none;
box-sizing: border-box;
font-size: 14px;
color: var(--color-text-primary);
display: block;
width: 100%;
outline: 0;
font-family: inherit;
resize: vertical;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border-primary);
border-radius: 4px;
padding-inline-start: 10px;
padding-inline-end: 30px;
height: 41px;
@media screen and (width <= 600px) {
font-size: 16px;
}
}
}

View File

@ -0,0 +1,13 @@
@use 'variables' as *;
%emoji-color-inversion {
filter: invert(1);
}
.emojione {
@each $emoji in $emojis-requiring-inversion {
&[title=':#{$emoji}:'] {
@extend %emoji-color-inversion;
}
}
}

View File

@ -0,0 +1,411 @@
@use 'sass:color';
@use 'variables' as *;
.card {
& > a {
display: block;
text-decoration: none;
color: inherit;
overflow: hidden;
border-radius: 4px;
&:hover,
&:active,
&:focus {
.card__bar {
background: var(--color-bg-brand-softer);
}
}
}
&__img {
height: 130px;
position: relative;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border-primary);
border-bottom: none;
img {
display: block;
width: 100%;
height: 100%;
margin: 0;
object-fit: cover;
}
@media screen and (width <= 600px) {
height: 200px;
}
}
&__bar {
position: relative;
padding: 15px;
display: flex;
justify-content: flex-start;
align-items: center;
background: var(--color-bg-primary);
border: 1px solid var(--color-border-primary);
border-top: none;
.avatar {
flex: 0 0 auto;
width: 48px;
height: 48px;
padding-top: 2px;
img {
width: 100%;
height: 100%;
display: block;
margin: 0;
border-radius: 4px;
background: var(--color-bg-secondary);
object-fit: cover;
}
}
.display-name {
margin-inline-start: 15px;
text-align: start;
svg[data-hidden] {
display: none;
}
strong {
font-size: 15px;
color: var(--color-text-primary);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
}
span {
display: block;
font-size: 14px;
color: var(--color-text-secondary);
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.pagination {
padding: 30px 0;
text-align: center;
overflow: hidden;
a,
.current,
.newer,
.older,
.page,
.gap {
font-size: 14px;
color: var(--color-text-primary);
font-weight: 500;
display: inline-block;
padding: 6px 10px;
text-decoration: none;
}
.current {
color: var(--color-bg-inverted);
background: var(--color-text-on-inverted);
border-radius: 100px;
cursor: default;
margin: 0 10px;
}
.gap {
cursor: default;
}
.older,
.newer {
text-transform: uppercase;
color: var(--color-text-primary);
}
.older {
float: left;
padding-inline-start: 0;
}
.newer {
float: right;
padding-inline-end: 0;
}
.disabled {
cursor: default;
color: var(--color-text-disabled);
}
@media screen and (width <= 700px) {
padding: 30px 20px;
.page {
display: none;
}
.newer,
.older {
display: inline-block;
}
}
}
.nothing-here {
color: var(--color-text-secondary);
background: var(--color-bg-primary);
font-size: 14px;
font-weight: 500;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
cursor: default;
border-radius: 4px;
padding: 20px;
min-height: 30vh;
border: 1px solid var(--color-border-primary);
@media screen and (min-width: ($no-gap-breakpoint - 1)) {
border-top: 0;
}
&--no-toolbar {
border-top: 1px solid var(--color-border-primary);
}
&--under-tabs {
border-radius: 0 0 4px 4px;
}
&--flexible {
box-sizing: border-box;
min-height: 100%;
}
}
.information-badge,
.simple_form .overridden,
.simple_form .recommended,
.simple_form .not_recommended {
display: inline-block;
padding: 4px 6px;
cursor: default;
border-radius: 4px;
font-size: 12px;
line-height: 12px;
font-weight: 500;
color: var(--color-text-primary);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.information-badge,
.simple_form .overridden,
.simple_form .recommended,
.simple_form .not_recommended {
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border-primary);
}
.information-badge {
&.superapp {
color: var(--color-text-success);
background-color: var(--color-bg-success-softer);
border-color: var(--color-border-on-bg-success-softer);
}
}
.account-role {
display: inline-flex;
padding: 4px;
padding-inline-end: 8px;
border: 1px solid var(--color-text-brand);
color: var(--color-text-brand);
font-weight: 500;
font-size: 12px;
letter-spacing: 0.5px;
line-height: 16px;
gap: 4px;
border-radius: 6px;
align-items: center;
svg {
width: auto;
height: 15px;
opacity: 0.85;
fill: currentColor;
}
&__domain {
font-weight: 400;
opacity: 0.75;
letter-spacing: 0;
}
}
.simple_form .not_recommended {
color: var(--color-text-error);
background-color: var(--color-bg-error-softer);
border-color: var(--color-border-on-bg-error-softer);
}
.account__header__fields {
max-width: 100vw;
padding: 0;
margin: 15px -15px -15px;
border: 0 none;
border-top: 1px solid var(--color-border-primary);
border-bottom: 1px solid var(--color-border-primary);
font-size: 14px;
line-height: 20px;
dl {
display: flex;
border-bottom: 1px solid var(--color-border-primary);
}
dt,
dd {
box-sizing: border-box;
padding: 14px;
text-align: center;
max-height: 48px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
dt {
font-weight: 500;
width: 120px;
flex: 0 0 auto;
color: var(--color-text-primary);
background: var(--color-bg-secondary);
}
dd {
flex: 1 1 auto;
color: var(--color-text-secondary);
}
a {
color: var(--color-text-brand);
text-decoration: none;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
.verified {
border: 1px solid var(--color-border-on-bg-success-softer);
background: var(--color-bg-success-softer);
a {
color: var(--color-text-success);
font-weight: 500;
}
&__mark {
color: var(--color-text-success);
}
}
dl:last-child {
border-bottom: 0;
}
}
.directory__tag .trends__item__current {
width: auto;
}
.pending-account {
&__header {
color: var(--color-text-secondary);
a {
color: var(--color-text-primary);
text-decoration: none;
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
strong {
color: var(--color-text-primary);
font-weight: 700;
}
.warning-hint {
font-weight: normal !important;
}
}
&__body {
margin-top: 10px;
}
}
.batch-table__row--muted {
color: var(--color-text-tertiary);
}
.batch-table__row--muted .pending-account__header,
.batch-table__row--muted .accounts-table,
.batch-table__row--muted .name-tag {
&,
a,
strong {
color: var(--color-text-tertiary);
}
}
.batch-table__row--muted .name-tag .avatar {
opacity: 0.5;
}
.batch-table__row--muted .accounts-table {
tbody td.accounts-table__extra,
&__count,
&__count small {
color: var(--color-text-tertiary);
}
}
.batch-table__row--attention {
color: var(--color-text-warning);
}
.batch-table__row--attention .pending-account__header,
.batch-table__row--attention .accounts-table,
.batch-table__row--attention .name-tag {
&,
a,
strong {
color: var(--color-text-warning);
}
}
.batch-table__row--attention .accounts-table {
tbody td.accounts-table__extra,
&__count,
&__count small {
color: var(--color-text-warning);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,342 @@
@use 'variables' as *;
:root {
--indigo-1: #17063b;
--indigo-2: #2f0c7a;
--indigo-3: #562cfc;
--indigo-5: #858afa;
--indigo-6: #cccfff;
--lime: #baff3b;
--goldenrod-2: #ffc954;
}
.annual-report {
flex: 0 0 auto;
background: var(--indigo-1);
padding: 24px;
&__header {
margin-bottom: 16px;
h1 {
font-size: 25px;
font-weight: 600;
line-height: 30px;
color: var(--lime);
margin-bottom: 8px;
}
p {
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: var(--indigo-6);
}
}
&__bento {
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
0,
auto
);
&__box {
padding: 16px;
border-radius: 8px;
background: var(--indigo-2);
color: var(--indigo-5);
}
}
&__summary {
&__most-boosted-post {
grid-column: span 2;
grid-row: span 2;
padding: 0;
.status__content,
.content-warning {
color: var(--indigo-6);
}
.detailed-status {
border: 0;
}
.content-warning {
border: 0;
background: var(--indigo-1);
.link-button {
color: var(--indigo-5);
}
}
.detailed-status__meta__line {
border-bottom-color: var(--indigo-3);
}
.detailed-status__meta {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.detailed-status__meta,
.poll__footer,
.poll__link,
.detailed-status .logo,
.detailed-status__display-name {
color: var(--indigo-5);
}
.detailed-status__meta .animated-number,
.detailed-status__display-name strong {
color: var(--indigo-6);
}
.poll__chart {
background-color: var(--indigo-3);
&.leading {
background-color: var(--goldenrod-2);
}
}
.status-card,
.hashtag-bar {
display: none;
}
}
&__followers {
grid-column: span 1;
text-align: center;
position: relative;
overflow: hidden;
padding-block-start: 24px;
padding-block-end: 24px;
--sparkline-gradient-top: rgba(86, 44, 252, 50%);
--sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
&__foreground {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
position: relative;
z-index: 1;
}
&__number {
font-size: 31px;
font-weight: 600;
line-height: 37px;
color: var(--lime);
}
&__label {
font-size: 14px;
font-weight: 600;
line-height: 17px;
color: var(--indigo-6);
}
&__footnote {
display: block;
font-weight: 400;
opacity: 0.5;
}
svg {
position: absolute;
bottom: 0;
inset-inline-end: 0;
pointer-events: none;
z-index: 0;
height: 70%;
width: auto;
path:first-child {
fill: url('#gradient') !important;
fill-opacity: 1 !important;
}
path:last-child {
stroke: var(--color-graph-primary-stroke) !important;
fill: none !important;
}
}
}
&__archetype {
grid-column: span 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 8px;
padding: 0;
img {
display: block;
width: 100%;
height: auto;
border-radius: 8px;
}
&__label {
padding: 16px;
padding-bottom: 8px;
font-size: 14px;
line-height: 17px;
font-weight: 600;
color: var(--lime);
}
}
&__most-used-app {
grid-column: span 1;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
box-sizing: border-box;
&__label {
font-size: 14px;
line-height: 17px;
font-weight: 600;
color: var(--indigo-6);
}
&__icon {
font-size: 14px;
line-height: 17px;
font-weight: 600;
color: var(--goldenrod-2);
}
}
&__percentile {
grid-row: span 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
text-align: center;
text-wrap: balance;
padding: 16px 8px;
&__label {
font-size: 14px;
line-height: 17px;
}
&__number {
font-size: 54px;
font-weight: 600;
line-height: 73px;
color: var(--goldenrod-2);
}
&__footnote {
font-size: 11px;
line-height: 14px;
opacity: 0.5;
}
}
&__new-posts {
grid-column: span 2;
text-align: center;
position: relative;
overflow: hidden;
&__label {
font-size: 20px;
font-weight: 600;
line-height: 24px;
color: var(--indigo-6);
z-index: 1;
position: relative;
}
&__number {
font-size: 76px;
font-weight: 600;
line-height: 91px;
color: var(--goldenrod-2);
z-index: 1;
position: relative;
}
svg {
position: absolute;
inset-inline-start: -7px;
top: -4px;
z-index: 0;
}
}
&__most-used-hashtag {
grid-column: span 2;
text-align: center;
overflow: hidden;
&__hashtag {
font-size: 42px;
font-weight: 600;
line-height: 58px;
color: var(--indigo-6);
margin-inline-start: -100%;
margin-inline-end: -100%;
}
&__label {
font-size: 14px;
font-weight: 600;
line-height: 17px;
}
}
}
}
.annual-report-modal {
max-width: 600px;
background: var(--indigo-1);
border-radius: 16px;
display: flex;
flex-direction: column;
overflow-y: auto;
.loading-indicator .circular-progress {
color: var(--lime);
}
@media screen and (max-width: $no-columns-breakpoint) {
border-bottom: 0;
border-radius: 16px 16px 0 0;
}
}
.notification-group--annual-report {
.notification-group__icon {
color: var(--lime);
}
.notification-group__main .link-button {
font-weight: 500;
color: var(--lime);
}
}

View File

@ -0,0 +1,300 @@
@use 'variables' as *;
html.has-modal {
&,
body {
touch-action: none;
overscroll-behavior: none;
-webkit-overflow-scrolling: auto;
scrollbar-gutter: stable;
}
body {
overflow: hidden !important;
}
}
body {
font-family: $font-sans-serif, sans-serif;
background: var(--color-bg-ambient);
font-size: 13px;
line-height: 18px;
font-weight: 400;
color: var(--color-text-primary);
text-rendering: optimizelegibility;
// Disable kerning for Japanese text to preserve monospaced alignment for readability
&:not(:lang(ja)) {
font-feature-settings: 'kern';
}
text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
-webkit-tap-highlight-color: transparent;
&.system-font {
// system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+)
// -apple-system => Safari <11 specific
// BlinkMacSystemFont => Chrome <56 on macOS specific
// Segoe UI => Windows 7/8/10
// Oxygen => KDE
// Ubuntu => Unity/Ubuntu
// Cantarell => GNOME
// Fira Sans => Firefox OS
// Droid Sans => Older Androids (<4.0)
// Helvetica Neue => Older macOS <10.11
// $font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0)
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
$font-sans-serif,
sans-serif;
}
&.app-body {
padding: 0;
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
box-sizing: border-box;
&.layout-single-column {
height: auto;
min-height: 100vh;
min-height: 100dvh;
overflow-y: scroll;
}
&.layout-multiple-columns {
position: absolute;
width: 100%;
height: 100%;
padding-bottom: env(safe-area-inset-bottom);
}
}
&.player {
padding: 0;
margin: 0;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
& > div {
height: 100%;
}
.video-player video {
width: 100%;
height: 100%;
max-height: 100vh;
}
.media-gallery {
margin-top: 0;
height: 100% !important;
border-radius: 0;
}
.media-gallery__item {
border-radius: 0;
}
}
&.embed {
margin: 0;
padding-bottom: 0;
overflow: hidden;
}
&.admin {
padding: 0;
background: var(--color-bg-primary);
}
&.error {
position: absolute;
text-align: center;
width: 100%;
height: 100%;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
.dialog {
vertical-align: middle;
margin: 20px;
&__illustration {
img {
display: block;
max-width: 470px;
width: 100%;
height: auto;
margin-top: -120px;
margin-bottom: -45px;
}
}
h1 {
font-size: 20px;
line-height: 28px;
font-weight: 400;
}
}
}
}
a {
&:focus {
border-radius: 4px;
outline: var(--outline-focus-default);
}
&:focus:not(:focus-visible) {
outline: none;
}
}
button {
font-family: inherit;
cursor: pointer;
&:focus:not(:focus-visible) {
outline: none;
}
}
.app-holder {
&,
& > div,
& > noscript {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
outline: 0 !important;
}
& > noscript {
min-height: 100vh;
min-height: 100dvh;
}
}
.layout-single-column .app-holder {
&,
& > div {
min-height: 100vh;
min-height: 100dvh;
}
}
.layout-multiple-columns .app-holder {
&,
& > div {
height: 100%;
}
}
.error-boundary,
.app-holder noscript {
flex-direction: column;
font-size: 16px;
font-weight: 400;
line-height: 1.7;
color: var(--color-text-error);
text-align: center;
& > div {
max-width: 500px;
}
p {
margin-bottom: 0.85em;
&:last-child {
margin-bottom: 0;
}
}
a {
color: var(--color-text-brand);
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
&__footer {
color: var(--color-text-secondary);
font-size: 13px;
a {
color: var(--color-text-secondary);
}
}
button {
display: inline;
border: 0;
background: transparent;
color: var(--color-text-secondary);
font: inherit;
padding: 0;
margin: 0;
line-height: inherit;
cursor: pointer;
outline: 0;
transition: color 300ms linear;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
&.copied {
color: var(--mas-status-success-color);
transition: none;
}
}
}
.logo-resources {
// Not using display: none because of https://bugs.chromium.org/p/chromium/issues/detail?id=258029
visibility: hidden;
user-select: none;
pointer-events: none;
width: 0;
height: 0;
overflow: hidden;
position: absolute;
top: 0;
inset-inline-start: 0;
z-index: -1000;
}
// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
// to set the z-index to a high value, which messes with modals and dropdowns.
// Blocked elements can in theory only be media and frames/embeds, so they
// should only appear in statuses, under divs and articles.
body,
div,
article {
.__ns__pop2top {
z-index: unset !important;
}
}

View File

@ -0,0 +1,5 @@
@use 'variables' as *;
.logo {
color: var(--color-text-primary);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,166 @@
@use 'variables' as *;
.container-alt {
width: 700px;
margin: 0 auto;
@media screen and (width <= 740px) {
width: 100%;
margin: 0;
}
}
.logo-container {
margin: 50px auto;
h1 {
display: flex;
justify-content: center;
align-items: center;
.logo {
height: 42px;
margin-inline-end: 10px;
}
a {
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text-primary);
text-decoration: none;
outline: 0;
padding: 12px 16px;
line-height: 32px;
font-weight: 500;
font-size: 14px;
}
}
}
.compose-standalone {
.compose-form {
width: 400px;
margin: 0 auto;
padding: 10px 0;
padding-bottom: 20px;
box-sizing: border-box;
@media screen and (width <= 400px) {
width: 100%;
padding: 20px;
}
}
}
.account-header {
width: 400px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
line-height: 20px;
box-sizing: border-box;
padding: 20px 0;
margin-top: 40px;
margin-bottom: 10px;
border-bottom: 1px solid var(--color-border-primary);
@media screen and (width <= 440px) {
width: 100%;
margin: 0;
padding: 20px;
}
.avatar {
width: 48px;
height: 48px;
flex: 0 0 auto;
img {
width: 100%;
height: 100%;
display: block;
margin: 0;
border-radius: var(--avatar-border-radius);
}
}
.name {
flex: 1 1 auto;
color: var(--color-text-primary);
.username {
display: block;
font-size: 16px;
line-height: 24px;
text-overflow: ellipsis;
overflow: hidden;
color: var(--color-text-primary);
}
}
.logout-link {
display: block;
font-size: 32px;
line-height: 40px;
flex: 0 0 auto;
}
}
.redirect {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
font-size: 14px;
line-height: 18px;
&__logo {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
img {
height: 48px;
}
}
&__message {
text-align: center;
h1 {
font-size: 17px;
line-height: 22px;
font-weight: 700;
margin-bottom: 30px;
}
p {
margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
}
a {
color: var(--color-text-brand);
font-weight: 500;
text-decoration: none;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
}
&__link {
margin-top: 15px;
}
}

View File

@ -0,0 +1,228 @@
@use 'theme_utils' as utils;
:root {
--color-black: #000;
--color-grey-950: #181821;
--color-grey-800: #292938;
--color-grey-700: #444664;
--color-grey-600: #545778;
--color-grey-500: #696d91;
--color-grey-400: #8b8dac;
--color-grey-300: #b4b6cb;
--color-grey-200: #d8d9e3;
--color-grey-100: #f0f0f5;
--color-grey-50: #f0f1ff;
--color-white: #fff;
--color-indigo-600: #6147e6;
--color-indigo-400: #8886ff;
--color-indigo-300: #a5abfd;
--color-indigo-200: #c8cdfe;
--color-indigo-100: #e0e3ff;
--color-indigo-50: #f0f1ff;
--color-red-500: #ff637e;
--color-red-600: #ec003f;
--color-yellow-400: #ffb900;
--color-yellow-600: #e17100;
--color-green-400: #05df72;
--color-green-600: #00a63e;
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-50);
--color-text-secondary: var(--color-grey-400);
--color-text-tertiary: var(--color-grey-500);
--color-text-on-inverted: var(--color-grey-950);
--color-text-brand: var(--color-indigo-400);
--color-text-brand-soft: color-mix(
in oklab,
var(--color-text-primary),
var(--color-text-brand)
);
--color-text-on-brand-base: var(--color-white);
--color-text-error: var(--color-red-500);
--color-text-on-error-base: var(--color-white);
--color-text-warning: var(--color-yellow-400);
--color-text-on-warning-base: var(--color-white);
--color-text-success: var(--color-green-400);
--color-text-on-success-base: var(--color-white);
--color-text-disabled: var(--color-grey-600);
--color-text-on-disabled: var(--color-grey-400);
--color-text-bookmark-highlight: var(--color-text-error);
--color-text-favourite-highlight: var(--color-text-warning);
--color-text-on-media: var(--color-white);
--color-text-status-links: color-mix(
in oklab,
var(--color-text-primary),
var(--color-text-secondary)
);
/* BACKGROUND TOKENS */
// Neutrals
--color-bg-primary: var(--color-grey-950);
--overlay-strength-secondary: 10%;
--color-bg-secondary-base: var(--color-indigo-200);
--color-bg-secondary: #{utils.css-alpha(
var(--color-bg-secondary-base),
var(--overlay-strength-secondary)
)};
--color-bg-secondary-solid: color-mix(
in srgb,
var(--color-bg-primary),
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
);
--color-bg-tertiary: color-mix(
in oklab,
var(--color-bg-primary),
var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary))
);
// Utility
--color-bg-ambient: var(--color-bg-primary);
--color-bg-elevated: var(--color-grey-800);
--color-bg-inverted: var(--color-grey-50);
--color-bg-media-base: var(--color-black);
--color-bg-media-strength: 65%;
--color-bg-media: #{utils.css-alpha(
var(--color-bg-media-base),
var(--color-bg-media-strength)
)};
--color-bg-overlay: var(--color-bg-primary);
--color-bg-disabled: var(--color-grey-700);
// Brand
--overlay-strength-brand: 10%;
--color-bg-brand-base: var(--color-indigo-600);
--color-bg-brand-base-hover: color-mix(
in oklab,
var(--color-bg-brand-base),
black var(--overlay-strength-brand)
);
--color-bg-brand-soft: #{utils.css-alpha(
var(--color-bg-brand-base),
calc(var(--overlay-strength-brand) * 1.5)
)};
--color-bg-brand-softer: #{utils.css-alpha(
var(--color-bg-brand-base),
var(--overlay-strength-brand)
)};
// Error
--overlay-strength-error: 12%;
--color-bg-error-base: var(--color-red-600);
--color-bg-error-base-hover: color-mix(
in oklab,
var(--color-bg-error-base),
black var(--overlay-strength-error)
);
--color-bg-error-soft: #{utils.css-alpha(
var(--color-bg-error-base),
calc(var(--overlay-strength-error) * 1.5)
)};
--color-bg-error-softer: #{utils.css-alpha(
var(--color-bg-error-base),
var(--overlay-strength-error)
)};
// Warning
--overlay-strength-warning: 10%;
--color-bg-warning-base: var(--color-yellow-600);
--color-bg-warning-base-hover: color-mix(
in oklab,
var(--color-bg-warning-base),
black var(--overlay-strength-warning)
);
--color-bg-warning-soft: #{utils.css-alpha(
var(--color-bg-warning-base),
calc(var(--overlay-strength-warning) * 1.5)
)};
--color-bg-warning-softer: #{utils.css-alpha(
var(--color-bg-warning-base),
var(--overlay-strength-warning)
)};
// Success
--overlay-strength-success: 15%;
--color-bg-success-base: var(--color-green-600);
--color-bg-success-base-hover: color-mix(
in oklab,
var(--color-bg-success-base),
black var(--overlay-strength-success)
);
--color-bg-success-soft: #{utils.css-alpha(
var(--color-bg-success-base),
calc(var(--overlay-strength-success) * 1.5)
)};
--color-bg-success-softer: #{utils.css-alpha(
var(--color-bg-success-base),
var(--overlay-strength-success)
)};
/* BORDER TOKENS */
--border-strength-primary: 18%;
--color-border-primary: #{utils.css-alpha(
var(--color-indigo-200),
var(--border-strength-primary)
)};
--color-border-media: rgb(252 248 255 / 15%);
--color-border-on-bg-secondary: #{utils.css-alpha(
var(--color-indigo-200),
calc(var(--border-strength-primary) / 1.5)
)};
--color-border-on-bg-brand-softer: var(--color-border-primary);
--color-border-on-bg-error-softer: #{utils.css-alpha(
var(--color-text-error),
50%
)};
--color-border-on-bg-warning-softer: #{utils.css-alpha(
var(--color-text-warning),
50%
)};
--color-border-on-bg-success-softer: #{utils.css-alpha(
var(--color-text-success),
50%
)};
--color-border-on-bg-inverted: var(--color-border-primary);
/* SHADOW TOKENS */
--shadow-strength-primary: 80%;
--color-shadow-primary: #{utils.css-alpha(
var(--color-black),
var(--shadow-strength-primary)
)};
--dropdown-shadow:
0 20px 25px -5px var(--color-shadow-primary),
0 8px 10px -6px var(--color-shadow-primary);
--overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary));
/* GRAPHS/CHARTS TOKENS */
--color-graph-primary-stroke: var(--color-text-brand);
--color-graph-primary-fill: var(--color-bg-brand-softer);
--color-graph-warning-stroke: var(--color-text-warning);
--color-graph-warning-fill: var(--color-bg-warning-softer);
--color-graph-disabled-stroke: var(--color-text-disabled);
--color-graph-disabled-fill: var(--color-bg-disabled);
/* LEGACY TOKENS */
--rich-text-container-color: rgb(87 24 60 / 100%);
--rich-text-text-color: rgb(255 175 212 / 100%);
--rich-text-decorations-color: rgb(128 58 95 / 100%);
/* MISCELLANEOUS */
--outline-focus-default: 2px solid var(--color-text-brand);
--avatar-border-radius: 8px;
}
body {
// Variable for easily inverting directional UI elements,
--text-x-direction: 1;
&.rtl {
--text-x-direction: -1;
}
}

View File

@ -0,0 +1,120 @@
@use 'variables' as *;
.dashboard__counters {
display: flex;
flex-wrap: wrap;
margin: 0 -5px;
margin-bottom: 20px;
& > div {
box-sizing: border-box;
flex: 0 0 33.333%;
padding: 0 5px;
margin-bottom: 10px;
& > div,
& > a {
padding: 20px;
background: var(--color-bg-primary);
border-radius: 4px;
border: 1px solid var(--color-border-primary);
box-sizing: border-box;
height: 100%;
}
& > a {
text-decoration: none;
color: inherit;
display: block;
&:hover,
&:focus,
&:active {
background: var(--color-bg-brand-softer);
}
}
}
&__num,
&__text {
text-align: center;
font-weight: 500;
font-size: 24px;
color: var(--color-text-primary);
margin-bottom: 20px;
line-height: 30px;
}
&__text {
font-size: 18px;
}
&__label {
font-size: 14px;
color: var(--color-text-secondary);
text-align: center;
font-weight: 500;
}
}
.dashboard {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
gap: 10px;
@media screen and (width <= 1350px) {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
&__item {
&--span-double-column {
grid-column: span 2;
}
&--span-double-row {
grid-row: span 2;
}
h4 {
padding-top: 20px;
}
}
&__quick-access {
display: flex;
align-items: baseline;
border-radius: 4px;
background: var(--color-bg-brand-base);
color: var(--color-text-on-brand-base);
transition: all 100ms ease-in;
font-size: 14px;
padding: 8px 16px;
text-decoration: none;
margin-bottom: 4px;
&:active,
&:focus,
&:hover {
background-color: var(--color-bg-brand-base-hover);
transition: all 200ms ease-out;
}
&.positive {
background: var(--color-bg-success-softer);
color: var(--color-text-success);
}
&.negative {
background: var(--color-bg-error-softer);
color: var(--color-text-error);
}
span {
flex: 1 1 auto;
}
strong {
font-weight: 700;
}
}
}

View File

@ -0,0 +1,248 @@
@use 'variables' as *;
.emoji-mart {
font-size: 13px;
display: inline-block;
&,
* {
box-sizing: border-box;
line-height: 1.15;
}
.emoji-mart-emoji {
padding: 6px;
}
}
.emoji-mart-bar {
&:first-child {
background: var(--color-bg-tertiary);
border-bottom: 1px solid var(--color-border-primary);
}
}
.emoji-mart-anchors {
display: flex;
justify-content: space-between;
padding: 0 6px;
line-height: 0;
}
.emoji-mart-anchor {
position: relative;
flex: 1;
text-align: center;
padding: 12px 4px;
overflow: hidden;
transition: color 0.1s ease-out;
cursor: pointer;
background: transparent;
border: 0;
color: var(--color-text-secondary);
&:hover {
color: color-mix(
in oklab,
var(--color-text-primary),
var(--color-text-secondary)
);
}
}
.emoji-mart-anchor-selected {
color: var(--color-text-brand);
&:hover {
color: var(--color-text-brand-soft);
}
.emoji-mart-anchor-bar {
bottom: -1px;
}
}
.emoji-mart-anchor-bar {
position: absolute;
bottom: -5px;
inset-inline-start: 0;
width: 100%;
height: 4px;
background-color: var(--color-text-brand);
}
.emoji-mart-anchors {
i {
display: inline-block;
width: 100%;
max-width: 22px;
}
svg {
fill: currentColor;
max-height: 18px;
}
}
.emoji-mart-scroll {
overflow-y: scroll;
height: 270px;
max-height: 35vh;
padding: 0 6px 6px;
will-change: transform;
}
.emoji-mart-search {
padding: 10px;
padding-inline-end: 45px;
position: relative;
input {
font-size: 16px;
font-weight: 400;
padding: 7px 9px;
padding-inline-end: 25px;
font-family: inherit;
display: block;
width: 100%;
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-primary);
border-radius: 4px;
&::-moz-focus-inner {
border: 0;
}
&:active,
&:focus {
outline: none !important;
border-width: 1px !important;
}
&::-webkit-search-cancel-button {
display: none;
}
}
}
.emoji-mart-search-icon {
position: absolute;
top: 18px;
inset-inline-end: 45px + 5px;
z-index: 2;
padding: 2px 5px 1px;
border: 0;
background: none;
transition: all 100ms linear;
transition-property: opacity;
pointer-events: auto;
&:disabled {
cursor: default;
pointer-events: none;
}
svg {
fill: currentColor;
}
}
.emoji-mart-category .emoji-mart-emoji {
cursor: pointer;
span {
z-index: 1;
position: relative;
text-align: center;
display: inline-flex !important;
align-items: center;
justify-content: center;
}
&:hover::before {
z-index: -1;
content: '';
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
background-color: var(--color-bg-brand-softer);
border-radius: 100%;
}
}
.emoji-mart-category-label {
z-index: 2;
position: relative;
position: -webkit-sticky;
position: sticky;
top: 0;
span {
display: block;
width: 100%;
font-weight: 500;
padding: 5px 6px;
}
}
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
.emoji-mart-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
border: 0;
}
.emoji-mart-category-list {
margin: 0;
padding: 0;
}
.emoji-mart-category-list li {
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
.emoji-mart-emoji {
position: relative;
display: inline-block;
background: transparent;
border: 0;
padding: 0;
font-size: 0;
span {
width: 22px;
height: 22px;
}
}
.emoji-mart-no-results {
font-size: 14px;
color: var(--color-text-tertiary);
text-align: center;
padding: 5px 6px;
padding-top: 70px;
.emoji-mart-no-results-label {
margin-top: 0.2em;
}
.emoji-mart-emoji:hover::before {
cursor: default;
content: none;
}
}
.emoji-mart-preview {
display: none;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
.no-list {
list-style: none;
li {
display: inline-block;
margin: 0 5px;
}
}
.recovery-codes {
list-style: none;
margin: 0 auto;
li {
font-size: 125%;
line-height: 1.5;
letter-spacing: 1px;
}
}

View File

@ -0,0 +1,53 @@
@use 'variables' as *;
.modal-layout {
background: var(--color-bg-brand-softer);
display: flex;
flex-direction: column;
height: 100vh;
padding: 0;
}
.modal-layout__mastodon {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-end;
> div {
flex: 1;
max-height: 235px;
position: relative;
img {
max-height: 100%;
max-width: 100%;
height: 100%;
position: absolute;
bottom: 0;
inset-inline-start: 0;
}
}
}
@media screen and (width <= 600px) {
.account-header {
margin-top: 0;
}
}
.with-zig-zag-decoration {
&::after {
content: '';
position: absolute;
inset: auto 0 0;
height: 32px;
background-color: var(--color-bg-brand-softer);
/* Decorative zig-zag pattern at the bottom of the page */
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="black"/></svg>');
mask-position: bottom;
mask-repeat: repeat-x;
z-index: -1;
}
}

View File

@ -0,0 +1,232 @@
@use 'sass:color';
@use 'variables' as *;
.poll {
margin-top: 16px;
font-size: 14px;
li {
margin-bottom: 10px;
position: relative;
}
&__chart {
border-radius: 4px;
display: block;
background: rgb(from var(--color-text-brand) r g b / 60%);
height: 5px;
min-width: 1%;
&.leading {
background: var(--color-text-brand);
}
}
progress {
border: 0;
display: block;
width: 100%;
height: 5px;
appearance: none;
background: transparent;
&::-webkit-progress-bar {
background: transparent;
}
// Those rules need to be entirely separate or they won't work, hence the
// duplication
&::-moz-progress-bar {
border-radius: 4px;
background: rgb(from var(--color-text-brand) r g b / 60%);
}
&::-webkit-progress-value {
border-radius: 4px;
background: rgb(from var(--color-text-brand) r g b / 60%);
}
}
&__option {
position: relative;
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 0;
line-height: 18px;
cursor: default;
overflow: hidden;
&__text {
display: inline-block;
overflow-wrap: break-word;
max-width: calc(100% - 45px - 25px);
}
input[type='radio'],
input[type='checkbox'] {
display: none;
}
.autosuggest-input {
flex: 1 1 auto;
}
input[type='text'] {
display: block;
box-sizing: border-box;
width: 100%;
font-size: 14px;
color: var(--color-text-primary);
outline: 0;
font-family: inherit;
background: var(--color-bg-primary);
border: 1px solid var(--color-text-secondary);
border-radius: 4px;
padding: 8px 12px;
&:focus {
border-color: var(--color-text-brand);
}
@media screen and (width <= 600px) {
font-size: 16px;
line-height: 24px;
letter-spacing: 0.5px;
}
}
&.selectable {
cursor: pointer;
}
&.editable,
&.disabled {
align-items: center;
overflow: visible;
}
}
&__input {
display: block;
position: relative;
border: 1px solid var(--color-text-secondary);
box-sizing: border-box;
width: 17px;
height: 17px;
border-radius: 50%;
flex: 0 0 auto;
&.checkbox {
border-radius: 4px;
}
&:active,
&:focus,
&:hover {
border-color: var(--color-text-success);
border-width: 4px;
}
&.active {
background-color: var(--color-bg-success-base);
border-color: var(--color-text-success);
}
&::-moz-focus-inner {
outline: 0 !important;
border: 0;
}
&:focus,
&:active {
outline: 0 !important;
}
&.disabled {
border-color: var(--color-text-disabled);
&.active {
background: var(--color-text-disabled);
}
&:active,
&:focus,
&:hover {
border-color: var(--color-text-disabled);
border-width: 1px;
}
}
}
&__option.editable &__input,
&__option.disabled &__input {
&:active,
&:focus,
&:hover {
border-color: var(--color-text-primary);
border-width: 1px;
}
}
&__number {
display: inline-block;
width: 45px;
font-weight: 700;
flex: 0 0 45px;
}
&__voted {
padding: 0 5px;
display: inline-block;
&__mark {
font-size: 18px;
}
}
&__footer {
padding-top: 6px;
padding-bottom: 5px;
color: var(--color-text-tertiary);
}
&__link {
display: inline;
background: transparent;
padding: 0;
margin: 0;
border: 0;
color: var(--color-text-tertiary);
text-decoration: underline;
font-size: inherit;
&:hover {
text-decoration: none;
}
&:active,
&:focus {
background-color: var(--color-bg-secondary);
}
}
.button {
height: 36px;
padding: 0 16px;
margin-inline-end: 10px;
font-size: 14px;
}
}
.muted .poll {
color: var(--color-text-tertiary);
&__chart {
background: rgb(from var(--color-text-brand) r g b / 40%);
&.leading {
background: rgb(from var(--color-text-brand) r g b / 60%);
}
}
}

View File

@ -0,0 +1,58 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
html:has(body.custom-scrollbars) {
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
}

View File

@ -0,0 +1,116 @@
.status__content__text,
.e-content,
.edit-indicator__content,
.reply-indicator__content {
code {
background: var(--rich-text-container-color);
padding: 4px;
border-radius: 4px;
color: var(--rich-text-text-color);
font-size: 0.85em;
}
pre {
background: var(--rich-text-container-color);
padding: 8px;
border-radius: 4px;
color: var(--rich-text-text-color);
code {
padding: 0;
background: transparent;
}
}
pre,
blockquote {
margin-bottom: 22px;
white-space: pre-wrap;
unicode-bidi: plaintext;
&:last-child {
margin-bottom: 0;
}
}
blockquote {
padding-inline-start: 32px;
color: var(--rich-text-text-color);
white-space: normal;
position: relative;
&::before {
display: block;
content: '';
width: 24px;
height: 20px;
mask-image: url('@/images/quote.svg');
background-color: var(--rich-text-decorations-color);
position: absolute;
inset-inline-start: 0;
top: 0;
}
blockquote {
margin-top: 4px;
border-inline-start: 3px solid var(--rich-text-decorations-color);
padding-inline-start: 16px;
&::before {
display: none;
}
}
p:last-of-type {
margin-bottom: 0;
}
}
& > ul,
& > ol {
margin-bottom: 22px;
&:last-child {
margin-bottom: 0;
}
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
ul,
ol {
padding-inline-start: 24px;
li {
padding-inline-start: 8px;
&::marker {
text-align: end;
}
}
p {
margin: 0;
}
}
ul {
list-style-type: '';
li::marker {
text-align: start;
}
}
ol {
list-style-type: decimal;
}
}

View File

@ -0,0 +1,50 @@
@use 'variables' as *;
body.rtl {
direction: rtl;
.reactions-bar {
direction: rtl;
}
.announcements__mastodon,
.drawer__inner__mastodon > img {
transform: scaleX(-1);
}
.compose-form .autosuggest-textarea__textarea {
padding-right: 10px;
padding-left: 10px + 22px;
}
.columns-area {
direction: rtl;
}
.account__avatar-wrapper {
float: right;
}
.column-header__setting-arrows {
float: left;
}
.admin-wrapper {
direction: rtl;
}
.react-swipeable-view-container > * {
direction: rtl;
}
.column-back-button__icon {
transform: scale(-1, 1);
}
.dismissable-banner,
.warning-banner {
&__action {
float: left;
}
}
}

View File

@ -0,0 +1,375 @@
@use 'variables' as *;
.table {
width: 100%;
max-width: 100%;
border-spacing: 0;
border-collapse: collapse;
th,
td {
padding: 8px;
line-height: 18px;
vertical-align: top;
border-bottom: 1px solid var(--color-border-primary);
text-align: start;
background: var(--color-bg-primary);
&.critical {
font-weight: 700;
color: var(--color-text-warning);
}
}
& > thead > tr > th {
vertical-align: bottom;
font-weight: 500;
}
& > tbody > tr > th {
font-weight: 500;
}
& > tbody > tr:nth-child(odd) > td,
& > tbody > tr:nth-child(odd) > th {
background: var(--color-bg-primary);
}
& > tbody > tr:last-child > td,
& > tbody > tr:last-child > th {
border-bottom: 0;
}
a {
color: var(--color-text-secondary);
text-decoration: none;
&:hover {
color: var(--color-text-brand);
}
}
strong {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
&.inline-table {
& > tbody > tr:nth-child(odd) {
& > td,
& > th {
background: transparent;
}
}
& > tbody > tr:first-child {
& > td,
& > th {
border-top: 0;
}
}
}
&.horizontal-table {
border-collapse: collapse;
border-style: hidden;
& > tbody > tr > th,
& > tbody > tr > td {
padding: 11px 10px;
background: transparent;
border: 1px solid var(--color-border-primary);
color: var(--color-text-primary);
}
& > tbody > tr > th {
color: var(--color-text-secondary);
font-weight: 600;
}
}
&.batch-table {
& > thead > tr > th {
background: var(--color-bg-primary);
border-top: 1px solid var(--color-border-primary);
border-bottom: 1px solid var(--color-border-primary);
&:first-child {
border-radius: 4px 0 0;
border-inline-start: 1px solid var(--color-border-primary);
}
&:last-child {
border-radius: 0 4px 0 0;
border-inline-end: 1px solid var(--color-border-primary);
}
}
}
&--invites tbody td {
vertical-align: middle;
}
}
.table-wrapper {
overflow: auto;
margin-bottom: 20px;
}
samp {
font-family: $font-monospace, monospace;
}
button.table-action-link {
background: transparent;
border: 0;
font: inherit;
}
button.table-action-link,
a.table-action-link {
text-decoration: none;
display: inline-block;
margin-inline-end: 5px;
padding: 0 10px;
color: var(--color-text-secondary);
font-weight: 500;
white-space: nowrap;
&:hover {
color: var(--color-text-brand);
}
&:first-child {
padding-inline-start: 0;
}
}
.batch-table {
&--no-toolbar {
.batch-table__toolbar {
position: static;
height: 4px;
border-bottom: none;
}
}
&__toolbar,
&__row {
display: flex;
&__select {
box-sizing: border-box;
padding: 8px 16px;
cursor: pointer;
min-height: 100%;
input {
margin-top: 8px;
}
&--aligned {
display: flex;
align-items: center;
input {
margin-top: 0;
}
}
}
&__actions,
&__content {
padding: 8px 0;
padding-inline-end: 16px;
flex: 1 1 auto;
}
}
&__toolbar {
position: sticky;
top: 0;
z-index: 200;
border: 1px solid var(--color-border-primary);
background: var(--color-bg-primary);
border-radius: 4px 4px 0 0;
height: 47px;
align-items: center;
&__actions {
text-align: end;
padding-inline-end: 16px - 5px;
.table-action-link {
padding: 0;
}
}
}
&__select-all {
background: var(--color-bg-primary);
height: 47px;
align-items: center;
justify-content: center;
border: 1px solid var(--color-border-primary);
border-top: 0;
color: var(--color-text-primary);
display: none;
&.active {
display: flex;
}
.selected,
.not-selected {
display: none;
&.active {
display: block;
}
}
strong {
font-weight: 700;
}
span {
padding: 8px;
display: inline-block;
}
button {
background: transparent;
border: 0;
font: inherit;
color: var(--color-text-brand);
border-radius: 4px;
font-weight: 700;
padding: 8px;
&:hover,
&:focus,
&:active {
background: var(--color-bg-secondary);
}
}
}
&__form {
padding: 16px;
border: 1px solid var(--color-border-primary);
border-top: 0;
background: var(--color-bg-primary);
.fields-row {
padding-top: 0;
margin-bottom: 0;
}
}
&__row {
border: 1px solid var(--color-border-primary);
border-top: 0;
background: var(--color-bg-primary);
@media screen and (max-width: $no-gap-breakpoint) {
.optional &:first-child {
border-top: 1px solid var(--color-border-primary);
}
}
&:last-child {
border-radius: 0 0 4px 4px;
}
&__content {
padding-top: 12px;
padding-bottom: 16px;
overflow: hidden;
&--unpadded {
padding: 0;
}
&--padded {
padding: 12px 16px 16px;
}
&--with-image {
display: flex;
align-items: center;
}
&__image {
flex: 0 0 auto;
display: flex;
justify-content: center;
align-items: center;
margin-inline-end: 10px;
.emojione {
width: 32px;
height: 32px;
}
}
&__text {
flex: 1 1 auto;
}
&__quote {
padding: 12px;
padding-top: 0;
}
&__extra {
flex: 0 0 auto;
text-align: end;
color: var(--color-text-secondary);
font-weight: 500;
}
}
.directory__tag {
margin: 0;
width: 100%;
a {
background: transparent;
border-radius: 0;
}
}
}
&.optional .batch-table__toolbar,
&.optional .batch-table__row__select {
@media screen and (max-width: $no-gap-breakpoint) {
display: none;
}
}
// Reset the status card to not have borders, background or padding when
// inline in the table of statuses
.batch-table__row__content > .status__card {
border: none;
background: none;
padding: 0;
}
@media screen and (width <= 870px) {
.accounts-table tbody td.optional {
display: none;
}
}
}
.one-liner {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -0,0 +1,183 @@
@use 'sass:color';
@use 'variables' as *;
.directory {
&__tag {
box-sizing: border-box;
margin-bottom: 10px;
& > a,
& > div {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid var(--color-border-primary);
border-radius: 4px;
padding: 15px;
text-decoration: none;
color: inherit;
box-shadow: 0 0 15px var(--color-shadow-primary);
}
& > a {
&:hover,
&:active,
&:focus {
background: var(--color-bg-primary);
}
}
&.active > a {
background: var(--color-bg-brand-base);
cursor: default;
}
&.disabled > div {
opacity: 0.5;
cursor: default;
}
h4 {
flex: 1 1 auto;
font-size: 18px;
font-weight: 700;
color: var(--color-text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.fa {
color: var(--color-text-secondary);
}
small {
display: block;
font-weight: 400;
font-size: 15px;
margin-top: 8px;
color: var(--color-text-secondary);
}
}
&.active h4 {
&,
.fa,
small,
.trends__item__current {
color: var(--color-text-primary);
}
}
.avatar-stack {
flex: 0 0 auto;
width: (36px + 4px) * 3;
}
&.active .avatar-stack .account__avatar {
border-color: var(--color-text-brand);
}
.trends__item__current {
padding-inline-end: 0;
}
}
}
.accounts-table {
width: 100%;
.account {
max-width: calc(56px + 30ch);
padding: 0;
border: 0;
}
strong {
font-weight: 700;
}
thead th {
text-align: center;
text-transform: uppercase;
color: var(--color-text-secondary);
font-weight: 700;
padding: 10px;
&:first-child {
text-align: start;
}
}
tbody td {
padding: 15px 0;
vertical-align: middle;
border-bottom: 1px solid var(--color-border-primary);
}
tbody tr:last-child td {
border-bottom: 0;
}
&__count {
width: 120px;
text-align: center;
font-size: 15px;
font-weight: 500;
color: var(--color-text-primary);
small {
display: block;
color: var(--color-text-secondary);
font-weight: 400;
font-size: 14px;
}
}
tbody td.accounts-table__extra {
width: 120px;
text-align: end;
color: var(--color-text-secondary);
padding-inline-end: 16px;
a {
text-decoration: none;
color: inherit;
&:focus,
&:hover,
&:active {
color: var(--color-text-brand);
}
}
}
&__comment {
width: 50%;
vertical-align: initial !important;
}
tbody td.accounts-table__interrelationships {
width: 21px;
padding-inline-end: 16px;
}
.icon {
&.active {
color: var(--color-text-brand);
}
&.passive {
color: var(--color-text-warning);
}
&.active.passive {
color: var(--color-text-success);
}
}
@media screen and (max-width: $no-gap-breakpoint) {
tbody td.optional {
display: none;
}
}
}

View File

@ -18,5 +18,5 @@
domain: @domain_block.domain domain: @domain_block.domain
.actions .actions
= link_to t('.cancel'), admin_instances_path, class: 'button button-tertiary' = link_to t('.cancel'), admin_instances_path, class: 'button button-secondary'
= f.button :submit, t('.confirm'), class: 'button button--dangerous', name: :confirm = f.button :submit, t('.confirm'), class: 'button button--dangerous', name: :confirm

View File

@ -76,7 +76,7 @@
%hr.spacer/ %hr.spacer/
.actions .actions
= link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary' = link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-secondary'
= form.button t('admin.reports.confirm'), = form.button t('admin.reports.confirm'),
name: :confirm, name: :confirm,
class: 'button', class: 'button',

View File

@ -1,7 +1,7 @@
.announcements-list__item .announcements-list__item
- if can?(:update, role) - if can?(:update, role)
= link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do
%span.user-role %span
= material_symbol 'group' = material_symbol 'group'
- if role.everyone? - if role.everyone?
@ -10,13 +10,12 @@
= role.name = role.name
- else - else
%span.announcements-list__item__title %span.announcements-list__item__title
%span.user-role = material_symbol 'group'
= material_symbol 'group'
- if role.everyone? - if role.everyone?
= t('admin.roles.everyone') = t('admin.roles.everyone')
- else - else
= role.name = role.name
.announcements-list__item__action-bar .announcements-list__item__action-bar
.announcements-list__item__meta .announcements-list__item__meta

View File

@ -27,4 +27,4 @@
.stacked-actions .stacked-actions
- accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token) - accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token)
= link_to t('auth.rules.accept'), accept_path, class: 'button' = link_to t('auth.rules.accept'), accept_path, class: 'button'
= link_to t('auth.rules.back'), root_path, class: 'button button-tertiary' = link_to t('auth.rules.back'), root_path, class: 'button button-secondary'

View File

@ -1,7 +1,7 @@
- content_for :header_tags do - content_for :header_tags do
= vite_typescript_tag 'public.tsx', crossorigin: 'anonymous' = vite_typescript_tag 'public.tsx', crossorigin: 'anonymous'
- content_for :body_classes, 'modal-layout compose-standalone' - content_for :body_classes, 'modal-layout with-zig-zag-decoration compose-standalone'
- content_for :content do - content_for :content do
- if user_signed_in? && !@hide_header - if user_signed_in? && !@hide_header

View File

@ -11,5 +11,5 @@
.simple_form .simple_form
.actions .actions
= link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-tertiary' = link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-secondary'
= link_to t('generic.confirm'), confirm_settings_import_path(@bulk_import), method: :post, class: 'button' = link_to t('generic.confirm'), confirm_settings_import_path(@bulk_import), method: :post, class: 'button'

View File

@ -43,6 +43,10 @@ export function MastodonThemes(): Plugin {
for (const [themeName, themePath] of Object.entries(themes)) { for (const [themeName, themePath] of Object.entries(themes)) {
entrypoints[`themes/${themeName}`] = path.resolve(jsRoot, themePath); entrypoints[`themes/${themeName}`] = path.resolve(jsRoot, themePath);
entrypoints[`themes/${themeName}_theme_tokens`] = path.resolve(
jsRoot,
themePath.replace('styles/', 'styles_new/'),
);
} }
return { return {
@ -64,7 +68,11 @@ export function MastodonThemes(): Plugin {
// Rewrite the URL to the entrypoint if it matches a theme. // Rewrite the URL to the entrypoint if it matches a theme.
if (isThemeFile(req.url ?? '', themes)) { if (isThemeFile(req.url ?? '', themes)) {
const themeName = pathToThemeName(req.url ?? ''); const themeName = pathToThemeName(req.url ?? '');
req.url = `/packs-dev/${themes[themeName]}`; const themePath = `/packs-dev/${themes[themeName]}`;
const isThemeTokenRequest = req.url.includes('_theme_tokens');
req.url = isThemeTokenRequest
? themePath.replace('styles/', 'styles_new/')
: themePath;
} }
next(); next();
}); });
@ -77,7 +85,7 @@ export function MastodonThemes(): Plugin {
const themePathToName = new Map( const themePathToName = new Map(
Object.entries(themes).map(([themeName, themePath]) => [ Object.entries(themes).map(([themeName, themePath]) => [
path.resolve(jsRoot, themePath), path.resolve(jsRoot, themePath),
`/themes/${themeName}`, `/themes/${areThemeTokensEnabled() ? `${themeName}_theme_tokens` : themeName}`,
]), ]),
); );
const themeNames = new Set<string>(); const themeNames = new Set<string>();
@ -140,6 +148,7 @@ async function loadThemesFromConfig(root: string) {
console.warn(`Invalid theme path "${themePath}" in themes.yml, skipping`); console.warn(`Invalid theme path "${themePath}" in themes.yml, skipping`);
continue; continue;
} }
themes[themeName] = themePath; themes[themeName] = themePath;
} }
@ -151,7 +160,7 @@ async function loadThemesFromConfig(root: string) {
} }
function pathToThemeName(file: string) { function pathToThemeName(file: string) {
const basename = path.basename(file); const basename = path.basename(file.replace('_theme_tokens', ''));
return basename.split(/[.?]/)[0] ?? ''; return basename.split(/[.?]/)[0] ?? '';
} }
@ -163,3 +172,12 @@ function isThemeFile(file: string, themes: Themes) {
const basename = pathToThemeName(file); const basename = pathToThemeName(file);
return basename in themes; return basename in themes;
} }
function areThemeTokensEnabled() {
const raw = process.env.EXPERIMENTAL_FEATURES ?? '';
const features = raw
.split(',')
.map((s) => s.trim())
.filter(Boolean);
return features.includes('theme_tokens');
}

View File

@ -2,6 +2,7 @@ module.exports = {
extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'],
ignoreFiles: [ ignoreFiles: [
'app/javascript/styles/mastodon/reset.scss', 'app/javascript/styles/mastodon/reset.scss',
'app/javascript/styles_new/mastodon/reset.scss',
'coverage/**/*', 'coverage/**/*',
'node_modules/**/*', 'node_modules/**/*',
'public/assets/**/*', 'public/assets/**/*',
@ -31,7 +32,7 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
'files': ['app/javascript/styles/entrypoints/mailer.scss'], 'files': ['app/javascript/styles/entrypoints/mailer.scss', 'app/javascript/styles_new/entrypoints/mailer.scss'],
rules: { rules: {
'property-no-unknown': [ 'property-no-unknown': [
true, true,