diff --git a/.gitignore b/.gitignore index a6c8d35a05..473113af7e 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,9 @@ bun.lockb # files from the gallery screenshots docs/_static/gallery +# File copied during build +code-cells-myst-nb.mystnb.ipynb + # Our site profile tests profile.svg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a78d98185b..08cc79c404 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,8 +63,9 @@ repos: args: [--fix] additional_dependencies: # stylelint itself needs to be here when using additional_dependencies. - - stylelint@16.5.0 - - stylelint-config-standard-scss@13.1.0 + - stylelint + - stylelint-scss + - stylelint-config-standard-scss - repo: "https://github.com/pre-commit/pre-commit-hooks" rev: v5.0.0 diff --git a/.stylelintrc.json b/.stylelintrc.json index e43bfb5e59..d7076bd602 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,5 +1,6 @@ { "extends": "stylelint-config-standard-scss", + "plugins": ["stylelint-scss"], "rules": { "selector-class-pattern": null, "no-descending-specificity": null, diff --git a/docs/conf.py b/docs/conf.py index a0b9f8b8b3..c4bb120d56 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,8 +5,8 @@ https://www.sphinx-doc.org/en/master/usage/configuration.html """ -# -- Path setup -------------------------------------------------------------- import os +import shutil import sys from pathlib import Path @@ -18,7 +18,10 @@ import pydata_sphinx_theme +# -- Path setup -------------------------------------------------------------- + sys.path.append(str(Path(".").resolve())) +HERE = Path(__file__).parent.resolve() # -- Project information ----------------------------------------------------- @@ -40,11 +43,11 @@ "sphinx_design", "sphinx_copybutton", "autoapi.extension", - # custom extentions + # custom extensions (extensions defined in this repo) "_extension.gallery_directive", "_extension.component_directive", # For extension examples and demos - "myst_parser", + "myst_nb", # includes myst_parser "ablog", "jupyter_sphinx", "sphinxcontrib.youtube", @@ -55,7 +58,17 @@ "sphinx_favicon", ] -jupyterlite_config = "jupyterlite_config.json" +# This is a hack to use both nbsphinx and myst_nb Sphinx extensions in the same +# Sphinx build. If we want a notebook to be processed by the myst_nb Sphinx +# extension, we give it a .mystnb.ipynb suffix instead of just .ipynb. Here we +# map each suffix to the desired Sphinx extension. We also have to map .md and +# .rst at the same time or else the build fails. +source_suffix = { + ".rst": "restructuredtext", + ".md": "myst-nb", + ".mystnb.ipynb": "myst-nb", + ".ipynb": "jupyter_notebook", # this is the name of nbsphinx's parser +} # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -67,6 +80,28 @@ intersphinx_mapping = {"sphinx": ("https://www.sphinx-doc.org/en/master", None)} +# -- MyST-NB ---------------------------------------------------------------- + +# Even though we already map ".mystnb" in `source_suffix` above, we also have to +# set this configuration variable for MyST-NB to process it correctly. +nb_custom_formats = { + ".mystnb.ipynb": [ + "nbformat.reads", + {"as_version": 4}, + True, # parse as CommonMark instead of MyST + ], +} + +# -- JupyterLite Sphinx ------------------------------------------------------ + +jupyterlite_config = "jupyterlite_config.json" + + +# We want .ipynb files in the repo to be handled by the notebook parsing +# extension (nbsphinx or MyST-NB), NOT JupyterLite. +jupyterlite_bind_ipynb_suffix = False + + # -- Sitemap ----------------------------------------------------------------- # ReadTheDocs has its own way of generating sitemaps, etc. @@ -344,6 +379,16 @@ def to_main(link: str) -> str: context["to_main"] = to_main +def copy_ipynb(app: Sphinx): + """Copy the example Jupyter notebook. + + One copy will be converted by nbsphinx, the other by MyST-NB. + """ + src = HERE / "examples" / "code-cells.ipynb" + dst = HERE / "examples" / "code-cells-myst-nb.mystnb.ipynb" + shutil.copyfile(src, dst) + + def setup(app: Sphinx) -> Dict[str, Any]: """Add custom configuration to sphinx app. @@ -352,6 +397,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: Returns: the 2 parallel parameters set to ``True``. """ + app.connect("builder-inited", copy_ipynb) app.connect("html-page-context", setup_to_main) return { diff --git a/docs/examples/code-cells.ipynb b/docs/examples/code-cells.ipynb new file mode 100644 index 0000000000..1a418a9538 --- /dev/null +++ b/docs/examples/code-cells.ipynb @@ -0,0 +1,1079 @@ +{ + "cells": [ + { + "cell_type": "raw", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "MIT License\n", + "\n", + "Copyright (c) 2015-2024 Matthias Geier\n", + "\n", + "Permission is hereby granted, free of charge, to any person obtaining a copy\n", + "of this software and associated documentation files (the \"Software\"), to deal\n", + "in the Software without restriction, including without limitation the rights\n", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", + "copies of the Software, and to permit persons to whom the Software is\n", + "furnished to do so, subject to the following conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be included in\n", + "all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM\n", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", + "THE SOFTWARE.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "# Code Cells (MyST-NB)\n", + "This page demonstrates a notebook converted with MyST-NB so you can see how it looks with the PyData Sphinx theme.\n", + "Note: it should look basically the same [converted with nbsphinx](code-cells.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "# Code Cells (nbsphinx)\n", + "This page demonstrates a notebook converted with nbsphinx, so you can see how it looks with the PyData Sphinx theme.\n", + "Note: it should look basically the same [converted with MyST-NB](code-cells-myst-nb.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This document is a modified copy of an example notebook in the nbsphinx docs.\n", + "The [original source](https://github.com/spatialaudio/nbsphinx/blob/9ee9da673b08e9db99ae1475fc83db6a62683e97/doc/code-cells.ipynb)\n", + "is in the nbsphinx GitHub repo." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code, Output, Streams\n", + "\n", + "An empty code cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Two empty lines:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Leading/trailing empty lines:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 2 empty lines before, 1 after\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A simple output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "6 * 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The standard output stream:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Hello, world!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Normal output + standard output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Hello, world!\")\n", + "6 * 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Normal output comes last. When both standard error and output are present, which one comes first can vary by kernel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "\n", + "print(\"I'll appear on the standard error stream\", file=sys.stderr)\n", + "print(\"I'll appear on the standard output stream\")\n", + "\"I'm the 'normal' output\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Note\n", + "\n", + "Using the IPython kernel, the order is actually mixed up,\n", + "see https://github.com/ipython/ipykernel/issues/280.\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Long input and output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\" # NOQA: E501" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Error / traceback" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "traceback # NOQA: F821" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special Display Formats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Local Image Files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Image\n", + "\n", + "\n", + "i = Image(filename=\"images/notebook_icon.png\")\n", + "i" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See also [SVG support for LaTeX](https://nbsphinx.readthedocs.io/en/0.9.7/markdown-cells.html#SVG-support-for-LaTeX)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import SVG\n", + "\n", + "\n", + "SVG(filename=\"images/python_logo.svg\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Image URLs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "PNG format" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://www.python.org/static/img/python-logo-large.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "PNG format, `embed=True`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://www.python.org/static/img/python-logo-large.png\", embed=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "SVG format" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Image(url=\"https://jupyter.org/assets/homepage/main-logo.svg\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Math" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Math\n", + "\n", + "\n", + "eq = Math(r\"\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) dx = f(x_0)\")\n", + "eq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(eq)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Latex\n", + "\n", + "\n", + "Latex(\n", + " r\"A lot of extra text \"\n", + " r\"to test wrapping \"\n", + " r\"when rendered to HTML. \"\n", + " r\"This is an equation: $a^2 + b^2 = c^2$\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some elements are repeated in the following equation to make it longer. This makes it easier to test whether the equation looks good on a mobile screen, for example, where the width of the equation does not fit within the width of the screen:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%latex\n", + "\\begin{equation}\n", + "\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) \\delta(x - x_0) dx = f(x_0)\n", + "\\end{equation}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Note\n", + "\n", + "Make sure to use at least version 0.1.6 of the `matplotlib-inline` package\n", + "(which is an automatic dependency of the `ipython` package).\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, the plots created with the \"inline\" backend have the wrong size.\n", + "More specifically, PNG plots (the default) will be slightly larger than SVG and PDF plots.\n", + "\n", + "This can be fixed easily by creating a file named `matplotlibrc`\n", + "(in the directory where your Jupyter notebooks live,\n", + "e.g. in this directory: [matplotlibrc](https://github.com/spatialaudio/nbsphinx/blob/9ee9da673b08e9db99ae1475fc83db6a62683e97/doc/matplotlibrc)\n", + "and adding the following line:\n", + "\n", + " figure.dpi: 96\n", + "\n", + "If you are using Git to manage your files,\n", + "don't forget to commit this local configuration file to your repository.\n", + "Different directories can have different local configurations.\n", + "If a given configuration should apply to multiple directories,\n", + "symbolic links can be created in each directory.\n", + "\n", + "For more details, see\n", + "[Default Values for Matplotlib's \"inline\" Backend](https://nbviewer.org/github/mgeier/python-audio/blob/master/plotting/matplotlib-inline-defaults.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, plots are generated in the PNG format.\n", + "In most cases,\n", + "it looks better\n", + "if SVG plots are used for HTML output\n", + "and PDF plots are used for LaTeX/PDF.\n", + "This can be achieved by setting\n", + "[nbsphinx_execute_arguments](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_execute_arguments)\n", + "in your `conf.py` file like this:\n", + "\n", + "```python\n", + "nbsphinx_execute_arguments = [\n", + " \"--InlineBackend.figure_formats={'svg', 'pdf'}\",\n", + "]\n", + "```\n", + " \n", + "In the following example, `nbsphinx` should use an SVG image in the HTML output\n", + "and a PDF image for LaTeX/PDF output\n", + "(other Jupyter clients like JupyterLab will still show the default PNG format)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['svg']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=[6, 3])\n", + "ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For comparison,\n", + "this is how it would look in PNG format ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['png']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and in `'png2x'` (a.k.a. `'retina'`) format:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['png2x']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pandas Dataframes\n", + "\n", + "[Pandas dataframes](https://pandas.pydata.org/pandas-docs/stable/user_guide/dsintro.html#dataframe)\n", + "should be displayed as nicely formatted HTML tables (if you are using HTML output)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame(\n", + " np.random.randint(0, 100, size=[10, 4]),\n", + " columns=[r\"$\\alpha$\", r\"$\\beta$\", r\"$\\gamma$\", r\"$\\delta$\"],\n", + ")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Xarray Datasets\n", + "\n", + "[Xarray datasets](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html)\n", + "should also be shown in their beautiful HTML representation.\n", + "LaTeX/PDF output will fall back to the plain text representation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import xarray as xr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = {}\n", + "for name in [\"t\", \"q\"]:\n", + " da = xr.DataArray(\n", + " [\n", + " [[11, 12, 13], [21, 22, 23], [31, 32, 33]],\n", + " [[14, 15, 16], [24, 25, 26], [34, 35, 36]],\n", + " ],\n", + " coords={\"level\": [\"500\", \"700\"], \"x\": [1, 2, 3], \"y\": [4, 5, 6]},\n", + " name=name,\n", + " attrs={\"param\": name},\n", + " )\n", + " a[name] = da\n", + "\n", + "ds = xr.Dataset(a, attrs={\"title\": \"Demo dataset\", \"year\": 2024})\n", + "ds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Markdown Content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Markdown" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "md = Markdown(\"\"\"\n", + "# Markdown\n", + "\n", + "It *should* show up as **formatted** text\n", + "with things like [links] and images.\n", + "\n", + "[links]: https://jupyter.org/\n", + "\n", + "![Jupyter notebook icon](images/notebook_icon.png)\n", + "\n", + "## Markdown Extensions\n", + "\n", + "There might also be mathematical equations like\n", + "$a^2 + b^2 = c^2$\n", + "and even tables:\n", + "\n", + "A | B | A and B\n", + "------|-------|--------\n", + "False | False | False\n", + "True | False | False\n", + "False | True | False\n", + "True | True | True\n", + "\n", + "\"\"\")\n", + "md" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### YouTube Videos" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import YouTubeVideo\n", + "\n", + "\n", + "YouTubeVideo(\"9_OIs49m56E\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interactive Widgets (HTML only)\n", + "\n", + "The basic widget infrastructure is provided by\n", + "the [ipywidgets](https://ipywidgets.readthedocs.io/) module.\n", + "More advanced widgets are available in separate packages,\n", + "see for example https://jupyter.org/widgets.\n", + "\n", + "The JavaScript code which is needed to display Jupyter widgets\n", + "is loaded automatically (using RequireJS).\n", + "If you want to use non-default URLs or local files,\n", + "you can use the\n", + "[nbsphinx_widgets_path](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_widgets_path) and\n", + "[nbsphinx_requirejs_path](https://nbsphinx.readthedocs.io/en/0.9.7/configuration.html#nbsphinx_requirejs_path)\n", + "settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as w" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider = w.IntSlider()\n", + "slider.value = 42\n", + "slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A widget typically consists of a so-called \"model\" and a \"view\" into that model.\n", + "\n", + "If you display a widget multiple times,\n", + "all instances act as a \"view\" into the same \"model\".\n", + "That means that their state is synchronized.\n", + "You can move either one of these sliders to try this out:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also link different widgets. \n", + "\n", + "Widgets can be linked via the kernel\n", + "(which of course only works while a kernel is running)\n", + "or directly in the client\n", + "(which even works in the rendered HTML pages).\n", + "\n", + "Widgets can be linked uni- or bi-directionally. \n", + "\n", + "Examples for all 4 combinations, all linked to the slider in the previous code\n", + "cell, are shown here:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# requires kernel, bi-directional\n", + "link = w.IntSlider(description=\"link\")\n", + "w.link((slider, \"value\"), (link, \"value\"))\n", + "\n", + "# runs in rendered HTML, bi-directional\n", + "jslink = w.IntSlider(description=\"jslink\")\n", + "w.jslink((slider, \"value\"), (jslink, \"value\"))\n", + "\n", + "# requires kernel, uni-directional\n", + "dlink = w.IntSlider(description=\"dlink\")\n", + "w.dlink((slider, \"value\"), (dlink, \"value\"))\n", + "\n", + "# runs in rendered HTML, bi-directional\n", + "jsdlink = w.IntSlider(description=\"jsdlink\")\n", + "w.jsdlink((slider, \"value\"), (jsdlink, \"value\"))\n", + "\n", + "w.VBox([link, jslink, dlink, jsdlink])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The source slider for all 4 of the sliders above is the one stored in the\n", + "variable named `slider`, defined prior.\n", + "\n", + "If you are in a notebook app with a running kernel, such as Jupyter Notebook,\n", + "and if you change the source slider, then it will update all 4 of the linked\n", + "sliders above. Going the other way, if you update either of the `link` or\n", + "`jslink` sliders, then it will also update the source slider, which in turn\n", + "updates the other 3 linked sliders. That's what makes those sliders\n", + "bi-directional: updates go both from and to the source slider. The\n", + "uni-directional \"dlink\" sliders can only receive updates from the source slider.\n", + "If you change them, they do not update the source slider.\n", + "\n", + "If you are not in a notebook app, but you are reading this in a web browser\n", + "(with JavaScript enabled, which is most browsers), then only the \"js\"-prefixed\n", + "sliders will work as described in the previous paragraph. The kernel-linked\n", + "sliders, `link` and `dlink`, will neither receive nor send updates outside of a\n", + "notebook app with a running kernel." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here the slider and some other widgets created earlier in this notebook are\n", + "gathered together and put into a tabbed interface widget:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tabs = w.Tab()\n", + "for idx, obj in enumerate([df, fig, eq, i, md, slider]):\n", + " out = w.Output()\n", + " with out:\n", + " display(obj)\n", + " tabs.children += (out,)\n", + " tabs.set_title(idx, obj.__class__.__name__)\n", + "tabs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "Other Languages\n", + "\n", + "The examples shown here are using Python,\n", + "but the widget technology can also be used with\n", + "different Jupyter kernels\n", + "(i.e. with different programming languages).\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Troubleshooting\n", + "\n", + "To obtain more information if widgets are not displayed as expected, you will need to look at the error message in the web browser console.\n", + "\n", + "> To figure out how to open the web browser console, you may look at the web browser documentation: \n", + "> Chrome: https://developer.chrome.com/docs/devtools/open/#shortcuts \n", + "> Firefox: https://developer.mozilla.org/en-US/docs/Tools/Web_Console#opening-the-web-console\n", + "\n", + "The error is most probably linked to the JavaScript files not being loaded or loaded in the wrong order within the HTML file. To analyze the error, you can inspect the HTML file within the web browser (e.g.: right-click on the page and select *View Page Source*) and look at the `` section of the page. That section should contain\n", + "some JavaScript libraries. Those relevant for widgets are:\n", + "\n", + "```html\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "```\n", + "\n", + "The two first elements are mandatory. The third one is required only if you designed your own widgets but did not publish them on npm.js.\n", + "\n", + "If those libraries appear in a different order, the widgets won't be displayed. \n", + "\n", + "Here is a list of possible solutions:\n", + "\n", + "- If the widgets are **not displayed**, see [#519](https://github.com/spatialaudio/nbsphinx/issues/519).\n", + "- If the widgets are **displayed multiple times**, see [#378](https://github.com/spatialaudio/nbsphinx/issues/378)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Arbitrary JavaScript Output (HTML only)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%javascript\n", + "\n", + "var text = document.createTextNode(\"Hello, I was generated with JavaScript!\");\n", + "// Content appended to \"element\" will be visible in the output area:\n", + "element.appendChild(text);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Unsupported Output Types\n", + "\n", + "If a code cell produces data with an unsupported MIME type, the Jupyter Notebook doesn't generate any output.\n", + "`nbsphinx`, however, shows a warning message." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-output" + ] + }, + "outputs": [], + "source": [ + "display(\n", + " {\n", + " \"text/x-python\": 'print(\"Hello, world!\")',\n", + " \"text/x-haskell\": 'main = putStrLn \"Hello, world!\"',\n", + " },\n", + " raw=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ANSI Colors\n", + "\n", + "The standard output and standard error streams may contain [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) to change the text and background colors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"BEWARE: \\x1b[1;33;41mugly colors\\x1b[m!\", file=sys.stderr)\n", + "print(\n", + " \"AB\\x1b[43mCD\\x1b[35mEF\\x1b[1mGH\\x1b[4mIJ\\x1b[7m\"\n", + " \"KL\\x1b[49mMN\\x1b[39mOP\\x1b[22mQR\\x1b[24mST\\x1b[27mUV\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code showing the 8 basic ANSI colors is based on https://web.archive.org/web/20231225185739/https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html.\n", + "Each of the 8 colors has an \"intense\" variation, which is used for bold text." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "text = \" XYZ \"\n", + "formatstring = \"\\x1b[{}m\" + text + \"\\x1b[m\"\n", + "\n", + "print(\n", + " \" \" * 6\n", + " + \" \" * len(text)\n", + " + \"\".join(\"{:^{}}\".format(bg, len(text)) for bg in range(40, 48))\n", + ")\n", + "for fg in range(30, 38):\n", + " for bold in False, True:\n", + " fg_code = (\"1;\" if bold else \"\") + str(fg)\n", + " print(\n", + " \" {:>4} \".format(fg_code)\n", + " + formatstring.format(fg_code)\n", + " + \"\".join(\n", + " formatstring.format(fg_code + \";\" + str(bg)) for bg in range(40, 48)\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ANSI also supports a set of 256 indexed colors.\n", + "The following code showing all of them is based on [http://bitmote.com/index.php?post/2012/11/19/Using-ANSI-Color-Codes-to-Colorize-Your-Bash-Prompt-on-Linux](https://web.archive.org/web/20190109005413/http://bitmote.com/index.php?post/2012/11/19/Using-ANSI-Color-Codes-to-Colorize-Your-Bash-Prompt-on-Linux)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "formatstring = \"\\x1b[38;5;{0};48;5;{0}mX\\x1b[1mX\\x1b[m\"\n", + "\n", + "print(\" + \" + \"\".join(\"{:2}\".format(i) for i in range(36)))\n", + "print(\" 0 \" + \"\".join(formatstring.format(i) for i in range(16)))\n", + "for i in range(7):\n", + " i = i * 36 + 16\n", + " print(\n", + " \"{:3} \".format(i)\n", + " + \"\".join(formatstring.format(i + j) for j in range(36) if i + j < 256)\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can even use 24-bit RGB colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "start = 255, 0, 0\n", + "end = 0, 0, 255\n", + "length = 79\n", + "out = []\n", + "\n", + "for i in range(length):\n", + " rgb = [start[c] + int(i * (end[c] - start[c]) / length) for c in range(3)]\n", + " out.append(\n", + " \"\\x1b[\"\n", + " \"38;2;{rgb[2]};{rgb[1]};{rgb[0]};\"\n", + " \"48;2;{rgb[0]};{rgb[1]};{rgb[2]}mX\\x1b[m\".format(rgb=rgb)\n", + " )\n", + "print(\"\".join(out))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/execution.rst b/docs/examples/execution.rst index 3991c9d6ec..2c3c6afc4d 100644 --- a/docs/examples/execution.rst +++ b/docs/examples/execution.rst @@ -1,18 +1,26 @@ Execution Libraries =================== -Many execution libraries can be used to display the output of IPyhton cells. We used ``MySTnb`` to parse and display the outputs presented in :doc:`./pydata`. In this section we'll show alternatives that runs code for you using a Jupyter like kernel. +Many execution libraries can be used to display the output of IPython cells. We +used ``nbsphinx`` to parse and display the outputs presented in :doc:`./pydata`. +In this section we'll show alternatives that run code for you using a Jupyter +like kernel. -Jupyterlite +JupyterLite ----------- .. warning:: - The jupyterLite lib is not yet providing a handle to switch from light to dark theme. If you consider using it in your documentation you should also enforce the light theme to your users. + As of April 2025, we have not found a way to sync JupyterLite widgets with the theme's light/dark mode. + You can see this for yourself if you change this page from light to dark mode. The rest of the page will + appear in dark mode but the JupyterLite widget below will still be in light mode. Follow https://github.com/jupyterlite/jupyterlite-sphinx/issues/69 for more information. -``jupyterlite-sphinx`` brings the power of `JupyterLite `__ to your Sphinx documentation. It makes a full JupyterLite deployment in your docs and provide some utilities for using that deployment easily. +``jupyterlite-sphinx`` brings the power of `JupyterLite +`__ to your Sphinx documentation. +It does a full JupyterLite deployment in your docs and provides some utilities +for using that deployment easily. -This section demonstrate how it displays in a **pydata-sphinx-theme** context: +This section demonstrates how it displays in a **pydata-sphinx-theme** context: .. replite:: :kernel: python diff --git a/docs/examples/images/notebook_icon.png b/docs/examples/images/notebook_icon.png new file mode 100644 index 0000000000..5e50eee511 Binary files /dev/null and b/docs/examples/images/notebook_icon.png differ diff --git a/docs/examples/images/python_logo.svg b/docs/examples/images/python_logo.svg new file mode 100644 index 0000000000..116eaac311 --- /dev/null +++ b/docs/examples/images/python_logo.svg @@ -0,0 +1,269 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/examples/index.rst b/docs/examples/index.rst index ad62877e8e..8fd99bf44f 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -11,12 +11,16 @@ See the sections in the primary sidebar and below to explore. gallery +.. Note: the source for code-cells-myst-nb is actually code-cells.ipynb but it is copied to a separate file at build time + .. toctree:: :maxdepth: 2 :caption: Theme-specific styles kitchen-sink/index pydata + Code Cells (nbsphinx) + Code Cells (MyST-NB) execution graphviz diff --git a/docs/examples/pydata.ipynb b/docs/examples/pydata.ipynb index b8de05084a..c2a19c6754 100644 --- a/docs/examples/pydata.ipynb +++ b/docs/examples/pydata.ipynb @@ -36,6 +36,55 @@ "df" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All the cells in the previous data frame are roughly the same length/width. The next table has more varied types of data. This is useful for checking the effects of CSS." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# car data taken from Bokeh sample data (`pip install bokeh-sampledata`)\n", + "\n", + "# from bokeh_sampledata.autompg2 import autompg2 as mpg\n", + "# del mpg['Unnamed 0']\n", + "# mpg_csv = mpg.sample(n=20).to_csv()\n", + "\n", + "import io\n", + "\n", + "\n", + "mpg_csv = io.StringIO(\n", + " \",manufacturer,model,displ,year,cyl,trans,drv,cty,hwy,fl,class\\n\"\n", + " \"162,Subaru,Forester Awd,2.5,2008,4,manual(m5),4x4,19,25,p,suv\\n\"\n", + " \"36,Chevrolet,Malibu,3.6,2008,6,auto(s6),front,17,26,r,midsize\\n\"\n", + " \"133,Land Rover,Range Rover,4.6,1999,8,auto(l4),4x4,11,15,p,suv\\n\"\n", + " \"102,Honda,Civic,1.6,1999,4,manual(m5),front,23,29,p,subcompact\\n\"\n", + " \"47,Dodge,Caravan 2wd,4.0,2008,6,auto(l6),front,16,23,r,minivan\\n\"\n", + " \"84,Ford,F150 Pickup 4wd,4.2,1999,6,manual(m5),4x4,14,17,r,pickup\\n\"\n", + " \"168,Subaru,Impreza Awd,2.5,1999,4,auto(l4),4x4,19,26,r,subcompact\\n\"\n", + " \"149,Nissan,Maxima,3.5,2008,6,auto(av),front,19,25,p,midsize\\n\"\n", + " \"158,Pontiac,Grand Prix,5.3,2008,8,auto(s4),front,16,25,p,midsize\\n\"\n", + " \"50,Dodge,Dakota Pickup 4wd,3.9,1999,6,auto(l4),4x4,13,17,r,pickup\\n\"\n", + " \"58,Dodge,Durango 4wd,4.7,2008,8,auto(l5),4x4,13,17,r,suv\\n\"\n", + " \"28,Chevrolet,K1500 Tahoe 4wd,5.3,2008,8,auto(l4),4x4,14,19,r,suv\\n\"\n", + " \"116,Hyundai,Tiburon,2.0,1999,4,manual(m5),front,19,29,r,subcompact\\n\"\n", + " \"97,Ford,Mustang,4.6,2008,8,auto(l5),rear,15,22,r,subcompact\\n\"\n", + " \"80,Ford,Explorer 4wd,4.0,2008,6,auto(l5),4x4,13,19,r,suv\\n\"\n", + " \"232,Volkswagen,Passat,2.8,1999,6,manual(m5),front,18,26,p,midsize\\n\"\n", + " \"219,Volkswagen,Jetta,2.8,1999,6,auto(l4),front,16,23,r,compact\\n\"\n", + " \"72,Dodge,Ram 1500 Pickup 4wd,5.7,2008,8,auto(l5),4x4,13,17,r,pickup\\n\"\n", + " \"200,Toyota,Toyota Tacoma 4wd,2.7,1999,4,manual(m5),4x4,15,20,r,pickup\\n\"\n", + " \"145,Nissan,Altima,3.5,2008,6,manual(m6),front,19,27,p,midsize\\n\"\n", + ")\n", + "mpg_df = pd.read_csv(mpg_csv, index_col=0)\n", + "mpg_df" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/docs/user_guide/fonts.rst b/docs/user_guide/fonts.rst index 1a49c727fe..e6b6f8dabc 100644 --- a/docs/user_guide/fonts.rst +++ b/docs/user_guide/fonts.rst @@ -53,5 +53,5 @@ The default body and header fonts can be changed as follows: before the CSS is parsed, but should be used with care. .. _pydata-css-variables: https://github.com/pydata/pydata-sphinx-theme/tree/main/src/pydata_sphinx_theme/assets/styles/variables -.. _pydata-css-colors: https://github.com/pydata/pydata-sphinx-theme/blob/main/src/pydata_sphinx_theme/assets/styles/variables/_color.scss +.. _pydata-css-colors: https://github.com/pydata/pydata-sphinx-theme/blob/main/src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss .. _css-variable-help: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties diff --git a/docs/user_guide/styling.rst b/docs/user_guide/styling.rst index 6cae059058..d63353adf5 100644 --- a/docs/user_guide/styling.rst +++ b/docs/user_guide/styling.rst @@ -190,7 +190,7 @@ The following image should help you understand these overlays: https://github.com/pygments/pygments/issues/2130). So for now it's just a raw download link. -For a complete list of the theme colors that you may override, see the :download:`PyData theme CSS colors stylesheet <../../src/pydata_sphinx_theme/assets/styles/variables/_color.scss>`. +For a complete list of the theme colors that you may override, see the :download:`PyData theme CSS colors stylesheet <../../src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss>`. Configure pygments theme ======================== diff --git a/package-lock.json b/package-lock.json index 5688e18349..eb35566b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,8 @@ "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", "webpack": "^5.94.0", - "webpack-cli": "^5.0.0" + "webpack-cli": "^5.0.0", + "webpack-remove-empty-scripts": "^1.1.1" } }, "node_modules/@discoveryjs/json-ext": { @@ -569,6 +570,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -3551,6 +3562,26 @@ "node": ">=10.0.0" } }, + "node_modules/webpack-remove-empty-scripts": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webpack-remove-empty-scripts/-/webpack-remove-empty-scripts-1.1.1.tgz", + "integrity": "sha512-FqmIy7joxXd0/7jz8UjzMXOKc6B7LR+ynfgaaaH72xUT917h3A94ERxOLvGM8a8XdGIvUsdTLIUt8aBQM2Pdqg==", + "dev": true, + "license": "ISC", + "dependencies": { + "ansis": "4.0.0-node10" + }, + "engines": { + "node": ">=12.14" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/biodiscus" + }, + "peerDependencies": { + "webpack": ">=5.32.0" + } + }, "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", diff --git a/package.json b/package.json index b2beebc85c..4c1178e17a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", "webpack": "^5.94.0", - "webpack-cli": "^5.0.0" + "webpack-cli": "^5.0.0", + "webpack-remove-empty-scripts": "^1.1.1" }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", diff --git a/pyproject.toml b/pyproject.toml index 83198b50ff..69adbc3fd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ additional-compiled-static-assets = [ "vendor/", "scripts/bootstrap.js", "scripts/fontawesome.js", + "styles/nbsphinx-pydata-theme.css", + "styles/myst-nb-pydata-theme.css", "locale/", ] @@ -61,7 +63,7 @@ doc = [ "sphinx-sitemap<2.7.0", "sphinx-autoapi>=3.0.0", # For examples section - "myst-parser", + "myst-nb", # includes "myst-parser" "ablog>=0.11.8", "jupyter_sphinx", "pandas", diff --git a/src/pydata_sphinx_theme/__init__.py b/src/pydata_sphinx_theme/__init__.py index 0b1ed7d4fe..29e8cff210 100644 --- a/src/pydata_sphinx_theme/__init__.py +++ b/src/pydata_sphinx_theme/__init__.py @@ -14,7 +14,17 @@ from sphinx.builders.dirhtml import DirectoryHTMLBuilder from sphinx.errors import ExtensionError -from . import edit_this_page, logo, pygments, short_link, toctree, translator, utils +from . import ( + edit_this_page, + logo, + myst_nb, + nbsphinx, + pygments, + short_link, + toctree, + translator, + utils, +) __version__ = "0.16.2dev0" @@ -202,14 +212,8 @@ def update_and_remove_templates( # - manually linked in `webpack-macros.html` if "css_files" in context: theme_css_name = "_static/styles/pydata-sphinx-theme.css" - for i in range(len(context["css_files"])): - asset = context["css_files"][i] - # TODO: eventually the contents of context['css_files'] etc should probably - # only be _CascadingStyleSheet etc. For now, assume mixed with strings. - asset_path = getattr(asset, "filename", str(asset)) - if asset_path == theme_css_name: - del context["css_files"][i] - break + utils._delete_from_css_files(context["css_files"], theme_css_name) + # Add links for favicons in the topbar for favicon in context.get("theme_favicons", []): icon_type = Path(favicon["href"]).suffix.strip(".") @@ -286,12 +290,16 @@ def setup(app: Sphinx) -> Dict[str, str]: app.connect("builder-inited", translator.setup_translators) app.connect("builder-inited", update_config) + app.connect("builder-inited", nbsphinx.delete_nbsphinx_css) + app.connect("builder-inited", myst_nb.delete_myst_nb_css) app.connect("html-page-context", _fix_canonical_url) app.connect("html-page-context", edit_this_page.setup_edit_url) app.connect("html-page-context", toctree.add_toctree_functions) app.connect("html-page-context", update_and_remove_templates) app.connect("html-page-context", logo.setup_logo_path) app.connect("html-page-context", utils.set_secondary_sidebar_items) + app.connect("html-page-context", nbsphinx.point_nbsphinx_pages_to_our_css) + app.connect("html-page-context", myst_nb.point_myst_nb_pages_to_our_css) app.connect("build-finished", pygments.overwrite_pygments_css) app.connect("build-finished", logo.copy_logo_images) diff --git a/src/pydata_sphinx_theme/assets/styles/README.md b/src/pydata_sphinx_theme/assets/styles/README.md new file mode 100644 index 0000000000..9d734bf7c1 --- /dev/null +++ b/src/pydata_sphinx_theme/assets/styles/README.md @@ -0,0 +1,40 @@ +# PyData Sphinx Theme Styles Folder + +This `assets/styles/` folder contains all of the CSS that the theme adds to +projects that use it. + +It follows the [Sphinx Theme Builder's expected Filesystem +Layout](https://sphinx-theme-builder.readthedocs.io/en/latest/filesystem-layout/). + +## How does it work? + +The CSS is written in Sass (SCSS) and broken across multiple files for better +code organization. These files are imported into a single main file named +`pydata-sphinx-theme.scss`. That file is compiled into a plain CSS file when the +theme is installed or packaged. This is handled by the Sphinx Theme Builder when +it runs the `stb compile` command. This command in turns calls `webpack`, which +is configured in `webpack.config.js`. + +How does `webpack` find the main SCSS file for compilation? The SCSS file is +imported by the main JavaScript file for this site, `pydata-sphinx-theme.js`, +and the path to that JavaScript file is in the `webpack` config file. + +## Sub-folders + +Here are some notes about some of the sub-folders in this directory: + +- `abstracts/` contains SCSS variables (**not** CSS variables), functions, + mixins and other re-usable SCSS constructs to be imported by other SCSS +- `extensions/` contains styles for Sphinx extensions that commonly co-occur + with this theme +- `variables/` strictly for files that output CSS custom properties (variables) + +## SCSS versus CSS variables + +Read the [SCSS variables](https://sass-lang.com/documentation/variables/) +documentation for a summary of the differences between CSS and SCSS variables. + +- SCSS variables that are meant to be shared across SCSS files go in + `abstracts/_vars.scss`. +- CSS variables go into one of the files in the `variables/` folder. These + variables will be defined on every web page that uses the theme. diff --git a/src/pydata_sphinx_theme/assets/styles/abstracts/_all.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_all.scss index 18e15e84f0..2602cab427 100644 --- a/src/pydata_sphinx_theme/assets/styles/abstracts/_all.scss +++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_all.scss @@ -2,3 +2,5 @@ @import "color"; @import "mixins"; @import "links"; +@import "vars"; +@import "vars_bootstrap"; diff --git a/src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss index 8898fc7322..8af51a5949 100644 --- a/src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss +++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_color.scss @@ -1,5 +1,5 @@ /** - * Miscellaneous color functions and mixins + * Color definitions, functions, variables and mixins **/ @use "sass:list"; @@ -115,3 +115,305 @@ @return $dec; } + +/******************************************************************************* +* Color definitions +* +* Figma design file: https://www.figma.com/community/file/1443191723065200671 +*/ + +/* Assign base colors for the PyData theme */ +$color-palette: ( + // Primary color + "teal": ( + "50": #f4fbfc, + "100": #e9f6f8, + "200": #d0ecf1, + "300": #abdde6, + "400": #3fb1c5, + "500": #0a7d91, + "600": #085d6c, + "700": #064752, + "800": #042c33, + "900": #021b1f, + ), + // Secondary color + "violet": ( + "50": #f4eefb, + "100": #e0c7ff, + "200": #d5b4fd, + "300": #b780ff, + "400": #9c5ffd, + "500": #8045e5, + "600": #6432bd, + "700": #4b258f, + "800": #341a61, + "900": #1e0e39, + ), + // Neutrals + "gray": ( + "50": #f9f9fa, + "100": #f3f4f5, + "200": #e5e7ea, + "300": #d1d5da, + "400": #9ca4af, + "500": #677384, + "600": #48566b, + "700": #29313d, + "800": #222832, + "900": #14181e, + ), + // Accent color + "pink": ( + "50": #fcf8fd, + "100": #fcf0fa, + "200": #f8dff5, + "300": #f3c7ee, + "400": #e47fd7, + "500": #c132af, + "600": #912583, + "700": #6e1c64, + "800": #46123f, + "900": #2b0b27, + ), + "foundation": ( + "white": #ffffff, + // gray-900 + "black": #14181e, + ) +); + +// Static SCSS variables used thoroughout the theme +// Minimum contrast ratio used for the theme. +// Acceptable values for WCAG 2.0 are 3, 4.5 and 7. +// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast +// 4.5 - is for text that is 14pt or less +$min-contrast-ratio-4: 4.5; + +// 3 is for text that is 18pt or bold, or for non-text elements +$min-contrast-ratio-3: 3; + +// Customize the light and dark text colors for use in our color contrast function. +$foundation-black: #14181e; +$foundation-white: #fff; + +// This is a custom - calculated color between gray 100 and 200 - to reduce +// the contrast ratio (avoid a jarring effect) +$base-light-text: #ced6dd; + +// used in sphinx_design - gray 100 +$foundation-light-gray: #f3f4f5; + +// used in sphinx_design - gray 700 +$foundation-muted-gray: #29313d; + +// used in sphinx_design - gray 800 +$foundation-dark-gray: #222832; +$pst-semantic-colors: ( + "primary": ( + "light": #{map-deep-get($color-palette, "teal", "500")}, + "bg-light": #{map-deep-get($color-palette, "teal", "200")}, + "dark": #{map-deep-get($color-palette, "teal", "400")}, + "bg-dark": #{map-deep-get($color-palette, "teal", "800")}, + ), + "secondary": ( + "light": #{map-deep-get($color-palette, "violet", "500")}, + "bg-light": #{map-deep-get($color-palette, "violet", "100")}, + "dark": #{map-deep-get($color-palette, "violet", "400")}, + "bg-dark": #{map-deep-get($color-palette, "violet", "800")}, + ), + "accent": ( + "light": #{map-deep-get($color-palette, "pink", "500")}, + "bg-light": #{map-deep-get($color-palette, "pink", "200")}, + "dark": #{map-deep-get($color-palette, "pink", "400")}, + "bg-dark": #{map-deep-get($color-palette, "pink", "800")}, + ), + "info": ( + "light": #276be9, + "bg-light": #dce7fc, + "dark": #79a3f2, + "bg-dark": #06245d, + ), + "warning": ( + "light": #f66a0a, + "bg-light": #f8e3d0, + "dark": #ff9245, + "bg-dark": #652a02, + ), + "success": ( + "light": #00843f, + "bg-light": #d6ece1, + "dark": #5fb488, + "bg-dark": #002f17, + ), + // This is based on the warning color + "attention": ( + "light": var(--pst-color-warning), + "bg-light": var(--pst-color-warning-bg), + "dark": var(--pst-color-warning), + "bg-dark": var(--pst-color-warning-bg), + ), + "danger": ( + "light": #d72d47, + "bg-light": #f9e1e4, + "dark": #e78894, + "bg-dark": #4e111b, + ), + "text-base": ( + "light": #{map-deep-get($color-palette, "gray", "800")}, + "dark": $base-light-text, + ), + "text-muted": ( + "light": #{map-deep-get($color-palette, "gray", "600")}, + "dark": #{map-deep-get($color-palette, "gray", "400")}, + ), + "shadow": ( + "light": rgba(0, 0, 0, 0.1), + "dark": rgba(0, 0, 0, 0.2), + ), + "border": ( + "light": #{map-deep-get($color-palette, "gray", "300")}, + "dark": #{map-deep-get($color-palette, "gray", "600")}, + ), + "border-muted": ( + "light": rgba(23, 23, 26, 0.2), + "dark": #{map-deep-get($color-palette, "gray", "700")}, + ), + "blockquote-notch": ( + // These colors have a contrast ratio > 3.0 against both the background and + // surface colors that the notch is sandwiched between + "light": #{map-deep-get($color-palette, "gray", "500")}, + "dark": #{map-deep-get($color-palette, "gray", "400")}, + ), + "inline-code": ( + "light": #{map-deep-get($color-palette, "pink", "600")}, + "dark": #{map-deep-get($color-palette, "pink", "300")}, + ), + "link-higher-contrast": ( + // teal-600 provides higher contrast than teal-500 (our regular light mode + // link color) for off-white or non-white backgrounds + "light": #{map-deep-get($color-palette, "teal", "600")}, + // teal-400 is actually the same color already used for links in dark mode, + // but it actually works for most of our other dark mode backgrounds + "dark": #{map-deep-get($color-palette, "teal", "400")}, + ), + "target": ( + "light": #f3cf95, + "dark": #675c04, + ), + "table": ( + "light": #{map-deep-get($color-palette, "foundation", "black")}, + "dark": #{map-deep-get($color-palette, "foundation", "white")}, + ), + "table-row-hover": ( + "bg-light": #{map-deep-get($color-palette, "violet", "200")}, + "bg-dark": #{map-deep-get($color-palette, "violet", "700")}, + ), + "table-inner-border": ( + "light": #{map-deep-get($color-palette, "gray", "200")}, + "dark": #364150, + ), + // DEPTH COLORS - you can see the elevation colours and shades + // in the Figma file https://www.figma.com/file/rUrrHGhUBBIAAjQ82x6pz9/PyData-Design-system---proposal-for-implementation-(2)?node-id=1492%3A922&t=sQeQZehkOzposYEg-1 + // background: color of the canvas / the furthest back layer + "background": ( + "light": #{map-deep-get($color-palette, "foundation", "white")}, + "dark": #{map-deep-get($color-palette, "foundation", "black")}, + ), + // on-background: provides slight contrast against background + // (by use of shadows in light theme) + "on-background": ( + "light": #{map-deep-get($color-palette, "foundation", "white")}, + "dark": #{map-deep-get($color-palette, "gray", "800")}, + ), + "surface": ( + "light": #{map-deep-get($color-palette, "gray", "100")}, + "dark": #{map-deep-get($color-palette, "gray", "700")}, + ), + // on_surface: object on top of surface object (without shadows) + "on-surface": ( + "light": #{map-deep-get($color-palette, "gray", "800")}, + "dark": $foundation-light-gray, + ), +); + +/******************************************************************************* +* write the color rules for each theme (light/dark) +*/ + +/* NOTE: + * Mixins enable us to reuse the same definitions for the different modes + * https://sass-lang.com/documentation/at-rules/mixin + * #{something} inserts a variable into a CSS selector or property name + * https://sass-lang.com/documentation/interpolation + */ +@mixin theme-colors($mode) { + // check if this color is defined differently for light/dark + @each $col-name, $definition in $pst-semantic-colors { + @if meta.type-of($definition) == map { + @each $key, $val in $definition { + @if string.index($key, $mode) { + // since now we define the bg colours in the semantic colours and not + // by changing opacity, we need to check if the key contains bg and the + // correct mode (light/dark) + @if string.index($key, "bg") { + --pst-color-#{$col-name}-bg: #{$val}; + } @else { + --pst-color-#{$col-name}: #{$val}; + } + } + } + } @else { + --pst-color-#{$col-name}: #{$definition}; + } + } + + // assign the "duplicate" colors (ones that just reference other variables) + & { + // From 0.16.1, the preferred variable for headings is --pst-color-heading + // if you have --pst-heading-color, this variable will be used, otherwise the default + // --pst-color-heading will be used + --pst-color-heading: var(--pst-color-text-base); + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-secondary); + --pst-color-table-outer-border: var(--pst-color-surface); + --pst-color-table-heading-bg: var(--pst-color-surface); + --pst-color-table-row-zebra-high-bg: var(--pst-color-on-background); + --pst-color-table-row-zebra-low-bg: var(--pst-color-surface); + } + + // adapt to light/dark-specific content + @if $mode == "light" { + .only-dark, + .only-dark ~ figcaption { + display: none !important; + } + } @else { + .only-light, + .only-light ~ figcaption { + display: none !important; + } + + /* Adjust images in dark mode (unless they have class .only-dark or + * .dark-light, in which case assume they're already optimized for dark + * mode). + */ + img:not(.only-dark, .dark-light) { + filter: brightness(0.8) contrast(1.2); + } + + /* Give images a light background in dark mode in case they have + * transparency and black text (unless they have class .only-dark or .dark-light, in + * which case assume they're already optimized for dark mode). + */ + .bd-content img:not(.only-dark, .dark-light) { + background-color: rgb(255 255 255); + border-radius: 0.25rem; + } + + // MathJax SVG outputs should be filled to same color as text. + .MathJax_SVG * { + fill: var(--pst-color-text-base); + } + } +} diff --git a/src/pydata_sphinx_theme/assets/styles/abstracts/_mixins.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_mixins.scss index e21cf7b05d..9bfade5b93 100644 --- a/src/pydata_sphinx_theme/assets/styles/abstracts/_mixins.scss +++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_mixins.scss @@ -12,14 +12,22 @@ } /** - * Set background of some cell outputs to white-ish to make sure colors work - * This is because many libraries make output that only looks good on white + * Set background of some cell outputs to white-ish to make sure colors work. + * This is because many libraries make output that only looks good on white. */ -@mixin cell-output-background { - color: var(--pst-color-on-background); - background-color: var(--pst-color-text-base); - border-radius: 0.25rem; - padding: 0.5rem; +@mixin cell-output-force-light-background { + html[data-theme="dark"] & { + color: var(--pst-color-on-background); + background-color: var(--pst-color-text-base); + border-radius: 0.25rem; + padding: 0.5rem; + + // For cell outputs that this mixin has forced to a light background even + // when the user has chosen dark mode, change the focus ring to light mode. + :focus-visible { + outline-color: #{map-deep-get($pst-semantic-colors, "accent", "light")}; + } + } } @mixin table-colors { diff --git a/src/pydata_sphinx_theme/assets/styles/abstracts/_vars.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_vars.scss new file mode 100644 index 0000000000..4ab35acd37 --- /dev/null +++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_vars.scss @@ -0,0 +1,41 @@ +/********************************************* +* SASS Variables (NOT CSS vars) +*********************************************/ + +/******************************************************************************* +* Breakpoints that trigger UI changes +* +* Note that media-breakpoint-down begins at the next highest level! +* So we should choose a media-breakpoint-down one *lower* than when we want to start +* example: media-breakpoint-up(md) and media-breakpoint-down(sm) trigger at the same time +* ref: https://github.com/twbs/bootstrap/issues/31214 +*/ +$breakpoint-sidebar-primary: lg; // When we collapse the primary sidebar +$breakpoint-sidebar-secondary: xl; // When we collapse the secondary sidebar +$breakpoint-page-width: 88rem; // taken from sphinx-basic-ng, which we are ultimately going to inherit + +/******************************************************************************* +* Define the animation behaviour +*/ +$animation-time: 200ms; + +/******************************************************************************* +* UI shaping and padding +*/ +$admonition-border-radius: 0.25rem; + +// In this theme, some focus rings have rounded corners while others do not. +// This variable sets the border radius for the rounded focus rings. +$focus-ring-radius: 0.125rem; // 2px at 100% zoom and 16px base font. + +$navbar-link-padding-y: 0.25rem; + +// Ensure that there is no overlap of bumper disks (smallest circle that +// contains the bounding box of the interactive element). +// - https://github.com/Quansight-Labs/czi-scientific-python-mgmt/issues/81#issuecomment-2251325783 +$nav-icon-column-gap: 1.12rem; + +// Determines vertical space between entries in both the section (left/primary +// sidebar) and page (right/secondary sidebar) table of contents +$toc-item-padding-y: 0.25rem; +$line-height-body: 1.65; diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_bootstrap.scss b/src/pydata_sphinx_theme/assets/styles/abstracts/_vars_bootstrap.scss similarity index 91% rename from src/pydata_sphinx_theme/assets/styles/variables/_bootstrap.scss rename to src/pydata_sphinx_theme/assets/styles/abstracts/_vars_bootstrap.scss index eaf373b797..07b373f5ca 100644 --- a/src/pydata_sphinx_theme/assets/styles/variables/_bootstrap.scss +++ b/src/pydata_sphinx_theme/assets/styles/abstracts/_vars_bootstrap.scss @@ -1,4 +1,7 @@ -// Override bootstrap variables +/********************************************* +* Set Sass vars for Bootstrap +*********************************************/ + $spacer: 1rem; $container-max-widths: ( sm: 540px, diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss b/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss deleted file mode 100644 index 2f1a9c103b..0000000000 --- a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Styles for various Sphinx execution libraries to display pre-executed notebooks. - * For now, where these define output sections, we simply revert their background - * to be a "light theme" background. This ensures that inputs/outputs behave similarly, - * because the CSS is often controlled by each package. - * In the future, we might add dark theme support for specific packages. - */ - -/******************************************************************************* - * nbsphinx - */ -html div.rendered_html, -// NBsphinx ipywidgets output selector -html .jp-RenderedHTMLCommon { - // Add some margin around the element box for the focus ring. Otherwise the - // focus ring gets clipped because the containing elements have `overflow: - // hidden` applied to them (via the `.lm-Widget` selector) - margin: $focus-ring-width; - - table { - table-layout: auto; - } -} - -.bd-content .nboutput { - .output_area { - &.rendered_html, - .jp-RenderedHTMLCommon { - // pandas - table.dataframe { - @include table-colors; - - tbody { - tr:hover { - background-color: var(--pst-color-table-row-hover-bg); - } - } - } - } - - // Dark theme special-cases - html[data-theme="dark"] & { - &.rendered_html:not(:has(table.dataframe)), - // ipywidgets - .widget-subarea { - @include cell-output-background; - } - - &.stderr { - background-color: var(--pst-color-danger); - } - } - } -} - -// Add extra padding to the final item in an nbsphinx container -div.nblast.container { - margin-bottom: 1rem; -} - -// Override nbsphinx's colors for notebook cell prompts because they do not have -// sufficient contrast. Colors chosen from accessible-pygments -// a11y-high-contrast-{light,dark} themes. - -// Notebook cell input line number. Replace nbsphinx's low contrast blue with -// higher contrast blues. -.nbinput.container .prompt pre { - html[data-theme="light"] & { - // Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light) - color: #005b82; - } - - html[data-theme="dark"] & { - // Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark) - color: #00e0e0; - } -} - -// Notebook cell output line number. Replace nbsphinx's low contrast red with -// higher contrast red / orange. -.nboutput.container .prompt pre { - html[data-theme="light"] & { - // Copied from accessible-pygments [a11y-high-contrast-light](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_light) - color: #a12236; - } - - html[data-theme="dark"] & { - // Copied from accessible-pygments [a11y-high-contrast-dark](https://github.com/Quansight-Labs/accessible-pygments/tree/main/a11y_pygments/a11y_high_contrast_dark) - color: #ffa07a; - } -} - -/******************************************************************************* - * myst NB - */ - -div.cell_output .output { - max-width: 100%; - overflow-x: auto; -} - -.bd-content div.cell_output { - // pandas - table.dataframe { - @include table-colors; - - tbody { - tr:hover { - background-color: var(--pst-color-table-row-hover-bg); - } - } - } - - html[data-theme="dark"] & { - img, - .text_html:not(:has(table.dataframe)), - // ipywidgets - .widget-subarea { - @include cell-output-background; - } - } -} - -// Prevent tables from scrunching together -.bd-content { - div.cell_input { - display: flex; - flex-direction: column; - justify-content: stretch; - } - - div.cell_input, - div.output { - border-radius: $admonition-border-radius; - } - - div.output { - table { - table-layout: auto; - } - } -} diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/_sphinx_design.scss b/src/pydata_sphinx_theme/assets/styles/extensions/_sphinx_design.scss index d5e46983a8..3fe5d2c5b7 100644 --- a/src/pydata_sphinx_theme/assets/styles/extensions/_sphinx_design.scss +++ b/src/pydata_sphinx_theme/assets/styles/extensions/_sphinx_design.scss @@ -5,7 +5,6 @@ * NOTE: sphinx-design uses !important quite liberally, so here we must do the * same for our overrides to have any effect. */ -@use "../variables/color" as pst-color; @use "sass:color"; @use "sass:map"; @use "sass:meta"; diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/myst-nb-pydata-theme.scss b/src/pydata_sphinx_theme/assets/styles/extensions/myst-nb-pydata-theme.scss new file mode 100644 index 0000000000..f6d8462f01 --- /dev/null +++ b/src/pydata_sphinx_theme/assets/styles/extensions/myst-nb-pydata-theme.scss @@ -0,0 +1,2265 @@ +/******************************************************************************* + * PyData Sphinx Theme replacement stylesheet for MyST-NB + * + * MyST-NB is a Sphinx extension for turning Jupyter notebooks (.ipynb) into + * doc pages. + * + * This stylesheet is a replacement for MyST-NB's default stylesheet, + * mystnb.{sha256}.css. Some of the style rules in this file were copied + * from there. + * + * This file implements Isabela's design for notebook code cells: + * https://github.com/Quansight-Labs/czi-scientific-python-mgmt/issues/142#issuecomment-2759132381. + */ + +@import "../abstracts/all"; + +div.container.cell { + // Vertical whitespace between notebook code cell area (input + output) and + // surrounding prose + margin-top: 1.2em; + margin-bottom: 1.2em; + + // Vertical bar (runs down the left, connects input to output) + position: relative; // must set position relative in order to set position absolute on ::before pseudo-element + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 0.2em; + background: var(--pst-color-primary); + border-radius: 1px; + } + + // Space between vertical left bar and code cells + padding-left: 1em; + + // "In:" and "Out:" labels + .cell_input::before { + content: "In:"; + } + + .cell_output::before { + content: "Out:"; + } + + .cell_input, + .cell_output { + // shared label styles + &::before { + display: block; + font-family: var(--pst-font-family-monospace); + font-size: 0.875rem; // match nbsphinx label font-size + line-height: 1; // align top of label with top of left bar + margin-bottom: 0.7em; + } + } + + .cell_input { + margin-bottom: 1.2em; + } + + .cell_output { + margin-top: 1.2em; + + .stderr pre { + background-color: var(--pst-color-danger-bg); + } + + .output { + &.text_html { + max-width: 100%; + overflow-x: auto; + } + + :last-child:not(.highlight, .widget-tab-contents, .jupyter-widgets) { + margin-top: 0; + margin-bottom: 0; + } + + &.traceback { + background: transparent; + border: none; + + pre { + border: 1px solid var(--pst-color-danger); + } + } + } + + // Override our own styles in _math.scss + div.math { + display: block; + overflow: visible; + } + + span.math { + display: inline; + } + + .widget-slider { + // Fix accessibility contrast failure. (This was added primarily to + // prevent the theme docs from having an accessibility violation, but + // putting it here extends this fix to other sites that use this theme.) + .noUi-handle { + border-color: var(--pst-gray-600); + } + + .noUi-connects { + background: var(--pst-gray-600); + } + } + + // pandas + table.dataframe { + @include table-colors; + + tbody { + tr:hover { + background-color: var(--pst-color-table-row-hover-bg); + } + } + } + + // some notebook outputs (such as images and ipywidgets) are typically + // generated for light mode and do not adapt well to dark mode + .text_html:not( + :has( + /* exclude pandas because we have styled it for dark mode */ + table.dataframe, + /* exclude Xarray because it has been styled for dark mode */ + .xr-wrap + ) + ), + // ipywidgets + .widget-subarea { + @include cell-output-force-light-background; + } + } +} + +/* Font colors for translated ANSI escape sequences +Color values are copied from Jupyter Notebook +https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21 +Background colors from +https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors +*/ +div.highlight .-Color-Bold { + font-weight: bold; +} + +div.highlight .-Color[class*="-Black"] { + color: #3e424d; +} + +div.highlight .-Color[class*="-Red"] { + color: #e75c58; +} + +div.highlight .-Color[class*="-Green"] { + color: #00a250; +} + +div.highlight .-Color[class*="-Yellow"] { + color: #ddb62b; +} + +div.highlight .-Color[class*="-Blue"] { + color: #208ffb; +} + +div.highlight .-Color[class*="-Magenta"] { + color: #d160c4; +} + +div.highlight .-Color[class*="-Cyan"] { + color: #60c6c8; +} + +div.highlight .-Color[class*="-White"] { + color: #c5c1b4; +} + +div.highlight .-Color[class*="-BGBlack"] { + background-color: #3e424d; +} + +div.highlight .-Color[class*="-BGRed"] { + background-color: #e75c58; +} + +div.highlight .-Color[class*="-BGGreen"] { + background-color: #00a250; +} + +div.highlight .-Color[class*="-BGYellow"] { + background-color: #ddb62b; +} + +div.highlight .-Color[class*="-BGBlue"] { + background-color: #208ffb; +} + +div.highlight .-Color[class*="-BGMagenta"] { + background-color: #d160c4; +} + +div.highlight .-Color[class*="-BGCyan"] { + background-color: #60c6c8; +} + +div.highlight .-Color[class*="-BGWhite"] { + background-color: #c5c1b4; +} + +/* Font colors for 8-bit ANSI */ + +div.highlight .-Color[class*="-C0"] { + color: #000; +} + +div.highlight .-Color[class*="-BGC0"] { + background-color: #000; +} + +div.highlight .-Color[class*="-C1"] { + color: #800000; +} + +div.highlight .-Color[class*="-BGC1"] { + background-color: #800000; +} + +div.highlight .-Color[class*="-C2"] { + color: #008000; +} + +div.highlight .-Color[class*="-BGC2"] { + background-color: #008000; +} + +div.highlight .-Color[class*="-C3"] { + color: #808000; +} + +div.highlight .-Color[class*="-BGC3"] { + background-color: #808000; +} + +div.highlight .-Color[class*="-C4"] { + color: #000080; +} + +div.highlight .-Color[class*="-BGC4"] { + background-color: #000080; +} + +div.highlight .-Color[class*="-C5"] { + color: #800080; +} + +div.highlight .-Color[class*="-BGC5"] { + background-color: #800080; +} + +div.highlight .-Color[class*="-C6"] { + color: #008080; +} + +div.highlight .-Color[class*="-BGC6"] { + background-color: #008080; +} + +div.highlight .-Color[class*="-C7"] { + color: #c0c0c0; +} + +div.highlight .-Color[class*="-BGC7"] { + background-color: #c0c0c0; +} + +div.highlight .-Color[class*="-C8"] { + color: #808080; +} + +div.highlight .-Color[class*="-BGC8"] { + background-color: #808080; +} + +div.highlight .-Color[class*="-C9"] { + color: #f00; +} + +div.highlight .-Color[class*="-BGC9"] { + background-color: #f00; +} + +div.highlight .-Color[class*="-C10"] { + color: #0f0; +} + +div.highlight .-Color[class*="-BGC10"] { + background-color: #0f0; +} + +div.highlight .-Color[class*="-C11"] { + color: #ff0; +} + +div.highlight .-Color[class*="-BGC11"] { + background-color: #ff0; +} + +div.highlight .-Color[class*="-C12"] { + color: #00f; +} + +div.highlight .-Color[class*="-BGC12"] { + background-color: #00f; +} + +div.highlight .-Color[class*="-C13"] { + color: #f0f; +} + +div.highlight .-Color[class*="-BGC13"] { + background-color: #f0f; +} + +div.highlight .-Color[class*="-C14"] { + color: #0ff; +} + +div.highlight .-Color[class*="-BGC14"] { + background-color: #0ff; +} + +div.highlight .-Color[class*="-C15"] { + color: #fff; +} + +div.highlight .-Color[class*="-BGC15"] { + background-color: #fff; +} + +div.highlight .-Color[class*="-C16"] { + color: #000; +} + +div.highlight .-Color[class*="-BGC16"] { + background-color: #000; +} + +div.highlight .-Color[class*="-C17"] { + color: #00005f; +} + +div.highlight .-Color[class*="-BGC17"] { + background-color: #00005f; +} + +div.highlight .-Color[class*="-C18"] { + color: #000087; +} + +div.highlight .-Color[class*="-BGC18"] { + background-color: #000087; +} + +div.highlight .-Color[class*="-C19"] { + color: #0000af; +} + +div.highlight .-Color[class*="-BGC19"] { + background-color: #0000af; +} + +div.highlight .-Color[class*="-C20"] { + color: #0000d7; +} + +div.highlight .-Color[class*="-BGC20"] { + background-color: #0000d7; +} + +div.highlight .-Color[class*="-C21"] { + color: #00f; +} + +div.highlight .-Color[class*="-BGC21"] { + background-color: #00f; +} + +div.highlight .-Color[class*="-C22"] { + color: #005f00; +} + +div.highlight .-Color[class*="-BGC22"] { + background-color: #005f00; +} + +div.highlight .-Color[class*="-C23"] { + color: #005f5f; +} + +div.highlight .-Color[class*="-BGC23"] { + background-color: #005f5f; +} + +div.highlight .-Color[class*="-C24"] { + color: #005f87; +} + +div.highlight .-Color[class*="-BGC24"] { + background-color: #005f87; +} + +div.highlight .-Color[class*="-C25"] { + color: #005faf; +} + +div.highlight .-Color[class*="-BGC25"] { + background-color: #005faf; +} + +div.highlight .-Color[class*="-C26"] { + color: #005fd7; +} + +div.highlight .-Color[class*="-BGC26"] { + background-color: #005fd7; +} + +div.highlight .-Color[class*="-C27"] { + color: #005fff; +} + +div.highlight .-Color[class*="-BGC27"] { + background-color: #005fff; +} + +div.highlight .-Color[class*="-C28"] { + color: #008700; +} + +div.highlight .-Color[class*="-BGC28"] { + background-color: #008700; +} + +div.highlight .-Color[class*="-C29"] { + color: #00875f; +} + +div.highlight .-Color[class*="-BGC29"] { + background-color: #00875f; +} + +div.highlight .-Color[class*="-C30"] { + color: #008787; +} + +div.highlight .-Color[class*="-BGC30"] { + background-color: #008787; +} + +div.highlight .-Color[class*="-C31"] { + color: #0087af; +} + +div.highlight .-Color[class*="-BGC31"] { + background-color: #0087af; +} + +div.highlight .-Color[class*="-C32"] { + color: #0087d7; +} + +div.highlight .-Color[class*="-BGC32"] { + background-color: #0087d7; +} + +div.highlight .-Color[class*="-C33"] { + color: #0087ff; +} + +div.highlight .-Color[class*="-BGC33"] { + background-color: #0087ff; +} + +div.highlight .-Color[class*="-C34"] { + color: #00af00; +} + +div.highlight .-Color[class*="-BGC34"] { + background-color: #00af00; +} + +div.highlight .-Color[class*="-C35"] { + color: #00af5f; +} + +div.highlight .-Color[class*="-BGC35"] { + background-color: #00af5f; +} + +div.highlight .-Color[class*="-C36"] { + color: #00af87; +} + +div.highlight .-Color[class*="-BGC36"] { + background-color: #00af87; +} + +div.highlight .-Color[class*="-C37"] { + color: #00afaf; +} + +div.highlight .-Color[class*="-BGC37"] { + background-color: #00afaf; +} + +div.highlight .-Color[class*="-C38"] { + color: #00afd7; +} + +div.highlight .-Color[class*="-BGC38"] { + background-color: #00afd7; +} + +div.highlight .-Color[class*="-C39"] { + color: #00afff; +} + +div.highlight .-Color[class*="-BGC39"] { + background-color: #00afff; +} + +div.highlight .-Color[class*="-C40"] { + color: #00d700; +} + +div.highlight .-Color[class*="-BGC40"] { + background-color: #00d700; +} + +div.highlight .-Color[class*="-C41"] { + color: #00d75f; +} + +div.highlight .-Color[class*="-BGC41"] { + background-color: #00d75f; +} + +div.highlight .-Color[class*="-C42"] { + color: #00d787; +} + +div.highlight .-Color[class*="-BGC42"] { + background-color: #00d787; +} + +div.highlight .-Color[class*="-C43"] { + color: #00d7af; +} + +div.highlight .-Color[class*="-BGC43"] { + background-color: #00d7af; +} + +div.highlight .-Color[class*="-C44"] { + color: #00d7d7; +} + +div.highlight .-Color[class*="-BGC44"] { + background-color: #00d7d7; +} + +div.highlight .-Color[class*="-C45"] { + color: #00d7ff; +} + +div.highlight .-Color[class*="-BGC45"] { + background-color: #00d7ff; +} + +div.highlight .-Color[class*="-C46"] { + color: #0f0; +} + +div.highlight .-Color[class*="-BGC46"] { + background-color: #0f0; +} + +div.highlight .-Color[class*="-C47"] { + color: #00ff5f; +} + +div.highlight .-Color[class*="-BGC47"] { + background-color: #00ff5f; +} + +div.highlight .-Color[class*="-C48"] { + color: #00ff87; +} + +div.highlight .-Color[class*="-BGC48"] { + background-color: #00ff87; +} + +div.highlight .-Color[class*="-C49"] { + color: #00ffaf; +} + +div.highlight .-Color[class*="-BGC49"] { + background-color: #00ffaf; +} + +div.highlight .-Color[class*="-C50"] { + color: #00ffd7; +} + +div.highlight .-Color[class*="-BGC50"] { + background-color: #00ffd7; +} + +div.highlight .-Color[class*="-C51"] { + color: #0ff; +} + +div.highlight .-Color[class*="-BGC51"] { + background-color: #0ff; +} + +div.highlight .-Color[class*="-C52"] { + color: #5f0000; +} + +div.highlight .-Color[class*="-BGC52"] { + background-color: #5f0000; +} + +div.highlight .-Color[class*="-C53"] { + color: #5f005f; +} + +div.highlight .-Color[class*="-BGC53"] { + background-color: #5f005f; +} + +div.highlight .-Color[class*="-C54"] { + color: #5f0087; +} + +div.highlight .-Color[class*="-BGC54"] { + background-color: #5f0087; +} + +div.highlight .-Color[class*="-C55"] { + color: #5f00af; +} + +div.highlight .-Color[class*="-BGC55"] { + background-color: #5f00af; +} + +div.highlight .-Color[class*="-C56"] { + color: #5f00d7; +} + +div.highlight .-Color[class*="-BGC56"] { + background-color: #5f00d7; +} + +div.highlight .-Color[class*="-C57"] { + color: #5f00ff; +} + +div.highlight .-Color[class*="-BGC57"] { + background-color: #5f00ff; +} + +div.highlight .-Color[class*="-C58"] { + color: #5f5f00; +} + +div.highlight .-Color[class*="-BGC58"] { + background-color: #5f5f00; +} + +div.highlight .-Color[class*="-C59"] { + color: #5f5f5f; +} + +div.highlight .-Color[class*="-BGC59"] { + background-color: #5f5f5f; +} + +div.highlight .-Color[class*="-C60"] { + color: #5f5f87; +} + +div.highlight .-Color[class*="-BGC60"] { + background-color: #5f5f87; +} + +div.highlight .-Color[class*="-C61"] { + color: #5f5faf; +} + +div.highlight .-Color[class*="-BGC61"] { + background-color: #5f5faf; +} + +div.highlight .-Color[class*="-C62"] { + color: #5f5fd7; +} + +div.highlight .-Color[class*="-BGC62"] { + background-color: #5f5fd7; +} + +div.highlight .-Color[class*="-C63"] { + color: #5f5fff; +} + +div.highlight .-Color[class*="-BGC63"] { + background-color: #5f5fff; +} + +div.highlight .-Color[class*="-C64"] { + color: #5f8700; +} + +div.highlight .-Color[class*="-BGC64"] { + background-color: #5f8700; +} + +div.highlight .-Color[class*="-C65"] { + color: #5f875f; +} + +div.highlight .-Color[class*="-BGC65"] { + background-color: #5f875f; +} + +div.highlight .-Color[class*="-C66"] { + color: #5f8787; +} + +div.highlight .-Color[class*="-BGC66"] { + background-color: #5f8787; +} + +div.highlight .-Color[class*="-C67"] { + color: #5f87af; +} + +div.highlight .-Color[class*="-BGC67"] { + background-color: #5f87af; +} + +div.highlight .-Color[class*="-C68"] { + color: #5f87d7; +} + +div.highlight .-Color[class*="-BGC68"] { + background-color: #5f87d7; +} + +div.highlight .-Color[class*="-C69"] { + color: #5f87ff; +} + +div.highlight .-Color[class*="-BGC69"] { + background-color: #5f87ff; +} + +div.highlight .-Color[class*="-C70"] { + color: #5faf00; +} + +div.highlight .-Color[class*="-BGC70"] { + background-color: #5faf00; +} + +div.highlight .-Color[class*="-C71"] { + color: #5faf5f; +} + +div.highlight .-Color[class*="-BGC71"] { + background-color: #5faf5f; +} + +div.highlight .-Color[class*="-C72"] { + color: #5faf87; +} + +div.highlight .-Color[class*="-BGC72"] { + background-color: #5faf87; +} + +div.highlight .-Color[class*="-C73"] { + color: #5fafaf; +} + +div.highlight .-Color[class*="-BGC73"] { + background-color: #5fafaf; +} + +div.highlight .-Color[class*="-C74"] { + color: #5fafd7; +} + +div.highlight .-Color[class*="-BGC74"] { + background-color: #5fafd7; +} + +div.highlight .-Color[class*="-C75"] { + color: #5fafff; +} + +div.highlight .-Color[class*="-BGC75"] { + background-color: #5fafff; +} + +div.highlight .-Color[class*="-C76"] { + color: #5fd700; +} + +div.highlight .-Color[class*="-BGC76"] { + background-color: #5fd700; +} + +div.highlight .-Color[class*="-C77"] { + color: #5fd75f; +} + +div.highlight .-Color[class*="-BGC77"] { + background-color: #5fd75f; +} + +div.highlight .-Color[class*="-C78"] { + color: #5fd787; +} + +div.highlight .-Color[class*="-BGC78"] { + background-color: #5fd787; +} + +div.highlight .-Color[class*="-C79"] { + color: #5fd7af; +} + +div.highlight .-Color[class*="-BGC79"] { + background-color: #5fd7af; +} + +div.highlight .-Color[class*="-C80"] { + color: #5fd7d7; +} + +div.highlight .-Color[class*="-BGC80"] { + background-color: #5fd7d7; +} + +div.highlight .-Color[class*="-C81"] { + color: #5fd7ff; +} + +div.highlight .-Color[class*="-BGC81"] { + background-color: #5fd7ff; +} + +div.highlight .-Color[class*="-C82"] { + color: #5fff00; +} + +div.highlight .-Color[class*="-BGC82"] { + background-color: #5fff00; +} + +div.highlight .-Color[class*="-C83"] { + color: #5fff5f; +} + +div.highlight .-Color[class*="-BGC83"] { + background-color: #5fff5f; +} + +div.highlight .-Color[class*="-C84"] { + color: #5fff87; +} + +div.highlight .-Color[class*="-BGC84"] { + background-color: #5fff87; +} + +div.highlight .-Color[class*="-C85"] { + color: #5fffaf; +} + +div.highlight .-Color[class*="-BGC85"] { + background-color: #5fffaf; +} + +div.highlight .-Color[class*="-C86"] { + color: #5fffd7; +} + +div.highlight .-Color[class*="-BGC86"] { + background-color: #5fffd7; +} + +div.highlight .-Color[class*="-C87"] { + color: #5fffff; +} + +div.highlight .-Color[class*="-BGC87"] { + background-color: #5fffff; +} + +div.highlight .-Color[class*="-C88"] { + color: #870000; +} + +div.highlight .-Color[class*="-BGC88"] { + background-color: #870000; +} + +div.highlight .-Color[class*="-C89"] { + color: #87005f; +} + +div.highlight .-Color[class*="-BGC89"] { + background-color: #87005f; +} + +div.highlight .-Color[class*="-C90"] { + color: #870087; +} + +div.highlight .-Color[class*="-BGC90"] { + background-color: #870087; +} + +div.highlight .-Color[class*="-C91"] { + color: #8700af; +} + +div.highlight .-Color[class*="-BGC91"] { + background-color: #8700af; +} + +div.highlight .-Color[class*="-C92"] { + color: #8700d7; +} + +div.highlight .-Color[class*="-BGC92"] { + background-color: #8700d7; +} + +div.highlight .-Color[class*="-C93"] { + color: #8700ff; +} + +div.highlight .-Color[class*="-BGC93"] { + background-color: #8700ff; +} + +div.highlight .-Color[class*="-C94"] { + color: #875f00; +} + +div.highlight .-Color[class*="-BGC94"] { + background-color: #875f00; +} + +div.highlight .-Color[class*="-C95"] { + color: #875f5f; +} + +div.highlight .-Color[class*="-BGC95"] { + background-color: #875f5f; +} + +div.highlight .-Color[class*="-C96"] { + color: #875f87; +} + +div.highlight .-Color[class*="-BGC96"] { + background-color: #875f87; +} + +div.highlight .-Color[class*="-C97"] { + color: #875faf; +} + +div.highlight .-Color[class*="-BGC97"] { + background-color: #875faf; +} + +div.highlight .-Color[class*="-C98"] { + color: #875fd7; +} + +div.highlight .-Color[class*="-BGC98"] { + background-color: #875fd7; +} + +div.highlight .-Color[class*="-C99"] { + color: #875fff; +} + +div.highlight .-Color[class*="-BGC99"] { + background-color: #875fff; +} + +div.highlight .-Color[class*="-C100"] { + color: #878700; +} + +div.highlight .-Color[class*="-BGC100"] { + background-color: #878700; +} + +div.highlight .-Color[class*="-C101"] { + color: #87875f; +} + +div.highlight .-Color[class*="-BGC101"] { + background-color: #87875f; +} + +div.highlight .-Color[class*="-C102"] { + color: #878787; +} + +div.highlight .-Color[class*="-BGC102"] { + background-color: #878787; +} + +div.highlight .-Color[class*="-C103"] { + color: #8787af; +} + +div.highlight .-Color[class*="-BGC103"] { + background-color: #8787af; +} + +div.highlight .-Color[class*="-C104"] { + color: #8787d7; +} + +div.highlight .-Color[class*="-BGC104"] { + background-color: #8787d7; +} + +div.highlight .-Color[class*="-C105"] { + color: #8787ff; +} + +div.highlight .-Color[class*="-BGC105"] { + background-color: #8787ff; +} + +div.highlight .-Color[class*="-C106"] { + color: #87af00; +} + +div.highlight .-Color[class*="-BGC106"] { + background-color: #87af00; +} + +div.highlight .-Color[class*="-C107"] { + color: #87af5f; +} + +div.highlight .-Color[class*="-BGC107"] { + background-color: #87af5f; +} + +div.highlight .-Color[class*="-C108"] { + color: #87af87; +} + +div.highlight .-Color[class*="-BGC108"] { + background-color: #87af87; +} + +div.highlight .-Color[class*="-C109"] { + color: #87afaf; +} + +div.highlight .-Color[class*="-BGC109"] { + background-color: #87afaf; +} + +div.highlight .-Color[class*="-C110"] { + color: #87afd7; +} + +div.highlight .-Color[class*="-BGC110"] { + background-color: #87afd7; +} + +div.highlight .-Color[class*="-C111"] { + color: #87afff; +} + +div.highlight .-Color[class*="-BGC111"] { + background-color: #87afff; +} + +div.highlight .-Color[class*="-C112"] { + color: #87d700; +} + +div.highlight .-Color[class*="-BGC112"] { + background-color: #87d700; +} + +div.highlight .-Color[class*="-C113"] { + color: #87d75f; +} + +div.highlight .-Color[class*="-BGC113"] { + background-color: #87d75f; +} + +div.highlight .-Color[class*="-C114"] { + color: #87d787; +} + +div.highlight .-Color[class*="-BGC114"] { + background-color: #87d787; +} + +div.highlight .-Color[class*="-C115"] { + color: #87d7af; +} + +div.highlight .-Color[class*="-BGC115"] { + background-color: #87d7af; +} + +div.highlight .-Color[class*="-C116"] { + color: #87d7d7; +} + +div.highlight .-Color[class*="-BGC116"] { + background-color: #87d7d7; +} + +div.highlight .-Color[class*="-C117"] { + color: #87d7ff; +} + +div.highlight .-Color[class*="-BGC117"] { + background-color: #87d7ff; +} + +div.highlight .-Color[class*="-C118"] { + color: #87ff00; +} + +div.highlight .-Color[class*="-BGC118"] { + background-color: #87ff00; +} + +div.highlight .-Color[class*="-C119"] { + color: #87ff5f; +} + +div.highlight .-Color[class*="-BGC119"] { + background-color: #87ff5f; +} + +div.highlight .-Color[class*="-C120"] { + color: #87ff87; +} + +div.highlight .-Color[class*="-BGC120"] { + background-color: #87ff87; +} + +div.highlight .-Color[class*="-C121"] { + color: #87ffaf; +} + +div.highlight .-Color[class*="-BGC121"] { + background-color: #87ffaf; +} + +div.highlight .-Color[class*="-C122"] { + color: #87ffd7; +} + +div.highlight .-Color[class*="-BGC122"] { + background-color: #87ffd7; +} + +div.highlight .-Color[class*="-C123"] { + color: #87ffff; +} + +div.highlight .-Color[class*="-BGC123"] { + background-color: #87ffff; +} + +div.highlight .-Color[class*="-C124"] { + color: #af0000; +} + +div.highlight .-Color[class*="-BGC124"] { + background-color: #af0000; +} + +div.highlight .-Color[class*="-C125"] { + color: #af005f; +} + +div.highlight .-Color[class*="-BGC125"] { + background-color: #af005f; +} + +div.highlight .-Color[class*="-C126"] { + color: #af0087; +} + +div.highlight .-Color[class*="-BGC126"] { + background-color: #af0087; +} + +div.highlight .-Color[class*="-C127"] { + color: #af00af; +} + +div.highlight .-Color[class*="-BGC127"] { + background-color: #af00af; +} + +div.highlight .-Color[class*="-C128"] { + color: #af00d7; +} + +div.highlight .-Color[class*="-BGC128"] { + background-color: #af00d7; +} + +div.highlight .-Color[class*="-C129"] { + color: #af00ff; +} + +div.highlight .-Color[class*="-BGC129"] { + background-color: #af00ff; +} + +div.highlight .-Color[class*="-C130"] { + color: #af5f00; +} + +div.highlight .-Color[class*="-BGC130"] { + background-color: #af5f00; +} + +div.highlight .-Color[class*="-C131"] { + color: #af5f5f; +} + +div.highlight .-Color[class*="-BGC131"] { + background-color: #af5f5f; +} + +div.highlight .-Color[class*="-C132"] { + color: #af5f87; +} + +div.highlight .-Color[class*="-BGC132"] { + background-color: #af5f87; +} + +div.highlight .-Color[class*="-C133"] { + color: #af5faf; +} + +div.highlight .-Color[class*="-BGC133"] { + background-color: #af5faf; +} + +div.highlight .-Color[class*="-C134"] { + color: #af5fd7; +} + +div.highlight .-Color[class*="-BGC134"] { + background-color: #af5fd7; +} + +div.highlight .-Color[class*="-C135"] { + color: #af5fff; +} + +div.highlight .-Color[class*="-BGC135"] { + background-color: #af5fff; +} + +div.highlight .-Color[class*="-C136"] { + color: #af8700; +} + +div.highlight .-Color[class*="-BGC136"] { + background-color: #af8700; +} + +div.highlight .-Color[class*="-C137"] { + color: #af875f; +} + +div.highlight .-Color[class*="-BGC137"] { + background-color: #af875f; +} + +div.highlight .-Color[class*="-C138"] { + color: #af8787; +} + +div.highlight .-Color[class*="-BGC138"] { + background-color: #af8787; +} + +div.highlight .-Color[class*="-C139"] { + color: #af87af; +} + +div.highlight .-Color[class*="-BGC139"] { + background-color: #af87af; +} + +div.highlight .-Color[class*="-C140"] { + color: #af87d7; +} + +div.highlight .-Color[class*="-BGC140"] { + background-color: #af87d7; +} + +div.highlight .-Color[class*="-C141"] { + color: #af87ff; +} + +div.highlight .-Color[class*="-BGC141"] { + background-color: #af87ff; +} + +div.highlight .-Color[class*="-C142"] { + color: #afaf00; +} + +div.highlight .-Color[class*="-BGC142"] { + background-color: #afaf00; +} + +div.highlight .-Color[class*="-C143"] { + color: #afaf5f; +} + +div.highlight .-Color[class*="-BGC143"] { + background-color: #afaf5f; +} + +div.highlight .-Color[class*="-C144"] { + color: #afaf87; +} + +div.highlight .-Color[class*="-BGC144"] { + background-color: #afaf87; +} + +div.highlight .-Color[class*="-C145"] { + color: #afafaf; +} + +div.highlight .-Color[class*="-BGC145"] { + background-color: #afafaf; +} + +div.highlight .-Color[class*="-C146"] { + color: #afafd7; +} + +div.highlight .-Color[class*="-BGC146"] { + background-color: #afafd7; +} + +div.highlight .-Color[class*="-C147"] { + color: #afafff; +} + +div.highlight .-Color[class*="-BGC147"] { + background-color: #afafff; +} + +div.highlight .-Color[class*="-C148"] { + color: #afd700; +} + +div.highlight .-Color[class*="-BGC148"] { + background-color: #afd700; +} + +div.highlight .-Color[class*="-C149"] { + color: #afd75f; +} + +div.highlight .-Color[class*="-BGC149"] { + background-color: #afd75f; +} + +div.highlight .-Color[class*="-C150"] { + color: #afd787; +} + +div.highlight .-Color[class*="-BGC150"] { + background-color: #afd787; +} + +div.highlight .-Color[class*="-C151"] { + color: #afd7af; +} + +div.highlight .-Color[class*="-BGC151"] { + background-color: #afd7af; +} + +div.highlight .-Color[class*="-C152"] { + color: #afd7d7; +} + +div.highlight .-Color[class*="-BGC152"] { + background-color: #afd7d7; +} + +div.highlight .-Color[class*="-C153"] { + color: #afd7ff; +} + +div.highlight .-Color[class*="-BGC153"] { + background-color: #afd7ff; +} + +div.highlight .-Color[class*="-C154"] { + color: #afff00; +} + +div.highlight .-Color[class*="-BGC154"] { + background-color: #afff00; +} + +div.highlight .-Color[class*="-C155"] { + color: #afff5f; +} + +div.highlight .-Color[class*="-BGC155"] { + background-color: #afff5f; +} + +div.highlight .-Color[class*="-C156"] { + color: #afff87; +} + +div.highlight .-Color[class*="-BGC156"] { + background-color: #afff87; +} + +div.highlight .-Color[class*="-C157"] { + color: #afffaf; +} + +div.highlight .-Color[class*="-BGC157"] { + background-color: #afffaf; +} + +div.highlight .-Color[class*="-C158"] { + color: #afffd7; +} + +div.highlight .-Color[class*="-BGC158"] { + background-color: #afffd7; +} + +div.highlight .-Color[class*="-C159"] { + color: #afffff; +} + +div.highlight .-Color[class*="-BGC159"] { + background-color: #afffff; +} + +div.highlight .-Color[class*="-C160"] { + color: #d70000; +} + +div.highlight .-Color[class*="-BGC160"] { + background-color: #d70000; +} + +div.highlight .-Color[class*="-C161"] { + color: #d7005f; +} + +div.highlight .-Color[class*="-BGC161"] { + background-color: #d7005f; +} + +div.highlight .-Color[class*="-C162"] { + color: #d70087; +} + +div.highlight .-Color[class*="-BGC162"] { + background-color: #d70087; +} + +div.highlight .-Color[class*="-C163"] { + color: #d700af; +} + +div.highlight .-Color[class*="-BGC163"] { + background-color: #d700af; +} + +div.highlight .-Color[class*="-C164"] { + color: #d700d7; +} + +div.highlight .-Color[class*="-BGC164"] { + background-color: #d700d7; +} + +div.highlight .-Color[class*="-C165"] { + color: #d700ff; +} + +div.highlight .-Color[class*="-BGC165"] { + background-color: #d700ff; +} + +div.highlight .-Color[class*="-C166"] { + color: #d75f00; +} + +div.highlight .-Color[class*="-BGC166"] { + background-color: #d75f00; +} + +div.highlight .-Color[class*="-C167"] { + color: #d75f5f; +} + +div.highlight .-Color[class*="-BGC167"] { + background-color: #d75f5f; +} + +div.highlight .-Color[class*="-C168"] { + color: #d75f87; +} + +div.highlight .-Color[class*="-BGC168"] { + background-color: #d75f87; +} + +div.highlight .-Color[class*="-C169"] { + color: #d75faf; +} + +div.highlight .-Color[class*="-BGC169"] { + background-color: #d75faf; +} + +div.highlight .-Color[class*="-C170"] { + color: #d75fd7; +} + +div.highlight .-Color[class*="-BGC170"] { + background-color: #d75fd7; +} + +div.highlight .-Color[class*="-C171"] { + color: #d75fff; +} + +div.highlight .-Color[class*="-BGC171"] { + background-color: #d75fff; +} + +div.highlight .-Color[class*="-C172"] { + color: #d78700; +} + +div.highlight .-Color[class*="-BGC172"] { + background-color: #d78700; +} + +div.highlight .-Color[class*="-C173"] { + color: #d7875f; +} + +div.highlight .-Color[class*="-BGC173"] { + background-color: #d7875f; +} + +div.highlight .-Color[class*="-C174"] { + color: #d78787; +} + +div.highlight .-Color[class*="-BGC174"] { + background-color: #d78787; +} + +div.highlight .-Color[class*="-C175"] { + color: #d787af; +} + +div.highlight .-Color[class*="-BGC175"] { + background-color: #d787af; +} + +div.highlight .-Color[class*="-C176"] { + color: #d787d7; +} + +div.highlight .-Color[class*="-BGC176"] { + background-color: #d787d7; +} + +div.highlight .-Color[class*="-C177"] { + color: #d787ff; +} + +div.highlight .-Color[class*="-BGC177"] { + background-color: #d787ff; +} + +div.highlight .-Color[class*="-C178"] { + color: #d7af00; +} + +div.highlight .-Color[class*="-BGC178"] { + background-color: #d7af00; +} + +div.highlight .-Color[class*="-C179"] { + color: #d7af5f; +} + +div.highlight .-Color[class*="-BGC179"] { + background-color: #d7af5f; +} + +div.highlight .-Color[class*="-C180"] { + color: #d7af87; +} + +div.highlight .-Color[class*="-BGC180"] { + background-color: #d7af87; +} + +div.highlight .-Color[class*="-C181"] { + color: #d7afaf; +} + +div.highlight .-Color[class*="-BGC181"] { + background-color: #d7afaf; +} + +div.highlight .-Color[class*="-C182"] { + color: #d7afd7; +} + +div.highlight .-Color[class*="-BGC182"] { + background-color: #d7afd7; +} + +div.highlight .-Color[class*="-C183"] { + color: #d7afff; +} + +div.highlight .-Color[class*="-BGC183"] { + background-color: #d7afff; +} + +div.highlight .-Color[class*="-C184"] { + color: #d7d700; +} + +div.highlight .-Color[class*="-BGC184"] { + background-color: #d7d700; +} + +div.highlight .-Color[class*="-C185"] { + color: #d7d75f; +} + +div.highlight .-Color[class*="-BGC185"] { + background-color: #d7d75f; +} + +div.highlight .-Color[class*="-C186"] { + color: #d7d787; +} + +div.highlight .-Color[class*="-BGC186"] { + background-color: #d7d787; +} + +div.highlight .-Color[class*="-C187"] { + color: #d7d7af; +} + +div.highlight .-Color[class*="-BGC187"] { + background-color: #d7d7af; +} + +div.highlight .-Color[class*="-C188"] { + color: #d7d7d7; +} + +div.highlight .-Color[class*="-BGC188"] { + background-color: #d7d7d7; +} + +div.highlight .-Color[class*="-C189"] { + color: #d7d7ff; +} + +div.highlight .-Color[class*="-BGC189"] { + background-color: #d7d7ff; +} + +div.highlight .-Color[class*="-C190"] { + color: #d7ff00; +} + +div.highlight .-Color[class*="-BGC190"] { + background-color: #d7ff00; +} + +div.highlight .-Color[class*="-C191"] { + color: #d7ff5f; +} + +div.highlight .-Color[class*="-BGC191"] { + background-color: #d7ff5f; +} + +div.highlight .-Color[class*="-C192"] { + color: #d7ff87; +} + +div.highlight .-Color[class*="-BGC192"] { + background-color: #d7ff87; +} + +div.highlight .-Color[class*="-C193"] { + color: #d7ffaf; +} + +div.highlight .-Color[class*="-BGC193"] { + background-color: #d7ffaf; +} + +div.highlight .-Color[class*="-C194"] { + color: #d7ffd7; +} + +div.highlight .-Color[class*="-BGC194"] { + background-color: #d7ffd7; +} + +div.highlight .-Color[class*="-C195"] { + color: #d7ffff; +} + +div.highlight .-Color[class*="-BGC195"] { + background-color: #d7ffff; +} + +div.highlight .-Color[class*="-C196"] { + color: #f00; +} + +div.highlight .-Color[class*="-BGC196"] { + background-color: #f00; +} + +div.highlight .-Color[class*="-C197"] { + color: #ff005f; +} + +div.highlight .-Color[class*="-BGC197"] { + background-color: #ff005f; +} + +div.highlight .-Color[class*="-C198"] { + color: #ff0087; +} + +div.highlight .-Color[class*="-BGC198"] { + background-color: #ff0087; +} + +div.highlight .-Color[class*="-C199"] { + color: #ff00af; +} + +div.highlight .-Color[class*="-BGC199"] { + background-color: #ff00af; +} + +div.highlight .-Color[class*="-C200"] { + color: #ff00d7; +} + +div.highlight .-Color[class*="-BGC200"] { + background-color: #ff00d7; +} + +div.highlight .-Color[class*="-C201"] { + color: #f0f; +} + +div.highlight .-Color[class*="-BGC201"] { + background-color: #f0f; +} + +div.highlight .-Color[class*="-C202"] { + color: #ff5f00; +} + +div.highlight .-Color[class*="-BGC202"] { + background-color: #ff5f00; +} + +div.highlight .-Color[class*="-C203"] { + color: #ff5f5f; +} + +div.highlight .-Color[class*="-BGC203"] { + background-color: #ff5f5f; +} + +div.highlight .-Color[class*="-C204"] { + color: #ff5f87; +} + +div.highlight .-Color[class*="-BGC204"] { + background-color: #ff5f87; +} + +div.highlight .-Color[class*="-C205"] { + color: #ff5faf; +} + +div.highlight .-Color[class*="-BGC205"] { + background-color: #ff5faf; +} + +div.highlight .-Color[class*="-C206"] { + color: #ff5fd7; +} + +div.highlight .-Color[class*="-BGC206"] { + background-color: #ff5fd7; +} + +div.highlight .-Color[class*="-C207"] { + color: #ff5fff; +} + +div.highlight .-Color[class*="-BGC207"] { + background-color: #ff5fff; +} + +div.highlight .-Color[class*="-C208"] { + color: #ff8700; +} + +div.highlight .-Color[class*="-BGC208"] { + background-color: #ff8700; +} + +div.highlight .-Color[class*="-C209"] { + color: #ff875f; +} + +div.highlight .-Color[class*="-BGC209"] { + background-color: #ff875f; +} + +div.highlight .-Color[class*="-C210"] { + color: #ff8787; +} + +div.highlight .-Color[class*="-BGC210"] { + background-color: #ff8787; +} + +div.highlight .-Color[class*="-C211"] { + color: #ff87af; +} + +div.highlight .-Color[class*="-BGC211"] { + background-color: #ff87af; +} + +div.highlight .-Color[class*="-C212"] { + color: #ff87d7; +} + +div.highlight .-Color[class*="-BGC212"] { + background-color: #ff87d7; +} + +div.highlight .-Color[class*="-C213"] { + color: #ff87ff; +} + +div.highlight .-Color[class*="-BGC213"] { + background-color: #ff87ff; +} + +div.highlight .-Color[class*="-C214"] { + color: #ffaf00; +} + +div.highlight .-Color[class*="-BGC214"] { + background-color: #ffaf00; +} + +div.highlight .-Color[class*="-C215"] { + color: #ffaf5f; +} + +div.highlight .-Color[class*="-BGC215"] { + background-color: #ffaf5f; +} + +div.highlight .-Color[class*="-C216"] { + color: #ffaf87; +} + +div.highlight .-Color[class*="-BGC216"] { + background-color: #ffaf87; +} + +div.highlight .-Color[class*="-C217"] { + color: #ffafaf; +} + +div.highlight .-Color[class*="-BGC217"] { + background-color: #ffafaf; +} + +div.highlight .-Color[class*="-C218"] { + color: #ffafd7; +} + +div.highlight .-Color[class*="-BGC218"] { + background-color: #ffafd7; +} + +div.highlight .-Color[class*="-C219"] { + color: #ffafff; +} + +div.highlight .-Color[class*="-BGC219"] { + background-color: #ffafff; +} + +div.highlight .-Color[class*="-C220"] { + color: #ffd700; +} + +div.highlight .-Color[class*="-BGC220"] { + background-color: #ffd700; +} + +div.highlight .-Color[class*="-C221"] { + color: #ffd75f; +} + +div.highlight .-Color[class*="-BGC221"] { + background-color: #ffd75f; +} + +div.highlight .-Color[class*="-C222"] { + color: #ffd787; +} + +div.highlight .-Color[class*="-BGC222"] { + background-color: #ffd787; +} + +div.highlight .-Color[class*="-C223"] { + color: #ffd7af; +} + +div.highlight .-Color[class*="-BGC223"] { + background-color: #ffd7af; +} + +div.highlight .-Color[class*="-C224"] { + color: #ffd7d7; +} + +div.highlight .-Color[class*="-BGC224"] { + background-color: #ffd7d7; +} + +div.highlight .-Color[class*="-C225"] { + color: #ffd7ff; +} + +div.highlight .-Color[class*="-BGC225"] { + background-color: #ffd7ff; +} + +div.highlight .-Color[class*="-C226"] { + color: #ff0; +} + +div.highlight .-Color[class*="-BGC226"] { + background-color: #ff0; +} + +div.highlight .-Color[class*="-C227"] { + color: #ffff5f; +} + +div.highlight .-Color[class*="-BGC227"] { + background-color: #ffff5f; +} + +div.highlight .-Color[class*="-C228"] { + color: #ffff87; +} + +div.highlight .-Color[class*="-BGC228"] { + background-color: #ffff87; +} + +div.highlight .-Color[class*="-C229"] { + color: #ffffaf; +} + +div.highlight .-Color[class*="-BGC229"] { + background-color: #ffffaf; +} + +div.highlight .-Color[class*="-C230"] { + color: #ffffd7; +} + +div.highlight .-Color[class*="-BGC230"] { + background-color: #ffffd7; +} + +div.highlight .-Color[class*="-C231"] { + color: #fff; +} + +div.highlight .-Color[class*="-BGC231"] { + background-color: #fff; +} + +div.highlight .-Color[class*="-C232"] { + color: #080808; +} + +div.highlight .-Color[class*="-BGC232"] { + background-color: #080808; +} + +div.highlight .-Color[class*="-C233"] { + color: #121212; +} + +div.highlight .-Color[class*="-BGC233"] { + background-color: #121212; +} + +div.highlight .-Color[class*="-C234"] { + color: #1c1c1c; +} + +div.highlight .-Color[class*="-BGC234"] { + background-color: #1c1c1c; +} + +div.highlight .-Color[class*="-C235"] { + color: #262626; +} + +div.highlight .-Color[class*="-BGC235"] { + background-color: #262626; +} + +div.highlight .-Color[class*="-C236"] { + color: #303030; +} + +div.highlight .-Color[class*="-BGC236"] { + background-color: #303030; +} + +div.highlight .-Color[class*="-C237"] { + color: #3a3a3a; +} + +div.highlight .-Color[class*="-BGC237"] { + background-color: #3a3a3a; +} + +div.highlight .-Color[class*="-C238"] { + color: #444; +} + +div.highlight .-Color[class*="-BGC238"] { + background-color: #444; +} + +div.highlight .-Color[class*="-C239"] { + color: #4e4e4e; +} + +div.highlight .-Color[class*="-BGC239"] { + background-color: #4e4e4e; +} + +div.highlight .-Color[class*="-C240"] { + color: #585858; +} + +div.highlight .-Color[class*="-BGC240"] { + background-color: #585858; +} + +div.highlight .-Color[class*="-C241"] { + color: #626262; +} + +div.highlight .-Color[class*="-BGC241"] { + background-color: #626262; +} + +div.highlight .-Color[class*="-C242"] { + color: #6c6c6c; +} + +div.highlight .-Color[class*="-BGC242"] { + background-color: #6c6c6c; +} + +div.highlight .-Color[class*="-C243"] { + color: #767676; +} + +div.highlight .-Color[class*="-BGC243"] { + background-color: #767676; +} + +div.highlight .-Color[class*="-C244"] { + color: #808080; +} + +div.highlight .-Color[class*="-BGC244"] { + background-color: #808080; +} + +div.highlight .-Color[class*="-C245"] { + color: #8a8a8a; +} + +div.highlight .-Color[class*="-BGC245"] { + background-color: #8a8a8a; +} + +div.highlight .-Color[class*="-C246"] { + color: #949494; +} + +div.highlight .-Color[class*="-BGC246"] { + background-color: #949494; +} + +div.highlight .-Color[class*="-C247"] { + color: #9e9e9e; +} + +div.highlight .-Color[class*="-BGC247"] { + background-color: #9e9e9e; +} + +div.highlight .-Color[class*="-C248"] { + color: #a8a8a8; +} + +div.highlight .-Color[class*="-BGC248"] { + background-color: #a8a8a8; +} + +div.highlight .-Color[class*="-C249"] { + color: #b2b2b2; +} + +div.highlight .-Color[class*="-BGC249"] { + background-color: #b2b2b2; +} + +div.highlight .-Color[class*="-C250"] { + color: #bcbcbc; +} + +div.highlight .-Color[class*="-BGC250"] { + background-color: #bcbcbc; +} + +div.highlight .-Color[class*="-C251"] { + color: #c6c6c6; +} + +div.highlight .-Color[class*="-BGC251"] { + background-color: #c6c6c6; +} + +div.highlight .-Color[class*="-C252"] { + color: #d0d0d0; +} + +div.highlight .-Color[class*="-BGC252"] { + background-color: #d0d0d0; +} + +div.highlight .-Color[class*="-C253"] { + color: #dadada; +} + +div.highlight .-Color[class*="-BGC253"] { + background-color: #dadada; +} + +div.highlight .-Color[class*="-C254"] { + color: #e4e4e4; +} + +div.highlight .-Color[class*="-BGC254"] { + background-color: #e4e4e4; +} + +div.highlight .-Color[class*="-C255"] { + color: #eee; +} + +div.highlight .-Color[class*="-BGC255"] { + background-color: #eee; +} diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/nbsphinx-pydata-theme.scss b/src/pydata_sphinx_theme/assets/styles/extensions/nbsphinx-pydata-theme.scss new file mode 100644 index 0000000000..9f1f3de4ae --- /dev/null +++ b/src/pydata_sphinx_theme/assets/styles/extensions/nbsphinx-pydata-theme.scss @@ -0,0 +1,375 @@ +/******************************************************************************* + * PyData Sphinx Theme replacement stylesheet for nbsphinx + * + * nbsphinx is a Sphinx extension for turning Jupyter notebooks (.ipynb) into + * doc pages. + * + * This stylesheet is a replacement for nbsphinx's default stylesheet, + * nbsphinx-code-cells.css. Some of the style rules in this file were copied + * from there. + * + * This file implements Isabela's design for notebook code cells: + * https://github.com/Quansight-Labs/czi-scientific-python-mgmt/issues/142#issuecomment-2759132381. + */ + +@import "../abstracts/all"; + +$space-between-vertical-bar-and-cell: 1em; +$top-and-bottom-container-space: 1.2em; + +// Common styles for both input and output containers +div.nbinput.container, +div.nboutput.container { + padding-left: $space-between-vertical-bar-and-cell; + + > div[class*="highlight"] { + margin: 0; // Override Sphinx base styles + } + + // Common styles for the both input and output labels + &::before { + display: block; + color: var(--pst-color-text-base); + font-family: var(--pst-font-family-monospace-system); + font-size: 0.875em; // * 16px = 14px (14px monospace font looks about the same size as 16px regular font) + line-height: 1; // align with top of container + margin-bottom: 0.7em; // space between label and in/output + } + + // Vertical bar (runs down the left, connects input to output) + position: relative; // must set position relative in order to set position absolute on ::after pseudo-element + &::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 0.2em; + background: var(--pst-color-primary); + + // TODO: figure out a way to create a single vertical bar that runs the + // entire height of both the input and output cells, rather than two + // stacked bars, that way we can add border radius to the ends without it + // looking funky. + + // border-radius: 1px; + } + + // Hide the label that nbsphinx generates. We add our own. + div.prompt { + display: none; + } +} + +// Styles for input containers (one per notebook code cell) +div.nbinput.container { + margin-top: $top-and-bottom-container-space; // space between the top of the notebook cell and the surrounding content + + // Label for input + &::before { + content: "In:"; + } + + div.input_area { + border: none; // border taken care of by
 styles elsewhere in the theme
