diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html
index 1870d95b8f..7a92b6f95f 100644
--- a/.storybook/preview-body.html
+++ b/.storybook/preview-body.html
@@ -1,2 +1,2 @@
-
+
\ No newline at end of file
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d80c050b85..e1b8ebf38d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -153,10 +153,8 @@ module ApplicationHelper
tag.meta(content: content, property: property)
end
- def body_classes
+ def html_classes
output = []
- output << content_for(:body_classes)
- output << "theme-#{current_theme.parameterize}"
output << 'system-font' if current_account&.user&.setting_system_font_ui
output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
@@ -164,6 +162,12 @@ module ApplicationHelper
output.compact_blank.join(' ')
end
+ def body_classes
+ output = []
+ output << content_for(:body_classes)
+ output.compact_blank.join(' ')
+ end
+
def cdn_host
Rails.configuration.action_controller.asset_host
end
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index e16e5368e7..a38653cf1c 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -1,7 +1,2 @@
-@use 'mastodon/css_variables';
@use 'mastodon/variables';
@use 'common';
-
-html {
- color-scheme: dark;
-}
diff --git a/app/javascript/styles/common.scss b/app/javascript/styles/common.scss
index d8a7f90a32..b56fa3f579 100644
--- a/app/javascript/styles/common.scss
+++ b/app/javascript/styles/common.scss
@@ -3,6 +3,7 @@
@use 'fonts/roboto-mono';
@use 'mastodon/reset';
+@use 'mastodon/theme';
@use 'mastodon/basics';
@use 'mastodon/branding';
@use 'mastodon/containers';
diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss
index af73c88fef..d89eaa610f 100644
--- a/app/javascript/styles/contrast.scss
+++ b/app/javascript/styles/contrast.scss
@@ -1,8 +1,3 @@
-@use 'mastodon/css_variables';
@use 'mastodon/variables';
@use 'common';
-@use 'contrast/diff';
-
-html {
- color-scheme: dark;
-}
+@use 'mastodon/high-contrast';
diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss
index 494efdbbde..11a92fb873 100644
--- a/app/javascript/styles/mastodon-light.scss
+++ b/app/javascript/styles/mastodon-light.scss
@@ -1,9 +1,4 @@
-@use 'mastodon-light/css_variables';
@use 'mastodon/variables' with (
$emojis-requiring-inversion: 'chains'
);
@use 'common';
-
-html {
- color-scheme: light;
-}
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index db584f67f1..d28150f12a 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -3,6 +3,20 @@
html {
color: var(--color-text-primary);
background: var(--color-bg-ambient);
+
+ &.custom-scrollbars {
+ scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
+ }
+
+ --outline-focus-default: 2px solid var(--color-text-brand);
+ --avatar-border-radius: 8px;
+
+ // Variable for easily inverting directional UI elements,
+ --text-x-direction: 1;
+
+ &.rtl {
+ --text-x-direction: -1;
+ }
}
html.has-modal {
@@ -37,7 +51,7 @@ body {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
-webkit-tap-highlight-color: transparent;
- &.system-font {
+ .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
diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/mastodon/high-contrast.scss
similarity index 63%
rename from app/javascript/styles/contrast/diff.scss
rename to app/javascript/styles/mastodon/high-contrast.scss
index f809c7cdc3..f55e7fae3b 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/mastodon/high-contrast.scss
@@ -1,17 +1,3 @@
-: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,
diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss
index 3644b94cdf..2c3efbddc4 100644
--- a/app/javascript/styles/mastodon/reset.scss
+++ b/app/javascript/styles/mastodon/reset.scss
@@ -52,7 +52,3 @@ table {
border-collapse: collapse;
border-spacing: 0;
}
-
-html:has(body.custom-scrollbars) {
- scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
-}
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 48935b75d7..a78f95f66d 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -1,6 +1,6 @@
@use 'variables' as *;
-body.rtl {
+html.rtl {
direction: rtl;
.reactions-bar {
diff --git a/app/javascript/styles/mastodon/theme/_base.scss b/app/javascript/styles/mastodon/theme/_base.scss
new file mode 100644
index 0000000000..94d592aa70
--- /dev/null
+++ b/app/javascript/styles/mastodon/theme/_base.scss
@@ -0,0 +1,27 @@
+@mixin palette {
+ --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-yellow-700: #bb4d00;
+ --color-green-400: #05df72;
+ --color-green-600: #00a63e;
+}
diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/theme/_dark.scss
similarity index 85%
rename from app/javascript/styles/mastodon/css_variables.scss
rename to app/javascript/styles/mastodon/theme/_dark.scss
index 98b61d2f0b..e6fd6d3cc1 100644
--- a/app/javascript/styles/mastodon/css_variables.scss
+++ b/app/javascript/styles/mastodon/theme/_dark.scss
@@ -1,31 +1,6 @@
-@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;
+@use 'utils';
+@mixin tokens {
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-50);
@@ -127,7 +102,7 @@
// Warning
--overlay-strength-warning: 10%;
- --color-bg-warning-base: var(--color-yellow-600);
+ --color-bg-warning-base: var(--color-yellow-700);
--color-bg-warning-base-hover: color-mix(
in oklab,
var(--color-bg-warning-base),
@@ -212,18 +187,18 @@
--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;
+@mixin contrast-overrides {
+ /* TEXT TOKENS */
- &.rtl {
- --text-x-direction: -1;
- }
+ --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%;
}
diff --git a/app/javascript/styles/mastodon-light/css_variables.scss b/app/javascript/styles/mastodon/theme/_light.scss
similarity index 86%
rename from app/javascript/styles/mastodon-light/css_variables.scss
rename to app/javascript/styles/mastodon/theme/_light.scss
index a96773f76c..f0dc1bdfbc 100644
--- a/app/javascript/styles/mastodon-light/css_variables.scss
+++ b/app/javascript/styles/mastodon/theme/_light.scss
@@ -1,31 +1,6 @@
-@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;
+@use 'utils';
+@mixin tokens {
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-950);
@@ -124,7 +99,7 @@
// Warning
--overlay-strength-warning: 10%;
- --color-bg-warning-base: var(--color-yellow-600);
+ --color-bg-warning-base: var(--color-yellow-700);
--color-bg-warning-base-hover: color-mix(
in oklab,
var(--color-bg-warning-base),
@@ -207,9 +182,18 @@
--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;
+}
+
+@mixin contrast-overrides {
+ /* TEXT TOKENS */
+
+ --color-text-primary: var(--color-black);
+ --color-text-secondary: var(--color-grey-800);
+ --color-text-tertiary: var(--color-grey-700);
+ --color-text-brand: var(--color-indigo-600);
+
+ /* BORDER TOKENS */
+
+ --border-strength-primary: 30%;
+ --color-border-on-bg-secondary: var(--color-grey-300);
}
diff --git a/app/javascript/styles/mastodon/_theme_utils.scss b/app/javascript/styles/mastodon/theme/_utils.scss
similarity index 100%
rename from app/javascript/styles/mastodon/_theme_utils.scss
rename to app/javascript/styles/mastodon/theme/_utils.scss
diff --git a/app/javascript/styles/mastodon/theme/index.scss b/app/javascript/styles/mastodon/theme/index.scss
new file mode 100644
index 0000000000..d78b7e2a99
--- /dev/null
+++ b/app/javascript/styles/mastodon/theme/index.scss
@@ -0,0 +1,48 @@
+@use 'base';
+@use 'dark';
+@use 'light';
+
+html {
+ @include base.palette;
+
+ &[data-user-theme='system'] {
+ color-scheme: dark light;
+
+ @media (prefers-color-scheme: dark) {
+ @include dark.tokens;
+
+ @media (prefers-contrast: more) {
+ @include dark.contrast-overrides;
+ }
+ }
+
+ @media (prefers-color-scheme: light) {
+ @include light.tokens;
+
+ @media (prefers-contrast: more) {
+ @include light.contrast-overrides;
+ }
+ }
+ }
+}
+
+.theme-dark,
+html:where(
+ :not([data-user-theme='mastodon-light'], [data-user-theme='system'])
+) {
+ color-scheme: dark;
+
+ @include dark.tokens;
+}
+
+html[data-user-theme='contrast'],
+html[data-user-theme='contrast'] .theme-dark {
+ @include dark.contrast-overrides;
+}
+
+.theme-light,
+html:where([data-user-theme='mastodon-light']) {
+ color-scheme: light;
+
+ @include light.tokens;
+}
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 3418f6d953..0bfa9e74f9 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,5 +1,5 @@
!!! 5
-%html{ lang: I18n.locale }
+%html{ lang: I18n.locale, class: html_classes, 'data-user-theme': current_theme.parameterize }
%head
%meta{ charset: 'utf-8' }/
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }/
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 942cc5103c..46dbd80de0 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -3,8 +3,8 @@
require 'rails_helper'
RSpec.describe ApplicationHelper do
- describe 'body_classes' do
- context 'with a body class string from a controller' do
+ describe 'html_classes' do
+ context 'with non-default user settings' do
before do
user = Fabricate :user
user.settings['web.use_system_font'] = true
@@ -15,19 +15,11 @@ RSpec.describe ApplicationHelper do
end
it 'uses the current theme and user settings classes in the result' do
- expect(helper.body_classes)
- .to match(/theme-default/)
- .and match(/system-font/)
+ expect(helper.html_classes)
+ .to match(/system-font/)
.and match(/reduce-motion/)
end
- it 'includes values set via content_for' do
- helper.content_for(:body_classes) { 'admin' }
-
- expect(helper.body_classes)
- .to match(/admin/)
- end
-
private
def controller_helpers
@@ -35,13 +27,22 @@ RSpec.describe ApplicationHelper do
def current_account
@current_account ||= Fabricate(:account, user: User.last)
end
-
- def current_theme = 'default'
end
end
end
end
+ describe 'body_classes' do
+ context 'with a body class string from a controller' do
+ it 'includes values set via content_for' do
+ helper.content_for(:body_classes) { 'admin' }
+
+ expect(helper.body_classes)
+ .to match(/admin/)
+ end
+ end
+ end
+
describe 'locale_direction' do
it 'adds rtl body class if locale is Arabic' do
I18n.with_locale(:ar) do