diff --git a/Languages/en_US/Profile.php b/Languages/en_US/Profile.php
index 292a6ee7e2..ab22589ddd 100644
--- a/Languages/en_US/Profile.php
+++ b/Languages/en_US/Profile.php
@@ -644,6 +644,8 @@
$txt['theme_opt_posting'] = 'Posting';
$txt['theme_opt_moderation'] = 'Moderation';
$txt['theme_opt_personal_messages'] = 'Personal Messages';
+$txt['theme_opt_colormode'] = 'Theme Color Mode';
+$txt['theme_opt_variant'] = 'Theme Variant';
$txt['export_profile_data'] = 'Download profile data';
$txt['export_profile_data_desc'] = 'This section allows you to export a copy of your forum profile data to a downloadable file, optionally including your posts and personal messages. Please note:
{list}
';
diff --git a/Languages/en_US/Themes.php b/Languages/en_US/Themes.php
index 31c14c5ced..ec63964b2c 100644
--- a/Languages/en_US/Themes.php
+++ b/Languages/en_US/Themes.php
@@ -65,6 +65,7 @@
$txt['theme_url_config'] = 'Theme URLs and Configuration';
$txt['theme_variants'] = 'Theme Variants';
+$txt['theme_colormodes'] = 'Theme Color Modes';
$txt['theme_options'] = 'Theme Options and Preferences';
$txt['actual_theme_name'] = 'This theme’s name: ';
$txt['actual_theme_dir'] = 'This theme’s directory: ';
@@ -73,6 +74,7 @@
$txt['current_theme_style'] = 'This theme’s style: ';
$txt['theme_variants_default'] = 'Default theme variant';
+$txt['variant_default'] = 'Default';
$txt['theme_variants_user_disable'] = 'Disable user variant selection';
$txt['site_slogan'] = 'Site slogan';
@@ -164,3 +166,11 @@
// Open Graph
$txt['og_image'] = 'Open Graph image';
$txt['og_image_desc'] = 'Suggested size: 175x175px. Open Graph is used for social media sharing.';
+
+// Theme Mode (dark, light, system, etc)
+$txt['theme_pick_colormode'] = 'Select Color Mode';
+$txt['theme_colormode_default'] = 'Default color mode';
+$txt['theme_colormode_user_disable'] = 'Disable user color mode selection';
+$txt['colormode_light'] = 'Light';
+$txt['colormode_dark'] = 'Dark';
+$txt['colormode_system'] = 'System Default';
diff --git a/Sources/Actions/Admin/Themes.php b/Sources/Actions/Admin/Themes.php
index 52a5fab539..06d7692f28 100644
--- a/Sources/Actions/Admin/Themes.php
+++ b/Sources/Actions/Admin/Themes.php
@@ -602,7 +602,51 @@ public function setOptions(): void
Utils::$context['sub_template'] = 'set_options';
Utils::$context['page_title'] = Lang::getTxt('theme_settings', file: 'Admin');
- Utils::$context['options'] = Utils::$context['theme_options'];
+ // Check for variants or dark mode
+ if (!empty(Theme::$current->settings['theme_variants']) || !empty(Theme::$current->settings['has_dark_mode'])) {
+ Utils::$context['options'] = [];
+
+ // Theme Variants
+ if (!empty(Theme::$current->settings['theme_variants'])) {
+ $available_variants = [];
+
+ foreach (Theme::$current->settings['theme_variants'] as $variant) {
+ $available_variants[$variant] = Lang::$txt['variant_' . $variant] ?? $variant;
+ }
+
+ Utils::$context['options'][] = Lang::$txt['theme_opt_variant'];
+ Utils::$context['options'][] = [
+ 'id' => 'theme_variant',
+ 'label' => Lang::$txt['theme_pick_variant'],
+ 'options' => $available_variants,
+ 'default' => isset(Theme::$current->settings['default_variant']) && !empty(Theme::$current->settings['default_variant']) ? Theme::$current->settings['default_variant'] : Theme::$current->settings['theme_variants'][0],
+ 'enabled' => !empty(Theme::$current->settings['theme_variants']),
+ ];
+ }
+
+ // Theme Color Mode
+ if (!empty(Theme::$current->settings['has_dark_mode'])) {
+ $available_modes = [];
+
+ foreach (Theme::$current->settings['theme_colormodes'] as $mode) {
+ $available_modes[$mode] = Lang::$txt['colormode_' . $mode] ?? $mode;
+ }
+
+ Utils::$context['options'][] = Lang::$txt['theme_opt_colormode'];
+ Utils::$context['options'][] = [
+ 'id' => 'theme_colormode',
+ 'label' => Lang::$txt['theme_pick_colormode'],
+ 'options' => $available_modes,
+ 'default' => isset(Theme::$current->settings['default_colormode']) && !empty(Theme::$current->settings['default_colormode']) ? Theme::$current->settings['default_colormode'] : Theme::$current->settings['theme_colormodes'][0],
+ 'enabled' => !empty(Theme::$current->settings['has_dark_mode']),
+ ];
+ }
+
+ Utils::$context['options'] = array_merge(Utils::$context['options'], Utils::$context['theme_options']);
+ } else {
+ Utils::$context['options'] = Utils::$context['theme_options'];
+ }
+
Utils::$context['theme_settings'] = Theme::$current->settings;
if (empty($_REQUEST['who'])) {
diff --git a/Sources/Theme.php b/Sources/Theme.php
index eea7b0e696..6751776352 100644
--- a/Sources/Theme.php
+++ b/Sources/Theme.php
@@ -2157,6 +2157,47 @@ protected function loadCss(): void
self::loadCSSFile('noscript.css', ['minimize' => true, 'order_pos' => 1, 'noscript' => true], 'smf_noscript');
}
+ /**
+ * Loads the theme mode, if applicable.
+ */
+ protected function loadMode(): void
+ {
+ Utils::$context['theme_colormode'] = '';
+
+ if (!empty($this->settings['has_dark_mode'])) {
+ // Theme Modes
+ $this->settings['theme_colormodes'] = ['light', 'system', 'dark'];
+
+ // Overriding - for previews and that ilk.
+ if (!empty($_REQUEST['mode'])) {
+ $_SESSION['theme_colormode'] = $_REQUEST['mode'];
+
+ // If the user is logged, save this to their profile
+ if (User::$me->is_logged && \in_array($_SESSION['theme_colormode'], $this->settings['theme_colormodes'])) {
+ Db::$db->insert(
+ 'replace',
+ '{db_prefix}themes',
+ ['id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'],
+ [self::$current->settings['theme_id'], User::$me->id, 'theme_colormode', $_SESSION['theme_colormode']],
+ ['id_theme', 'id_member', 'variable'],
+ );
+ }
+ }
+
+ // User selection?
+ if (empty($this->settings['disable_user_mode']) || User::$me->allowedTo('admin_forum')) {
+ Utils::$context['theme_colormode'] = !empty($_SESSION['theme_colormode']) && \in_array($_SESSION['theme_colormode'], $this->settings['theme_colormodes']) ? $_SESSION['theme_colormode'] : (!empty($this->options['theme_colormode']) && \in_array($this->options['theme_colormode'], $this->settings['theme_colormodes']) ? $this->options['theme_colormode'] : '');
+ }
+
+ // If no color mode, set a default
+ if (empty(Utils::$context['theme_colormode']) || !\in_array(Utils::$context['theme_colormode'], $this->settings['theme_colormodes'])) {
+ Utils::$context['theme_colormode'] = !empty($this->settings['default_colormode']) && \in_array($this->settings['default_colormode'], $this->settings['theme_colormodes']) ? $this->settings['default_colormode'] : $this->settings['theme_colormodes'][0];
+ }
+
+ self::loadCSSFile('dark.css', ['order_pos' => 2, 'attributes' => (Utils::$context['theme_colormode'] == 'system' ? ['media' => '(prefers-color-scheme: dark)'] : [])], 'smf_dark');
+ }
+ }
+
/**
* Loads the correct theme variant, if applicable.
*/
@@ -2167,11 +2208,34 @@ protected function loadVariant(): void
Utils::$context['theme_variant_url'] = '';
if (!empty($this->settings['theme_variants'])) {
+ // Add the default variant
+ $this->settings['theme_variants'] = array_unique(array_merge(['default'], $this->settings['theme_variants']));
+
// Overriding - for previews and that ilk.
if (!empty($_REQUEST['variant'])) {
$_SESSION['id_variant'] = $_REQUEST['variant'];
+
+ // If the user is logged, save this to their profile
+ if (User::$me->is_logged && \in_array($_SESSION['id_variant'], $this->settings['theme_variants'])) {
+ Db::$db->insert(
+ 'replace',
+ '{db_prefix}themes',
+ ['id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'],
+ [self::$current->settings['theme_id'], User::$me->id, 'theme_variant', $_SESSION['id_variant']],
+ ['id_theme', 'id_member', 'variable'],
+ );
+ }
}
+ /*
+ * Attempt to load a variants file for variable overriding
+ * using data attribute (:root[data-variant="variant"])
+ *
+ * This is useful when you only want a single file for
+ * recoloring the variants.
+ */
+ self::loadCSSFile('variants.css', ['order_pos' => 0], 'smf_variants');
+
// User selection?
if (empty($this->settings['disable_user_variant']) || User::$me->allowedTo('admin_forum')) {
Utils::$context['theme_variant'] = !empty($_SESSION['id_variant']) && \in_array($_SESSION['id_variant'], $this->settings['theme_variants']) ? $_SESSION['id_variant'] : (!empty($this->options['theme_variant']) && \in_array($this->options['theme_variant'], $this->settings['theme_variants']) ? $this->options['theme_variant'] : '');
@@ -2181,18 +2245,6 @@ protected function loadVariant(): void
if (Utils::$context['theme_variant'] == '' || !\in_array(Utils::$context['theme_variant'], $this->settings['theme_variants'])) {
Utils::$context['theme_variant'] = !empty($this->settings['default_variant']) && \in_array($this->settings['default_variant'], $this->settings['theme_variants']) ? $this->settings['default_variant'] : $this->settings['theme_variants'][0];
}
-
- // Do this to keep things easier in the templates.
- Utils::$context['theme_variant'] = '_' . Utils::$context['theme_variant'];
- Utils::$context['theme_variant_url'] = Utils::$context['theme_variant'] . '/';
-
- if (!empty(Utils::$context['theme_variant'])) {
- self::loadCSSFile('index' . Utils::$context['theme_variant'] . '.css', ['order_pos' => 300], 'smf_index' . Utils::$context['theme_variant']);
-
- if (Utils::$context['right_to_left']) {
- self::loadCSSFile('rtl' . Utils::$context['theme_variant'] . '.css', ['order_pos' => 4200], 'smf_rtl' . Utils::$context['theme_variant']);
- }
- }
}
}
diff --git a/Themes/default/Themes.template.php b/Themes/default/Themes.template.php
index e29ef74779..240a3c5fb1 100644
--- a/Themes/default/Themes.template.php
+++ b/Themes/default/Themes.template.php
@@ -511,8 +511,7 @@ function template_set_settings()
';
// Do we allow theme variants?
- if (!empty(Utils::$context['theme_variants']))
- {
+ if (!empty(Utils::$context['theme_variants'])) {
echo '
@@ -544,6 +543,38 @@ function template_set_settings()
';
}
+ // Color modes for the theme?
+ if (!empty(Theme::$current->settings['has_dark_mode'])) {
+ echo '
+
+
+ ', Lang::$txt['theme_colormodes'], '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
';
+ }
+
echo '
@@ -690,65 +721,66 @@ function template_set_settings()
function template_pick()
{
echo '
-