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; +})();