From db7e0e39f908f2497cd9b6b36168b0fb0e178df3 Mon Sep 17 00:00:00 2001 From: ameykanzarkar Date: Wed, 29 Jul 2026 18:41:54 +0530 Subject: [PATCH] Added dark mode support for RTD theme. Added dark mode support for RTD theme. This includes a CSS file and a JS file to handle the dark mode toggle and styling. Dark mode is injected via CI pipeline and is not part of source code. Since site is small manaully tested and verified that dark mode works as expected on all pages. --- .github/workflows/deploy_docs.yml | 8 +- docs/_static/css/dark_mode.css | 354 ++++++++++++++++++++++++++++++ docs/_static/js/dark_mode.js | 207 +++++++++++++++++ 3 files changed, 566 insertions(+), 3 deletions(-) create mode 100644 docs/_static/css/dark_mode.css create mode 100644 docs/_static/js/dark_mode.js diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 8c305388..7622eb19 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -97,11 +97,11 @@ jobs: # Prevent Jekyll from ignoring _static directories touch docs_output/.nojekyll - # Inject version flyout CSS and JS into all HTML files + # Inject version flyout and dark mode CSS/JS into all HTML files REPO_NAME="${{ github.event.repository.name }}" find docs_output -name '*.html' -exec sed -i \ - -e "s||\n|" \ - -e "s||\n|" \ + -e "s||\n\n|" \ + -e "s||\n\n|" \ {} + - name: Verify build output run: | @@ -195,7 +195,9 @@ jobs: # --- shared assets --- mkdir -p publish/_shared/css publish/_shared/js cp docs/_static/css/version_flyout.css publish/_shared/css/ + cp docs/_static/css/dark_mode.css publish/_shared/css/ cp docs/_static/js/version_flyout.js publish/_shared/js/ + cp docs/_static/js/dark_mode.js publish/_shared/js/ # --- root index & Jekyll bypass --- cp docs/_gh_pages/index.html publish/index.html diff --git a/docs/_static/css/dark_mode.css b/docs/_static/css/dark_mode.css new file mode 100644 index 00000000..626d1f8f --- /dev/null +++ b/docs/_static/css/dark_mode.css @@ -0,0 +1,354 @@ +/* + ******************************************************************************* + Copyright (c) 2026 Contributors to the Eclipse Foundation + + See the NOTICE file(s) distributed with this work for additional + information regarding copyright ownership. + + This program and the accompanying materials are made available under the + terms of the Apache License Version 2.0 which is available at + https://www.apache.org/licenses/LICENSE-2.0 + + SPDX-License-Identifier: Apache-2.0 + ******************************************************************************* +*/ + +/* Dark mode theme for Sphinx RTD Theme (class-driven so JS toggle can force mode) */ + +/* ======================================================================== + Base body & page wrappers + ======================================================================== */ +body.dark-mode-enabled { + background-color: #1a1a1a; + color: #e0e0e0; +} + +body.dark-mode-enabled .wy-nav-content-wrap { + background-color: #1a1a1a; +} + +body.dark-mode-enabled .wy-nav-content { + background-color: #1a1a1a; +} + +/* ======================================================================== + Sidebar navigation (the main broken area) + ======================================================================== */ +body.dark-mode-enabled .wy-nav-side { + background-color: #1e1e1e; +} + +body.dark-mode-enabled .wy-side-scroll { + background-color: #1e1e1e; +} + +body.dark-mode-enabled .wy-side-nav-search { + background-color: #263238; +} + +body.dark-mode-enabled .wy-side-nav-search input[type="text"] { + background-color: #2d2d2d; + color: #e0e0e0; + border-color: #404040; +} + +/* All sidebar links */ +body.dark-mode-enabled .wy-menu-vertical a { + color: #cfd8dc; +} + +body.dark-mode-enabled .wy-menu-vertical a:hover { + background-color: #2d2d2d; + color: #ffffff; +} + +/* Section caption headings (OVERVIEW, USAGE, etc.) */ +body.dark-mode-enabled .wy-menu-vertical p.caption { + color: #90a4ae; +} + +/* Current/expanded nav items — handled by the comprehensive block below */ + +/* Override ALL white/grey backgrounds RTD sets on expanded/current nav items. + RTD selectors we must beat: + .wy-menu-vertical li.current { background: #e3e3e3 } + .wy-menu-vertical li.current > a, li.on a { background: #fcfcfc } + .wy-menu-vertical li.toctree-l2.current > a { background: #c9c9c9 } + .wy-menu-vertical li.toctree-l3.current > a { background: #bdbdbd } + Using !important to ensure override regardless of load order. */ +body.dark-mode-enabled .wy-menu-vertical li.current { + background-color: #252525 !important; +} + +body.dark-mode-enabled .wy-menu-vertical li.current > a, +body.dark-mode-enabled .wy-menu-vertical li.current > a:hover, +body.dark-mode-enabled .wy-menu-vertical li.on a, +body.dark-mode-enabled .wy-menu-vertical li.on a:hover { + background: #2d2d2d !important; + color: #ffffff !important; +} + +body.dark-mode-enabled .wy-menu-vertical li.current a:hover { + background: #333333 !important; +} + +body.dark-mode-enabled .wy-menu-vertical li.toctree-l2.current > a, +body.dark-mode-enabled .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a, +body.dark-mode-enabled .wy-menu-vertical li.toctree-l3.current > a, +body.dark-mode-enabled .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { + background: #2a2a2a !important; + color: #e0e0e0 !important; +} + +body.dark-mode-enabled .wy-menu-vertical li.current li > a { + color: #b0bec5 !important; + background-color: #252525 !important; +} + +body.dark-mode-enabled .wy-menu-vertical li.current li > a:hover { + background-color: #333333 !important; + color: #ffffff !important; +} + +/* Active page link highlight */ +body.dark-mode-enabled .wy-menu-vertical li.current a.current { + color: #5dade2 !important; + background-color: #1a2e38 !important; + border-right: 3px solid #5dade2; +} + +/* ======================================================================== + Top mobile nav bar + ======================================================================== */ +body.dark-mode-enabled .wy-nav-top { + background-color: #263238; +} + +/* ======================================================================== + Content area + ======================================================================== */ +body.dark-mode-enabled .rst-content { + background-color: #1a1a1a; + color: #e0e0e0; +} + +/* Headings */ +body.dark-mode-enabled .rst-content h1, +body.dark-mode-enabled .rst-content h2, +body.dark-mode-enabled .rst-content h3, +body.dark-mode-enabled .rst-content h4, +body.dark-mode-enabled .rst-content h5, +body.dark-mode-enabled .rst-content h6 { + color: #e0e0e0; + border-color: #404040; +} + +/* Links */ +body.dark-mode-enabled a { + color: #5dade2; +} + +body.dark-mode-enabled a:visited { + color: #9b7db8; +} + +body.dark-mode-enabled a:hover { + color: #85c1e9; +} + +/* Code blocks */ +body.dark-mode-enabled .rst-content .highlight { + background-color: #2d2d2d; +} + +body.dark-mode-enabled .rst-content pre.literal-block, +body.dark-mode-enabled .rst-content .linenodiv pre { + background-color: #2d2d2d; + color: #e0e0e0; +} + +body.dark-mode-enabled .highlight pre { + background-color: #2d2d2d; + color: #e0e0e0; +} + +/* Inline code */ +body.dark-mode-enabled .rst-content code, +body.dark-mode-enabled .rst-content tt, +body.dark-mode-enabled code { + background-color: #2d2d2d; + color: #e0e0e0; + border-color: #404040; +} + +body.dark-mode-enabled .docutils.literal { + background-color: #2d2d2d; + color: #e0e0e0; + border-color: #404040; +} + +/* ======================================================================== + Tables + ======================================================================== */ +body.dark-mode-enabled .rst-content table.docutils, +body.dark-mode-enabled .rst-content table.field-list { + background-color: #252525; + border-color: #404040; +} + +body.dark-mode-enabled .rst-content table.docutils thead, +body.dark-mode-enabled .rst-content table.field-list thead { + background-color: #1e1e1e; +} + +body.dark-mode-enabled .rst-content table.docutils thead th, +body.dark-mode-enabled .rst-content table.field-list thead th { + background-color: #1e1e1e; + color: #e0e0e0; + border-color: #404040; +} + +/* Override RTD's .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { background-color: #f3f6f6 } + and .rst-content table.docutils td { background-color: transparent } */ +body.dark-mode-enabled .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: #2a2a2a !important; +} + +body.dark-mode-enabled .rst-content table.docutils td, +body.dark-mode-enabled .rst-content table.field-list td { + background-color: #252525 !important; + border-color: #404040; + color: #e0e0e0; +} + +body.dark-mode-enabled .rst-content table.docutils th, +body.dark-mode-enabled .rst-content table.field-list th { + border-color: #404040; + color: #e0e0e0; +} + +/* Links inside table cells */ +body.dark-mode-enabled .rst-content table.docutils a, +body.dark-mode-enabled .rst-content table.field-list a { + color: #5dade2; +} + +/* ======================================================================== + Admonitions + ======================================================================== */ +body.dark-mode-enabled .rst-content .admonition { + background-color: #2d2d2d; + border-color: #404040; +} + +body.dark-mode-enabled .rst-content .note { + background-color: #1e3a3a; + border-color: #2e5c5c; +} + +body.dark-mode-enabled .rst-content .warning { + background-color: #3a2e1e; + border-color: #5c4a2e; +} + +body.dark-mode-enabled .rst-content .important { + background-color: #3a1e1e; + border-color: #5c2e2e; +} + +body.dark-mode-enabled .rst-content .hint, +body.dark-mode-enabled .rst-content .tip { + background-color: #1e3a2e; + border-color: #2e5c47; +} + +/* ======================================================================== + Field lists & definition lists + ======================================================================== */ +body.dark-mode-enabled .rst-content dl dt { + background-color: #2d2d2d; + color: #e0e0e0; + border-top-color: #404040; +} + +body.dark-mode-enabled .rst-content .footnote { + color: #e0e0e0; +} + +/* ======================================================================== + Breadcrumb bar + ======================================================================== */ +body.dark-mode-enabled .wy-breadcrumbs li a { + color: #5dade2; +} + +body.dark-mode-enabled .wy-breadcrumbs { + color: #90a4ae; +} + +/* ======================================================================== + Footer & pagination + ======================================================================== */ +body.dark-mode-enabled footer { + color: #90a4ae; +} + +body.dark-mode-enabled .btn { + background-color: #2d2d2d; + color: #e0e0e0; + border-color: #404040; +} + +body.dark-mode-enabled .btn:hover { + background-color: #383838; +} + +/* Previous / Next navigation */ +body.dark-mode-enabled .rst-footer-buttons .btn { + background-color: #2d2d2d; + border-color: #404040; +} + +/* ======================================================================== + Search highlight + ======================================================================== */ +body.dark-mode-enabled .rst-content .highlighted { + background-color: #404040; +} + +/* ======================================================================== + Pygments / syntax highlighting + ======================================================================== */ +body.dark-mode-enabled .c, +body.dark-mode-enabled .c1, +body.dark-mode-enabled .cm { + color: #7d7d7d; +} + +/* ======================================================================== + Scrollbar (for WebKit browsers) + ======================================================================== */ +body.dark-mode-enabled ::-webkit-scrollbar { + background-color: #1a1a1a; +} + +body.dark-mode-enabled ::-webkit-scrollbar-thumb { + background-color: #404040; + border-radius: 4px; +} + + + +/* Dark mode toggle styling */ +.dark-mode-toggle { + cursor: pointer; + background: none; + border: none; + color: inherit; + font-size: inherit; + padding: 0; +} + +.dark-mode-toggle:hover { + opacity: 0.7; +} diff --git a/docs/_static/js/dark_mode.js b/docs/_static/js/dark_mode.js new file mode 100644 index 00000000..3ec44654 --- /dev/null +++ b/docs/_static/js/dark_mode.js @@ -0,0 +1,207 @@ +/* + ******************************************************************************* + Copyright (c) 2026 Contributors to the Eclipse Foundation + + See the NOTICE file(s) distributed with this work for additional + information regarding copyright ownership. + + This program and the accompanying materials are made available under the + terms of the Apache License Version 2.0 which is available at + https://www.apache.org/licenses/LICENSE-2.0 + + SPDX-License-Identifier: Apache-2.0 + ******************************************************************************* +*/ + +/** + * Dark Mode Toggle for Sphinx RTD Theme + * Provides automatic dark mode detection and manual toggle capability + */ + +(function() { + 'use strict'; + + const STORAGE_KEY = 'sphinx-rtd-theme-color-mode'; + const DARK_MODE_CLASS = 'dark-mode-enabled'; + + function getSystemPrefersDark() { + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + function applyMode(mode) { + const useDark = mode === 'dark' || (mode === 'auto' && getSystemPrefersDark()); + + if (useDark) { + document.body.classList.add(DARK_MODE_CLASS); + document.documentElement.style.colorScheme = 'dark'; + } else { + // Light mode: remove everything, let RTD theme render as default + document.body.classList.remove(DARK_MODE_CLASS); + document.documentElement.style.removeProperty('color-scheme'); + } + + updateToggleButton(); + } + + /** + * Initialize dark mode based on user preference or saved setting + */ + function initializeDarkMode() { + const savedPreference = localStorage.getItem(STORAGE_KEY); + + if (savedPreference === 'dark' || savedPreference === 'light') { + applyMode(savedPreference); + } else { + applyMode('auto'); + } + } + + /** + * Set dark mode on or off + * @param {boolean} isDark - Whether to enable dark mode + */ + function setDarkMode(isDark) { + const mode = isDark ? 'dark' : 'light'; + localStorage.setItem(STORAGE_KEY, mode); + applyMode(mode); + } + + /** + * Toggle dark mode + */ + function toggleDarkMode() { + const isDarkEnabled = document.body.classList.contains(DARK_MODE_CLASS); + setDarkMode(!isDarkEnabled); + } + + /** + * Update the toggle button to reflect current state + */ + function updateToggleButton() { + const toggle = document.getElementById('dark-mode-toggle'); + if (toggle) { + const isDarkEnabled = document.body.classList.contains(DARK_MODE_CLASS); + toggle.setAttribute('aria-pressed', isDarkEnabled); + toggle.title = isDarkEnabled ? 'Switch to light mode' : 'Switch to dark mode'; + toggle.innerHTML = isDarkEnabled ? '☀️' : '🌙'; + } + } + + /** + * Create and inject the dark mode toggle button + */ + function createToggleButton() { + // Check if toggle already exists + if (document.getElementById('dark-mode-toggle')) { + return; + } + + const toggle = document.createElement('button'); + toggle.id = 'dark-mode-toggle'; + toggle.className = 'dark-mode-toggle'; + toggle.setAttribute('aria-label', 'Toggle dark mode'); + toggle.type = 'button'; + toggle.addEventListener('click', toggleDarkMode); + + // Prefer RTD top nav when visible; fall back to a floating button. + const navTop = document.querySelector('.wy-nav-top'); + const canUseNavTop = navTop && window.getComputedStyle(navTop).display !== 'none'; + + if (canUseNavTop) { + toggle.style.cssText = ` + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); + font-size: 20px; + padding: 8px; + cursor: pointer; + background: none; + border: none; + color: inherit; + z-index: 1000; + `; + navTop.appendChild(toggle); + } else { + // Place inside the sidebar header (next to "Rules Score Doc" home link) + const sideNavSearch = document.querySelector('.wy-side-nav-search'); + if (sideNavSearch) { + toggle.style.cssText = ` + position: absolute; + top: 10px; + right: 10px; + width: 32px; + height: 32px; + border-radius: 50%; + border: none; + background: rgba(255, 255, 255, 0.15); + color: #ffffff; + font-size: 18px; + cursor: pointer; + z-index: 1000; + line-height: 32px; + text-align: center; + padding: 0; + `; + sideNavSearch.style.position = 'relative'; + sideNavSearch.appendChild(toggle); + } else { + // Fallback if sidebar not found (e.g. mobile collapsed) + toggle.style.cssText = ` + position: fixed; + top: 16px; + left: 16px; + width: 36px; + height: 36px; + border-radius: 50%; + border: 1px solid rgba(120, 120, 120, 0.35); + background: rgba(30, 30, 30, 0.9); + color: #ffffff; + font-size: 18px; + cursor: pointer; + z-index: 2000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + padding: 0; + `; + document.body.appendChild(toggle); + } + } + + updateToggleButton(); + } + + /** + * Listen for system theme changes + */ + function watchSystemTheme() { + const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + // Handle theme changes (for browsers that support this) + if (darkModeQuery.addEventListener) { + darkModeQuery.addEventListener('change', (e) => { + const savedPreference = localStorage.getItem(STORAGE_KEY); + + // Follow system only when no explicit user preference was stored. + if (savedPreference !== 'dark' && savedPreference !== 'light') { + applyMode('auto'); + } + }); + } + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + initializeDarkMode(); + createToggleButton(); + watchSystemTheme(); + }); + } else { + initializeDarkMode(); + createToggleButton(); + watchSystemTheme(); + } + + // Expose toggle function globally for manual control + window.toggleDarkMode = toggleDarkMode; +})();