From 826d84546faa60690777e7c5c74d702f2f510eac Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sat, 14 Feb 2026 06:40:10 +1000 Subject: [PATCH 1/4] Introduce ultraplot_theme extension and move docs skin assets into UltraTheme --- README.md | 9 +- setup.cfg | 10 +- sphinx_rtd_light_dark/__init__.py | 3 + sphinx_rtd_light_dark/static/light-dark.css | 348 ++++++- sphinx_rtd_light_dark/static/light-dark.js | 23 +- ultraplot_theme/__init__.py | 34 + ultraplot_theme/static/ultraplot-docs.css | 1032 +++++++++++++++++++ ultraplot_theme/static/ultraplot-docs.js | 735 +++++++++++++ 8 files changed, 2152 insertions(+), 42 deletions(-) create mode 100644 ultraplot_theme/__init__.py create mode 100644 ultraplot_theme/static/ultraplot-docs.css create mode 100644 ultraplot_theme/static/ultraplot-docs.js diff --git a/README.md b/README.md index 22524d8..92d875b 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,17 @@ Theme Usage ----------- Install with `pip install sphinx-rtd-light-dark`. Then in your `conf.py` file, -add the entry ``'sphinx_rtd_light_dark'`` to the `extensions` list, -and set `html_theme` to ``'sphinx_rtd_light_dark'``. +add the entry ``'ultraplot_theme'`` to the `extensions` list, +and set `html_theme` to ``'ultraplot_theme'``. +The legacy extension/theme name ``'sphinx_rtd_light_dark'`` remains supported. Example `conf.py`: ```python extensions = [ ... - 'sphinx_rtd_light_dark', + 'ultraplot_theme', ... ] -html_theme = 'sphinx_rtd_light_dark' +html_theme = 'ultraplot_theme' ``` diff --git a/setup.cfg b/setup.cfg index 6c506b2..af7c38b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,15 +25,19 @@ project_urls = Source Code = https://github.com/lukelbd/sphinx_rtd_light_dark [options] -packages = sphinx_rtd_light_dark +packages = + sphinx_rtd_light_dark + ultraplot_theme setup_requires = setuptools>=44; toml; setuptools_scm>=3.4.3 install_requires = pygments; sphinx_rtd_theme>=2.0.0 include_package_data = True python_requires = >=3.6.0 [options.package_data] -sphinx.sphinx_rtd_light_dark = theme.conf, static/*.css, static/*.js +sphinx_rtd_light_dark = theme.conf, static/*.css, static/*.js, static/pygments/*.css, layout.html, breadcrumbs.html +ultraplot_theme = static/*.css, static/*.js [options.entry_points] sphinx.html_themes = - name_of_theme = sphinx_rtd_light_dark + sphinx_rtd_light_dark = sphinx_rtd_light_dark + ultraplot_theme = ultraplot_theme diff --git a/sphinx_rtd_light_dark/__init__.py b/sphinx_rtd_light_dark/__init__.py index db9df9f..a098b1c 100644 --- a/sphinx_rtd_light_dark/__init__.py +++ b/sphinx_rtd_light_dark/__init__.py @@ -2,6 +2,7 @@ A clean variant on the read the docs theme with light mode dark mode toggling. """ import os + from pygments.formatters import HtmlFormatter # Declare themes. These should be kept updated with custom.js @@ -26,4 +27,6 @@ # Add entrypoint for theme # See: https://www.sphinx-doc.org/en/master/development/theming.html def setup(app): # noqa: E302 + # Keep legacy name for compatibility and add canonical UltraPlot name. app.add_html_theme('sphinx_rtd_light_dark', base) + app.add_html_theme('ultraplot_theme', base) diff --git a/sphinx_rtd_light_dark/static/light-dark.css b/sphinx_rtd_light_dark/static/light-dark.css index 0b531f2..9d0db3d 100644 --- a/sphinx_rtd_light_dark/static/light-dark.css +++ b/sphinx_rtd_light_dark/static/light-dark.css @@ -55,10 +55,10 @@ --call-color: #d0d0d0; --versions-color: #b0b0b0; --code-color: #ff8f4f; - --link-color: #69acff; + --link-color: #d2d6dc; --highlight-color: #b27600; - --link-hover-color: #549aeb; - --link-visited-color: #c194ff; + --link-hover-color: #ffffff; + --link-visited-color: #b8bdc5; --code-border-color: #606060; --menu-border-color: #808080; --search-border-color: #444; @@ -501,48 +501,338 @@ hr { display: None; } +/* Modern skin overrides (keeps RTD structure while refreshing visuals) */ +:root { + --dark-color: #0f172a; + --light-color: #f8fafc; + --main-color: #1f2937; + --call-color: #4b5563; + --versions-color: #6b7280; + --highlight-color: #fef08a; + --code-color: #c2410c; + --link-color: #0f62d6; + --link-hover-color: #0b4fb0; + --link-visited-color: #7c3aed; + --code-border-color: #dadde2; + --menu-border-color: #b8bcc2; + --search-border-color: #b8bcc2; + --search-shadow-color: rgba(15, 23, 42, 0.08); + --main-bg-color: #fbfbfc; + --code-bg-color: #f7f8fa; + --empty-bg-color: #f1f2f4; + --l1-bg-color: #e7e8ea; + --l2-bg-color: #dddfe2; + --l3-bg-color: #d3d6da; + --l4-bg-color: #c9cdd2; + --l5-bg-color: #bfc4ca; + --l6-bg-color: #b5bbc2; + --l7-bg-color: #aab2ba; + --block-bg-color: #f5f6f8; + --accent-bg-color: #d4d7dc; + --header-bg-color: #eceff3; + --header-text-color: #1f2937; + --header-border-color: #cfd4db; + --header-shadow: 0 2px 10px rgba(0, 0, 0, 0.07); +} + +[data-theme="dark"] { + --main-color: #f0f0f0; + --call-color: #d0d0d0; + --versions-color: #b0b0b0; + --code-color: #ffad7a; + --link-color: #d2d6dc; + --highlight-color: #b27600; + --link-hover-color: #ffffff; + --link-visited-color: #b8bdc5; + --code-border-color: #565656; + --menu-border-color: #4c4c4c; + --search-border-color: #4a4a4a; + --search-shadow-color: rgba(0, 0, 0, 0.45); + --main-bg-color: #202020; + --code-bg-color: #2a2a2a; + --empty-bg-color: #1a1a1a; + --l1-bg-color: #262626; + --l2-bg-color: #303030; + --l3-bg-color: #3c3c3c; + --l4-bg-color: #484848; + --l5-bg-color: #545454; + --l6-bg-color: #616161; + --l7-bg-color: #6e6e6e; + --block-bg-color: #2a2a2a; + --accent-bg-color: #505050; + --header-bg-color: #111111; + --header-text-color: #f2f2f2; + --header-border-color: #2a2a2a; + --header-shadow: 0 4px 14px rgba(0, 0, 0, 0.5); +} + +body, +legend, +h1, +h2, +h3, +h4, +h5, +h6, +.rst-content .toctree-wrapper p.caption { + font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} + +.rst-content div[class^="highlight"] pre, +.rst-content pre.literal-block, +.rst-content .linenodiv pre, +code, +tt, +pre { + font-family: "JetBrains Mono", "IBM Plex Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace; +} + +.wy-nav-content-wrap { + background: linear-gradient( + 180deg, + #f8f8f9 0%, + #f3f4f6 40%, + var(--empty-bg-color) 100% + ); +} + +[data-theme="dark"] .wy-nav-content-wrap { + background: + radial-gradient(1200px 500px at 100% -20%, rgba(255, 255, 255, 0.03), transparent 62%), + radial-gradient(900px 450px at 0% -20%, rgba(0, 0, 0, 0.4), transparent 68%), + var(--empty-bg-color); +} + +.wy-nav-content { + max-width: 920px; + border: 1px solid var(--accent-bg-color); + border-radius: 12px; + box-shadow: 0 14px 40px rgba(15, 23, 42, 0.08); +} + +[data-theme="dark"] .wy-nav-content { + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45); +} + +/* Header modernization and explicit de-blueing */ +.wy-nav-top { + background: var(--header-bg-color) !important; + color: var(--header-text-color) !important; + border-bottom: 1px solid var(--header-border-color); + box-shadow: var(--header-shadow); +} + +.wy-nav-top a, +.wy-nav-top i, +.wy-nav-top .fa-bars { + color: var(--header-text-color) !important; +} + +.wy-side-nav-search { + background: var(--header-bg-color) !important; + border-bottom: 1px solid var(--header-border-color); + box-shadow: var(--header-shadow); + padding-top: 0.9rem; + padding-bottom: 0.75rem; +} + +.wy-side-nav-search > a, +.wy-side-nav-search .wy-dropdown > a { + color: var(--header-text-color) !important; + font-weight: 650; + letter-spacing: 0.01em; +} + +.wy-side-nav-search input[type=text] { + border-radius: 9px; + padding: 0.55em 0.8em; + box-shadow: inset 0 1px 2px var(--search-shadow-color); + background-color: var(--main-bg-color); +} + +.wy-side-nav-search input[type=text]:focus { + outline: none; + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18); +} + +.wy-menu li.toctree-l1.current > a { + border-left: 3px solid var(--menu-border-color); +} + +.wy-menu-vertical a { + transition: color 120ms ease, background-color 120ms ease, border-color 120ms ease; + border-left: 2px solid transparent; +} + +.wy-menu-vertical a:hover { + border-left-color: var(--menu-border-color); +} + +.wy-menu li.toctree-l1.current > a, +.wy-menu li.toctree-l2.current > a { + font-weight: 600; +} + +.rst-content .admonition, +.rst-content dl:not(.docutils) .admonition, +.rst-content div:not(.stderr) > div[class^="highlight"], +.rst-content div.nbinput > div.input_area, +.rst-content pre.literal-block, +.rst-content table.docutils { + border-radius: 8px; + overflow: hidden; +} + +.btn { + border-radius: 8px; + border-color: var(--accent-bg-color); + transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; +} + +.btn:hover { + transform: translateY(-1px); +} + +a { + text-decoration-thickness: 0.08em; + text-underline-offset: 0.12em; +} + +body { + line-height: 1.65; + letter-spacing: 0.003em; +} + +h1, +h2, +h3 { + letter-spacing: -0.01em; +} + +h1 { + font-weight: 650; +} + +h2 { + font-weight: 620; + padding-bottom: 0.1em; + border-bottom: 1px solid var(--accent-bg-color); +} + +.rst-content p, +.rst-content li { + line-height: 1.7; +} + +.rst-content div:not(.stderr) > div[class^="highlight"], +.rst-content pre.literal-block, +.rst-content table.docutils, +.rst-content .admonition, +.rst-content dl:not(.docutils) .admonition { + border: 1px solid var(--accent-bg-color); +} + +.rst-content div:not(.stderr) > div[class^="highlight"], +.rst-content pre.literal-block { + box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06); +} + +[data-theme="dark"] .rst-content div:not(.stderr) > div[class^="highlight"], +[data-theme="dark"] .rst-content pre.literal-block { + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35); +} + /* Dark mode toggle switcher */ -/* See: https://dev.to/ananyaneogi/create-a-dark-light-mode-switch-with-css-variables-34l8 */ -/* Color changes cannot be in :before tag or they won't apply to padding */ #light-dark-li { float: right; } + #light-dark-label { border: 0; margin: 0; padding: 0; font-size: 100%; } + #light-dark-label input { - display: none; /* hides the check box */ + display: none; } + #light-dark-label div.btn-neutral { - padding: 0; - margin-left: 5px; - border-width: 5px; - border-style: solid; - border-radius: 2px; + min-width: 92px; + margin-left: 8px; + padding: 0.28em 0.66em; + border: 1px solid var(--menu-border-color); + border-radius: 999px; + background-color: var(--block-bg-color) !important; + color: var(--main-color) !important; cursor: pointer; - transition: all 0.1s linear; - overflow: hidden; -} -#light-dark-label:hover div.btn-neutral, -#light-dark-label input:checked + div.btn-neutral { - color: var(--light-color) !important; - border-color: var(--dark-color) !important; - background-color: var(--dark-color) !important; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.01em; + text-align: center; } -#light-dark-label div.btn-neutral, -#light-dark-label:hover input:checked + div.btn-neutral { - color: var(--dark-color) !important; - border-color: var(--light-color) !important; - background-color: var(--light-color) !important; + +#light-dark-label div.btn-neutral:before { + content: "Light"; } -#light-dark-label:hover div.btn-neutral:before, + #light-dark-label input:checked + div.btn-neutral:before { - content: "Dark mode"; + content: "Dark"; } -#light-dark-label div.btn-neutral:before, -#light-dark-label:hover input:checked + div.btn-neutral:before { - content: "Light mode"; + +/* Final dark-mode hard overrides: remove remaining blue accents. */ +[data-theme="dark"] .wy-nav-top, +[data-theme="dark"] .wy-side-nav-search { + background: #111111 !important; + color: #f0f0f0 !important; +} + +[data-theme="dark"] .wy-nav-top a, +[data-theme="dark"] .wy-nav-top .fa, +[data-theme="dark"] .wy-nav-top .fa-bars, +[data-theme="dark"] .wy-side-nav-search > a, +[data-theme="dark"] .wy-side-nav-search .wy-dropdown > a { + color: #f0f0f0 !important; +} + +[data-theme="dark"] a, +[data-theme="dark"] a code, +[data-theme="dark"] .rst-content a code.xref.docutils.literal, +[data-theme="dark"] .rst-content a tt.xref.docutils.literal { + color: #d2d6dc !important; +} + +[data-theme="dark"] a:hover, +[data-theme="dark"] a:hover code, +[data-theme="dark"] .rst-content a:hover code.xref.docutils.literal, +[data-theme="dark"] .rst-content a:hover tt.xref.docutils.literal { + color: #ffffff !important; +} + +[data-theme="dark"] a:visited, +[data-theme="dark"] a:visited code, +[data-theme="dark"] .rst-content a:visited code.xref.docutils.literal, +[data-theme="dark"] .rst-content a:visited tt.xref.docutils.literal { + color: #b8bdc5 !important; +} + +[data-theme="dark"] .wy-menu-vertical a, +[data-theme="dark"] .wy-menu-vertical a:hover, +[data-theme="dark"] .wy-menu-vertical a:visited { + color: #e2e2e2 !important; +} + +/* Match logo panel background to the left TOC/sidebar area. */ +.wy-side-nav-search, +[data-theme="dark"] .wy-side-nav-search { + background: var(--l1-bg-color) !important; + border-bottom: 1px solid var(--menu-border-color) !important; + box-shadow: none !important; +} + +.wy-side-nav-search .wy-dropdown > a img.logo, +.wy-side-nav-search > a img.logo { + background: transparent !important; } diff --git a/sphinx_rtd_light_dark/static/light-dark.js b/sphinx_rtd_light_dark/static/light-dark.js index 9e61f19..e6936ae 100644 --- a/sphinx_rtd_light_dark/static/light-dark.js +++ b/sphinx_rtd_light_dark/static/light-dark.js @@ -8,28 +8,39 @@ var regex = /(.*)\/.*(\.css$)/i const toggleSwitch = document.getElementById('light-dark-checkbox'); const pygmentsLink = document.getElementById('pygments-style'); +const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); function lightToggle() { document.documentElement.setAttribute('data-theme', 'light'); - pygmentsLink.href = pygmentsLink.href.replace(regex, '$1/pastie$2'); + if (pygmentsLink) { + pygmentsLink.href = pygmentsLink.href.replace(regex, '$1/pastie$2'); + } localStorage.setItem('theme', 'light'); } function darkToggle() { document.documentElement.setAttribute('data-theme', 'dark'); - pygmentsLink.href = pygmentsLink.href.replace(regex, '$1/monokai$2'); + if (pygmentsLink) { + pygmentsLink.href = pygmentsLink.href.replace(regex, '$1/monokai$2'); + } localStorage.setItem('theme', 'dark'); } function switchTheme(e) { e.target.checked ? darkToggle() : lightToggle() } -toggleSwitch.addEventListener('change', switchTheme, null); +if (toggleSwitch) { + toggleSwitch.addEventListener('change', switchTheme, null); +} /* Check for user preference on load */ -const currentTheme = localStorage.getItem('theme') || 'light'; +const currentTheme = localStorage.getItem('theme') || (prefersDark.matches ? 'dark' : 'light'); if (currentTheme === 'dark') { darkToggle(); - toggleSwitch.checked = true; + if (toggleSwitch) { + toggleSwitch.checked = true; + } } else { lightToggle(); - toggleSwitch.checked = false; + if (toggleSwitch) { + toggleSwitch.checked = false; + } } diff --git a/ultraplot_theme/__init__.py b/ultraplot_theme/__init__.py new file mode 100644 index 0000000..2d6a452 --- /dev/null +++ b/ultraplot_theme/__init__.py @@ -0,0 +1,34 @@ +""" +UltraPlot docs theme extension. + +This keeps the legacy RTD-light-dark theme registration while also injecting +shared UltraPlot docs static assets (CSS/JS) that are used with Shibuya. +""" + +from __future__ import annotations + +from pathlib import Path + +from sphinx_rtd_light_dark import setup as _legacy_setup + +_STATIC_DIR = Path(__file__).resolve().parent / "static" + + +def _add_static_path(app): + static_path = str(_STATIC_DIR) + if static_path not in app.config.html_static_path: + app.config.html_static_path.append(static_path) + + +def setup(app): + legacy = _legacy_setup(app) or {} + + app.connect("builder-inited", _add_static_path) + app.add_css_file("ultraplot-docs.css") + app.add_js_file("ultraplot-docs.js") + + return { + "version": legacy.get("version", "0.0"), + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/ultraplot_theme/static/ultraplot-docs.css b/ultraplot_theme/static/ultraplot-docs.css new file mode 100644 index 0000000..d181890 --- /dev/null +++ b/ultraplot_theme/static/ultraplot-docs.css @@ -0,0 +1,1032 @@ +:root { + /* Core surfaces */ + --uplt-color-panel-bg: #ffffff; /* page bg (light) */ + --uplt-color-sidebar-bg: #f4f4f4; /* TOC + notebook cell bg (light) */ + --uplt-color-card-bg: #f4f4f4; /* used by .card-img-top background */ + --uplt-color-white: #ffffff; + + /* Borders & shadows */ + --uplt-color-border-muted: #e1e4e5; + --uplt-color-button-border: #c5c5c5; + --uplt-color-shadow: rgba(0, 0, 0, 0.1); + + /* Text */ + --uplt-color-text-main: #404040; + --uplt-color-text-strong: #333333; + --uplt-color-text-secondary: #555555; + --uplt-color-text-muted: #606060; + + /* Accent */ + --uplt-color-accent: #0f766e; + --uplt-color-accent-hover: rgba(15, 118, 110, 0.1); + --uplt-color-accent-active: rgba(15, 118, 110, 0.15); + --uplt-color-accent-grad-start: rgba(15, 118, 110, 0.1); + --uplt-color-accent-grad-end: rgba(15, 118, 110, 0.02); + --uplt-color-accent-shadow-strong: rgba(15, 118, 110, 0.2); + --uplt-color-accent-shadow-soft: rgba(15, 118, 110, 0.1); + + /* Scrollbar */ + --uplt-color-scrollbar-track: #f1f1f1; + --uplt-color-scrollbar-thumb: #cdcdcd; + --uplt-color-scrollbar-thumb-hover: #9e9e9e; + + --uplt-color-code-bg: var(--uplt-color-sidebar-bg); /* same as page */ + --uplt-color-code-fg: #6a6a6a; /* gray code text (light) */ + --code-block-background: var(--uplt-color-code-bg); + --sy-c-link: var(--uplt-color-accent); + --sy-c-link-hover: #0b5f59; + --uplt-color-toc-bg: #e9e9e9; +} + +.sy-main .yue a, +.globaltoc a, +.localtoc a, +.sy-breadcrumbs a { + color: var(--sy-c-link); +} + +.sy-main .yue a:hover, +.globaltoc a:hover, +.localtoc a:hover, +.sy-breadcrumbs a:hover { + color: var(--sy-c-link-hover); +} + +.sy-main .yue a:not(.headerlink) { + border-bottom-color: transparent !important; + text-decoration: none !important; +} + +.sy-main .yue a:not(.headerlink):hover { + border-bottom-color: transparent !important; + text-decoration: underline !important; + text-decoration-color: var(--sy-c-link-hover); + text-decoration-thickness: 0.08em; + text-underline-offset: 0.14em; +} + +.sy-head .sy-head-links { + justify-content: flex-start !important; + column-gap: 1.8rem !important; + padding-left: 1.25rem !important; + padding-right: 1.25rem !important; +} + +.sy-head .sy-head-brand { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.2rem 0.04rem 0.2rem; + line-height: 1.25; +} + +.sy-head .sy-head-brand strong { + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.065em; + text-transform: uppercase; + line-height: 1.25; +} + +@media (min-width: 768px) { + .sy-head .sy-head-links > ul { + display: flex !important; + align-items: center; + justify-content: flex-start; + column-gap: 2.8rem !important; + margin: 0 !important; + padding: 0 !important; + text-align: left; + } + + .sy-head .sy-head-links > ul > li.link { + margin: 0 !important; + padding: 0 !important; + } +} + +.sy-head .sy-head-links a { + border: 0 !important; + border-bottom: 2px solid transparent !important; + border-radius: 0; + padding: 0.2rem 0.04rem 0.2rem; + line-height: 1.25; + font-size: 0.74rem; + font-weight: 600; + letter-spacing: 0.065em; + text-transform: uppercase; + color: var(--uplt-color-text-main); + background: transparent !important; + text-decoration: none !important; + transition: + border-bottom-color 0.2s ease, + color 0.2s ease, + opacity 0.2s ease; +} + +.sy-head .sy-head-links a:hover { + border-bottom-color: rgba(15, 118, 110, 0.35) !important; + color: var(--uplt-color-accent); + opacity: 1; +} + +.sy-head .sy-head-links a[href="#"], +.sy-head .sy-head-links a[aria-current="page"] { + color: var(--uplt-color-accent) !important; + border-bottom-color: var(--uplt-color-accent) !important; + opacity: 1; +} + +@media (min-width: 768px) { + .sy-head, + .sy-breadcrumbs, + .sy-lside { + transition: + opacity 0.24s ease, + transform 0.24s ease; + will-change: opacity, transform; + } + + html.uplt-chrome-hidden .sy-head, + html.uplt-chrome-hidden .sy-breadcrumbs { + opacity: 0; + transform: translateY(-14px); + pointer-events: none; + } + + html.uplt-chrome-hidden .sy-lside { + opacity: 0; + transform: translateX(-14px); + pointer-events: none; + } +} + +/* Content heading hierarchy */ +.sy-main .yue h1 { + font-size: clamp(2rem, 2.6vw, 2.5rem); + line-height: 1.12; + font-weight: 740; + letter-spacing: -0.018em; + margin: 0 0 1.1rem; + padding-bottom: 0.38rem; + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + column-gap: 0.7rem; + row-gap: 0.25rem; + color: var(--sy-c-heading); +} + +.sy-main .yue h1::before { + content: ""; + grid-row: 1; + grid-column: 1; + width: 0.5rem; + height: 1.05em; + border-radius: 999px; + background: linear-gradient(180deg, var(--uplt-color-accent) 0%, #0a5f58 100%); + box-shadow: 0 0 0 1px var(--uplt-color-accent-shadow-soft); +} + +.sy-main .yue h1::after { + content: ""; + display: block; + grid-row: 2; + grid-column: 2; + width: clamp(2.8rem, 8vw, 4.2rem); + height: 0.2rem; + border-radius: 999px; + background: linear-gradient(90deg, var(--uplt-color-accent) 0%, #0a5f58 100%); +} + +.sy-main .yue h2 { + font-size: clamp(1.35rem, 1.8vw, 1.65rem); + line-height: 1.25; + font-weight: 650; + margin: 2.2rem 0 0.8rem; + padding-bottom: 0.35rem; + border-bottom: 1px solid var(--sy-c-divider); + box-shadow: inset 0 -2px 0 0 var(--uplt-color-accent-hover); + color: var(--sy-c-heading); +} + +.sy-main .yue h3 { + font-size: 1.08rem; + line-height: 1.3; + font-weight: 620; + margin: 1.45rem 0 0.5rem; + padding-left: 0.55rem; + border-left: 3px solid var(--uplt-color-accent); + color: var(--sy-c-heading); +} + +.sy-main .yue h4, +.sy-main .yue h5, +.sy-main .yue h6 { + font-size: 0.98rem; + font-weight: 600; + margin: 1.1rem 0 0.35rem; + color: var(--sy-c-text); +} + +html.dark .sy-head .sy-head-links a, +html.dark-theme .sy-head .sy-head-links a, +[data-color-mode="dark"] .sy-head .sy-head-links a { + color: #dbe6e5; + opacity: 0.96; +} + +html.dark .sy-head .sy-head-links a:hover, +html.dark-theme .sy-head .sy-head-links a:hover, +[data-color-mode="dark"] .sy-head .sy-head-links a:hover { + color: #66d0c6; + border-bottom-color: rgba(102, 208, 198, 0.55) !important; +} + +html.dark .sy-head .sy-head-links a[href="#"], +html.dark-theme .sy-head .sy-head-links a[href="#"], +[data-color-mode="dark"] .sy-head .sy-head-links a[href="#"], +html.dark .sy-head .sy-head-links a[aria-current="page"], +html.dark-theme .sy-head .sy-head-links a[aria-current="page"], +[data-color-mode="dark"] .sy-head .sy-head-links a[aria-current="page"] { + color: #8be0d9 !important; + border-bottom-color: #8be0d9 !important; +} + +@media screen and (max-width: 1200px) { + .sy-head .sy-head-links { + column-gap: 3.8rem !important; + padding-left: 1rem !important; + padding-right: 1rem !important; + } +} + +.yue :not(pre) > code, +.yue code.docutils.literal.notranslate, +.yue code.docutils.literal.notranslate .pre { + color: var(--sy-c-link); + background-color: var(--uplt-color-accent-hover); + border: 1px solid var(--uplt-color-border-muted); + border-radius: 0.2rem; + padding: 0.06rem 0.28rem; +} + +html.dark, +html.dark-theme, +[data-color-mode="dark"] { + --uplt-color-accent: #1aa89a; + --uplt-color-accent-hover: rgba(26, 168, 154, 0.14); + --uplt-color-accent-active: rgba(26, 168, 154, 0.22); + --uplt-color-accent-grad-start: rgba(26, 168, 154, 0.16); + --uplt-color-accent-grad-end: rgba(26, 168, 154, 0.04); + --uplt-color-accent-shadow-strong: rgba(26, 168, 154, 0.26); + --uplt-color-accent-shadow-soft: rgba(26, 168, 154, 0.14); + --sy-c-link: #58d5c9; + --sy-c-link-hover: #84e8df; + --uplt-color-panel-bg: #202020; + --code-block-background: #141414; + --syntax-dark-background: #141414; + --syntax-dark-highlight: #2a2f2f; + --uplt-color-toc-bg: #171717; +} + +@media (prefers-color-scheme: dark) { + html:not(.light):not(.light-theme):not([data-color-mode="light"]) { + --uplt-color-panel-bg: #202020; + --code-block-background: #141414; + --syntax-dark-background: #141414; + --syntax-dark-highlight: #2a2f2f; + --uplt-color-toc-bg: #171717; + } +} + +.grid-item-card .card-img-top { + height: 100%; + object-fit: cover; + width: 100%; + background-color: var(--uplt-color-card-bg); +} + +/* Make all cards with this class use flexbox for vertical layout */ +.card-with-bottom-text { + display: flex !important; + flex-direction: column !important; + height: 100% !important; + border: 1px solid var(--uplt-color-border-muted) !important; + border-radius: 0.8rem !important; + background: linear-gradient( + 180deg, + var(--uplt-color-white) 0%, + var(--uplt-color-sidebar-bg) 100% + ) !important; + box-shadow: 0 4px 14px var(--uplt-color-shadow); + transition: + transform 0.18s ease, + box-shadow 0.18s ease, + border-color 0.18s ease; +} + +.card-with-bottom-text:hover { + transform: translateY(-2px); + border-color: var(--uplt-color-accent) !important; + box-shadow: 0 10px 22px var(--uplt-color-accent-shadow-soft); +} + +/* Style the card content areas */ +.card-with-bottom-text .sd-card-body { + display: flex !important; + flex-direction: column !important; + flex-grow: 1 !important; + gap: 0.25rem; + padding: 0.85rem 1rem 1rem !important; +} + +.card-with-bottom-text .sd-card-header { + background: linear-gradient( + 135deg, + var(--uplt-color-accent) 0%, + #0a5f58 100% + ) !important; + color: #ffffff !important; + border-bottom: 0 !important; + border-top-left-radius: 0.8rem !important; + border-top-right-radius: 0.8rem !important; + padding: 0.72rem 1rem !important; +} + +.card-with-bottom-text .sd-card-header .sd-card-text, +.card-with-bottom-text .sd-card-header strong { + color: #ffffff !important; +} + +.card-with-bottom-text .sd-card-title { + margin-bottom: 0.35rem; + font-weight: 650; + letter-spacing: -0.01em; +} + +/* Make images not grow or shrink */ +.card-with-bottom-text img { + flex-shrink: 0 !important; + margin-bottom: 0.5rem !important; + border-radius: 0.45rem; + border: 1px solid var(--uplt-color-border-muted); + background: var(--uplt-color-card-bg); +} + +/* Push the last paragraph to the bottom */ +.card-with-bottom-text .sd-card-body > p:last-child { + margin-top: auto !important; + padding-top: 0.5rem !important; + text-align: center !important; +} + +html.dark .card-with-bottom-text, +html.dark-theme .card-with-bottom-text, +[data-color-mode="dark"] .card-with-bottom-text { + background: linear-gradient(180deg, #252525 0%, #1f1f1f 100%) !important; + box-shadow: 0 7px 20px rgba(0, 0, 0, 0.3); +} + +html.dark .card-with-bottom-text .sd-card-header, +html.dark-theme .card-with-bottom-text .sd-card-header, +[data-color-mode="dark"] .card-with-bottom-text .sd-card-header { + background: linear-gradient(135deg, #178f84 0%, #0f6f67 100%) !important; +} + +.img-container img { + object-fit: cover; + width: 100%; + height: 100%; +} + +.right-toc-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 15px; + border-bottom: 1px solid var(--uplt-color-border-muted); +} + +.right-toc-title { + font-weight: 600; + font-size: 1.1em; + color: var(--uplt-color-accent); +} + +.right-toc-buttons { + display: flex; + align-items: center; +} + +.right-toc-toggle-btn { + background: none; + border: none; + color: var(--uplt-color-accent); + font-size: 16px; + cursor: pointer; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + padding: 0; + transition: background-color 0.2s; +} + +.right-toc-toggle-btn:hover { + background-color: var(--uplt-color-accent-hover); +} + +.right-toc-content { + padding: 15px 15px 15px 20px; + overflow-y: auto; + max-height: calc(100vh - 200px); +} + +.right-toc-list { + list-style-type: none; + padding-left: 0; + margin: 0; +} + +.right-toc-link { + display: block; + padding: 5px 0; + text-decoration: none; + color: var(--uplt-color-text-main); + border-radius: 4px; + transition: all 0.2s ease; + margin-bottom: 3px; +} + +.right-toc-link:hover { + background-color: var(--uplt-color-accent-hover); + padding-left: 5px; + color: var(--uplt-color-accent); +} + +.right-toc-level-h1 { + font-weight: 600; + font-size: 1em; +} + +.right-toc-level-h2 { + padding-left: 1.2em; + font-size: 0.95em; +} + +.right-toc-level-h3 { + padding-left: 2.4em; + font-size: 0.9em; + color: var(--uplt-color-text-muted); +} + +.right-toc-subtoggle { + background: none; + border: none; + color: var(--uplt-color-accent); + cursor: pointer; + font-size: 0.9em; + margin-right: 0.3em; + padding: 0; +} + +.right-toc-sublist { + list-style-type: none; + margin: 0.2em 0 0.4em 0; + padding-left: 1.2em; +} + +/* Active TOC item highlighting */ +.right-toc-link.active { + background-color: var(--uplt-color-accent-active); + color: var(--uplt-color-accent); + font-weight: 500; + padding-left: 5px; +} + +/* Collapsed state */ +.right-toc-collapsed { + width: auto; + border-left-width: 0; +} + +.right-toc-collapsed .right-toc-header { + border-bottom: none; + padding: 8px 12px; +} + +/* Scrollbar styling */ +.right-toc-content::-webkit-scrollbar { + width: 5px; +} + +.right-toc-content::-webkit-scrollbar-track { + background: var(--uplt-color-scrollbar-track); + border-radius: 10px; +} + +.right-toc-content::-webkit-scrollbar-thumb { + background: var(--uplt-color-scrollbar-thumb); + border-radius: 10px; +} + +.right-toc-content::-webkit-scrollbar-thumb:hover { + background: var(--uplt-color-scrollbar-thumb-hover); +} + +.toc-wrapper { + position: relative; + display: flex; + max-width: 1100px; /* match .rst-content if needed */ + margin: 0 auto; + gap: 20px; +} + +.rst-content { + flex: 1; +} +.right-toc { + position: fixed; + top: 90px; + width: 280px; + left: 1125px; + font-size: 0.9em; + background-color: var(--uplt-color-panel-bg); + z-index: 100; + border-radius: 6px; + box-shadow: 0 4px 10px var(--uplt-color-shadow); + border-left: 3px solid var(--uplt-color-accent); + transition: all 0.3s ease; + max-height: calc(100vh - 150px); +} + +.gallery-filter-controls { + margin: 1rem 0 2rem; + padding: 1rem 1.2rem; + border-radius: 16px; + background: linear-gradient( + 135deg, + var(--uplt-color-accent-grad-start), + var(--uplt-color-accent-grad-end) + ); + box-shadow: + 0 10px 24px var(--uplt-color-accent-shadow-strong), + 0 2px 6px var(--uplt-color-accent-shadow-soft); +} + +.gallery-filter-bar { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.gallery-filter-button { + border: 1px solid var(--uplt-color-button-border); + background-color: var(--uplt-color-white); + color: var(--uplt-color-text-strong); + padding: 0.35rem 0.85rem; + border-radius: 999px; + font-size: 0.9em; + cursor: pointer; + transition: + background-color 0.2s ease, + color 0.2s ease, + border-color 0.2s ease; +} + +.gallery-filter-button.is-active { + background-color: var(--uplt-color-accent); + border-color: var(--uplt-color-accent); + color: var(--uplt-color-white); +} + +.gallery-section-hidden { + display: none; +} + +body.gallery-filter-active .sphx-glr-thumbnails:not(.gallery-unified) { + display: none; +} + +body.gallery-filter-active .gallery-section-header, +body.gallery-filter-active .gallery-section-description { + display: none; +} + +body.whats_new .wy-menu-vertical li.toctree-l1.current > ul { + display: none; +} + +body.whats_new .wy-menu-vertical li.toctree-l2, +body.whats_new .wy-menu-vertical li.toctree-l3, +body.whats_new .wy-menu-vertical li.toctree-l4 { + display: none; +} + +body.whats_new .wy-menu-vertical a[href^="#"] { + display: none; +} + +body.whats_new .wy-menu-vertical li:has(> a[href^="#"]) { + display: none; +} + +/* Hide gallery subsections from left TOC */ +body.wy-body-for-nav + .wy-menu-vertical + .wy-menu-vertical-2 + a:is( + [href="#layouts"], + [href="#legends-and-colorbars"], + [href="#geoaxes"], + [href="#plot-types"], + [href="#colors-and-cycles"] + ), +body.wy-body-for-nav + .wy-menu-vertical + .wy-menu-vertical-2 + a:is( + [href="#layouts"], + [href="#legends-and-colorbars"], + [href="#geoaxes"], + [href="#plot-types"], + [href="#colors-and-cycles"] + ) + + ul, +body.wy-body-for-nav + .wy-menu-vertical + .wy-menu-vertical-2 + li[class*="toctree-l1"]:has( + :is( + a[href="#layouts"], + a[href="#legends-and-colorbars"], + a[href="#geoaxes"], + a[href="#plot-types"], + a[href="#colors-and-cycles"] + ) + ) { + display: none !important; +} + +/* Hide the section containers themselves */ +.gallery-section { + margin: 1.5em 0; +} + +:is( + section#layouts, + section#legends-and-colorbars, + section#geoaxes, + section#plot-types, + section#colors-and-cycles + ) + > :is(h1, p) { + display: none; +} + +/* Style for gallery section headers */ +.gallery-section-header { + font-size: 1.5em; + font-weight: bold; + display: block; + margin: 1.5em 0 0.5em 0; + border-bottom: 2px solid var(--uplt-color-accent); + padding-bottom: 0.3em; + color: var(--uplt-color-accent); +} + +.gallery-section-description { + margin: 0 0 1em 0; + color: var(--uplt-color-text-secondary); +} + +/* Gallery example pages: collapsible source code */ +.yue details.uplt-code-details { + margin-top: 0.9rem; + border: 1px solid var(--uplt-color-border-muted); + border-radius: 0.35rem; + background: var(--uplt-color-panel-bg); +} + +.yue details.uplt-code-details > summary.uplt-code-summary { + list-style: none; + cursor: pointer; + user-select: none; + padding: 0.45rem 0.7rem; + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 0.03em; + color: var(--sy-c-link); +} + +.yue details.uplt-code-details > summary.uplt-code-summary::-webkit-details-marker { + display: none; +} + +.yue details.uplt-code-details[open] > summary.uplt-code-summary { + border-bottom: 1px solid var(--uplt-color-border-muted); +} + +.yue details.uplt-code-details > .highlight-Python { + margin: 0; + border: 0; + border-radius: 0 0 0.35rem 0.35rem; +} + +.yue details.uplt-code-details > .nbinput.docutils.container { + margin: 0; + border: 0; + border-radius: 0 0 0.35rem 0.35rem; +} + +.yue details.uplt-code-details > .nbinput.docutils.container div.input_area { + border-radius: 0 0 0.35rem 0.35rem; + border-top: 0; +} + +/* Responsive adjustments */ +@media screen and (max-width: 1200px) { + .right-toc { + width: 230px; + } +} + +@media screen and (max-width: 992px) { + .right-toc { + display: none; /* Hide on smaller screens */ + } +} + +.output_area.docutils.container { + text-align: center; /* Centers inline or inline-block children horizontally */ +} + +/* Optional: Ensure image respects container boundaries */ +.output_area.docutils.container img { + width: 100%; + height: auto; + display: block; +} + +/* Shibuya: unify sidebar and notebook cell backgrounds */ +.sy-lside, +.sy-lside-inner, +.sy-rside, +.sy-rside-inner, +.sy-scrollbar { + background-color: var(--uplt-color-toc-bg); +} + +.yue div.nbinput.container > div.input_area, +.yue div.nboutput.container > div.output_area, +.yue .highlight, +.yue .highlight pre { + background-color: var(--code-block-background) !important; +} + +/* Shibuya right TOC: collapse sub-H1 headings under each H1 section */ +.sy-rside .localtoc { + margin-left: 0.55rem; + border: 1px solid var(--uplt-color-border-muted); + border-radius: 0.5rem; + padding: 0.7rem 0.75rem; + background: var(--uplt-color-panel-bg); + box-shadow: 0 1px 6px var(--uplt-color-shadow); +} + +.sy-rside .sy-rside-inner > div:empty { + display: none; +} + +.sy-rside .localtoc > h3 { + color: var(--sy-c-light); + font-family: var(--sy-f-heading); + font-size: 0.86rem; + font-weight: 500; + letter-spacing: 0.4px; + text-transform: uppercase; + margin: 0 0 0.5rem 0; + padding: 0 0 0.45rem 0; + border-bottom: 1px solid var(--sy-c-divider); +} + +.sy-rside .localtoc > ul li > a { + display: block; + padding: 0.08rem 0.2rem 0.08rem 0.45rem; + border-radius: 0.2rem; +} + +.sy-rside .localtoc > ul > li.uplt-toc-collapsible { + position: relative; + padding-left: 1.2rem; +} + +.sy-rside .localtoc > .uplt-toc-controls { + display: flex; + gap: 0.35rem; + margin: 0.35rem 0 0.75rem 0; +} + +.sy-rside .localtoc > .uplt-code-controls { + display: grid; + grid-template-columns: 1fr; + row-gap: 0.35rem; + margin: 0.85rem 0 0 0; + padding-top: 0.55rem; + border-top: 1px solid var(--sy-c-divider); +} + +.sy-rside .localtoc > .uplt-code-controls .uplt-code-btn { + width: 100%; + justify-self: stretch; + text-align: left; +} + +.sy-rside .localtoc .uplt-toc-btn { + border: 1px solid var(--sy-c-border); + background: var(--uplt-color-panel-bg); + color: var(--sy-c-text); + border-radius: 6px; + padding: 0.16rem 0.5rem; + font-size: 0.73rem; + font-weight: 600; + letter-spacing: 0.01em; + line-height: 1.25; + cursor: pointer; + box-shadow: 0 1px 2px var(--uplt-color-shadow); + transition: + border-color 0.2s ease, + color 0.2s ease, + background-color 0.2s ease, + box-shadow 0.2s ease; +} + +.sy-rside .localtoc .uplt-toc-btn:hover { + border-color: var(--sy-c-link); + color: var(--sy-c-link); + background: var(--sy-c-surface); + box-shadow: 0 1px 3px var(--uplt-color-shadow); +} + +.sy-rside .localtoc .uplt-toc-btn:focus-visible { + outline: 2px solid var(--sy-c-link); + outline-offset: 1px; +} + +.uplt-rside-show { + position: fixed; + right: 1rem; + top: 5.5rem; + z-index: 50; + border: 1px solid var(--sy-c-border); + background: var(--uplt-color-panel-bg); + color: var(--sy-c-text); + border-radius: 6px; + padding: 0.24rem 0.68rem; + font-size: 0.73rem; + font-weight: 600; + cursor: pointer; + display: none; + box-shadow: 0 2px 10px var(--uplt-color-shadow); +} + +.uplt-rside-hidden .uplt-rside-show { + display: inline-flex; + align-items: center; +} + +.uplt-rside-hidden .sy-rside { + display: none; +} + +.uplt-rside-hidden .rside-overlay { + display: none; +} + +.sy-rside .localtoc > ul > li > button.uplt-toc-toggle { + position: absolute; + left: -0.1rem; + top: 0.12rem; + width: 1.2rem; + height: 1.2rem; + border-radius: 3px; + border: none; + background: transparent; + color: var(--sy-c-light); + cursor: pointer; + font-size: 0; + line-height: 1; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s ease; +} + +.sy-rside .localtoc > ul > li > button.uplt-toc-toggle::before { + content: "▸"; + font-size: 2.02rem; + transform: rotate(0deg); + transition: transform 0.2s ease; +} + +.sy-rside + .localtoc + > ul + > li + > button.uplt-toc-toggle[aria-expanded="true"]::before { + transform: rotate(90deg); +} + +.sy-rside .localtoc > ul > li > button.uplt-toc-toggle:hover { + color: var(--sy-c-link); + background: var(--sy-c-surface); +} + +.globaltoc > ul a.current, +.localtoc > ul li.active > a { + color: var(--sy-c-link) !important; +} + +.globaltoc > ul a:hover, +.localtoc > ul li > a:hover { + color: var(--sy-c-link-hover) !important; +} + +/* Left TOC: subtle colored section markers */ +.globaltoc li.toctree-l1 { + border-left: 3px solid var(--uplt-color-border-muted); + padding-left: 0.45rem; + border-radius: 0.2rem; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 1) { + border-left-color: #7fb3ad; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 2) { + border-left-color: #8fb6cc; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 3) { + border-left-color: #b4b6d8; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 4) { + border-left-color: #c0b7ce; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 5) { + border-left-color: #b7c8a7; +} + +.globaltoc li.toctree-l1:nth-of-type(6n + 6) { + border-left-color: #d2b8a4; +} + +/* API pages: increase visual separation for summary and details blocks */ +.sy-main .yue [id^="api-"] p.rubric { + margin-top: 1.25rem; + margin-bottom: 0.45rem; + padding: 0.35rem 0.6rem; + border-left: 3px solid var(--uplt-color-accent); + background: linear-gradient( + 90deg, + var(--uplt-color-accent-grad-start), + var(--uplt-color-accent-grad-end) + ); + border-radius: 0.2rem; +} + +.sy-main .yue [id^="api-"] dl.py { + margin-top: 0.85rem; + padding: 0.6rem 0.8rem; + border: 1px solid var(--uplt-color-border-muted); + border-radius: 0.35rem; + background: var(--uplt-color-panel-bg); +} + +.sy-main .yue [id^="api-"] dl.py.attribute, +.sy-main .yue [id^="api-"] dl.py.data { + border-left: 3px solid #2f7a4a; +} + +.sy-main .yue [id^="api-"] dl.py.method, +.sy-main .yue [id^="api-"] dl.py.function { + border-left: 3px solid #1f6d9c; +} + +.sy-main .yue [id^="api-"] dl.py.class, +.sy-main .yue [id^="api-"] dl.py.exception { + border-left: 3px solid #7a4a1f; +} + +.sy-main .yue [id^="api-"] dl.py dt { + padding: 0.25rem 0.35rem; + border-radius: 0.2rem; + background: var(--uplt-color-sidebar-bg); +} diff --git a/ultraplot_theme/static/ultraplot-docs.js b/ultraplot_theme/static/ultraplot-docs.js new file mode 100644 index 0000000..53cd1ee --- /dev/null +++ b/ultraplot_theme/static/ultraplot-docs.js @@ -0,0 +1,735 @@ +function getDirectChildByTag(el, tagName) { + return ( + Array.from(el.children).find((child) => child.tagName === tagName) || null + ); +} + +function getDirectToggleButton(item) { + return ( + Array.from(item.children).find( + (child) => + child.tagName === "BUTTON" && + child.classList.contains("uplt-toc-toggle"), + ) || null + ); +} + +function setTocItemExpanded(item, expanded) { + const childList = getDirectChildByTag(item, "UL"); + const toggle = getDirectToggleButton(item); + if (!childList || !toggle) return; + childList.hidden = !expanded; + childList.style.display = expanded ? "" : "none"; + toggle.setAttribute("aria-expanded", expanded ? "true" : "false"); + toggle.classList.toggle("is-expanded", expanded); + toggle.textContent = ""; +} + +function localtocHasMeaningfulEntries(localtoc) { + const links = Array.from(localtoc.querySelectorAll("a.reference.internal")); + return links.some((link) => { + const href = (link.getAttribute("href") || "").trim(); + const text = (link.textContent || "").trim(); + return text && href && href !== "#"; + }); +} + +function getCodeDetailsBlocks() { + return Array.from(document.querySelectorAll("details.uplt-code-details")); +} + +function initScrollChromeFade() { + const topBar = document.querySelector(".sy-head"); + const leftBar = document.querySelector(".sy-lside"); + if (!topBar && !leftBar) return; + + let lastY = window.scrollY || 0; + let ticking = false; + const minDelta = 6; + const revealThreshold = 96; + + const setHidden = (hidden) => { + document.documentElement.classList.toggle("uplt-chrome-hidden", hidden); + }; + + const update = () => { + const y = window.scrollY || 0; + const delta = y - lastY; + const expanded = (document.body.getAttribute("data-expanded") || "").trim(); + const isMobileMenuOpen = + expanded.includes("head-nav") || + expanded.includes("lside") || + expanded.includes("rside"); + + if (window.innerWidth < 768 || isMobileMenuOpen || y < revealThreshold) { + setHidden(false); + } else if (delta > minDelta) { + setHidden(true); + } else if (delta < -minDelta) { + setHidden(false); + } + + lastY = y; + ticking = false; + }; + + window.addEventListener( + "scroll", + () => { + if (!ticking) { + window.requestAnimationFrame(update); + ticking = true; + } + }, + { passive: true }, + ); + window.addEventListener("resize", update, { passive: true }); + update(); +} + +function syncRightTocCodeButtons(localtoc) { + if (!localtoc) return; + const blocks = getCodeDetailsBlocks(); + let codeControls = + Array.from(localtoc.children).find( + (child) => + child.classList && child.classList.contains("uplt-code-controls"), + ) || null; + if (!blocks.length) { + if (codeControls) { + codeControls.remove(); + } + return; + } + + if (!codeControls) { + codeControls = document.createElement("div"); + codeControls.className = "uplt-code-controls"; + localtoc.appendChild(codeControls); + } + + let collapseCodeBtn = codeControls.querySelector(".uplt-code-collapse"); + if (!collapseCodeBtn) { + collapseCodeBtn = document.createElement("button"); + collapseCodeBtn.type = "button"; + collapseCodeBtn.className = "uplt-toc-btn uplt-code-btn uplt-code-collapse"; + collapseCodeBtn.addEventListener("click", function () { + const codeBlocks = getCodeDetailsBlocks(); + const allCollapsed = codeBlocks.length > 0 && codeBlocks.every((block) => !block.open); + if (allCollapsed) { + codeBlocks.forEach((block) => { + block.open = true; + }); + } else { + codeBlocks.forEach((block) => { + block.open = false; + }); + } + updateCodeButtonLabels(); + }); + codeControls.appendChild(collapseCodeBtn); + } + + const updateCodeButtonLabels = () => { + const codeBlocks = getCodeDetailsBlocks(); + const allCollapsed = codeBlocks.length > 0 && codeBlocks.every((block) => !block.open); + collapseCodeBtn.textContent = allCollapsed ? "Show all code" : "Collapse code"; + }; + + blocks.forEach((block) => { + if (block.dataset.upltCodeSync !== "1") { + block.addEventListener("toggle", updateCodeButtonLabels); + block.dataset.upltCodeSync = "1"; + } + }); + updateCodeButtonLabels(); +} + +function initShibuyaRightToc() { + const shibuyaRightToc = document.querySelector(".sy-rside"); + if (!shibuyaRightToc) return; + const path = window.location.pathname || ""; + const isGalleryIndexPage = + /\/gallery\/?$/.test(path) || + /\/gallery\/index(?:_new)?\.html$/.test(path); + const forceHideRightToc = + document.body.classList.contains("no-right-toc") || + isGalleryIndexPage || + !!document.querySelector(".sphx-glr-thumbcontainer") || + !!document.querySelector(".sphx-glr-thumbnails"); + if (forceHideRightToc) { + shibuyaRightToc.style.display = "none"; + const overlay = document.querySelector(".rside-overlay"); + if (overlay) overlay.style.display = "none"; + return; + } + + const localtoc = shibuyaRightToc.querySelector(".localtoc"); + if (!localtoc) return; + + const overlay = document.querySelector(".rside-overlay"); + if (!localtocHasMeaningfulEntries(localtoc)) { + shibuyaRightToc.style.display = "none"; + if (overlay) overlay.style.display = "none"; + return; + } + shibuyaRightToc.style.display = ""; + if (overlay) overlay.style.display = ""; + + const storageKey = "uplt.rside.hidden"; + const setRightTocHidden = (hidden) => { + document.body.classList.toggle("uplt-rside-hidden", hidden); + try { + localStorage.setItem(storageKey, hidden ? "1" : "0"); + } catch (_err) { + // Ignore storage errors in private/incognito environments. + } + }; + + if (!document.body.dataset.upltRsideStateInit) { + let restoreHidden = false; + try { + restoreHidden = localStorage.getItem(storageKey) === "1"; + } catch (_err) { + restoreHidden = false; + } + setRightTocHidden(restoreHidden); + document.body.dataset.upltRsideStateInit = "1"; + } + + let showBtn = document.querySelector(".uplt-rside-show"); + if (!showBtn) { + showBtn = document.createElement("button"); + showBtn.type = "button"; + showBtn.className = "uplt-rside-show"; + showBtn.textContent = "Show contents"; + showBtn.setAttribute("aria-label", "Show right table of contents"); + showBtn.addEventListener("click", function () { + setRightTocHidden(false); + }); + document.body.appendChild(showBtn); + } + + const topList = getDirectChildByTag(localtoc, "UL"); + if (!topList) return; + const topItems = Array.from(topList.children).filter( + (node) => node.tagName === "LI", + ); + const collapsibleItems = []; + const currentHash = (window.location.hash || "").trim(); + + topItems.forEach((item) => { + const link = + Array.from(item.children).find( + (child) => + child.tagName === "A" && + child.classList.contains("reference") && + child.classList.contains("internal"), + ) || null; + const childList = getDirectChildByTag(item, "UL"); + if (!link || !childList) return; + + item.classList.add("uplt-toc-collapsible"); + let toggle = getDirectToggleButton(item); + if (!toggle) { + toggle = document.createElement("button"); + toggle.type = "button"; + toggle.className = "uplt-toc-toggle"; + toggle.setAttribute("aria-label", "Toggle section"); + toggle.textContent = ""; + toggle.addEventListener("click", function () { + const expanded = toggle.getAttribute("aria-expanded") === "true"; + setTocItemExpanded(item, !expanded); + }); + item.insertBefore(toggle, link); + } + + const hashInChildren = + currentHash && + Array.from(childList.querySelectorAll("a.reference.internal")).some( + (a) => (a.getAttribute("href") || "").trim() === currentHash, + ); + const hashOnTop = currentHash && (link.getAttribute("href") || "") === currentHash; + if (!toggle.hasAttribute("aria-expanded")) { + setTocItemExpanded(item, !!(hashOnTop || hashInChildren)); + } else if (hashOnTop || hashInChildren) { + setTocItemExpanded(item, true); + } + + collapsibleItems.push(item); + }); + + let controls = + Array.from(localtoc.children).find( + (child) => + child.classList && child.classList.contains("uplt-toc-controls"), + ) || null; + if (!controls) { + const controls = document.createElement("div"); + controls.className = "uplt-toc-controls"; + + const collapseBtn = document.createElement("button"); + collapseBtn.type = "button"; + collapseBtn.className = "uplt-toc-btn"; + collapseBtn.textContent = "Collapse"; + collapseBtn.addEventListener("click", function () { + collapsibleItems.forEach((item) => setTocItemExpanded(item, false)); + }); + + const expandBtn = document.createElement("button"); + expandBtn.type = "button"; + expandBtn.className = "uplt-toc-btn"; + expandBtn.textContent = "Expand"; + expandBtn.addEventListener("click", function () { + collapsibleItems.forEach((item) => setTocItemExpanded(item, true)); + }); + + const hideBtn = document.createElement("button"); + hideBtn.type = "button"; + hideBtn.className = "uplt-toc-btn uplt-toc-btn-hide"; + hideBtn.textContent = "Hide"; + hideBtn.addEventListener("click", function () { + setRightTocHidden(true); + }); + + controls.appendChild(collapseBtn); + controls.appendChild(expandBtn); + controls.appendChild(hideBtn); + localtoc.insertBefore(controls, topList); + syncRightTocCodeButtons(localtoc); + return; + } + syncRightTocCodeButtons(localtoc); +} + +document.addEventListener("DOMContentLoaded", function () { + initScrollChromeFade(); + + if (document.querySelector(".sphx-glr-thumbcontainer")) { + document.body.classList.add("no-right-toc"); + } + + // Shibuya theme: right TOC controls and collapsible sub-sections. + initShibuyaRightToc(); + window.addEventListener("hashchange", initShibuyaRightToc); + + // Check if current page has opted out of the TOC + if (document.body.classList.contains("no-right-toc")) { + return; + } + + const isWhatsNewPage = + document.body.classList.contains("whats_new") || + window.location.pathname.endsWith("/whats_new.html") || + window.location.pathname.endsWith("/whats_new/"); + + if (isWhatsNewPage) { + const nav = document.querySelector(".wy-menu-vertical"); + if (nav) { + nav.querySelectorAll('li[class*="toctree-l"]').forEach((item) => { + if (!item.className.match(/toctree-l1/)) { + item.remove(); + } + }); + nav.querySelectorAll('a[href*="#"]').forEach((link) => { + const li = link.closest("li"); + if (li && !li.className.match(/toctree-l1/)) { + li.remove(); + } + }); + } + } + + const content = document.querySelector(".rst-content"); + if (!content) return; + + const isWhatsNew = isWhatsNewPage; + const headerSelector = isWhatsNew ? "h2" : "h1:not(.document-title), h2, h3"; + + // Find all headers in the main content + const headers = Array.from(content.querySelectorAll(headerSelector)).filter( + (header) => !header.classList.contains("no-toc"), + ); + + // Only create TOC if there are headers + if (headers.length === 0) return; + + // Create TOC container + const toc = document.createElement("div"); + toc.className = "right-toc"; + toc.innerHTML = + '
' + + '
On This Page
' + + '
' + + '' + + "
" + + '
'; + + const tocList = toc.querySelector(".right-toc-list"); + const tocContent = toc.querySelector(".right-toc-content"); + const tocToggleBtn = toc.querySelector(".right-toc-toggle-btn"); + + // Set up the toggle button + tocToggleBtn.addEventListener("click", function () { + if (tocContent.style.display === "none") { + tocContent.style.display = "block"; + tocToggleBtn.textContent = "−"; + toc.classList.remove("right-toc-collapsed"); + localStorage.setItem("tocVisible", "true"); + } else { + tocContent.style.display = "none"; + tocToggleBtn.textContent = "+"; + toc.classList.add("right-toc-collapsed"); + localStorage.setItem("tocVisible", "false"); + } + }); + + // Check saved state + if (localStorage.getItem("tocVisible") === "false") { + tocContent.style.display = "none"; + tocToggleBtn.textContent = "+"; + toc.classList.add("right-toc-collapsed"); + } + + // Track used IDs to avoid duplicates + const usedIds = new Set(); + + // Get all existing IDs in the document + document.querySelectorAll("[id]").forEach((el) => { + usedIds.add(el.id); + }); + + // Generate unique IDs for headers that need them + headers.forEach((header, index) => { + // If header already has an ID, keep it + if (header.id) { + usedIds.add(header.id); + return; + } + + // Create a slug from the header text + let headerText = header.textContent || ""; + + // Clean the text (remove icons and special characters) + headerText = headerText.replace(/\s*\uf0c1\s*$/, ""); + headerText = headerText.replace(/\s*[¶§#†‡]\s*$/, ""); + headerText = headerText.trim(); + + let slug = headerText + .toLowerCase() + .replace(/[^\w\s-]/g, "") + .replace(/\s+/g, "-") + .replace(/--+/g, "-") + .trim(); + + // Make sure slug is not empty + if (!slug) { + slug = "section"; + } + + // Ensure the ID is unique + let uniqueId = slug; + let counter = 1; + + while (usedIds.has(uniqueId)) { + uniqueId = `${slug}-${counter}`; + counter++; + } + + // Set the unique ID and add to our tracking set + header.id = uniqueId; + usedIds.add(uniqueId); + }); + + if (isWhatsNew) { + headers.forEach((header) => { + const tag = header.tagName.toLowerCase(); + const rawText = header.textContent || ""; + const cleanText = rawText + .replace(/\s*\uf0c1\s*$/, "") + .replace(/\s*[¶§#†‡]\s*$/, "") + .trim(); + const isReleaseHeading = tag === "h2" && /^v\d/i.test(cleanText || ""); + + if (isReleaseHeading) { + const item = document.createElement("li"); + const link = document.createElement("a"); + + link.href = "#" + header.id; + + link.textContent = cleanText; + link.className = "right-toc-link right-toc-level-h1"; + item.appendChild(link); + tocList.appendChild(item); + } + }); + } else { + // Add entries for each header + headers.forEach((header) => { + const item = document.createElement("li"); + const link = document.createElement("a"); + + link.href = "#" + header.id; + + // Get clean text without icons + let headerText = header.textContent || ""; + headerText = headerText.replace(/\s*\uf0c1\s*$/, ""); + headerText = headerText.replace(/\s*[¶§#†‡]\s*$/, ""); + + link.textContent = headerText.trim(); + link.className = + "right-toc-link right-toc-level-" + header.tagName.toLowerCase(); + + item.appendChild(link); + tocList.appendChild(item); + }); + } + + // Add TOC to page + document.body.appendChild(toc); + + // Add active link highlighting + const tocLinks = document.querySelectorAll(".right-toc-link"); + const headerElements = Array.from(headers); + + if (tocLinks.length > 0 && headerElements.length > 0) { + // Highlight the current section on scroll + window.addEventListener( + "scroll", + debounce(function () { + let currentSection = null; + let smallestDistanceFromTop = Infinity; + + headerElements.forEach((header) => { + const distance = Math.abs(header.getBoundingClientRect().top); + if (distance < smallestDistanceFromTop) { + smallestDistanceFromTop = distance; + currentSection = header.id; + } + }); + + tocLinks.forEach((link) => { + link.classList.remove("active"); + if (link.getAttribute("href") === `#${currentSection}`) { + link.classList.add("active"); + } + }); + }, 100), + ); + } +}); + +document.addEventListener("DOMContentLoaded", function () { + const wrapWithCodeToggle = (block) => { + if (!block || !block.parentNode) return; + if (block.closest("details.uplt-code-details")) return; + const details = document.createElement("details"); + details.className = "uplt-code-details"; + const summary = document.createElement("summary"); + summary.className = "uplt-code-summary"; + summary.textContent = "Show code"; + details.appendChild(summary); + block.parentNode.insertBefore(details, block); + details.appendChild(block); + details.open = false; + details.addEventListener("toggle", function () { + summary.textContent = details.open ? "Hide code" : "Show code"; + }); + }; + + // Gallery example pages: collapse source code blocks by default. + const galleryExampleCodeBlocks = Array.from( + document.querySelectorAll( + "section.sphx-glr-example-title div.highlight-Python.notranslate", + ), + ); + galleryExampleCodeBlocks.forEach((block) => { + wrapWithCodeToggle(block); + }); + + // Notebook-style tutorial pages: collapse input code cells by default. + const notebookInputBlocks = Array.from( + document.querySelectorAll("div.nbinput.docutils.container"), + ); + notebookInputBlocks.forEach((block) => { + wrapWithCodeToggle(block); + }); + + // Re-sync right TOC controls now that code wrappers exist. + initShibuyaRightToc(); + + const navLinks = document.querySelectorAll( + ".wy-menu-vertical a.reference.internal", + ); + navLinks.forEach((link) => { + const href = link.getAttribute("href") || ""; + const isGalleryLink = href.includes("gallery/"); + const isGalleryIndex = href.includes("gallery/index"); + if (isGalleryLink && !isGalleryIndex) { + const item = link.closest("li"); + if (item) { + item.remove(); + } + } + }); + + const galleryRoot = document.querySelector(".sphx-glr-thumbcontainer"); + if (galleryRoot) { + const gallerySections = [ + "layouts", + "legends-and-colorbars", + "geoaxes", + "plot-types", + "colors-and-cycles", + ]; + gallerySections.forEach((sectionId) => { + const heading = document.querySelector( + `section#${sectionId} .gallery-section-header`, + ); + if (heading) { + heading.classList.add("no-toc"); + } + }); + } + + const thumbContainers = Array.from( + document.querySelectorAll(".sphx-glr-thumbcontainer"), + ); + if (thumbContainers.length < 6) { + return; + } + + const topicList = [ + { id: "layouts", label: "Layouts", slug: "layouts" }, + { + id: "legends_colorbars", + label: "Legends & Colorbars", + slug: "legends-colorbars", + }, + { id: "geo", label: "GeoAxes", slug: "geoaxes" }, + { id: "plot_types", label: "Plot Types", slug: "plot-types" }, + { id: "colors", label: "Colors", slug: "colors" }, + ]; + const topicMap = Object.fromEntries( + topicList.map((topic) => [topic.id, topic]), + ); + const originalThumbnails = new Set(); + + function getTopicInfo(thumb) { + const link = thumb.querySelector("a.reference.internal"); + if (!link) { + return { label: "Other", slug: "other" }; + } + const href = link.getAttribute("href") || ""; + const path = new URL(href, window.location.href).pathname; + const match = path.match(/\/gallery\/([^/]+)\//); + const key = match ? match[1] : ""; + return topicMap[key] || { label: "Other", slug: "other" }; + } + + thumbContainers.forEach((thumb) => { + const info = getTopicInfo(thumb); + thumb.dataset.topic = info.slug; + const group = thumb.closest(".sphx-glr-thumbnails"); + if (group) { + originalThumbnails.add(group); + } + }); + + const topics = topicList.filter((topic) => + thumbContainers.some((thumb) => thumb.dataset.topic === topic.slug), + ); + + if (topics.length === 0) { + return; + } + + const firstGroup = thumbContainers[0].closest(".sphx-glr-thumbnails"); + const parent = + (firstGroup && firstGroup.parentNode) || + document.querySelector(".rst-content"); + if (!parent) { + return; + } + + const controls = document.createElement("div"); + controls.className = "gallery-filter-controls"; + + const filterBar = document.createElement("div"); + filterBar.className = "gallery-filter-bar"; + + function makeButton(label, slug) { + const button = document.createElement("button"); + button.type = "button"; + button.className = "gallery-filter-button"; + button.textContent = label; + button.dataset.topic = slug; + return button; + } + + const buttons = [ + makeButton("All", "all"), + ...topics.map((topic) => makeButton(topic.label, topic.slug)), + ]; + + const counts = {}; + thumbContainers.forEach((thumb) => { + const topic = thumb.dataset.topic || "other"; + counts[topic] = (counts[topic] || 0) + 1; + }); + counts.all = thumbContainers.length; + + buttons.forEach((button) => { + const topic = button.dataset.topic; + const count = counts[topic] || 0; + button.textContent = `${button.textContent} (${count})`; + filterBar.appendChild(button); + }); + + const unified = document.createElement("div"); + unified.className = "sphx-glr-thumbnails gallery-unified"; + thumbContainers.forEach((thumb) => unified.appendChild(thumb)); + + controls.appendChild(filterBar); + controls.appendChild(unified); + parent.insertBefore(controls, firstGroup); + + originalThumbnails.forEach((group) => { + group.classList.add("gallery-section-hidden"); + }); + document + .querySelectorAll(".gallery-section-header, .gallery-section-description") + .forEach((node) => { + node.classList.add("gallery-section-hidden"); + }); + document.body.classList.add("gallery-filter-active"); + + function setFilter(slug) { + buttons.forEach((button) => { + button.classList.toggle("is-active", button.dataset.topic === slug); + }); + thumbContainers.forEach((thumb) => { + const matches = slug === "all" || thumb.dataset.topic === slug; + thumb.style.display = matches ? "" : "none"; + }); + } + + buttons.forEach((button) => { + button.addEventListener("click", () => { + setFilter(button.dataset.topic); + }); + }); + + setFilter("all"); +}); + +// Debounce function to limit scroll event firing +function debounce(func, wait) { + let timeout; + return function () { + const context = this; + const args = arguments; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait); + }; +} From 5d63451a34afe409da0ab4025fcf03acbf0bd71c Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sat, 14 Feb 2026 06:51:10 +1000 Subject: [PATCH 2/4] Right TOC: place Hide next to title and only show Collapse/Expand for collapsible sections --- ultraplot_theme/static/ultraplot-docs.css | 28 +++++++++- ultraplot_theme/static/ultraplot-docs.js | 65 ++++++++++++++++------- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/ultraplot_theme/static/ultraplot-docs.css b/ultraplot_theme/static/ultraplot-docs.css index d181890..000c2a3 100644 --- a/ultraplot_theme/static/ultraplot-docs.css +++ b/ultraplot_theme/static/ultraplot-docs.css @@ -814,6 +814,28 @@ body.wy-body-for-nav border-bottom: 1px solid var(--sy-c-divider); } +.sy-rside .localtoc > .uplt-toc-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.45rem; + margin: 0 0 0.5rem 0; + padding: 0 0 0.45rem 0; + border-bottom: 1px solid var(--sy-c-divider); +} + +.sy-rside .localtoc > .uplt-toc-head > h3 { + color: var(--sy-c-light); + font-family: var(--sy-f-heading); + font-size: 0.86rem; + font-weight: 500; + letter-spacing: 0.4px; + text-transform: uppercase; + margin: 0; + padding: 0; + border: 0; +} + .sy-rside .localtoc > ul li > a { display: block; padding: 0.08rem 0.2rem 0.08rem 0.45rem; @@ -828,7 +850,7 @@ body.wy-body-for-nav .sy-rside .localtoc > .uplt-toc-controls { display: flex; gap: 0.35rem; - margin: 0.35rem 0 0.75rem 0; + margin: 0 0 0.75rem 0; } .sy-rside .localtoc > .uplt-code-controls { @@ -865,6 +887,10 @@ body.wy-body-for-nav box-shadow 0.2s ease; } +.sy-rside .localtoc > .uplt-toc-head .uplt-toc-btn-hide { + padding: 0.14rem 0.42rem; +} + .sy-rside .localtoc .uplt-toc-btn:hover { border-color: var(--sy-c-link); color: var(--sy-c-link); diff --git a/ultraplot_theme/static/ultraplot-docs.js b/ultraplot_theme/static/ultraplot-docs.js index 53cd1ee..4bd28d2 100644 --- a/ultraplot_theme/static/ultraplot-docs.js +++ b/ultraplot_theme/static/ultraplot-docs.js @@ -212,6 +212,32 @@ function initShibuyaRightToc() { const topList = getDirectChildByTag(localtoc, "UL"); if (!topList) return; + + let headRow = + Array.from(localtoc.children).find( + (child) => child.classList && child.classList.contains("uplt-toc-head"), + ) || null; + const directHeading = getDirectChildByTag(localtoc, "H3"); + if (!headRow && directHeading) { + headRow = document.createElement("div"); + headRow.className = "uplt-toc-head"; + localtoc.insertBefore(headRow, directHeading); + headRow.appendChild(directHeading); + } + if (headRow) { + let hideBtn = headRow.querySelector(".uplt-toc-btn-hide"); + if (!hideBtn) { + hideBtn = document.createElement("button"); + hideBtn.type = "button"; + hideBtn.className = "uplt-toc-btn uplt-toc-btn-hide"; + hideBtn.textContent = "Hide"; + hideBtn.addEventListener("click", function () { + setRightTocHidden(true); + }); + headRow.appendChild(hideBtn); + } + } + const topItems = Array.from(topList.children).filter( (node) => node.tagName === "LI", ); @@ -264,41 +290,42 @@ function initShibuyaRightToc() { (child) => child.classList && child.classList.contains("uplt-toc-controls"), ) || null; + if (!collapsibleItems.length) { + if (controls) controls.remove(); + syncRightTocCodeButtons(localtoc); + return; + } + if (!controls) { - const controls = document.createElement("div"); + controls = document.createElement("div"); controls.className = "uplt-toc-controls"; + localtoc.insertBefore(controls, topList); + } - const collapseBtn = document.createElement("button"); + let collapseBtn = controls.querySelector(".uplt-toc-btn-collapse"); + if (!collapseBtn) { + collapseBtn = document.createElement("button"); collapseBtn.type = "button"; - collapseBtn.className = "uplt-toc-btn"; + collapseBtn.className = "uplt-toc-btn uplt-toc-btn-collapse"; collapseBtn.textContent = "Collapse"; collapseBtn.addEventListener("click", function () { collapsibleItems.forEach((item) => setTocItemExpanded(item, false)); }); + controls.appendChild(collapseBtn); + } - const expandBtn = document.createElement("button"); + let expandBtn = controls.querySelector(".uplt-toc-btn-expand"); + if (!expandBtn) { + expandBtn = document.createElement("button"); expandBtn.type = "button"; - expandBtn.className = "uplt-toc-btn"; + expandBtn.className = "uplt-toc-btn uplt-toc-btn-expand"; expandBtn.textContent = "Expand"; expandBtn.addEventListener("click", function () { collapsibleItems.forEach((item) => setTocItemExpanded(item, true)); }); - - const hideBtn = document.createElement("button"); - hideBtn.type = "button"; - hideBtn.className = "uplt-toc-btn uplt-toc-btn-hide"; - hideBtn.textContent = "Hide"; - hideBtn.addEventListener("click", function () { - setRightTocHidden(true); - }); - - controls.appendChild(collapseBtn); controls.appendChild(expandBtn); - controls.appendChild(hideBtn); - localtoc.insertBefore(controls, topList); - syncRightTocCodeButtons(localtoc); - return; } + syncRightTocCodeButtons(localtoc); } From e34273915467e613a996abed042070a13b6e93a7 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sat, 14 Feb 2026 21:55:36 +1000 Subject: [PATCH 3/4] Theme: use full-width notebook output panels with theme-aware contrast --- ultraplot_theme/static/ultraplot-docs.css | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ultraplot_theme/static/ultraplot-docs.css b/ultraplot_theme/static/ultraplot-docs.css index 000c2a3..6028770 100644 --- a/ultraplot_theme/static/ultraplot-docs.css +++ b/ultraplot_theme/static/ultraplot-docs.css @@ -24,6 +24,8 @@ --uplt-color-accent-grad-end: rgba(15, 118, 110, 0.02); --uplt-color-accent-shadow-strong: rgba(15, 118, 110, 0.2); --uplt-color-accent-shadow-soft: rgba(15, 118, 110, 0.1); + --uplt-color-plot-panel-bg: #f2f4f6; + --uplt-color-plot-panel-border: #d9dde2; /* Scrollbar */ --uplt-color-scrollbar-track: #f1f1f1; @@ -281,6 +283,8 @@ html.dark-theme, --uplt-color-accent-grad-end: rgba(26, 168, 154, 0.04); --uplt-color-accent-shadow-strong: rgba(26, 168, 154, 0.26); --uplt-color-accent-shadow-soft: rgba(26, 168, 154, 0.14); + --uplt-color-plot-panel-bg: #1b2024; + --uplt-color-plot-panel-border: #313940; --sy-c-link: #58d5c9; --sy-c-link-hover: #84e8df; --uplt-color-panel-bg: #202020; @@ -782,12 +786,35 @@ body.wy-body-for-nav } .yue div.nbinput.container > div.input_area, -.yue div.nboutput.container > div.output_area, .yue .highlight, .yue .highlight pre { background-color: var(--code-block-background) !important; } +.yue div.nboutput.container { + display: block !important; +} + +.yue div.nboutput.container > div.prompt { + display: none !important; +} + +.yue div.nboutput.container > div.output_area { + background-color: var(--uplt-color-plot-panel-bg) !important; + border: 1px solid var(--uplt-color-plot-panel-border); + border-radius: 0.45rem; + padding: 0.4rem; + width: 100%; + max-width: 100%; + margin: 0.25rem 0 !important; + overflow: visible; +} + +.yue div.nboutput.container > div.output_area > * { + margin-left: auto; + margin-right: auto; +} + /* Shibuya right TOC: collapse sub-H1 headings under each H1 section */ .sy-rside .localtoc { margin-left: 0.55rem; From c9430c1d043c3e1dd729627f81d08c102f9d10e9 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sat, 14 Feb 2026 22:02:33 +1000 Subject: [PATCH 4/4] Theme: apply green gradient styling to header brand text --- ultraplot_theme/static/ultraplot-docs.css | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ultraplot_theme/static/ultraplot-docs.css b/ultraplot_theme/static/ultraplot-docs.css index 6028770..d30a8f0 100644 --- a/ultraplot_theme/static/ultraplot-docs.css +++ b/ultraplot_theme/static/ultraplot-docs.css @@ -88,6 +88,22 @@ letter-spacing: 0.065em; text-transform: uppercase; line-height: 1.25; + color: #138a73; + background-image: linear-gradient( + 90deg, + #0f6d5f 0%, + #11806b 12%, + #139378 24%, + #15a685 36%, + #17b793 48%, + #19a988 60%, + #1a9c7d 72%, + #1c8f73 84%, + #1e8268 100% + ); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } @media (min-width: 768px) { @@ -238,6 +254,24 @@ html.dark-theme .sy-head .sy-head-links a, opacity: 0.96; } +html.dark .sy-head .sy-head-brand strong, +html.dark-theme .sy-head .sy-head-brand strong, +[data-color-mode="dark"] .sy-head .sy-head-brand strong { + color: #6ee0c8; + background-image: linear-gradient( + 90deg, + #47cdb2 0%, + #53d6bc 12%, + #5fdec6 24%, + #6be6d0 36%, + #77edd9 48%, + #6be6d0 60%, + #5fdec6 72%, + #53d6bc 84%, + #47cdb2 100% + ); +} + html.dark .sy-head .sy-head-links a:hover, html.dark-theme .sy-head .sy-head-links a:hover, [data-color-mode="dark"] .sy-head .sy-head-links a:hover {