diff --git a/docs/.sphinx/_static/css/custom.css b/docs/.sphinx/_static/css/custom.css index 8649f712..1d0527a5 100644 --- a/docs/.sphinx/_static/css/custom.css +++ b/docs/.sphinx/_static/css/custom.css @@ -391,3 +391,78 @@ p code.literal { .highlight .o { color: #BB5400; } + +/* Hoverxref tooltip dark mode support + The hoverxref extension uses Tippy.js which renders tooltips with light backgrounds by default. + Tippy appends tooltips directly to body, so we use a .dark-mode class added by JavaScript + (see hoverxref-dark-mode.js) rather than relying on theme context selectors. */ + +/* Light mode tooltip styles (default) */ +.tippy-box[data-theme~='material'] { + background-color: #fff; + color: #111; + border: 1px solid #e0e0e0; +} + +.tippy-box[data-theme~='material'] .tippy-content { + background-color: #fff; + color: #111; +} + +.tippy-box[data-theme~='material'][data-placement^='top'] > .tippy-arrow::before { + border-top-color: #e0e0e0; +} + +.tippy-box[data-theme~='material'][data-placement^='bottom'] > .tippy-arrow::before { + border-bottom-color: #e0e0e0; +} + +.tippy-box[data-theme~='material'][data-placement^='left'] > .tippy-arrow::before { + border-left-color: #e0e0e0; +} + +.tippy-box[data-theme~='material'][data-placement^='right'] > .tippy-arrow::before { + border-right-color: #e0e0e0; +} + +/* Dark mode tooltip styles - applied via .dark-mode class by JavaScript */ +.tippy-box[data-theme~='material'].dark-mode { + background-color: #2b2b2b; + color: #f7f7f7; + border: 1px solid #555; +} + +.tippy-box[data-theme~='material'].dark-mode .tippy-content { + background-color: #2b2b2b; + color: #f7f7f7; +} + +.tippy-box[data-theme~='material'].dark-mode[data-placement^='top'] > .tippy-arrow::before { + border-top-color: #555; +} + +.tippy-box[data-theme~='material'].dark-mode[data-placement^='bottom'] > .tippy-arrow::before { + border-bottom-color: #555; +} + +.tippy-box[data-theme~='material'].dark-mode[data-placement^='left'] > .tippy-arrow::before { + border-left-color: #555; +} + +.tippy-box[data-theme~='material'].dark-mode[data-placement^='right'] > .tippy-arrow::before { + border-right-color: #555; +} + +/* Style links within dark mode tooltips */ +.tippy-box[data-theme~='material'].dark-mode a { + color: #58a6ff; +} + +/* Style code blocks within dark mode tooltips */ +.tippy-box[data-theme~='material'].dark-mode code { + background-color: #3b3b3b; + color: #e0e0e0; + border: 1px solid #555; + padding: 0.1em 0.3em; + border-radius: 3px; +} diff --git a/docs/.sphinx/_static/js/hoverxref-dark-mode.js b/docs/.sphinx/_static/js/hoverxref-dark-mode.js new file mode 100644 index 00000000..4e2dfd67 --- /dev/null +++ b/docs/.sphinx/_static/js/hoverxref-dark-mode.js @@ -0,0 +1,156 @@ +/** + * Hoverxref Dark Mode Support + * + * This script ensures that hoverxref tooltips (powered by Tippy.js) + * adapt to the current theme (light/dark mode). + * + * Tippy.js appends tooltip elements directly to the body, outside the + * themed content area, so we need to dynamically update their styling + * based on the current theme. + */ + +(function() { + 'use strict'; + + /** + * Get the current theme from the body's data-theme attribute + * @returns {string} 'light', 'dark', or 'auto' + */ + function getCurrentTheme() { + return document.body.getAttribute('data-theme') || 'auto'; + } + + /** + * Determine if dark mode should be active + * @returns {boolean} + */ + function isDarkMode() { + const theme = getCurrentTheme(); + if (theme === 'dark') { + return true; + } else if (theme === 'light') { + return false; + } else { + // Theme is 'auto', check system preference + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + } + + /** + * Update all visible tooltips to match the current theme + */ + function updateTooltipThemes() { + const tooltips = document.querySelectorAll('.tippy-box'); + const darkMode = isDarkMode(); + + tooltips.forEach(function(tooltip) { + if (darkMode) { + tooltip.classList.add('dark-mode'); + } else { + tooltip.classList.remove('dark-mode'); + } + }); + } + + /** + * Set up a MutationObserver to watch for new tooltips being added to the DOM + */ + function observeTooltips() { + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + // Check if the added node is a tooltip or contains tooltips + if (node.nodeType === 1) { // Element node + if (node.classList && node.classList.contains('tippy-box')) { + // A tooltip was added + if (isDarkMode()) { + node.classList.add('dark-mode'); + } + } else if (node.querySelectorAll) { + // Check for tooltips within the added node + const tooltips = node.querySelectorAll('.tippy-box'); + if (tooltips.length > 0 && isDarkMode()) { + tooltips.forEach(function(tooltip) { + tooltip.classList.add('dark-mode'); + }); + } + } + } + }); + }); + }); + + // Observe the body for tooltip additions + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + /** + * Watch for theme changes on the body element + */ + function observeThemeChanges() { + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { + updateTooltipThemes(); + } + }); + }); + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['data-theme'] + }); + } + + /** + * Watch for system color scheme preference changes + */ + function observeSystemThemeChanges() { + if (window.matchMedia) { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + // Modern browsers + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', function() { + // Only update if theme is set to 'auto' + if (getCurrentTheme() === 'auto') { + updateTooltipThemes(); + } + }); + } else if (mediaQuery.addListener) { + // Fallback for older browsers + mediaQuery.addListener(function() { + if (getCurrentTheme() === 'auto') { + updateTooltipThemes(); + } + }); + } + } + } + + /** + * Initialize the dark mode support + */ + function init() { + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + observeTooltips(); + observeThemeChanges(); + observeSystemThemeChanges(); + updateTooltipThemes(); + }); + } else { + observeTooltips(); + observeThemeChanges(); + observeSystemThemeChanges(); + updateTooltipThemes(); + } + } + + // Start the script + init(); +})(); diff --git a/docs/conf.py b/docs/conf.py index 2db22a42..69d7632a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -178,6 +178,7 @@ "js/header-nav.js", "js/github_issue_links.js", "js/bundle.js", + "js/hoverxref-dark-mode.js", ] source_suffix = {