+    overflow: visible;
+
+    > div.highlight {
+      overflow: visible; // necessary for focus ring
+    }
+  }
+}
+
+// Styles for output containers (0, 1, or more per notebook code cell)
+div.nboutput.container {
+  // Space between the input portion and the output portion. Cannot use margin
+  // here because we do not want to create a break in the vertical bar running
+  // down the left side.
+  padding-top: $top-and-bottom-container-space;
+
+  /* Label for output
+   *
+   * Why the next sibling (+) selector? The way that nbsphinx converts notebook
+   * code cells, they have exactly one div.nbinput but can have 0, 1 or multiple
+   * div.nboutput. We only want one "Out" label and we want it above the first
+   * .nboutput that follows a .nbinput.
+   */
+  div.nbinput.container + &::before {
+    content: "Out:";
+  }
+
+  div.output_area {
+    // What this selector attempts to do is to override a bunch of CSS rules
+    // that come from multiple places that add top and bottom whitespace to elements,
+    // like paragraph elements. These rules mess up the alignment between the
+    // bottom of the notebook cell content and the left bar that we've added.
+    // They also create unnecessary extra whitespace at the bottom of notebook
+    // cells. However, this selector may be a bit too wide, too blunt. We may
+    // need find a different way to achieve this.
+    :last-child:not(.highlight, .widget-tab-contents, .jupyter-widgets) {
+      margin-top: 0;
+      margin-bottom: 0;
+    }
+
+    &.rendered_html {
+      overflow-x: auto;
+    }
+
+    &.stderr {
+      div[class*="highlight"] > pre {
+        background-color: var(--pst-color-danger-bg);
+      }
+    }
+
+    // Override our own styles in _math.scss
+    div.math {
+      display: block;
+      overflow: visible;
+    }
+
+    span.math {
+      display: inline;
+    }
+
+    /* Some of these rules were taken from the Jupyter notebook CSS */
+    &.rendered_html,
+    .jp-RenderedHTMLCommon {
+      table {
+        border: none;
+        border-collapse: collapse;
+        border-spacing: 0;
+        color: black;
+        font-size: 12px;
+
+        thead {
+          border-bottom: 1px solid black;
+          vertical-align: bottom;
+        }
+
+        tr,
+        th,
+        td {
+          text-align: right;
+          vertical-align: middle;
+          padding: 0.5em;
+          line-height: normal;
+          white-space: normal;
+          max-width: none;
+          border: none;
+        }
+
+        th {
+          font-weight: bold;
+        }
+
+        tbody tr:nth-child(odd) {
+          background: #131111;
+        }
+
+        tbody tr:hover {
+          background: rgb(66 165 245 / 20%);
+        }
+
+        // pandas
+        &.dataframe {
+          @include table-colors;
+
+          tbody {
+            tr:hover {
+              background-color: var(--pst-color-table-row-hover-bg);
+            }
+          }
+        }
+      }
+    }
+
+    .widget-slider {
+      // Fix accessibility contrast failure. (This was added primarily to
+      // prevent the theme docs from having an accessibility violation, but
+      // putting it here extends this fix to other sites that use this theme.)
+      .noUi-handle {
+        border-color: var(--pst-gray-600);
+      }
+
+      .noUi-connects {
+        background: var(--pst-gray-600);
+      }
+    }
+
+    // Special cases, dark mode
+    &.rendered_html:not(
+      :has(
+        /* exclude pandas because we have styled it for dark mode */
+        table.dataframe,
+        /* exclude Xarray because it has been styled for dark mode */
+        .xr-wrap
+      )
+    ),
+    // ipywidgets
+    .widget-subarea {
+      @include cell-output-force-light-background;
+    }
+  }
+}
+
+// Styles for the last container in the group that represents a notebook code
+// cell. Examples:
+// - Code cell with only input:
+//   - div.nbinput.nblast
+// - Code cell with input and output:
+//   - div.nbinput
+//   - div.nboutput.nblast
+// - Code cell with one input and multiple output containers:
+//   - div.nbinput
+//   - div.nboutput
+//   - div.nboutput.nblast
+div.container.nblast {
+  margin-bottom: $top-and-bottom-container-space;
+}
+
+/*
+ * Syntax highlighting for console/terminal output
+ */
+.ansi-black-fg {
+  color: #3e424d;
+}
+
+.ansi-black-bg {
+  background-color: #3e424d;
+}
+
+.ansi-black-intense-fg {
+  color: #282c36;
+}
+
+.ansi-black-intense-bg {
+  background-color: #282c36;
+}
+
+.ansi-red-fg {
+  color: #e75c58;
+}
+
+.ansi-red-bg {
+  background-color: #e75c58;
+}
+
+.ansi-red-intense-fg {
+  color: #b22b31;
+}
+
+.ansi-red-intense-bg {
+  background-color: #b22b31;
+}
+
+.ansi-green-fg {
+  color: #00a250;
+}
+
+.ansi-green-bg {
+  background-color: #00a250;
+}
+
+.ansi-green-intense-fg {
+  color: #007427;
+}
+
+.ansi-green-intense-bg {
+  background-color: #007427;
+}
+
+.ansi-yellow-fg {
+  color: #ddb62b;
+}
+
+.ansi-yellow-bg {
+  background-color: #ddb62b;
+}
+
+.ansi-yellow-intense-fg {
+  color: #b27d12;
+}
+
+.ansi-yellow-intense-bg {
+  background-color: #b27d12;
+}
+
+.ansi-blue-fg {
+  color: #208ffb;
+}
+
+.ansi-blue-bg {
+  background-color: #208ffb;
+}
+
+.ansi-blue-intense-fg {
+  color: #0065ca;
+}
+
+.ansi-blue-intense-bg {
+  background-color: #0065ca;
+}
+
+.ansi-magenta-fg {
+  color: #d160c4;
+}
+
+.ansi-magenta-bg {
+  background-color: #d160c4;
+}
+
+.ansi-magenta-intense-fg {
+  color: #a03196;
+}
+
+.ansi-magenta-intense-bg {
+  background-color: #a03196;
+}
+
+.ansi-cyan-fg {
+  color: #60c6c8;
+}
+
+.ansi-cyan-bg {
+  background-color: #60c6c8;
+}
+
+.ansi-cyan-intense-fg {
+  color: #258f8f;
+}
+
+.ansi-cyan-intense-bg {
+  background-color: #258f8f;
+}
+
+.ansi-white-fg {
+  color: #c5c1b4;
+}
+
+.ansi-white-bg {
+  background-color: #c5c1b4;
+}
+
+.ansi-white-intense-fg {
+  color: #a1a6b2;
+}
+
+.ansi-white-intense-bg {
+  background-color: #a1a6b2;
+}
+
+.ansi-default-inverse-fg {
+  color: #fff;
+}
+
+.ansi-default-inverse-bg {
+  background-color: #000;
+}
+
+.ansi-bold {
+  font-weight: bold;
+}
+
+.ansi-underline {
+  text-decoration: underline;
+}
diff --git a/src/pydata_sphinx_theme/assets/styles/pydata-sphinx-theme.scss b/src/pydata_sphinx_theme/assets/styles/pydata-sphinx-theme.scss
index bf4573aee2..8baf53832b 100644
--- a/src/pydata_sphinx_theme/assets/styles/pydata-sphinx-theme.scss
+++ b/src/pydata_sphinx_theme/assets/styles/pydata-sphinx-theme.scss
@@ -1,7 +1,7 @@
 // IMPORTANT: our Bootstrap variable overrides must be imported before the
 // Bootstrap files that reference those variables; otherwise our overrides will
 // have no effect.
