diff --git a/CHANGES.rst b/CHANGES.rst index f0f94fda396..681271bc6bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,6 +68,12 @@ Features added Patch by Jean-François B. * #13508: Initial support for :pep:`695` type aliases. Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner. +* #13742: HTML themes: Add dark mode support to built-in themes (``basic``, + ``classic``, ``haiku``, ``nature``, and ``sphinxdoc``). Dark mode + automatically activates based on system preferences using + ``prefers-color-scheme`` media query. An optional JavaScript toggle + (``theme_toggle.js``) is also provided for manual theme switching. + Patch by Fazeel Usmani. Bugs fixed ---------- diff --git a/sphinx/themes/basic/static/basic.css.jinja b/sphinx/themes/basic/static/basic.css.jinja index d2411760b18..1e09baf444e 100644 --- a/sphinx/themes/basic/static/basic.css.jinja +++ b/sphinx/themes/basic/static/basic.css.jinja @@ -904,3 +904,266 @@ div.math:hover a.headerlink { display: none; } } + +/* -- theme toggle button --------------------------------------------------- */ + +.theme-toggle-button { + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid #ccc; + border-radius: 4px; + padding: 5px 10px; + cursor: pointer; + font-size: 1.2em; + transition: background-color 0.3s ease; +} + +.theme-toggle-button:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +.theme-toggle-icon { + display: inline-block; +} + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #121212; + color: #e0e0e0; + } + + a { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + ul.search li p.context { + color: #aaa; + } + + table.indextable tr.cap { + background-color: #2a2a2a; + } + + div.modindex-jumpbox, + div.genindex-jumpbox { + border-top: 1px solid #444; + border-bottom: 1px solid #444; + } + + div.sidebar, + aside.sidebar { + border: 1px solid #555; + background-color: #2a2a2a; + } + + nav.contents, + aside.topic, + div.topic { + border: 1px solid #444; + background-color: #1e1e1e; + } + + div.admonition { + background-color: #1e1e1e; + border: 1px solid #444; + } + + table.docutils td, + table.docutils th { + border-bottom: 1px solid #555; + } + + table.citation { + border-left: solid 1px #666; + } + + dt:target, + span.highlighted { + background-color: #4a4a00; + } + + rect.highlighted { + fill: #4a4a00; + } + + .system-message { + background-color: #4a1f1f; + border: 3px solid #a00; + } + + .footnote:target { + background-color: #4a4a00; + } + + code, + pre { + background-color: #1e1e1e; + color: #f1f1f1; + } + + div.code-block-caption { + background-color: #2a2a2a; + } + + td.linenos pre { + background-color: transparent; + color: #666; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + } + + .sig.c .k, .sig.c .kt, + .sig.cpp .k, .sig.cpp .kt { + color: #6db3f2; + } + + .sig.c .m, + .sig.cpp .m { + color: #80cbc4; + } + + .sig.c .s, .sig.c .sc, + .sig.cpp .s, .sig.cpp .sc { + color: #90ee90; + } +} + +/* -- manual dark mode (via data-theme attribute) --------------------------- */ + +[data-theme="dark"] body { + background-color: #121212; + color: #e0e0e0; +} + +[data-theme="dark"] a { + color: #80cbc4; +} + +[data-theme="dark"] a:visited { + color: #ce93d8; +} + +[data-theme="dark"] div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; +} + +[data-theme="dark"] ul.search li p.context { + color: #aaa; +} + +[data-theme="dark"] table.indextable tr.cap { + background-color: #2a2a2a; +} + +[data-theme="dark"] div.modindex-jumpbox, +[data-theme="dark"] div.genindex-jumpbox { + border-top: 1px solid #444; + border-bottom: 1px solid #444; +} + +[data-theme="dark"] div.sidebar, +[data-theme="dark"] aside.sidebar { + border: 1px solid #555; + background-color: #2a2a2a; +} + +[data-theme="dark"] nav.contents, +[data-theme="dark"] aside.topic, +[data-theme="dark"] div.topic { + border: 1px solid #444; + background-color: #1e1e1e; +} + +[data-theme="dark"] div.admonition { + background-color: #1e1e1e; + border: 1px solid #444; +} + +[data-theme="dark"] table.docutils td, +[data-theme="dark"] table.docutils th { + border-bottom: 1px solid #555; +} + +[data-theme="dark"] table.citation { + border-left: solid 1px #666; +} + +[data-theme="dark"] dt:target, +[data-theme="dark"] span.highlighted { + background-color: #4a4a00; +} + +[data-theme="dark"] rect.highlighted { + fill: #4a4a00; +} + +[data-theme="dark"] .system-message { + background-color: #4a1f1f; + border: 3px solid #a00; +} + +[data-theme="dark"] .footnote:target { + background-color: #4a4a00; +} + +[data-theme="dark"] code, +[data-theme="dark"] pre { + background-color: #1e1e1e; + color: #f1f1f1; +} + +[data-theme="dark"] div.code-block-caption { + background-color: #2a2a2a; +} + +[data-theme="dark"] td.linenos pre { + background-color: transparent; + color: #666; +} + +[data-theme="dark"] div.viewcode-block:target { + background-color: #3a3a2a; +} + +[data-theme="dark"] .sig.c .k, +[data-theme="dark"] .sig.c .kt, +[data-theme="dark"] .sig.cpp .k, +[data-theme="dark"] .sig.cpp .kt { + color: #6db3f2; +} + +[data-theme="dark"] .sig.c .m, +[data-theme="dark"] .sig.cpp .m { + color: #80cbc4; +} + +[data-theme="dark"] .sig.c .s, +[data-theme="dark"] .sig.c .sc, +[data-theme="dark"] .sig.cpp .s, +[data-theme="dark"] .sig.cpp .sc { + color: #90ee90; +} + +[data-theme="dark"] .theme-toggle-button { + background-color: rgba(0, 0, 0, 0.3); + border-color: #555; +} + +[data-theme="dark"] .theme-toggle-button:hover { + background-color: rgba(0, 0, 0, 0.5); +} diff --git a/sphinx/themes/basic/static/theme_toggle.js b/sphinx/themes/basic/static/theme_toggle.js new file mode 100644 index 00000000000..ddc4ab314a4 --- /dev/null +++ b/sphinx/themes/basic/static/theme_toggle.js @@ -0,0 +1,128 @@ +/** + * Sphinx theme toggle - Manual dark/light mode switcher + * + * This script provides a toggle button to manually switch between light and dark modes, + * overriding the system preference when desired. + */ + +(function () { + "use strict"; + + // Check for saved theme preference or default to system preference + function getThemePreference() { + const saved = localStorage.getItem("sphinx-theme"); + if (saved) { + return saved; + } + // Check system preference + if ( + window.matchMedia + && window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + return "dark"; + } + return "light"; + } + + // Apply theme to document + function applyTheme(theme) { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("sphinx-theme", theme); + } + + // Create and insert toggle button + function createToggleButton() { + const button = document.createElement("button"); + button.id = "theme-toggle"; + button.className = "theme-toggle-button"; + button.setAttribute("aria-label", "Toggle dark mode"); + button.title = "Toggle dark/light mode"; + + // Add button text/icon + button.innerHTML = '🌙'; + + button.addEventListener("click", function () { + const current = + document.documentElement.getAttribute("data-theme") || "light"; + const next = current === "dark" ? "light" : "dark"; + applyTheme(next); + updateButtonIcon(next); + }); + + // Insert button into the page (try multiple locations) + const insertLocations = [".sphinxsidebar", ".related", "body"]; + + for (const selector of insertLocations) { + const container = document.querySelector(selector); + if (container) { + if (selector === ".sphinxsidebar") { + // Insert at top of sidebar + container.insertBefore(button, container.firstChild); + } else if (selector === ".related") { + // Insert into navigation + const nav = container.querySelector("ul"); + if (nav) { + const li = document.createElement("li"); + li.className = "right"; + li.appendChild(button); + nav.appendChild(li); + } + } else { + // Fallback: fixed position button + button.style.position = "fixed"; + button.style.bottom = "20px"; + button.style.right = "20px"; + button.style.zIndex = "1000"; + container.appendChild(button); + } + break; + } + } + + return button; + } + + // Update button icon based on current theme + function updateButtonIcon(theme) { + const button = document.getElementById("theme-toggle"); + if (button) { + const icon = button.querySelector(".theme-toggle-icon"); + if (icon) { + icon.textContent = theme === "dark" ? "☀️" : "🌙"; + } + } + } + + // Initialize on page load + function init() { + const theme = getThemePreference(); + applyTheme(theme); + + // Wait for DOM to be ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", function () { + createToggleButton(); + updateButtonIcon(theme); + }); + } else { + createToggleButton(); + updateButtonIcon(theme); + } + + // Listen for system theme changes + if (window.matchMedia) { + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", function (e) { + // Only apply if user hasn't set a manual preference + if (!localStorage.getItem("sphinx-theme")) { + const newTheme = e.matches ? "dark" : "light"; + applyTheme(newTheme); + updateButtonIcon(newTheme); + } + }); + } + } + + init(); +})(); diff --git a/sphinx/themes/classic/static/classic.css.jinja b/sphinx/themes/classic/static/classic.css.jinja index 24b67fa76ed..cf6daf01dc8 100644 --- a/sphinx/themes/classic/static/classic.css.jinja +++ b/sphinx/themes/classic/static/classic.css.jinja @@ -344,3 +344,165 @@ div.code-block-caption { color: #efefef; background-color: #1c4e63; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + html { + background-color: #1a1a1a; + } + + body { + background-color: #1e1e1e; + color: #e0e0e0; + } + + div.document { + background-color: #252525; + } + + div.body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.footer { + color: #aaa; + } + + div.footer a { + color: #aaa; + } + + div.related { + background-color: #2a2a2a; + color: #e0e0e0; + } + + div.related a { + color: #80cbc4; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h3 a, + div.sphinxsidebar h4, + div.sphinxsidebar p, + div.sphinxsidebar ul { + color: #e0e0e0; + } + + div.sphinxsidebar a { + color: #80cbc4; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + {% if theme_collapsiblesidebar|tobool %} + #sidebarbutton { + background-color: #3a3a3a; + color: #e0e0e0; + border-{{side}}: 1px solid #2a2a2a; + } + + #sidebarbutton:hover { + background-color: #2a2a2a; + } + {% endif %} + + a { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + {% if theme_externalrefs|tobool %} + a.external { + border-bottom: 1px dashed #80cbc4; + } + + a.external:visited { + border-bottom: 1px dashed #ce93d8; + } + {% endif %} + + div.body h1, + div.body h2, + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #252525; + color: #6db3f2; + border-bottom: 1px solid #444; + } + + a.headerlink { + color: #80cbc4; + } + + a.headerlink:hover { + background-color: #80cbc4; + color: #1a1a1a; + } + + div.note { + background-color: #1e3a1e; + border: 1px solid #444; + } + + div.seealso { + background-color: #3a3a1e; + border: 1px solid #666; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #252525; + } + + div.warning { + background-color: #3a1e1e; + border: 1px solid #a66; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #555; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + th, dl.field-list > dt { + background-color: #2a2a2a; + } + + .warning code { + background: #4a2a2a; + } + + .note code { + background: #2a3a2a; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + color: #e0e0e0; + background-color: #2a4a5a; + } +} diff --git a/sphinx/themes/haiku/static/haiku.css.jinja b/sphinx/themes/haiku/static/haiku.css.jinja index 676e34ceeaf..5907358ea83 100644 --- a/sphinx/themes/haiku/static/haiku.css.jinja +++ b/sphinx/themes/haiku/static/haiku.css.jinja @@ -367,3 +367,112 @@ div.viewcode-block:target { div.math p { text-align: center; } + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + html { + background: #1a1a1a url(bg-page.png) top left repeat-x; + } + + body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.header h1, + div.header h1 a, + h1, h2, h3, h4 { + color: #6db3f2; + } + + div.header h2 { + color: #888; + } + + div.title { + color: #6db3f2; + border-bottom: dotted thin #444; + } + + div.bottomnav { + background: #2a2a2a; + } + + a:link { + color: #ff7742; + } + + a:visited { + color: #b388ff; + } + + a:hover, a:active { + color: #ffa06e; + } + + a.headerlink { + color: #a7ce38; + } + + table.index { + border-color: #444; + } + + table.index tr.heading { + background-color: #2a2a2a; + } + + table.index tr.index { + background-color: #222; + } + + table.index a:link { + color: #ff7742; + } + + table.index a:hover, table.index a:active { + color: #ffa06e; + } + + div.admonition { + border-color: #444; + background-color: #252525; + } + + div.note { + background: #1e3a1e url(alert_info_32.png) 15px 15px no-repeat; + } + + div.warning { + background: #3a3a1e url(alert_warning_32.png) 15px 15px no-repeat; + } + + div.seealso { + background: #1e3a1e; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + pre { + background-color: #1e1e1e; + border-color: #6db3f2; + color: #e0e0e0; + } + + hr { + border-top-color: #444; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + h1, h2 { + border-bottom: dotted thin #444; + } +} diff --git a/sphinx/themes/nature/static/nature.css.jinja b/sphinx/themes/nature/static/nature.css.jinja index 3c8205382c6..56237affbf1 100644 --- a/sphinx/themes/nature/static/nature.css.jinja +++ b/sphinx/themes/nature/static/nature.css.jinja @@ -243,3 +243,165 @@ div.code-block-caption { color: #222; border: 1px solid #C6C9CB; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #1a1a1a; + color: #b0b0b0; + } + + hr { + border: 1px solid #555; + } + + div.document { + background-color: #252525; + } + + div.body { + background-color: #1a1a1a; + color: #e0e0e0; + } + + div.footer { + color: #aaa; + } + + div.footer a { + color: #aaa; + } + + div.related { + background-color: #2a5a1e; + color: #e0e0e0; + text-shadow: 0px 1px 0 #000; + } + + div.related a { + color: #b8e699; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h4 { + color: #e0e0e0; + background-color: #2a2a2a; + text-shadow: 1px 1px 0 #000; + } + + div.sphinxsidebar h3 a { + color: #e0e0e0; + } + + div.sphinxsidebar p { + color: #aaa; + } + + div.sphinxsidebar ul { + color: #e0e0e0; + } + + div.sphinxsidebar a { + color: #80cbc4; + } + + div.sphinxsidebar input { + border: 1px solid #555; + background-color: #2a2a2a; + color: #e0e0e0; + } + + a { + color: #80cbc4; + } + + a:hover { + color: #ffa06e; + } + + a:visited { + color: #ce93d8; + } + + div.body h1, + div.body h2, + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #2a4a5a; + color: #6db3f2; + text-shadow: 0px 1px 0 #000; + } + + div.body h1 { + border-top: 20px solid #1a1a1a; + } + + div.body h2 { + background-color: #2a3a4a; + } + + div.body h3, + div.body h4, + div.body h5, + div.body h6 { + background-color: #2a3a3a; + } + + a.headerlink { + color: #ff7742; + } + + a.headerlink:hover { + background-color: #ff7742; + color: #1a1a1a; + } + + div.note { + background-color: #252525; + border: 1px solid #444; + } + + div.seealso { + background-color: #3a3a1e; + border: 1px solid #666; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #252525; + } + + div.warning { + background-color: #3a1e1e; + border: 1px solid #a66; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #444; + -webkit-box-shadow: 1px 1px 1px #000; + -moz-box-shadow: 1px 1px 1px #000; + } + + code { + background-color: #2a2a2a; + color: #f1f1f1; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + background-color: #2a2a2a; + color: #e0e0e0; + border: 1px solid #444; + } +} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja b/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja index 07a0166d012..b8f50560ff8 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja @@ -347,3 +347,184 @@ div.code-block-caption { color: #222; border: 1px solid #ccc; } + +/* -- dark mode support ----------------------------------------------------- */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #2a3a3d; + color: #e0e0e0; + border: 1px solid #555; + } + + div.document { + background-color: #1a1a1a; + background-image: none; + } + + div.bodywrapper { + border-right: 1px solid #444; + } + + div.related ul { + background-image: none; + background-color: #2a2a2a; + border-top: 1px solid #444; + border-bottom: 1px solid #444; + } + + div.related ul li a { + color: #ffb347; + } + + div.related ul li a:hover { + color: #80cbc4; + } + + div.sphinxsidebar h3, + div.sphinxsidebar h4 { + color: #e0e0e0; + border: 1px solid #555; + background-color: #3a4a4d; + } + + div.sphinxsidebar h3 a { + color: #e0e0e0; + } + + div.footer { + background-color: #2a3a3d; + color: #888; + } + + div.footer a { + color: #888; + } + + a { + color: #ffb347; + } + + a:hover { + color: #80cbc4; + } + + a:visited { + color: #ce93d8; + } + + h1 { + color: #6db3f2; + } + + div.body h1 a, + div.body h2 a, + div.body h3 a, + div.body h4 a, + div.body h5 a, + div.body h6 a { + color: #e0e0e0 !important; + } + + h1 a.anchor, + h2 a.anchor, + h3 a.anchor, + h4 a.anchor, + h5 a.anchor, + h6 a.anchor { + color: #666 !important; + } + + h1 a.anchor:hover, + h2 a.anchor:hover, + h3 a.anchor:hover, + h4 a.anchor:hover, + h5 a.anchor:hover, + h6 a.anchor:hover { + color: #aaa; + background-color: #2a2a2a; + } + + a.headerlink { + color: #ff7742 !important; + } + + a.headerlink:hover { + background-color: #444; + color: #e0e0e0 !important; + } + + code { + background-color: #2a2a2a; + border-bottom: 1px solid #444; + color: #f1f1f1; + } + + hr { + border: 1px solid #555; + } + + a code { + color: #ffb347; + } + + a code:hover { + color: #80cbc4; + } + + pre { + background-color: #1e1e1e; + color: #f1f1f1; + border: 1px solid #444; + } + + div.quotebar { + background-color: #2a2a2a; + border: 1px solid #444; + } + + nav.contents, + aside.topic, + div.topic { + background-color: #2a2a2a; + } + + div.admonition, + div.warning { + border: 1px solid #555; + background-color: #252525; + } + + div.admonition p.admonition-title, + div.warning p.admonition-title { + color: #e0e0e0; + border-bottom: 1px solid #555; + background-color: #3a4a4d; + } + + div.warning { + border: 1px solid #a66; + } + + div.warning p.admonition-title { + background-color: #8a2020; + border-bottom-color: #a66; + } + + div.versioninfo { + border: 1px solid #444; + background-color: #2a4a5a; + } + + div.viewcode-block:target { + background-color: #3a3a2a; + border-top: 1px solid #6db3f2; + border-bottom: 1px solid #6db3f2; + } + + div.code-block-caption { + background-color: #2a2a2a; + color: #e0e0e0; + border: 1px solid #444; + } +}