Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions kitsune/sumo/jinja2/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
{% if request.LANGUAGE_CODE == 'xx' %}
{% set meta = [('robots', 'noindex')] %}
{% endif %}
{# Read the theme cookie set by theme-toggle.js so we can pre-render the correct #}
{# data-theme and background inline — this eliminates a potential navigation flash entirely #}
{# because the HTML arrives from the server already styled, before any JS runs. #}
{% set _theme_cookie = request.COOKIES.get('sumo-theme', '') %}
{% set _is_dark = _theme_cookie == 'dark' %}
<!DOCTYPE html>
<html class="no-js" lang="{{ request.LANGUAGE_CODE }}"
{% if _is_dark %}data-theme="dark" style="background-color:#1C1B22;color-scheme:dark"{% elif _theme_cookie == 'light' %}data-theme="light" style="color-scheme:light"{% endif %}
{% if DIR %}dir="{{ DIR }}"{% endif %}
{% if settings.GTM_CONTAINER_ID %}data-gtm-container-id="{{ settings.GTM_CONTAINER_ID }}"{% endif %}
{% if ga_content_group %}data-ga-content-group="{{ ga_content_group }}"{% endif %}
Expand All @@ -15,6 +21,28 @@
{% if settings.GA_CONSOLE_LOGGING %}data-ga-console-logging="true"{% endif %}
{% if settings.GA_DEBUG_MODE %}data-ga-debug-mode="true"{% endif %}>
<head>
{# ============================================================ #}
{# Theme flash prevention #}
{# Primary: data-theme + style are pre-rendered by Django from #}
{# the sumo-theme cookie (set by theme-toggle.js on toggle). #}
{# Fallback: inline style + script cover first-time visitors #}
{# (no cookie yet) who rely on OS dark mode. #}
{# #1C1B22 = --color-dark-gray-09 = --page-bg in dark mode. #}
{# ============================================================ #}
<style>
:root { color-scheme: light dark; }
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]),
html:not([data-theme="light"]) body { background-color: #1C1B22; color-scheme: dark; }
}
html[data-theme="dark"],
html[data-theme="dark"] body { background-color: #1C1B22; color-scheme: dark; }
html[data-theme="light"],
html[data-theme="light"] body { color-scheme: light; }
</style>
{# Fallback for first-time OS-dark visitors with no cookie yet. #}
<script>(function(){if(document.documentElement.getAttribute('data-theme'))return;var p=window.matchMedia('(prefers-color-scheme:dark)').matches;if(p){var e=document.documentElement;e.style.backgroundColor='#1C1B22';e.style.colorScheme='dark';}})();</script>

{% include "includes/google-analytics.html" %}

{% block head_top %}{% endblock %}
Expand Down Expand Up @@ -137,6 +165,19 @@
{% endif %}

</a>
<div class="sumo-nav--theme-toggle" x-data="themeToggle()">
<button
type="button"
class="sumo-nav--theme-toggle-button"
x-on:click="toggle()"
aria-label="{{ _('Toggle dark mode') }}">
{# Moon: shown in light mode — click to go dark #}
<svg class="theme-icon theme-icon--moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
{# Sun: shown in dark mode — click to go light #}
<svg class="theme-icon theme-icon--sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
</button>
</div>

<div class="mzp-c-navigation-items sumo-nav--list-wrap" id="main-navigation">
<div class="mzp-c-navigation-menu">
<nav class="mzp-c-menu mzp-is-basic">
Expand Down
20 changes: 10 additions & 10 deletions kitsune/sumo/jinja2/includes/common_macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
<section class="mzp-c-menu-item mzp-has-icon sumo-nav--dropdown-item">
<div class="mzp-c-menu-item-head">
<svg class="mzp-c-menu-item-icon" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="translate(3 3)" stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd"
<g transform="translate(3 3)" stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd"
stroke-linecap="round">
<rect stroke-linejoin="round" width="18" height="18" rx="2" />
<path d="M4 5h10M4 9h10M4 13h4" />
Expand Down Expand Up @@ -228,7 +228,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Explore by product') }}</h4>
<section class="mzp-c-menu-item mzp-has-icon sumo-nav--dropdown-item">
<div class="mzp-c-menu-item-head">
<svg class="mzp-c-menu-item-icon" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="translate(3 3)" stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd"
<g transform="translate(3 3)" stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd"
stroke-linecap="round">
<rect stroke-linejoin="round" width="18" height="18" rx="2" />
<path d="M4 5h10M4 9h10M4 13h4" />
Expand All @@ -255,7 +255,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Explore by topic') }}</h4>
<div class="mzp-c-menu-item-head">
<svg class="mzp-c-menu-item-icon" width="24" height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<g stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
<g stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round">
<path
d="M17 17l-1.051 3.154a1 1 0 01-1.898 0L13 17H5a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2h-2zM7 8h10M7 12h10" />
Expand Down Expand Up @@ -334,7 +334,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Browse by product') }}</h4>
viewBox="0 0 24 24">
<path
d="M10.5 9.5L3 17c-1 1.667-1 3 0 4s2.333.833 4-.5l7.5-7.5c2.333 1.054 4.333.734 6-.96 1.667-1.693 1.833-3.707.5-6.04l-3 3-2.5-.5L15 6l3-3c-2.333-1.333-4.333-1.167-6 .5-1.667 1.667-2.167 3.667-1.5 6z"
stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<h4 class="mzp-c-menu-item-title">{{ _('Browse all forum threads by topic') }}</h4>
Expand All @@ -361,7 +361,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Browse all forum threads by topic') }}</
<div class="mzp-c-menu-item-head">
<svg class="mzp-c-menu-item-icon" width="24" height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<g stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
<g stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round">
<path d="M11 3H4a2 2 0 00-2 2v12a2 2 0 002 2h9l2 4 2-4h2a2 2 0 002-2v-6" />
<path d="M17.5 2.5a2.121 2.121 0 013 3L13 13l-4 1 1-4 7.5-7.5z" />
Expand Down Expand Up @@ -521,7 +521,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Get help with') }}</h4>
data-event-parameters='{"link_name": "main-menu.contributor-discussions"}'>
<svg class="mzp-c-menu-item-icon" width="24" height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<g stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
<g stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round">
<path
d="M17 17l-1.051 3.154a1 1 0 01-1.898 0L13 17H5a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2h-2zM7 8h10M7 12h10" />
Expand All @@ -543,7 +543,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Contributor discussions') }}</h4>
viewBox="0 0 24 24">
<path
d="M10.5 9.5L3 17c-1 1.667-1 3 0 4s2.333.833 4-.5l7.5-7.5c2.333 1.054 4.333.734 6-.96 1.667-1.693 1.833-3.707.5-6.04l-3 3-2.5-.5L15 6l3-3c-2.333-1.333-4.333-1.167-6 .5-1.667 1.667-2.167 3.667-1.5 6z"
stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<h4 class="mzp-c-menu-item-title">{{ _('Contributor tools') }}</h4>
Expand Down Expand Up @@ -595,7 +595,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Contributor tools') }}</h4>
<section class="mzp-c-menu-item mzp-has-icon sumo-nav--dropdown-item">
<a class="mzp-c-menu-item-link" rel="nofollow" href="{{ profile_url(user) }}">
<svg class="mzp-c-menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="translate(5 3)" stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd">
<g transform="translate(5 3)" stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd">
<path d="M0 18c1-4 3.333-6 7-6s6 2 7 6" stroke-linecap="round" />
<circle cx="7" cy="4" r="4" />
</g>
Expand All @@ -615,7 +615,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('View Profile') }}</h4>
<section class="mzp-c-menu-item mzp-has-icon sumo-nav--dropdown-item">
<a class="mzp-c-menu-item-link" rel="nofollow" href="{{ url('users.edit_settings') }}">
<svg class="mzp-c-menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="translate(1 1)" stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd"
<g transform="translate(1 1)" stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="3" />
<path
Expand All @@ -633,7 +633,7 @@ <h4 class="mzp-c-menu-item-title">{{ _('Settings') }}</h4>
<section class="mzp-c-menu-item mzp-has-icon sumo-nav--dropdown-item">
<a class="mzp-c-menu-item-link" href="{{ url('messages.inbox') }}">
<svg class="mzp-c-menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
<g stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round"
stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<path d="M22 6l-10 7L2 6" />
Expand Down
3 changes: 3 additions & 0 deletions kitsune/sumo/static/sumo/js/alpine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import Alpine from "@alpinejs/csp";
import trackEvent from "sumo/js/analytics";
// we need to import surveyForm here so it's available to Alpine components
import surveyForm from "sumo/js/survey_form";
import { themeToggle } from "sumo/js/theme-toggle";

window.Alpine = Alpine;
// Add trackEvent to the window object so it's available to Alpine components
window.trackEvent = trackEvent;

Alpine.data('themeToggle', themeToggle);

Alpine.start();
2 changes: 1 addition & 1 deletion kitsune/sumo/static/sumo/js/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ $(function() {
message = interpolate(gettext('%s characters remaining'), [delta]);
$summaryCount.text(message);
if (maxCount - currentCount >= 10) {
$summaryCount.css('color', 'black');
$summaryCount.css('color', '');
} else {
$summaryCount.css('color', 'red');
if (currentCount >= maxCount) {
Expand Down
61 changes: 61 additions & 0 deletions kitsune/sumo/static/sumo/js/theme-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const STORAGE_KEY = 'sumo-theme';
const COOKIE_NAME = 'sumo-theme';
// 1 year in seconds
const COOKIE_MAX_AGE = 31536000;

function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

function getSavedTheme() {
const saved = localStorage.getItem(STORAGE_KEY);
return (saved === 'dark' || saved === 'light') ? saved : null;
}

function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
// Persist as a cookie so Django can pre-render data-theme server-side,
// eliminating the navigation flash before any JS runs.
document.cookie = `${COOKIE_NAME}=${theme};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
}

/**
* Alpine component for the theme toggle button.
* Register via: Alpine.data('themeToggle', themeToggle)
*/
export function themeToggle() {
return {
theme: getSavedTheme() || getSystemTheme(),

get isDark() {
return this.theme === 'dark';
},

toggle() {
this.theme = this.isDark ? 'light' : 'dark';
localStorage.setItem(STORAGE_KEY, this.theme);
applyTheme(this.theme);
},

init() {
// Sync with what the server-rendered data-theme (from cookie) already set.
const htmlTheme = document.documentElement.getAttribute('data-theme');
if (htmlTheme === 'dark' || htmlTheme === 'light') {
this.theme = htmlTheme;
} else {
// No server-side cookie yet (e.g. first visit) — apply and set cookie now.
applyTheme(this.theme);
}

// Stay in sync when the user changes preference in another tab.
window.addEventListener('storage', (e) => {
if (e.key === STORAGE_KEY) {
this.theme = (e.newValue === 'dark' || e.newValue === 'light')
? e.newValue
: getSystemTheme();
applyTheme(this.theme);
}
});
},
};
}
4 changes: 2 additions & 2 deletions kitsune/sumo/static/sumo/js/wiki.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,9 @@ import collapsibleAccordionInit from "sumo/js/protocol-details-init";
var currentCount = $summaryBox.val().length;
$summaryCount.text(warningCount - currentCount);
if (warningCount - currentCount >= 0) {
$summaryCount.css('color', 'black');
$summaryCount.css('color', '');
} else {
$summaryCount.css('color', 'red');
$summaryCount.css('color', 'var(--color-error)');
if (currentCount >= maxCount) {
$summaryBox.val($summaryBox.val().substr(0, maxCount));
}
Expand Down
1 change: 1 addition & 0 deletions kitsune/sumo/static/sumo/scss/base/_reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ body {
display: flex;
flex-direction: column;
height: auto;
background-color: var(--page-bg);
}
*, *:before, *:after {
box-sizing: border-box;
Expand Down
36 changes: 36 additions & 0 deletions kitsune/sumo/static/sumo/scss/base/_table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,39 @@ td.status,
td.ready-for-l10n.yes {
background: var(--color-green-03);
}

@media (prefers-color-scheme: dark) {
td.needs-update.yes,
td.stale.yes {
background: rgba(255, 213, 0, 0.15);
}

td.status,
td.ready-for-l10n.yes {
background: rgba(63, 180, 111, 0.15);
}
}

[data-theme="dark"] {
td.needs-update.yes,
td.stale.yes {
background: rgba(255, 213, 0, 0.15);
}

td.status,
td.ready-for-l10n.yes {
background: rgba(63, 180, 111, 0.15);
}
}

[data-theme="light"] {
td.needs-update.yes,
td.stale.yes {
background: var(--color-yellow-03);
}

td.status,
td.ready-for-l10n.yes {
background: var(--color-green-03);
}
}
10 changes: 10 additions & 0 deletions kitsune/sumo/static/sumo/scss/base/forms/_buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ input[type=submit]:focus {
}
}

@media (prefers-color-scheme: dark) {
.is-details .icon-button button {
filter: invert(1);
}
}

[data-theme="dark"] .is-details .icon-button button {
filter: invert(1);
}


.sumo-close-button {
appearance: none;
Expand Down
35 changes: 30 additions & 5 deletions kitsune/sumo/static/sumo/scss/base/forms/_fields.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@
--field-border-color-disabled: var(--color-marketing-gray-03);
--field-background-color-disabled: var(--color-marketing-gray-01);
--field-color-disabled: var(--color-marketing-gray-03);
--field-bg: var(--color-white);
}

@media (prefers-color-scheme: dark) {
:root {
--field-border-color-default: var(--color-border);
--field-border-color-hover: var(--color-click);
--field-border-color-disabled: var(--color-border);
--field-background-color-disabled: var(--color-shade-bg);
--field-color-disabled: var(--color-text-light);
--field-bg: var(--card-bg);
}
}

[data-theme="dark"] {
--field-border-color-default: var(--color-border);
--field-border-color-hover: var(--color-click);
--field-border-color-disabled: var(--color-border);
--field-background-color-disabled: var(--color-shade-bg);
--field-color-disabled: var(--color-text-light);
--field-bg: var(--card-bg);
}

[data-theme="light"] {
--field-bg: var(--color-white);
}

// Forms
Expand Down Expand Up @@ -58,7 +83,7 @@ label {
display: block;
margin-bottom: p.$spacing-sm;
@include c.text-body-md;
color: var(--color-marketing-gray-07);
color: var(--color-text);
font-weight: bold;

.required {
Expand Down Expand Up @@ -217,8 +242,8 @@ textarea,
height: 40px;
border: 2px solid var(--field-border-color-default);
border-radius: var(--global-radius);
background-color: var(--color-white);
color: #0c0c0d;
background-color: var(--field-bg);
color: var(--color-text);
outline: 0;
transition-duration: 150ms;
transition-property: border-color;
Expand Down Expand Up @@ -293,7 +318,7 @@ select,
.ts-select-trigger {
position: relative;
background-image: c.svg-url('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 10"><path fill="none" vector-effect="non-scaling-stroke" stroke="#42435A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M1 1l8 8 8-8"/></svg>');
background-color: var(--color-white);
background-color: var(--field-bg);
@include p.bidi(((background-position, top 14px right 14px, top 14px left 14px),
(padding-right, 40px, p.$spacing-sm),
(padding-left, p.$spacing-sm, 40px),
Expand Down Expand Up @@ -476,7 +501,7 @@ label.required::after {
}

.field > label.disabled {
color: #d0d0d7;
color: var(--color-text-light);
}

#edit-document {
Expand Down
Loading