-@import "variables/bootstrap";
+@import "abstracts/all";
 
 // Import all of Bootstrap scss.
 @import "~bootstrap/scss/bootstrap";
@@ -12,7 +12,7 @@
 @import "~@fortawesome/fontawesome-free/scss/regular";
 @import "~@fortawesome/fontawesome-free/scss/brands";
 
-// Variables
+// These files output *CSS* variables (not to be confused with SCSS variables)
 @import "variables/layout";
 @import "variables/fonts";
 @import "variables/icons";
@@ -20,9 +20,6 @@
 @import "variables/versionmodified";
 @import "variables/color";
 
-// re-usable SCSS functions / classes
-@import "abstracts/all";
-
 // Basic styling applied throughout site
 @import "./base/base";
 
@@ -81,7 +78,6 @@
 @import "./extensions/pydata";
 @import "./extensions/sphinx_design";
 @import "./extensions/togglebutton";
-@import "./extensions/notebooks";
 @import "./extensions/leaflet";
 
 // Page-specific CSS
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
index 730468ff06..267911937b 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss
@@ -1,89 +1,7 @@
-/*******************************************************************************
-* master color map. Only the colors that actually differ between light and dark
-* themes are specified separately.
-*
-* To see the full list of colors see https://www.figma.com/file/rUrrHGhUBBIAAjQ82x6pz9/PyData-Design-system---proposal-for-implementation-(2)?node-id=1234%3A765&t=ifcFT1JtnrSshGfi-1
-*/
-
 @use "sass:map";
 @use "sass:meta";
 @use "sass:string";
 
-/**
-* Function to get items from nested maps
-*/
-// @param {Map} $map - Map
-// @param {Arglist} $keys - Keys to fetch
-// @return {*}
-@function map-deep-get($map, $keys...) {
-  @each $key in $keys {
-    $map: map.get($map, $key);
-  }
-
-  @return $map;
-}
-
-/* Assign base colors for the PyData theme */
-$color-palette: (
-  // Primary color
-  "teal": (
-      "50": #f4fbfc,
-      "100": #e9f6f8,
-      "200": #d0ecf1,
-      "300": #abdde6,
-      "400": #3fb1c5,
-      "500": #0a7d91,
-      "600": #085d6c,
-      "700": #064752,
-      "800": #042c33,
-      "900": #021b1f,
-    ),
-  // Secondary color
-  "violet": (
-      "50": #f4eefb,
-      "100": #e0c7ff,
-      "200": #d5b4fd,
-      "300": #b780ff,
-      "400": #9c5ffd,
-      "500": #8045e5,
-      "600": #6432bd,
-      "700": #4b258f,
-      "800": #341a61,
-      "900": #1e0e39,
-    ),
-  // Neutrals
-  "gray": (
-      "50": #f9f9fa,
-      "100": #f3f4f5,
-      "200": #e5e7ea,
-      "300": #d1d5da,
-      "400": #9ca4af,
-      "500": #677384,
-      "600": #48566b,
-      "700": #29313d,
-      "800": #222832,
-      "900": #14181e,
-    ),
-  // Accent color
-  "pink": (
-      "50": #fcf8fd,
-      "100": #fcf0fa,
-      "200": #f8dff5,
-      "300": #f3c7ee,
-      "400": #e47fd7,
-      "500": #c132af,
-      "600": #912583,
-      "700": #6e1c64,
-      "800": #46123f,
-      "900": #2b0b27,
-    ),
-  "foundation": (
-    "white": #ffffff,
-    // gray-900
-    "black": #14181e,
-  )
-);
-
 :root {
   // Add theme colours to the html root element
   @each $group-color, $color in $color-palette {
@@ -93,241 +11,6 @@ $color-palette: (
   }
 }
 
-// Static SCSS variables used thoroughout the theme
-// Minimum contrast ratio used for the theme.
-// Acceptable values for WCAG 2.0 are 3, 4.5 and 7.
-// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
-// 4.5 - is for text that is 14pt or less
-$min-contrast-ratio-4: 4.5;
-
-// 3 is for text that is 18pt or bold, or for non-text elements
-$min-contrast-ratio-3: 3;
-
-// Customize the light and dark text colors for use in our color contrast function.
-$foundation-black: #14181e;
-$foundation-white: #fff;
-
-// This is a custom - calculated  color between gray 100 and 200 - to reduce
-// the contrast ratio (avoid a jarring effect)
-$base-light-text: #ced6dd;
-
-// used in sphinx_design - gray 100
-$foundation-light-gray: #f3f4f5;
-
-// used in sphinx_design - gray 700
-$foundation-muted-gray: #29313d;
-
-// used in sphinx_design - gray 800
-$foundation-dark-gray: #222832;
-$pst-semantic-colors: (
-  "primary": (
-    "light": #{map-deep-get($color-palette, "teal", "500")},
-    "bg-light": #{map-deep-get($color-palette, "teal", "200")},
-    "dark": #{map-deep-get($color-palette, "teal", "400")},
-    "bg-dark": #{map-deep-get($color-palette, "teal", "800")},
-  ),
-  "secondary": (
-    "light": #{map-deep-get($color-palette, "violet", "500")},
-    "bg-light": #{map-deep-get($color-palette, "violet", "100")},
-    "dark": #{map-deep-get($color-palette, "violet", "400")},
-    "bg-dark": #{map-deep-get($color-palette, "violet", "800")},
-  ),
-  "accent": (
-    "light": #{map-deep-get($color-palette, "pink", "500")},
-    "bg-light": #{map-deep-get($color-palette, "pink", "200")},
-    "dark": #{map-deep-get($color-palette, "pink", "400")},
-    "bg-dark": #{map-deep-get($color-palette, "pink", "800")},
-  ),
-  "info": (
-    "light": #276be9,
-    "bg-light": #dce7fc,
-    "dark": #79a3f2,
-    "bg-dark": #06245d,
-  ),
-  "warning": (
-    "light": #f66a0a,
-    "bg-light": #f8e3d0,
-    "dark": #ff9245,
-    "bg-dark": #652a02,
-  ),
-  "success": (
-    "light": #00843f,
-    "bg-light": #d6ece1,
-    "dark": #5fb488,
-    "bg-dark": #002f17,
-  ),
-  // This is based on the warning color
-  "attention": (
-      "light": var(--pst-color-warning),
-      "bg-light": var(--pst-color-warning-bg),
-      "dark": var(--pst-color-warning),
-      "bg-dark": var(--pst-color-warning-bg),
-    ),
-  "danger": (
-    "light": #d72d47,
-    "bg-light": #f9e1e4,
-    "dark": #e78894,
-    "bg-dark": #4e111b,
-  ),
-  "text-base": (
-    "light": #{map-deep-get($color-palette, "gray", "800")},
-    "dark": $base-light-text,
-  ),
-  "text-muted": (
-    "light": #{map-deep-get($color-palette, "gray", "600")},
-    "dark": #{map-deep-get($color-palette, "gray", "400")},
-  ),
-  "shadow": (
-    "light": rgba(0, 0, 0, 0.1),
-    "dark": rgba(0, 0, 0, 0.2),
-  ),
-  "border": (
-    "light": #{map-deep-get($color-palette, "gray", "300")},
-    "dark": #{map-deep-get($color-palette, "gray", "600")},
-  ),
-  "border-muted": (
-    "light": rgba(23, 23, 26, 0.2),
-    "dark": #{map-deep-get($color-palette, "gray", "700")},
-  ),
-  "blockquote-notch": (
-    // These colors have a contrast ratio > 3.0 against both the background and
-    // surface colors that the notch is sandwiched between
-    "light": #{map-deep-get($color-palette, "gray", "500")},
-    "dark": #{map-deep-get($color-palette, "gray", "400")},
-  ),
-  "inline-code": (
-    "light": #{map-deep-get($color-palette, "pink", "600")},
-    "dark": #{map-deep-get($color-palette, "pink", "300")},
-  ),
-  "link-higher-contrast": (
-    // teal-600 provides higher contrast than teal-500 (our regular light mode
-    // link color) for off-white or non-white backgrounds
-    "light": #{map-deep-get($color-palette, "teal", "600")},
-    // teal-400 is actually the same color already used for links in dark mode,
-    // but it actually works for most of our other dark mode backgrounds
-    "dark": #{map-deep-get($color-palette, "teal", "400")},
-  ),
-  "target": (
-    "light": #f3cf95,
-    "dark": #675c04,
-  ),
-  "table": (
-    "light": #{map-deep-get($color-palette, "foundation", "black")},
-    "dark": #{map-deep-get($color-palette, "foundation", "white")},
-  ),
-  "table-row-hover": (
-    "bg-light": #{map-deep-get($color-palette, "violet", "200")},
-    "bg-dark": #{map-deep-get($color-palette, "violet", "700")},
-  ),
-  "table-inner-border": (
-    "light": #{map-deep-get($color-palette, "gray", "200")},
-    "dark": #364150,
-  ),
-  // DEPTH COLORS - you can see the elevation colours and shades
-  // in the Figma file https://www.figma.com/file/rUrrHGhUBBIAAjQ82x6pz9/PyData-Design-system---proposal-for-implementation-(2)?node-id=1492%3A922&t=sQeQZehkOzposYEg-1
-  // background: color of the canvas / the furthest back layer
-  "background": (
-      "light": #{map-deep-get($color-palette, "foundation", "white")},
-      "dark": #{map-deep-get($color-palette, "foundation", "black")},
-    ),
-  // on-background: provides slight contrast against background
-  // (by use of shadows in light theme)
-  "on-background": (
-      "light": #{map-deep-get($color-palette, "foundation", "white")},
-      "dark": #{map-deep-get($color-palette, "gray", "800")},
-    ),
-  "surface": (
-    "light": #{map-deep-get($color-palette, "gray", "100")},
-    "dark": #{map-deep-get($color-palette, "gray", "700")},
-  ),
-  // on_surface: object on top of surface object (without shadows)
-  "on-surface": (
-      "light": #{map-deep-get($color-palette, "gray", "800")},
-      "dark": $foundation-light-gray,
-    ),
-);
-
-/*******************************************************************************
-* write the color rules for each theme (light/dark)
-*/
-
-/* NOTE:
- * Mixins enable us to reuse the same definitions for the different modes
- * https://sass-lang.com/documentation/at-rules/mixin
- * #{something} inserts a variable into a CSS selector or property name
- * https://sass-lang.com/documentation/interpolation
- */
-@mixin theme-colors($mode) {
-  // check if this color is defined differently for light/dark
-  @each $col-name, $definition in $pst-semantic-colors {
-    @if meta.type-of($definition) == map {
-      @each $key, $val in $definition {
-        @if string.index($key, $mode) {
-          // since now we define the bg colours in the semantic colours and not
-          // by changing opacity, we need to check if the key contains bg and the
-          // correct mode (light/dark)
-          @if string.index($key, "bg") {
-            --pst-color-#{$col-name}-bg: #{$val};
-          } @else {
-            --pst-color-#{$col-name}: #{$val};
-          }
-        }
-      }
-    } @else {
-      --pst-color-#{$col-name}: #{$definition};
-    }
-  }
-
-  // assign the "duplicate" colors (ones that just reference other variables)
-  & {
-    // From 0.16.1, the preferred variable for headings is --pst-color-heading
-    // if you have --pst-heading-color, this variable will be used, otherwise the default
-    // --pst-color-heading will be used
-    --pst-color-heading: var(--pst-color-text-base);
-    --pst-color-link: var(--pst-color-primary);
-    --pst-color-link-hover: var(--pst-color-secondary);
-    --pst-color-table-outer-border: var(--pst-color-surface);
-    --pst-color-table-heading-bg: var(--pst-color-surface);
-    --pst-color-table-row-zebra-high-bg: var(--pst-color-on-background);
-    --pst-color-table-row-zebra-low-bg: var(--pst-color-surface);
-  }
-
-  // adapt to light/dark-specific content
-  @if $mode == "light" {
-    .only-dark,
-    .only-dark ~ figcaption {
-      display: none !important;
-    }
-  } @else {
-    .only-light,
-    .only-light ~ figcaption {
-      display: none !important;
-    }
-
-    /* Adjust images in dark mode (unless they have class .only-dark or
-     * .dark-light, in which case assume they're already optimized for dark
-     * mode).
-     */
-    img:not(.only-dark, .dark-light) {
-      filter: brightness(0.8) contrast(1.2);
-    }
-
-    /* Give images a light background in dark mode in case they have
-    *  transparency and black text (unless they have class .only-dark or .dark-light, in
-    *  which case assume they're already optimized for dark mode).
-    */
-    .bd-content img:not(.only-dark, .dark-light) {
-      background-color: rgb(255 255 255);
-      border-radius: 0.25rem;
-    }
-
-    // MathJax SVG outputs should be filled to same color as text.
-    .MathJax_SVG * {
-      fill: var(--pst-color-text-base);
-    }
-  }
-}
-
 /* Defaults to light mode if data-theme is not set */
 html:not([data-theme]) {
   @include theme-colors("light");
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss b/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
index e7d3c293da..9b23d44e14 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_fonts.scss
@@ -43,6 +43,3 @@ html {
   --pst-font-family-heading: var(--pst-font-family-base-system);
   --pst-font-family-monospace: var(--pst-font-family-monospace-system);
 }
-
-$line-height-body: 1.65;
-$fa-font-path: "vendor/fontawesome/webfonts/";
diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_layout.scss b/src/pydata_sphinx_theme/assets/styles/variables/_layout.scss
index 28eeebe2ee..e7f2936ba9 100644
--- a/src/pydata_sphinx_theme/assets/styles/variables/_layout.scss
+++ b/src/pydata_sphinx_theme/assets/styles/variables/_layout.scss
@@ -9,40 +9,3 @@ html {
   --pst-header-article-height: calc(var(--pst-header-height) * 2 / 3);
   --pst-sidebar-secondary: 17rem;
 }
-
-/*******************************************************************************
-* Breakpoints that trigger UI changes
-*
-* Note that media-breakpoint-down begins at the next highest level!
-* So we should choose a media-breakpoint-down one *lower* than when we want to start
-* example: media-breakpoint-up(md) and media-breakpoint-down(sm) trigger at the same time
-* ref: https://github.com/twbs/bootstrap/issues/31214
-*/
-$breakpoint-sidebar-primary: lg; // When we collapse the primary sidebar
-$breakpoint-sidebar-secondary: xl; // When we collapse the secondary sidebar
-$breakpoint-page-width: 88rem; // taken from sphinx-basic-ng, which we are ultimately going to inherit
-
-/*******************************************************************************
-* Define the animation behaviour
-*/
-$animation-time: 200ms;
-
-/*******************************************************************************
-* UI shaping and padding
-*/
-$admonition-border-radius: 0.25rem;
-
-// In this theme, some focus rings have rounded corners while others do not.
-// This variable sets the border radius for the rounded focus rings.
-$focus-ring-radius: 0.125rem; // 2px at 100% zoom and 16px base font.
-
-$navbar-link-padding-y: 0.25rem;
-
-// Ensure that there is no overlap of bumper disks (smallest circle that
-// contains the bounding box of the interactive element).
-// - https://github.com/Quansight-Labs/czi-scientific-python-mgmt/issues/81#issuecomment-2251325783
-$nav-icon-column-gap: 1.12rem;
-
-// Determines vertical space between entries in both the section (left/primary
-// sidebar) and page (right/secondary sidebar) table of contents
-$toc-item-padding-y: 0.25rem;
diff --git a/src/pydata_sphinx_theme/myst_nb.py b/src/pydata_sphinx_theme/myst_nb.py
new file mode 100644
index 0000000000..75eceebc08
--- /dev/null
+++ b/src/pydata_sphinx_theme/myst_nb.py
@@ -0,0 +1,62 @@
+"""Interoperate with Myst-NB Sphinx extension."""
+
+import hashlib
+
+from importlib import resources
+from pathlib import Path
+
+from sphinx.application import Sphinx
+
+from . import utils
+
+
+replacement_css_filename = "myst-nb-pydata-theme.css"
+
+
+def myst_nb_css_filename():
+    """For projects using MyST-NB, get the name of MyST-NB's CSS file."""
+    # Local import because not every project installs myst_nb
+    from myst_nb import static
+
+    with resources.as_file(resources.files(static).joinpath("mystnb.css")) as path:
+        hash = hashlib.sha256(path.read_bytes()).hexdigest()
+        return f"mystnb.{hash}.css"
+
+
+def delete_myst_nb_css(app: Sphinx) -> None:
+    """For projects using Myst-NB, delete MyST-NB's CSS.
+    We replace it later with our own.
+    """
+    if "myst_nb" not in app.config.extensions:
+        return
+
+    myst_nb_css = Path(app.builder.outdir) / "_static" / myst_nb_css_filename()
+    if myst_nb_css.exists():
+        myst_nb_css.unlink()
+    else:
+        # Here is the main purpose of this function. The main purpose is not so
+        # much to delete the MyST-NB CSS file (despite this function's name) as
+        # to help us catch a breaking change on MyST-NB's side if they ever
+        # rename or move its CSS file. This is important because the other
+        # function in this file, which gets executed later in the build process,
+        # also depends on MyST-NB's CSS being at a particular path. If it's not
+        # there, then without this warning the CSS replacement would fail
+        # silently, and projects using this theme would load MyST-NB's notebook
+        # styles instead of this theme's.
+        utils.maybe_warn(app, f"MyST-NB CSS not found in expected place: {myst_nb_css}")
+
+
+def point_myst_nb_pages_to_our_css(
+    app: Sphinx, pagename: str, templatename: str, context, doctree
+) -> None:
+    """For projects using MyST-NB, point to our notebook CSS."""
+    if "myst_nb" not in app.config.extensions:
+        return
+
+    if "css_files" not in context:
+        return
+
+    myst_nb_css = "_static/" + myst_nb_css_filename()
+    found = utils._delete_from_css_files(context["css_files"], myst_nb_css)
+    if found:
+        app.add_css_file("styles/" + replacement_css_filename)
diff --git a/src/pydata_sphinx_theme/nbsphinx.py b/src/pydata_sphinx_theme/nbsphinx.py
new file mode 100644
index 0000000000..1bb4a51671
--- /dev/null
+++ b/src/pydata_sphinx_theme/nbsphinx.py
@@ -0,0 +1,57 @@
+"""Interoperate with nbpshinx extension."""
+
+from pathlib import Path
+
+from sphinx.application import Sphinx
+
+from . import utils
+
+
+nbsphinx_css_filename = "nbsphinx-code-cells.css"
+replacement_css_filename = "nbsphinx-pydata-theme.css"
+
+
+def delete_nbsphinx_css(app: Sphinx) -> None:
+    """For projects using nbsphinx, delete nbsphinx's CSS.
+    We replace it later with our own.
+    """
+    if "nbsphinx" not in app.config.extensions:
+        return
+
+    nbsphinx_css = Path(app.builder.outdir) / "_static" / nbsphinx_css_filename
+    if nbsphinx_css.exists():
+        nbsphinx_css.unlink()
+    else:
+        # Here is the main purpose of this function. The main purpose is not so
+        # much to delete the nbsphinx CSS file (despite this function's name) as
+        # to help us catch a breaking change on nbsphinx's side if they ever
+        # rename or move its CSS file. This is important because the other
+        # function in this file, which gets executed later in the build process,
+        # also depends on nbsphinx's CSS being at a particular path. If it's not
+        # there, then without this warning the CSS replacement would fail
+        # silently, and projects using this theme would load nbsphinx's notebook
+        # styles instead of this theme's.
+        utils.maybe_warn(
+            app, f"nbpshinx CSS not found in expected place: {nbsphinx_css}"
+        )
+
+
+def point_nbsphinx_pages_to_our_css(
+    app: Sphinx, pagename: str, templatename: str, context, doctree
+) -> None:
+    """For projects using nbsphinx, point to our notebook CSS.
+    nbsphinx selectively adds its CSS to certain pages (the ones it converted
+    from notebook files) by calling the Sphinx `add_css_file()` method on
+    "html-page-context" event. Here we update those pages to point to our
+    alternative CSS file.
+    """
+    if "nbsphinx" not in app.config.extensions:
+        return
+
+    if "css_files" not in context:
+        return
+
+    nbsphinx_css = "_static/" + nbsphinx_css_filename
+    found = utils._delete_from_css_files(context["css_files"], nbsphinx_css)
+    if found:
+        app.add_css_file("styles/" + replacement_css_filename)
diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/.gitignore b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/.gitignore
index 5b2bb8d6e2..7626f3ae11 100644
--- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/.gitignore
+++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/.gitignore
@@ -3,6 +3,10 @@ scripts/pydata-sphinx-theme.js
 scripts/pydata-sphinx-theme.js.map
 styles/pydata-sphinx-theme.css
 styles/pydata-sphinx-theme.css.map
+styles/nbsphinx-pydata-theme.css
+styles/nbsphinx-pydata-theme.css.map
+styles/myst-nb-pydata-theme.css
+styles/myst-nb-pydata-theme.css.map
 
 # bootstrap generated webpack outputs
 scripts/bootstrap.js
diff --git a/src/pydata_sphinx_theme/utils.py b/src/pydata_sphinx_theme/utils.py
index ba75446173..948f661996 100644
--- a/src/pydata_sphinx_theme/utils.py
+++ b/src/pydata_sphinx_theme/utils.py
@@ -1,17 +1,34 @@
 """General helpers for the management of config parameters."""
 
+from __future__ import annotations
+
 import copy
 import os
 import re
 
-from typing import Any, Callable, Dict, Iterable, List, Optional, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Iterable,
+    Protocol,
+)
 
 from docutils.nodes import Node
 from sphinx.application import Sphinx
 from sphinx.util import logging, matching
 
 
-def get_theme_options_dict(app: Sphinx) -> Dict[str, Any]:
+if TYPE_CHECKING:
+    try:
+        from sphinx.builders.html._assets import _CascadingStyleSheet
+    except ImportError:
+
+        class _CascadingStyleSheet(Protocol):
+            filename: str | os.PathLike[str]
+
+
+def get_theme_options_dict(app: Sphinx) -> dict[str, Any]:
     """Return theme options for the application w/ a fallback if they don't exist.
 
     The "top-level" mapping (the one we should usually check first, and modify
@@ -34,7 +51,7 @@ def config_provided_by_user(app: Sphinx, key: str) -> bool:
 
 
 def traverse_or_findall(
-    node: Node, condition: Union[Callable, type], **kwargs
+    node: Node, condition: Callable | type, **kwargs
 ) -> Iterable[Node]:
     """Triage node.traverse (docutils <0.18.1) vs node.findall.
 
@@ -86,11 +103,11 @@ def set_secondary_sidebar_items(
 
 def _update_and_remove_templates(
     app: Sphinx,
-    context: Dict[str, Any],
-    templates: Union[List, str],
+    context: dict[str, Any],
+    templates: list | str,
     section: str,
-    templates_skip_empty_check: Optional[List[str]] = None,
-) -> List[str]:
+    templates_skip_empty_check: list[str] | None = None,
+) -> list[str]:
     """
     Update templates to include html suffix if needed; remove templates which render
     empty.
@@ -151,8 +168,8 @@ def _update_and_remove_templates(
 
 
 def _get_matching_sidebar_items(
-    pagename: str, sidebars: Dict[str, List[str]]
-) -> List[str]:
+    pagename: str, sidebars: dict[str, list[str]]
+) -> list[str]:
     """Get the matching sidebar templates to render for the given pagename.
 
     If a page matches more than one pattern, a warning is emitted, and the templates
@@ -185,3 +202,20 @@ def _has_wildcard(pattern: str) -> bool:
     Taken from sphinx.builders.StandaloneHTMLBuilder.add_sidebars.
     """
     return any(char in pattern for char in "*?[")
+
+
+def _delete_from_css_files(
+    css_files: list[_CascadingStyleSheet | str], path: str
+) -> bool:
+    """Remove entry from page's context["css_files"] if it exists.
+    Mutates list and returns True if found and removed, False otherwise.
+    """
+    for i in range(len(css_files)):
+        asset = css_files[i]
+        # TODO: eventually the contents of context['css_files'] etc should probably
+        # only be _CascadingStyleSheet etc. For now, assume mixed with strings.
+        asset_path = getattr(asset, "filename", str(asset))
+        if asset_path == path:
+            del css_files[i]
+            return True
+    return False
diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index a335b45238..649b6ea483 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -256,7 +256,7 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     page.goto(urljoin(url_base, "/examples/pydata.html"))
 
     # A "plain" notebook output
-    nb_output = page.locator("css=#Pandas > .nboutput > .output_area")
+    nb_output = page.locator("css=#Pandas > .nboutput > .output_area").first
 
     # At the default viewport size (1280 x 720) the Pandas data table has
     # overflow...
diff --git a/tests/warning_list.txt b/tests/warning_list.txt
index 42499be08e..a7f9030043 100644
--- a/tests/warning_list.txt
+++ b/tests/warning_list.txt
@@ -24,3 +24,5 @@ WARNING: autosummary: stub file not found 'urllib.parse.SplitResult.index'. Chec
 WARNING: autosummary: stub file not found 'urllib.parse.SplitResultBytes.count'. Check your autosummary_generate setting.
 WARNING: autosummary: stub file not found 'urllib.parse.SplitResultBytes.index'. Check your autosummary_generate setting.
 ERROR: Unexpected indentation.
+WARNING: py:class reference target not found: sphinx.builders.html._assets._CascadingStyleSheet
+WARNING: py:class reference target not found: os.PathLike
diff --git a/webpack.config.js b/webpack.config.js
index 05eaf25acd..53d97058af 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -11,6 +11,7 @@
 
 const { resolve } = require("path");
 const HtmlWebpackPlugin = require("html-webpack-plugin");
+const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
 const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
 const TerserPlugin = require("terser-webpack-plugin");
@@ -22,6 +23,7 @@ const { exec } = require("child_process");
  * Paths for various assets (sources and destinations)
  */
 
+const scssPath = resolve(__dirname, "src/pydata_sphinx_theme/assets/styles/extensions")
 const scriptPath = resolve(__dirname, "src/pydata_sphinx_theme/assets/scripts");
 const staticPath = resolve(__dirname, "src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static");
 const localePath = resolve(__dirname, "src/pydata_sphinx_theme/locale");
@@ -35,8 +37,6 @@ const localePath = resolve(__dirname, "src/pydata_sphinx_theme/locale");
 function stylesheet(css) { return ``; }
 function preloadScript(js) { return ``; }
 function deferScript(js) { return ``; }
-// Adding FA without preloading
-function script(js) { return ``; }
 
 /*******************************************************************************
  * the assets to load in the macro
@@ -105,6 +105,11 @@ var config = {
     "pydata-sphinx-theme": resolve(scriptPath, "pydata-sphinx-theme.js"),
     "fontawesome": resolve(scriptPath, "fontawesome.js"),
     "bootstrap": resolve(scriptPath, "bootstrap.js"),
+    // note: the name of the dictionary key becomes the name of the output files
+    // (js and css - doesn't seem like there is a way to tell webpack to just
+    // build a scss file)
+    "nbsphinx-pydata-theme": resolve(scssPath, "nbsphinx-pydata-theme.scss"),
+    "myst-nb-pydata-theme": resolve(scssPath, "myst-nb-pydata-theme.scss")
   },
   output: {
     filename: "scripts/[name].js",
@@ -163,6 +168,7 @@ var config = {
     },],
   },
   plugins: [
+    new RemoveEmptyScriptsPlugin(), // removes the empty `.js` files generated by webpack
     htmlWebpackPlugin,
     new MiniCssExtractPlugin({
       filename: "styles/[name].css",