Repository: facelessuser/coloraide (language: python)

Issue:
Allow adjusting interpolation domain
By default, the interpolation domain is between 0 and 1. This is nice, but sometimes it is useful to adjust the domain to fit some data.

For instance, we may want colors to be associated with temperature. Maybe a range of -10˚ F to 90˚ F. Instead of having to go through the trouble to fit the data to 0 and 1, we could just alter the domain:

```python
>>> i = Color.interpolate(['blue', 'red'], domain=[-10, 90.0])
>>> i(-10)
color(srgb 0 0 1 / 1)
>>> i(90)
color(srgb 1 0 0 / 1)
```

What's more, we could fit certain temperatures to certain colors:

```python
>>> i = Color.interpolate(['blue', 'green', 'yellow', 'orange', 'red'], domain=[-10, 32, 60, 85, 95])
>>> i(60)
color(srgb 1 1 0 / 1)
>>> i(80)
color(srgb 1.0064 0.71907 0.00009 / 1)
```

There would be no rule that states you have to have as many points in your domain as color as this really controls the domain of the progress that stretches the entire interpolation across all colors, but it is easy to visualize the usefulness when you match it up as we do above.

This is actually inspired by https://gka.github.io/chroma.js/#scale-domain. One thing we wouldn't do is allow you to flip the domain. If you want the colors to work in reverse order, just flip the color inputs and have your scale apply to them.

As can be seen in this, we already have a prototype, so this is open to track the work.


Discussion / hints:

Fix patch:
diff --git a/coloraide/__meta__.py b/coloraide/__meta__.py
index 7bdc5736d..d7ed0190b 100644
--- a/coloraide/__meta__.py
+++ b/coloraide/__meta__.py
@@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(1, 5, 0, ".dev")
+__version_info__ = Version(1, 5, 0, "final")
 __version__ = __version_info__._get_canonical()
diff --git a/coloraide/color.py b/coloraide/color.py
index 9a7f34cae..1518c52a3 100644
--- a/coloraide/color.py
+++ b/coloraide/color.py
@@ -696,7 +696,11 @@ def mix(
 
         if not self._is_color(color) and not isinstance(color, (str, Mapping)):
             raise TypeError("Unexpected type '{}'".format(type(color)))
-        mixed = self.interpolate([self, color], **interpolate_args)(percent)
+        i = self.interpolate([self, color], **interpolate_args)
+        # Scale really needs to be between 0 and 1 as mix deals in percentages.
+        if i.domain:
+            i.domain = interpolate.normalize_domain(i.domain)
+        mixed = i(percent)
         return self.mutate(mixed) if in_place else mixed
 
     @classmethod
@@ -712,7 +716,11 @@ def steps(
     ) -> list[Color]:
         """Discrete steps."""
 
-        return cls.interpolate(colors, **interpolate_args).steps(steps, max_steps, max_delta_e, delta_e)
+        i = cls.interpolate(colors, **interpolate_args)
+        # Scale really needs to be between 0 and 1 or steps will break
+        if i.domain:
+            i.domain = interpolate.normalize_domain(i.domain)
+        return i.steps(steps, max_steps, max_delta_e, delta_e)
 
     @classmethod
     def interpolate(
@@ -725,6 +733,7 @@ def interpolate(
         hue: str = util.DEF_HUE_ADJ,
         premultiplied: bool = True,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         method: str = "linear",
         **kwargs: Any
     ) -> Interpolator:
@@ -750,6 +759,7 @@ def interpolate(
             hue=hue,
             premultiplied=premultiplied,
             extrapolate=extrapolate,
+            domain=domain,
             **kwargs
         )
 
diff --git a/coloraide/interpolate/__init__.py b/coloraide/interpolate/__init__.py
index defd5dad6..6cc536d1a 100644
--- a/coloraide/interpolate/__init__.py
+++ b/coloraide/interpolate/__init__.py
@@ -52,6 +52,19 @@ def hint(mid: float) -> Callable[..., float]:
     return functools.partial(midpoint, h=mid)
 
 
+def normalize_domain(d: list[float]) -> list[float]:
+    """Normalize domain between 0 and 1."""
+
+    total = d[-1] - d[0]
+    regions = len(d) - 1
+    values = [0.0]
+    for index in range(regions):
+        a, b = d[index:index + 2]
+        l = b - a
+        values.append(values[-1] + (l / total if total else 0))
+    return values
+
+
 class Interpolator(metaclass=ABCMeta):
     """Interpolator."""
 
@@ -67,6 +80,7 @@ def __init__(
         progress: Optional[Callable[..., float] | Mapping[str, Callable[..., float]]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ):
         """Initialize."""
@@ -91,6 +105,24 @@ def __init__(
             self.hue_index = -1
         self.premultiplied = premultiplied
 
+        # Ensure domain ascends.
+        # If we have a domain of length 1, we will duplicate it.
+        if domain is not None and domain:
+            length = len(domain)
+
+            # Ensure values are not descending
+            d = [domain[0]]
+            for index in range(length - 1):
+                b = domain[index + 1]
+                d.append(d[-1] if b <= d[-1] else b)
+
+            # We need at least two values, so duplicate the first.
+            if len(d) == 1:
+                d.append(d[0])
+            domain = d
+
+        self.domain = [] if domain is None else domain  # type: list[float]
+
         self.setup()
 
     def setup(self) -> None:
@@ -257,9 +289,39 @@ def ease(self, t: float, channel_index: int) -> float:
 
         return progress(t) if progress is not None else t
 
+    def scale(self, point: float) -> float:
+        """
+        Scale a point from a custom domain into a domain of 0 to 1.
+
+        This allows a user to have a custom domain, but for us to adapt back to 0 and 1
+        so that our logic can remain consistent.
+        """
+
+        if point < self.domain[0]:
+            point = (point - self.domain[0]) / (self.domain[-1] - self.domain[0]) if self.extrapolate else 0.0
+        elif point > self.domain[-1]:
+            point = 1.0 + (point - self.domain[-1]) / (self.domain[-1] - self.domain[0]) if self.extrapolate else 1.0
+        else:
+            regions = len(self.domain) - 1
+            size = (1 / regions)
+            index = 0
+            adjusted = 0.0
+            for index in range(regions):
+                a, b = self.domain[index:index + 2]
+                if point >= a and point <= b:
+                    l = b - a
+                    adjusted = ((point - a) / l) if l else 0.0
+                    break
+
+            point = size * index + (adjusted * size)
+        return point
+
     def __call__(self, point: float) -> Color:
         """Find which leg of the interpolation the request is between."""
 
+        if self.domain:
+            point = self.scale(point)
+
         # See if point extends past either the first or last stop
         if point < self.start:
             first, last = self.start, self.stops[1]
@@ -298,6 +360,8 @@ def interpolator(
         out_space: str,
         progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
         premultiplied: bool,
+        extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Get the interpolator object."""
@@ -482,6 +546,8 @@ def interpolator(
     progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
     hue: str,
     premultiplied: bool,
+    extrapolate: bool,
+    domain: Optional[list[float]] = None,
     **kwargs: Any
 ) -> Interpolator:
     """Get desired blend mode."""
@@ -576,5 +642,7 @@ def interpolator(
         out_space,
         process_mapping(progress, current._space.CHANNEL_ALIASES),
         premultiplied,
+        extrapolate,
+        domain,
         **kwargs
     )
diff --git a/coloraide/interpolate/bspline.py b/coloraide/interpolate/bspline.py
index 3995a4fa6..30222005c 100644
--- a/coloraide/interpolate/bspline.py
+++ b/coloraide/interpolate/bspline.py
@@ -174,6 +174,7 @@ def interpolator(
         progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Return the B-spline interpolator."""
@@ -188,5 +189,6 @@ def interpolator(
             out_space,
             progress,
             premultiplied,
-            extrapolate
+            extrapolate,
+            domain
         )
diff --git a/coloraide/interpolate/bspline_natural.py b/coloraide/interpolate/bspline_natural.py
index e160c64ae..c73d9dcdb 100644
--- a/coloraide/interpolate/bspline_natural.py
+++ b/coloraide/interpolate/bspline_natural.py
@@ -51,6 +51,7 @@ def interpolator(
         progress: Optional[Union[Mapping[str, Callable[..., float]], Callable[..., float]]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Return the natural B-spline interpolator."""
@@ -65,5 +66,6 @@ def interpolator(
             out_space,
             progress,
             premultiplied,
-            extrapolate
+            extrapolate,
+            domain
         )
diff --git a/coloraide/interpolate/catmull_rom.py b/coloraide/interpolate/catmull_rom.py
index a537d4c02..ea4543151 100644
--- a/coloraide/interpolate/catmull_rom.py
+++ b/coloraide/interpolate/catmull_rom.py
@@ -41,6 +41,7 @@ def interpolator(
         progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Return the Catmull-Rom interpolator."""
@@ -55,5 +56,6 @@ def interpolator(
             out_space,
             progress,
             premultiplied,
-            extrapolate
+            extrapolate,
+            domain
         )
diff --git a/coloraide/interpolate/linear.py b/coloraide/interpolate/linear.py
index 9b42a7b13..8325c2c06 100644
--- a/coloraide/interpolate/linear.py
+++ b/coloraide/interpolate/linear.py
@@ -103,6 +103,7 @@ def interpolator(
         progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Return the linear interpolator."""
@@ -117,5 +118,6 @@ def interpolator(
             out_space,
             progress,
             premultiplied,
-            extrapolate
+            extrapolate,
+            domain
         )
diff --git a/coloraide/interpolate/monotone.py b/coloraide/interpolate/monotone.py
index bee884a36..3c1592d63 100644
--- a/coloraide/interpolate/monotone.py
+++ b/coloraide/interpolate/monotone.py
@@ -37,6 +37,7 @@ def interpolator(
         progress: Optional[Mapping[str, Callable[..., float]] | Callable[..., float]],
         premultiplied: bool,
         extrapolate: bool = False,
+        domain: Optional[list[float]] = None,
         **kwargs: Any
     ) -> Interpolator:
         """Return the monotone interpolator."""
@@ -51,5 +52,6 @@ def interpolator(
             out_space,
             progress,
             premultiplied,
-            extrapolate
+            extrapolate,
+            domain
         )
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index 609300983..c030061ea 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -3,6 +3,7 @@
 ## 1.5
 
 - **NEW**: Formally add support for Python 3.11.
+- **NEW**: Add support for custom domains when interpolating.
 - **NEW**: `set()` can now take a dictionary of channels and values and set multiple channels at once.
 - **NEW**: `get()` can now take a list of channels and will return a list of those channel values.
 - **ENHANCE**: Simplify some type annotation syntax.
diff --git a/docs/src/markdown/api/index.md b/docs/src/markdown/api/index.md
index 68fdb4535..6a0175014 100644
--- a/docs/src/markdown/api/index.md
+++ b/docs/src/markdown/api/index.md
@@ -738,7 +738,8 @@ Parameters
     `out_space`     | `#!py3 None`      | Color space that the new color should be in. If `#!py3 None`, the color will be in the same color space as the base color.
     `hue`           | `#!py3 "shorter"` | Define how color spaces which have hue angles are interpolated. Default evaluates between the shortest angle.
     `premultiplied` | `#!py3 True`      | Use premultiplied alpha when interpolating.
-    `extrapolate`   | `#!py3 False`     | Interpolations should extrapolate when values exceed the 0 - 1 range.
+    `extrapolate`   | `#!py3 False`     | Interpolations should extrapolate when values exceed the domain range ([0, 1] by default).
+    `domain`        | `#!py3 None`      | A list of numbers defining the domain range of the interpolation.
     `method`        | `#!py3 "linear"`  | The interpolation method to use.
 
 Return
diff --git a/docs/src/markdown/color.md b/docs/src/markdown/color.md
index 0b59d4ae5..b5881972a 100644
--- a/docs/src/markdown/color.md
+++ b/docs/src/markdown/color.md
@@ -1,8 +1,11 @@
 # The Color Object
 
-The `Color` object is where all the magic of ColorAide happens. In order to manipulate, interpolate, or perform any
-action on a color, we must first create one. There are a number of ways to instantiate new colors. Here we will cover
-basic creating, cloning, and updating of the `Color` class object and a few other class specific topics.
+The `Color` object is where all the magic of ColorAide happens and provides access to all the color manipulation
+methods available. The `Color` object is used to represent a given color within a particular color space, and in order
+to perform most operations, you will need to create a color instance to begin.
+
+There are a number of ways to instantiate new colors. Here we will cover basic creating, cloning, and updating of the
+`Color` class object and a few other class specific topics.
 
 ## Importing
 
@@ -12,11 +15,12 @@ The `Color` object contains all the logic to create and manipulate colors. It ca
 from coloraide import Color
 ```
 
-By default, the `Color` object contains only a subset of the available color spaces, related distancing algorithms, etc.
-This is done in order to keep the `Color` object lighter. If desired, the `ColorAll` object can be used which includes
-_everything_. This is generally not recommended as most users do not need _everything_. In general, it is recommended to
-subclass the `Color` object and cherry pick any additional plugins that are required, but if you'd like to have access
-to all the color spaces that are available, along with any other optional plugins, you can import and use `ColorAll`.
+By default, the `Color` object registers only a subset of the available color spaces and features that are shipped with
+ColorAide. This keeps the object a bit lighter and provides the more commonly used color spaces and features. Color
+spaces, additional color distancing algorithms, gamut mapping algorithms, etc. are implemented via plugins. The normal
+way to get access to these additional spaces and features is to subclass the `Color` object and resister the desired
+spaces and features that are needed, but if you just want to explore all that ColorAide offers, you can import the
+`ColorAll` object from `everything`.
 
 ```py3
 from coloraide.everything import ColorAll as Color
@@ -27,54 +31,66 @@ from coloraide.everything import ColorAll as Color
 
 ## Creating Colors
 
-Once the `Color` class is imported, colors can be created using various forms of input. Colors can be created from
-strings, raw data input, and even simple raw data wrapped in dictionaries.
+Once the `Color` class is imported, colors can be created using various forms of input, including: numerical inputs,
+dictionaries, CSS color strings, and even other `Color` instances.
+
+### Numerical Inputs
 
-As far as strings are concerned, ColorAide has the ability to parse valid CSS syntax. Syntax includes legacy formats as
-defined in CSS Level 3, but also contains CSS Level 4!
+The quickest way to create a color is by simply specifying the color space, color coordinates, and the optional alpha
+channel. Numerical inputs require very little processing, but it should be noted that inputs must be specified according
+to the way the color points are stored. Some people may be aware of the old CSS convention of specifying sRGB colors
+with a range of 0 - 255, but ColorAide stores these as values between 0 - 1. If transparency is omitted, transparency is
+assumed to be fully opaque, or a value of 1.
 
 ```playground
-Color("red")
-Color("#00ff00")
-Color("rgb(0 0 255 / 1)")
+Color("srgb", [0.5, 0, 1], 0.3)
+Color("srgb", [0.5, 0, 1])
 ```
 
-!!! warning "Warning: CSS Level 4"
-    Though CSS Level 4 is supported, the CSS spec is not finalized and there could be some churn in relation to the
-    syntax as browsers begin to implement the spec.
+### Dictionary Inputs
 
-ColorAide not only supports colors in the CSS spec, but also some other additional color spaces as well. To bridge the
-gap with syntax, ColorAide allows all colors, whether in the CSS spec or not, to be recognized using the CSS color
-function (`#!css-color color(space coord ... / alpha)`). Even if the color is in the CSS spec and is not currently
-specified to use the `#!css-color color()` function, we still allow it.
+It may be desired to store and retrieve colors from some serialized format such as JSON. To make this easier, ColorAide
+allows exporting and importing colors via dictionaries as well.
 
-Essentially, we've adopted the `#!css-color color()` function as the universal way in which to serialize color strings.
-If the CSS spec does not formally recognize a color in this form, the color identifier will use two dashes as a prefix
-(`--color-id`). Check the [documentation of the given color space](./colors/index.md) to discover the appropriate CSS
-identifier name as the CSS identifier may not always match the color space name as specified in ColorAide.
+Dictionaries must define the `space` key and the `coords` key containing values for all of the color channels. The
+`alpha` channel is kept separate and can be omitted, and if so, will be assumed as 1.
 
 ```playground
-Color('color(--hsl 130 40% 75% / 0.5)')
+d = Color('red').to_dict()
+print(d)
+Color(d)
 ```
 
-While CSS input is useful, we can also insert raw data points directly. When doing things this way, we must be mindful
-of the actual accepted input range. For instance, RGB colors are not specified in ranges from 0 - 255, but from 0 - 1.
+### String Inputs
+
+By default, ColorAide accepts input strings as outlined in the CSS color specification. Accepted syntax includes legacy
+CSS color formats as defined in CSS Level 3, but also allows for CSS Level 4 Color syntax!
 
 ```playground
-Color("srgb", [0.5, 0, 1], 0.3)
+Color("red")
+Color("#00ff00")
+Color("rgb(0 0 255 / 1)")
 ```
 
-Colors can also be exported to and receive input from simple dictionaries. These can be useful when serializing to JSON
-or various other reasons. Dictionaries include the `space` key and all relevant color channels as a list under the
-`coords` key, and an optional `alpha` key will be assumed as `1` if omitted.
+ColorAide supports all the color spaces as defined in the CSS Level 4 Color spec, but is not restricted to only
+supported CSS colors. In order to support color strings for all colors, ColorAide allows for non-CSS color spaces to be
+represented via the Level 4 CSS `#!css-color color()` function. Essentially, we've adopted the `#!css-color color()`
+function as the universal way in which to serialize color strings.
+
+It should also be noted that `#!css-color color()` can be used to describe any color regardless of whether it is
+supported in the CSS spec in this way or not. For any color that is not explicitly supported in CSS via the
+`#!css-color color()` function, ColorAide will allow using this form if the color space uses a `--` prefix for the color
+space identifier. Check the [documentation of the given color space](./colors/index.md) to discover the appropriate CSS
+identifier name.
 
 ```playground
-d = Color('red').to_dict()
-print(d)
-Color(d)
+Color('color(--hsl 130 40% 75% / 0.5)')
 ```
 
-If another color instance is passed as the input, a new color will be created, essentially cloning the passed object.
+### Color Instance Inputs
+
+If another color instance is passed as the input, a new color object will be created using the color data from the
+input. This essentially clones the passed object.
 
 ```playground
 c1 = Color('red')
@@ -91,6 +107,10 @@ color1
 color1.new("blue")
 ```
 
+!!! tip
+    If the `Color` class has be subclassed, this is an easy way to convert between the different subclasses, assuming
+    the registered color spaces are compatible between the two different `Color` classes.
+
 ## Random
 
 If you'd like to generate a random color, simply call `Color.random` with a given color space and one will be generated.
@@ -100,9 +120,9 @@ If you'd like to generate a random color, simply call `Color.random` with a give
 ```
 
 Ranges are based on the color space's defined channel range. For color spaces with defined gamuts, the values will be
-confined to appropriate ranges. For color space's without defined gamuts, the ranges may be quite arbitrary. For color
-spaces with no hard, defined gamut, it is recommend to fit the colors to whatever gamut you'd like, or simply use a
-target space with a clear defined gamut.
+confined to appropriate ranges. For color space's without defined gamuts, the ranges may be quite arbitrary in some
+cases. For color spaces with no hard, defined gamut, or gamuts that that far exceed practical usage it is recommend to
+fit the colors to whatever gamut you'd like, or simply use a target space with a clear defined gamut.
 
 ```playground
 Color.random('lab').fit('srgb')
@@ -169,8 +189,8 @@ Colors can be converted to other color spaces as needed. Converting will always
 parameter is set to `#!py3 True`, in which case, the current color will be mutated to the new converted color and a
 reference to itself is returned.
 
-For instance, if we had a color `#!color yellow`, and we needed to work with it in another color space, such as CIELab,
-we could simply call the `convert` method with the desired color space.
+For instance, if we had a color `#!color yellow`, and we needed to work with it in another color space, we could simply
+call the `convert` method with the desired color space.
 
 ```playground
 Color('yellow').convert("lab")
@@ -275,7 +295,8 @@ valid color. As the buffer is an HTML document we also want to incorporate some
 or color names that are part of a CSS variable.
 
 Once we've crafted our regular expression, we can search the buffer to find locations in the buffer that are likely to
-be colors. Then we can run `Color.match()` on those positions within the buffer to see if we find a valid color!
+be colors. Then we can run `Color.match()` on those positions within the buffer to see if we find a valid color. This
+ends up being much more efficient!
 
 ```playground
 import re
@@ -314,11 +335,11 @@ The `Color` object was created to be extensible and has implemented various func
 color spaces, distancing algorithms, filters, etc. are all implemented as plugins. In order to keep things light,
 ColorAide does not register all the of the plugins by default unless the user as imported the `ColorAll` object.
 
-Additionally, ColorAide as implemented a number of defaults that can be tweaked within the `Color` class to alter how
+Additionally, ColorAide has implemented a number of defaults that can be tweaked within the `Color` class to alter how
 things are handled.
 
 Creating a custom class allows for a user to change some of the default settings and add or remove plugins to gain
-access to more color spaces, distancing algorithms, filters, etc.
+access to more color spaces, distancing algorithms, filters, and other functionality.
 
 In general, it is always recommended to subclass the `Color` object when setting up custom preferences or adding or
 removing plugins. This prevents modifying the base class which may affect other libraries relying on the module. When
@@ -351,9 +372,10 @@ Properties             | Defaults            | Description
 
 ### Plugins
 
-Currently, color spaces, delta E methods, chromatic adaptation, filters, contrast, and gamut mapping methods are exposed
-as plugins. As previously mentioned, `Color` does not register all plugins, and `ColorAll` is often more than a user
-needs by default. Registering exactly what you need is normally the recommend way.
+Currently, color spaces, delta E methods, chromatic adaptation, filters, contrast, interpolation, and gamut mapping
+methods are exposed as plugins. As previously mentioned, `Color` does not register all plugins, and `ColorAll` is often
+more than a user needs by default. Registering exactly what you need is normally the recommend approach when more
+functionality is required.
 
 While we won't go into a lot of details about creating plugins here, we will go over how to register new plugins and
 deregister existing plugins. To learn more about creating plugins, checkout the [plugin documentation](./plugins/index.md).
@@ -364,9 +386,6 @@ a plugin already registered with the same name (as dictated by the plugin) the o
 set to `#!py3 True`, the overwrite will not fail and the new plugin will be registered with the specified name in place
 of the existing plugin.
 
-As previously mentioned, the `Color` object does not include all plugins. This example shows how to register one of the
-many additional color spaces provided by ColorAide.
-
 ```playground
 from coloraide import Color
 from coloraide.spaces.xyy import xyY
@@ -395,10 +414,10 @@ Custom.register(DECMC(l=1, c=1), overwrite=True)
 Custom('red').delta_e('blue')
 ```
 
-If a deregistration was desired, the `deregister` method can be used. It takes a string that describes the plugin to
+If a deregistration is desired, the `deregister` method can be used. It takes a string that describes the plugin to
 deregister: `category:name`.
 
-Valid categories are `space`, `delta-e`, `cat`, `filter`, and `fit`.
+Valid categories are `space`, `delta-e`, `cat`, `filter`, `interpolate`, and `fit`.
 
 If the given plugin is not found, an error will be thrown, but if this notification is found to be unnecessary, `silent`
 can be enabled and the there will be no error thrown.
diff --git a/docs/src/markdown/index.md b/docs/src/markdown/index.md
index 50cbb6b69..e8b2d3295 100644
--- a/docs/src/markdown/index.md
+++ b/docs/src/markdown/index.md
@@ -28,10 +28,10 @@ ColorAide particularly has a focus on the following:
 - [x] Allow users to configure defaults to their liking.
 
 ColorAide is not meant to be the _one_ library to replace all other color libraries. There are many great libraries out
-there such such as: [Colour Science][colour-science], [Colorio][colorio], [`python-colormath`][color-math], and many
+there such such as: [Colour Science][colour-science], [Colorio][colorio], [Python Color Math][color-math], and many
 others. Some focus on the scientific aspects of colors and provide a wealth of various spaces, illuminants, access to
-complex color space visualizers, and numerous esoteric tools. Some are highly focused on speed. Some are more complex
-and more difficult to pick up and use for newcomers, and others are more accessible.
+complex color space visualizers, and numerous esoteric tools. Some are highly focused on speed. Some are powerful, but
+can be more complex to pick up by the average user.
 
 At its heart, ColorAide was designed for convenience, flexibility, and to be very easy to pick up and work with. There
 are, of course, some trade offs with speed when using a pure Python, object oriented approach, but there are also many
diff --git a/docs/src/markdown/interpolation.md b/docs/src/markdown/interpolation.md
index bbff4ba50..78d9307c5 100644
--- a/docs/src/markdown/interpolation.md
+++ b/docs/src/markdown/interpolation.md
@@ -10,7 +10,7 @@ ColorAide provides a number of useful utilities based on interpolation.
 
 One of the most common, and easiest ways to interpolate data between two points is to use linear interpolation. An easy
 way of thinking about this concept is to imagine drawing a straight line that connects two colors within a color space.
-We could then navigate up and down that line and return colors at different points to simulate mixing colors at various
+We could then navigate along that line and return colors at different points to simulate mixing colors at various
 percentages or return the whole range and create a gradient.
 
 To further illustrate this point, the example below shows a slice of the Oklab color space at a lightness of 70%. On
@@ -27,16 +27,16 @@ Interpolation performed at 50%
 </figcaption>
 </figure>
 
-The `interpolate` method allows a user to create a linear interpolation function using two or more colors. A returned
-interpolation function accepts an input between 0 - 1 and will cause a new color between the specified colors to be
-returned.
+The `interpolate` method allows a user to create a linear interpolation function using two or more colors. By default, a
+returned interpolation function accepts numerical input in the [domain](#domains) of [0, 1] and will cause a new color
+between the specified colors to be returned.
 
 By default, colors are interpolated in the perceptually uniform Oklab color space, though any supported color space can
 be used instead. This also applies to all methods that use interpolation, such as [`steps`](#steps), [`mix`](#mixing),
 etc.
 
 As an example, below we create an interpolation between `#!color rebeccapurple` and `#!color lch(85% 100 85)`. We then
-step through values of `0.1`, `0.2`, `0.3`, etc. This returns colors at various positions on the line that connects
+step through values of `0.0`, `0.1`, `0.2`, etc. This returns colors at various positions on the line that connects
 the two colors, `0` returning `#!color rebeccapurple` and `1` returning `#!color lch(85% 100 85)`.
 
 ```playground
@@ -62,24 +62,25 @@ Color.interpolate(
 
 ## Piecewise Interpolation
 
-Piecewise interpolation takes the idea of linear interpolation, and then applies it to multiple colors. As drawing a
-straight line through a series of points greater than two can be problematic to achieve, piecewise interpolation creates
-straight lines between each color.
+Piecewise interpolation takes the idea of linear interpolation and then applies it to multiple colors. As drawing a
+straight line through a series of points greater than two can be difficult to achieve, piecewise interpolation creates
+straight lines between each color in a chain of colors.
 
 ![Piecewise Interpolation](images/piecewise-interpolation.png)
 
 When the `interpolate` method receives more that two colors, the interpolation will utilize piecewise interpolation
 and interpolation will be broken up between each pair of colors. The function, just like when interpolating between two
-colors, still takes a range of 0 - 1, only it will now apply to the entire range that spans all the colors.
+colors, still operates by default in the domain of [0, 1], only it will now apply to the entire range of colors.
 
-Piecewise interpolation is simply a method of breaking up a data set and performing interpolation over small segments.
+Piecewise interpolation simply breaks up a series of data points into segments in order to apply interpolation
+individually on each segment.
 
 ```playground
 Color.interpolate(['black', 'red', 'white'])
 ```
 
 This approach generally works well, but since the placement of colors may not be in a straight line, you will often
-have pivot points and the transition may not be quite as smooth.
+have pivot points and the transition may not be quite as smooth at these locations.
 
 ## Cubic Spline Interpolation
 
@@ -88,16 +89,16 @@ that said, it doesn't always have the smoothest transitions. It turns out that t
 interpolate that can yield smoother results.
 
 Inspired by some efforts seen on the [web](catmull-observe) and in the great JavaScript library
-[Culori](https://culorjs.org), ColorAide implements a number of spline based interpolation methods. ColorAide
-implements 3 piecewise, cubic splines.
+[Culori](https://culorjs.org), ColorAide implements a number of spline based interpolation methods.
 
 ### B-Spline
 
 ![B-spline](images/bspline-interpolation.png)
 
 B-spline is a piecewise spline similar to Bezier curves. It utilizes "control points" that help shape the interpolation
-path through two or more colors. Like Bezier Curves, the path does not pass through all the colors, but it is clamped at
-the start and end.
+path through a series of colors. Like Bezier Curves, the path does not pass through the control points, but it is
+clamped at the start and end. Essentially, the interpolation path passes through both end colors and bends that path
+along the way towards the other colors being used as control points.
 
 It can be used by specifying `bspline` as the interpolation method.
 
@@ -109,9 +110,10 @@ Color.interpolate(['red', 'green', 'blue', 'orange'], method='bspline')
 
 ![Natural](images/natural-interpolation.png)
 
-The "natural" spline is an the same B-spline approach except an algorithm is applied that uses the colors as data
-points and calculates control points such that the interpolation passes through all the data points. The resultant
-spline has the continuity and properties of a natural spline, hence the name.
+The "natural" spline is the same as the B-spline approach except an algorithm is applied that uses the colors as data
+points and calculates new control points such that the interpolation passes through all the data points. This means
+that the path will pass through all the colors. The resultant spline has the continuity and properties of a natural
+spline, hence the name.
 
 One down side is that it can overshoot or undershoot a bit, and can occasionally cause the interpolation path to pass
 out of gamut if interpolating on an edge.
@@ -126,8 +128,9 @@ Color.interpolate(['red', 'green', 'blue', 'orange'], method='natural')
 
 ![Monotone](images/monotone-interpolation.png)
 
-The "monotone" spline is a piecewise interpolation spline that preserves monotonicity. As far as we are concerned, the
-important thing to note is that it greatly reduces any overshoot or undershoot in the interpolation.
+The "monotone" spline is a piecewise interpolation spline that passes through all its data points and helps to preserve
+monotonicity. As far as we are concerned, the important thing to note is that it greatly reduces any overshoot or
+undershoot in the interpolation.
 
 ```playground
 Color.interpolate(['red', 'green', 'blue', 'orange'], method='monotone')
@@ -161,9 +164,9 @@ Custom.interpolate(['red', 'green', 'blue', 'orange'], method='catrom')
 ## Hue Interpolation
 
 In interpolation, hues are handled special allowing us to control the way in which hues are evaluated. By default, the
-shortest angle between two hues is interpolated between, but the `hue` allows us to redefine this behavior in a number
-of different ways: `shorter`, `longer`, `increasing`, `decreasing`, and `specified`. Below, we can see how the
-interpolation varies using `shorter` vs `longer` (interpolate between the largest angle).
+shortest angle between two hues is targeted for interpolation, but the `hue` option allows us to redefine this behavior
+in a number of interesting ways: `shorter`, `longer`, `increasing`, `decreasing`, and `specified`. Below, we can see how
+the interpolation varies using `shorter` vs `longer` (interpolate between the longest angle).
 
 ```playground
 i = Color.interpolate(
@@ -239,16 +242,15 @@ Interpolating color channels is pretty straight forward and uses traditional lin
 introducing transparency to a color, interpolation uses a concept known as premultiplication which alters the normal
 interpolation process.
 
-Premultiplication is a technique that tends to produce better results when two colors have differing transparency.
-Consider the following two example. Normally, when transitioning to a "transparent" color, the colors will be more gray
+Premultiplication is a technique that tends to produce better results when two colors have differing transparency. It
+essentially accounts for the transparency and uses it to _weight_ how may a given color channel will contribute to the
+interpolation. A more transparent color's channels will naturally contribute less.
+
+Consider the following example. Normally, when transitioning to a "transparent" color, the colors will be more gray
 during the transition. This is because `#!color transparent` is actually black. But when using premultiplication, the
 transition looks just as one would expect as the transparent color's channels are weighted less due to the high
 transparency.
 
-The example below transitions from white to a fully transparent color for both non-premultiplied and premultiplied
-interpolation. We've forced a white background (_for dark theme users_) to make the grayish transition of the
-non-premultiplied results easy to see.
-
 <div style="--swatch-bg-color: hsl(0, 0%, 100%); --swatch-bg-alt-color: hsl(0, 0%, 90%) " markdown>
 
 ```playground
@@ -258,13 +260,10 @@ Color.interpolate(['white', 'transparent'], space='srgb')
 
 </div>
 
-Premultiplication does better because it takes into account the differing amounts of transparency that each color has.
-Colors that are more opaque are "weighted" more, and contribute more to the overall color.
-
-Consider the example below. Orange is fully opaque while blue is quite transparent. Logically, the blue shouldn't have
-as big an affect on the overall color as it is so faint, and yet, in the un-premultiplied example, when mixing the
+As a final example, below we have an opaque orange and a blue that is quite transparent. Logically, the blue shouldn't
+have as big an affect on the overall color as it is so faint, and yet, in the un-premultiplied example, when mixing the
 colors equally, we see that the resultant color is also equally influenced by the hue of both colors. In the
-premultiplied example, we see that orange is still quite dominant at 50%.
+premultiplied example, we see that orange is still quite dominant at 50% as it is fully opaque.
 
 
 ```playground
@@ -279,8 +278,8 @@ Color.interpolate(['orange', Color('blue').set('alpha', 0.25)], space='srgb', pr
 Color.interpolate(['orange', Color('blue').set('alpha', 0.25)], space='srgb')
 ```
 
-There may be some cases where it is desired to use a non-premultiplied. There are many reasons, one could simply be that
-you need to mimic the same behavior of a system that does not use premultiplied interpolation. If so, simply set
+There may be some cases where it is desired to use no premultiplication in alpha blending. One could simply be that you
+need to mimic the same behavior of a system that does not use premultiplied interpolation. If so, simply set
 `premultiplied` to `#!py3 False` as shown above.
 
 ## Masking
@@ -317,10 +316,6 @@ i = Color.interpolate(
 [i(x/10).to_string() for x in range(10)]
 ```
 
-!!! tip "Magic Behind Masking"
-    Masking actually clones the color, setting the specified channels to undefined values. To learn more about masking
-    and undefined values, check out [Undefined Handling](#null-handling).
-
 ## Easing Functions
 
 When interpolating, whether using linear interpolation or something like B-Spline interpolation, the transitioning
@@ -328,12 +323,14 @@ between colors is always linear in time, even if the path to those colors is not
 between 2 colors and you request a `#!py3 0.5` point on that line, it will always be in the middle. This is because, no
 matter how crooked the path, the rate of change on that path is always linear.
 
-By default, ColorAide always uses linear transitions when interpolating, but there are times that a different, more
-dynamic transition may be desired. This can be achieved by using the `progress` parameter on any of the interpolation
-related functions provided by ColorAide. `progress` accepts an easing function that takes a single `time` input and
-returns a new `time` input. This allows for a user to augment the rate of change when transition from one color to
-another. Inputs are almost always between 0 - 1 unless [`extrapolate`](#extrapolation) is enabled and the user has
-manually input a range beyond 0 - 1.
+By default, ColorAide uses linear transitions when interpolating, but there are times that a different, more dynamic
+transition may be desired. This can be achieved by using the `progress` parameter on any of the interpolation related
+functions provided by ColorAide.
+
+`progress` accepts an easing function that takes a single `time` input and returns a new `time` input. This allows for a
+user to augment the rate of change when transitioning from one color to another. Inputs are almost always between 0 - 1
+unless [`extrapolate`](#extrapolation) is enabled and the user has manually input a range beyond 0 - 1. Even a change in
+[domain](#domains) will not affect the range as once the domain is accounted for, internally the domain [0, 1] is used.
 
 ColorAide provides 5 basic easing functions out of the box along with `cubic_bezier` which is used to create all of the
 aforementioned easing function except `linear`, which simply returns what is given as an input.
@@ -453,7 +450,7 @@ Color.interpolate(
 ## Color Stops and Hints
 
 Color stops are the position where the transition to and from a color starts and ends. By default, color stops are
-evenly distributed between 0 - 1, but if desired, these color stops can be shifted.
+evenly distributed within the domain of [0, 1], but if desired, these color stops can be shifted.
 
 To specify color stops, simply wrap a color in a `coloraide.stop` object and specify the stop position. Stop positions
 will then cause the transition of the targeted color to be moved.
@@ -620,11 +617,82 @@ of the other supported `interpolate` features as well.
 Color.steps(['orange', stop('purple', 0.25), 'green'], method='bspline', steps=10)
 ```
 
+## Domains
+
+By default, interpolation has an input domain of [0, 1]. This domain applies to an entire interpolation, even ones that
+span multiple colors. Generally, this is sufficient and can be used to generate color scales, mixes, and steps in any
+way that a user needs, but there are times where a different domain would be helpful.
+
+Consider the use case of generating a color scale in relation to temperature. Temperature doesn't operate on a [0, 1]
+domain. We may want our color scale to span a particular range of temperature. We may also want to have the color scale
+align with specific temperature within that range. While the user can certainly craft the logic to translate these
+data points to a [0, 1] domain and calculate and apply color stops to align the, colors, it would be much more
+accessible if the user could simply change the domain to work with their data. Luckily, ColorAide is up to the
+challenge.
+
+```playground
+i = Color.interpolate(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    domain=[-32, 32, 60, 85, 95]
+)
+i(-32)
+i(47)
+i(89)
+i
+```
+
+It should be noted that you are not constrained to provide the exact same amount of domain values as you have colors and
+can have differing amounts, but if you want to align specific colors to certain data points, then it helps.
+
+```playground
+Color.interpolate(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    domain=[-32, 32, 60, 85, 95]
+)
+Color.interpolate(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    domain=[-32, 95]
+)
+```
+
+Lastly, domains must be specified in ascending order of values. If a value decreases in magnitude, it will assume the
+value that comes right before it. This means you cannot put a domain in reverse. If you need to reverse the order, just
+flip the color order and setup the domain accordingly.
+
+```playground
+i = Color.interpolate(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    domain=[-32, 32, 60, 20, 95]
+)
+i.domain
+i
+```
+
+Custom domains are most useful when working with `interpolate` directly, but you can use it in other methods like
+[`steps`](#steps) as well. As `steps` does not take a data point inputs like `interpolate`, we do not need to use the
+temperature data as an input except to set the domain, but the steps will be generated with the same alignment relative
+to the domain range.
+
+```playground
+Color.interpolate(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    domain=[-32, 32, 60, 85, 95]
+)
+Color.steps(
+    ['blue', 'green', 'yellow', 'orange', 'red'],
+    steps=11,
+    domain=[-32, 32, 60, 85, 95]
+)
+```
+
+Wile you can technically feed `domain` into [`mix`](#mix), it is probably not as useful. It will respect the domain
+alignment, but mix always accepts a percentage of [0, 1], regardless of the underlying domain.
+
 ## Extrapolation
 
-By default, ColorAide clamps the entire progress of an interpolation to always be between 0 and 1. In most cases, this
-is more what most user expects and why this is the default. It should be noted that this does not affect easing
-functions, as the clamping is done prior to any easing function calls.
+By default, ColorAide clamps the entire progress of an interpolation to always be within the domain ([0, 1] by default).
+In most cases, this is more what most user expects and why this is the default. It should be noted that this does not
+affect easing functions, as the clamping is done prior to any easing function calls.
 
 If it is desired to extrapolate past 0 and 1, `extrapolate` can set to `#!py3 True` on all interpolation methods.
 
diff --git a/docs/src/mkdocs.yml b/docs/src/mkdocs.yml
index daed517a3..51391e36b 100644
--- a/docs/src/mkdocs.yml
+++ b/docs/src/mkdocs.yml
@@ -208,7 +208,7 @@ extra_css:
   - assets/coloraide-extras/extra.css
 extra_javascript:
   - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
-  - playground-config-3625007a.js
+  - playground-config-52c8e5b3.js
   - https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
   - assets/coloraide-extras/extra-notebook.js
 
diff --git a/docs/src/py/notebook.py b/docs/src/py/notebook.py
index 04f4b6313..e6d5e2b2c 100644
--- a/docs/src/py/notebook.py
+++ b/docs/src/py/notebook.py
@@ -25,7 +25,7 @@
 from pygments.formatters import find_formatter_class
 import coloraide
 from coloraide import Color
-from coloraide.interpolate import Interpolator
+from coloraide.interpolate import Interpolator, normalize_domain
 try:
     from coloraide_extras.everything import ColorAll
 except ImportError:
@@ -101,6 +101,7 @@ def get_colors(result):
     """Get color from results."""
 
     colors = []
+    domain = []
     if isinstance(result, HtmlRow):
         colors = HtmlRow(
             [
@@ -114,7 +115,14 @@ def get_colors(result):
     elif isinstance(result, Color):
         colors.append(ColorTuple(result.to_string(fit=False), result.clone()))
     elif isinstance(result, Interpolator):
+        # Since we are auto showing the gradient, we need to scale the domain to something we expect.
+        if result.domain:
+            domain = result.domain
+            result.domain = normalize_domain(result.domain)
         colors = HtmlGradient(result.steps(steps=5, max_delta_e=2.3))
+        if domain:
+            result.domain = domain
+            domain = []
     elif isinstance(result, str):
         try:
             colors.append(ColorTuple(result, ColorAll(result)))
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js b/docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js
deleted file mode 100644
index 6dbfbbf6e..000000000
--- a/docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js
+++ /dev/null
@@ -1,2 +0,0 @@
-function _typeof(e){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_typeof(e)}var runtime=function(e){"use strict";var t,n=Object.prototype,o=n.hasOwnProperty,r="function"==typeof Symbol?Symbol:{},a=r.iterator||"@@iterator",i=r.asyncIterator||"@@asyncIterator",s=r.toStringTag||"@@toStringTag";function c(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{c({},"")}catch(e){c=function(e,t,n){return e[t]=n}}function l(e,t,n,o){var r=t&&t.prototype instanceof y?t:y,a=Object.create(r.prototype),i=new T(o||[]);return a._invoke=function(e,t,n){var o=d;return function(r,a){if(o===f)throw new Error("Generator is already running");if(o===m){if("throw"===r)throw a;return A()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=L(i,n);if(s){if(s===h)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===d)throw o=m,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=f;var c=u(e,t,n);if("normal"===c.type){if(o=n.done?m:p,c.arg===h)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o=m,n.method="throw",n.arg=c.arg)}}}(e,n,i),a}function u(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}e.wrap=l;var d="suspendedStart",p="suspendedYield",f="executing",m="completed",h={};function y(){}function g(){}function v(){}var w={};c(w,a,(function(){return this}));var _=Object.getPrototypeOf,x=_&&_(_(P([])));x&&x!==n&&o.call(x,a)&&(w=x);var b=v.prototype=y.prototype=Object.create(w);function E(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function k(e,t){function n(r,a,i,s){var c=u(e[r],e,a);if("throw"!==c.type){var l=c.arg,d=l.value;return d&&"object"===_typeof(d)&&o.call(d,"__await")?t.resolve(d.__await).then((function(e){n("next",e,i,s)}),(function(e){n("throw",e,i,s)})):t.resolve(d).then((function(e){l.value=e,i(l)}),(function(e){return n("throw",e,i,s)}))}s(c.arg)}var r;this._invoke=function(e,o){function a(){return new t((function(t,r){n(e,o,t,r)}))}return r=r?r.then(a,a):a()}}function L(e,n){var o=e.iterator[n.method];if(o===t){if(n.delegate=null,"throw"===n.method){if(e.iterator.return&&(n.method="return",n.arg=t,L(e,n),"throw"===n.method))return h;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return h}var r=u(o,e.iterator,n.arg);if("throw"===r.type)return n.method="throw",n.arg=r.arg,n.delegate=null,h;var a=r.arg;return a?a.done?(n[e.resultName]=a.value,n.next=e.nextLoc,"return"!==n.method&&(n.method="next",n.arg=t),n.delegate=null,h):a:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,h)}function S(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function C(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function T(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(S,this),this.reset(!0)}function P(e){if(e){var n=e[a];if(n)return n.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var r=-1,i=function n(){for(;++r<e.length;)if(o.call(e,r))return n.value=e[r],n.done=!1,n;return n.value=t,n.done=!0,n};return i.next=i}}return{next:A}}function A(){return{value:t,done:!0}}return g.prototype=v,c(b,"constructor",v),c(v,"constructor",g),g.displayName=c(v,s,"GeneratorFunction"),e.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===g||"GeneratorFunction"===(t.displayName||t.name))},e.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,v):(e.__proto__=v,c(e,s,"GeneratorFunction")),e.prototype=Object.create(b),e},e.awrap=function(e){return{__await:e}},E(k.prototype),c(k.prototype,i,(function(){return this})),e.AsyncIterator=k,e.async=function(t,n,o,r,a){void 0===a&&(a=Promise);var i=new k(l(t,n,o,r),a);return e.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},E(b),c(b,s,"Generator"),c(b,a,(function(){return this})),c(b,"toString",(function(){return"[object Generator]"})),e.keys=function(e){var t=[];for(var n in e)t.push(n);return t.reverse(),function n(){for(;t.length;){var o=t.pop();if(o in e)return n.value=o,n.done=!1,n}return n.done=!0,n}},e.values=P,T.prototype={constructor:T,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=t,this.done=!1,this.delegate=null,this.method="next",this.arg=t,this.tryEntries.forEach(C),!e)for(var n in this)"t"===n.charAt(0)&&o.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=t)},stop:function(){this.done=!0;var e=this.tryEntries[0].completion;if("throw"===e.type)throw e.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var n=this;function r(o,r){return s.type="throw",s.arg=e,n.next=o,r&&(n.method="next",n.arg=t),!!r}for(var a=this.tryEntries.length-1;a>=0;--a){var i=this.tryEntries[a],s=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var c=o.call(i,"catchLoc"),l=o.call(i,"finallyLoc");if(c&&l){if(this.prev<i.catchLoc)return r(i.catchLoc,!0);if(this.prev<i.finallyLoc)return r(i.finallyLoc)}else if(c){if(this.prev<i.catchLoc)return r(i.catchLoc,!0)}else{if(!l)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return r(i.finallyLoc)}}}},abrupt:function(e,t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev<r.finallyLoc){var a=r;break}}a&&("break"===e||"continue"===e)&&a.tryLoc<=t&&t<=a.finallyLoc&&(a=null);var i=a?a.completion:{};return i.type=e,i.arg=t,a?(this.method="next",this.next=a.finallyLoc,h):this.complete(i)},complete:function(e,t){if("throw"===e.type)throw e.arg;return"break"===e.type||"continue"===e.type?this.next=e.arg:"return"===e.type?(this.rval=this.arg=e.arg,this.method="return",this.next="end"):"normal"===e.type&&t&&(this.next=t),h},finish:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),C(n),h}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var o=n.completion;if("throw"===o.type){var r=o.arg;C(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,n,o){return this.delegate={iterator:P(e),resultName:n,nextLoc:o},"next"===this.method&&(this.arg=t),h}},e}("object"===("undefined"==typeof module?"undefined":_typeof(module))?module.exports:{});try{regeneratorRuntime=runtime}catch(e){"object"===("undefined"==typeof globalThis?"undefined":_typeof(globalThis))?globalThis.regeneratorRuntime=runtime:Function("r","regeneratorRuntime = r")(runtime)}!function(){"use strict";function e(){e=function(){return t};var t={},n=Object.prototype,o=n.hasOwnProperty,r=Object.defineProperty||function(e,t,n){e[t]=n.value},a="function"==typeof Symbol?Symbol:{},i=a.iterator||"@@iterator",s=a.asyncIterator||"@@asyncIterator",c=a.toStringTag||"@@toStringTag";function l(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,n){return e[t]=n}}function u(e,t,n,o){var a=t&&t.prototype instanceof f?t:f,i=Object.create(a.prototype),s=new S(o||[]);return r(i,"_invoke",{value:b(e,n,s)}),i}function d(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=u;var p={};function f(){}function m(){}function h(){}var y={};l(y,i,(function(){return this}));var g=Object.getPrototypeOf,v=g&&g(g(C([])));v&&v!==n&&o.call(v,i)&&(y=v);var w=h.prototype=f.prototype=Object.create(y);function _(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function x(e,t){function n(r,a,i,s){var c=d(e[r],e,a);if("throw"!==c.type){var l=c.arg,u=l.value;return u&&"object"==_typeof(u)&&o.call(u,"__await")?t.resolve(u.__await).then((function(e){n("next",e,i,s)}),(function(e){n("throw",e,i,s)})):t.resolve(u).then((function(e){l.value=e,i(l)}),(function(e){return n("throw",e,i,s)}))}s(c.arg)}var a;r(this,"_invoke",{value:function(e,o){function r(){return new t((function(t,r){n(e,o,t,r)}))}return a=a?a.then(r,r):r()}})}function b(e,t,n){var o="suspendedStart";return function(r,a){if("executing"===o)throw new Error("Generator is already running");if("completed"===o){if("throw"===r)throw a;return T()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=E(i,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===o)throw o="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o="executing";var c=d(e,t,n);if("normal"===c.type){if(o=n.done?"completed":"suspendedYield",c.arg===p)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o="completed",n.method="throw",n.arg=c.arg)}}}function E(e,t){var n=e.iterator[t.method];if(void 0===n){if(t.delegate=null,"throw"===t.method){if(e.iterator.return&&(t.method="return",t.arg=void 0,E(e,t),"throw"===t.method))return p;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return p}var o=d(n,e.iterator,t.arg);if("throw"===o.type)return t.method="throw",t.arg=o.arg,t.delegate=null,p;var r=o.arg;return r?r.done?(t[e.resultName]=r.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,p):r:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,p)}function k(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function L(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function S(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(k,this),this.reset(!0)}function C(e){if(e){var t=e[i];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n<e.length;)if(o.call(e,n))return t.value=e[n],t.done=!1,t;return t.value=void 0,t.done=!0,t};return r.next=r}}return{next:T}}function T(){return{value:void 0,done:!0}}return m.prototype=h,r(w,"constructor",{value:h,configurable:!0}),r(h,"constructor",{value:m,configurable:!0}),m.displayName=l(h,c,"GeneratorFunction"),t.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===m||"GeneratorFunction"===(t.displayName||t.name))},t.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,h):(e.__proto__=h,l(e,c,"GeneratorFunction")),e.prototype=Object.create(w),e},t.awrap=function(e){return{__await:e}},_(x.prototype),l(x.prototype,s,(function(){return this})),t.AsyncIterator=x,t.async=function(e,n,o,r,a){void 0===a&&(a=Promise);var i=new x(u(e,n,o,r),a);return t.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},_(w),l(w,c,"Generator"),l(w,i,(function(){return this})),l(w,"toString",(function(){return"[object Generator]"})),t.keys=function(e){var t=Object(e),n=[];for(var o in t)n.push(o);return n.reverse(),function e(){for(;n.length;){var o=n.pop();if(o in t)return e.value=o,e.done=!1,e}return e.done=!0,e}},t.values=C,S.prototype={constructor:S,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=void 0,this.done=!1,this.delegate=null,this.method="next",this.arg=void 0,this.tryEntries.forEach(L),!e)for(var t in this)"t"===t.charAt(0)&&o.call(this,t)&&!isNaN(+t.slice(1))&&(this[t]=void 0)},stop:function(){this.done=!0;var e=this.tryEntries[0].completion;if("throw"===e.type)throw e.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var t=this;function n(n,o){return i.type="throw",i.arg=e,t.next=n,o&&(t.method="next",t.arg=void 0),!!o}for(var r=this.tryEntries.length-1;r>=0;--r){var a=this.tryEntries[r],i=a.completion;if("root"===a.tryLoc)return n("end");if(a.tryLoc<=this.prev){var s=o.call(a,"catchLoc"),c=o.call(a,"finallyLoc");if(s&&c){if(this.prev<a.catchLoc)return n(a.catchLoc,!0);if(this.prev<a.finallyLoc)return n(a.finallyLoc)}else if(s){if(this.prev<a.catchLoc)return n(a.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return n(a.finallyLoc)}}}},abrupt:function(e,t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev<r.finallyLoc){var a=r;break}}a&&("break"===e||"continue"===e)&&a.tryLoc<=t&&t<=a.finallyLoc&&(a=null);var i=a?a.completion:{};return i.type=e,i.arg=t,a?(this.method="next",this.next=a.finallyLoc,p):this.complete(i)},complete:function(e,t){if("throw"===e.type)throw e.arg;return"break"===e.type||"continue"===e.type?this.next=e.arg:"return"===e.type?(this.rval=this.arg=e.arg,this.method="return",this.next="end"):"normal"===e.type&&t&&(this.next=t),p},finish:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),L(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var o=n.completion;if("throw"===o.type){var r=o.arg;L(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:C(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},t}function t(e,t,n,o,r,a,i){try{var s=e[a](i),c=s.value}catch(e){return void n(e)}s.done?t(c):Promise.resolve(c).then(o,r)}function n(e){return function(){var n=this,o=arguments;return new Promise((function(r,a){var i=e.apply(n,o);function s(e){t(i,r,a,s,c,"next",e)}function c(e){t(i,r,a,s,c,"throw",e)}s(void 0)}))}}function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var o,r,a=[],i=!0,s=!1;try{for(n=n.call(e);!(i=(o=n.next()).done)&&(a.push(o.value),!t||a.length!==t);i=!0);}catch(e){s=!0,r=e}finally{try{i||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,t)||r(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(e,t){if(e){if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?a(e,t):void 0}}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n<t;n++)o[n]=e[n];return o}function i(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=r(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var o=0,a=function(){};return{s:a,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,s=!0,c=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return s=e.done,e},e:function(e){c=!0,i=e},f:function(){try{s||null==n.return||n.return()}finally{if(c)throw i}}}}var s,c,l,u,d,p,f,m,h,y,g,v,w,_,x,b,E,k,L,S,C,T,P,A,I,F;s=null,c=!1,l="",u="",d={},p=/.*?_(\d+)$/,f=!1,m="",h=!1,y=/^( {1,4}|\t)/,g="\n\"\"\"\nExecute Python code in code blocks (color previews added specifically for ColorAide).\n\nThis can be executed in either a Pyodide environment of a normal Python environment.\n3rd party libraries (that are not available directly from Pyodide) are only loaded\nwhen needed so that Pyodide will have a chance to load them if necessary. This is also\nhardcoded to work with ColorAide.\n\nThis is meant to be executed by Pyodide on preformatted HTML to allow for live execution of\ncode snippets using `coloraide`.\n\nTransform Python code by executing it, transforming to a Python console output,\nand finding and outputting color previews.\n\"\"\"\nimport xml.etree.ElementTree as Etree\nfrom collections.abc import Sequence\nfrom collections import namedtuple\nimport ast\nfrom io import StringIO\nimport contextlib\nimport sys\nimport re\nfrom pygments import highlight\nfrom pygments.lexers import get_lexer_by_name\nfrom pygments.formatters import find_formatter_class\nimport coloraide\nfrom coloraide import Color\nfrom coloraide.interpolate import Interpolator\ntry:\n    from coloraide_extras.everything import ColorAll\nexcept ImportError:\n    from coloraide.everything import ColorAll\ntry:\n    import coloraide_extras\nexcept ImportError:\n    coloraide_extras = None\n\nWEBSPACE = \"srgb\"\nAST_BLOCKS = (ast.If, ast.For, ast.While, ast.Try, ast.With, ast.FunctionDef, ast.ClassDef)\n\nRE_COLOR_START = re.compile(\n    r\"(?i)(?:\\b(?<![-#&$])(?:color|hsla?|lch|lab|hwb|rgba?)\\(|\\b(?<![-#&$])[\\w]{3,}(?![(-])\\b|(?<![&])#)\"\n)\n\ntemplate = '''<div class=\"playground\" id=\"__playground_{el_id}\">\n<div class=\"playground-results\" id=\"__playground-results_{el_id}\">\n{results}\n</div>\n<div class=\"playground-code hidden\" id=\"__playground-code_{el_id}\">\n<form autocomplete=\"off\">\n<textarea class=\"playground-inputs\" id=\"__playground-inputs_{el_id}\" spellcheck=\"false\">{raw_source}</textarea>\n</form>\n</div>\n\n<button id=\"__playground-edit_{el_id}\" class=\"playground-edit\" title=\"Edit the current snippet\">Edit</button>\n<button id=\"__playground-share_{el_id}\" class=\"playground-share\" title=\"Copy URL to current snippet\">Share</button>\n<button id=\"__playground-run_{el_id}\" class=\"playground-run hidden\" title=\"Run code (Ctrl + Enter)\">Run</button>\n<button id=\"__playground-cancel_{el_id}\" class=\"playground-cancel hidden\" title=\"Cancel edit (Escape)\">Cancel</button>\n</div>'''\n\ncode_id = 0\n\n\nclass HtmlGradient(list):\n    \"\"\"HTML color gradient.\"\"\"\n\n\nclass HtmlSteps(list):\n    \"\"\"HTML color steps.\"\"\"\n\n\nclass ColorTuple(namedtuple('ColorTuple', ['string', 'color'])):\n    \"\"\"Color tuple.\"\"\"\n\n\nclass HtmlRow(list):\n    \"\"\"Create a row with the given colors.\"\"\"\n\n\ndef _escape(txt):\n    \"\"\"Basic HTML escaping.\"\"\"\n\n    txt = txt.replace('&', '&amp;')\n    txt = txt.replace('<', '&lt;')\n    txt = txt.replace('>', '&gt;')\n    return txt\n\n\n@contextlib.contextmanager\ndef std_output(stdout=None):\n    \"\"\"Capture standard out.\"\"\"\n    old = sys.stdout\n    if stdout is None:\n        stdout = StringIO()\n    sys.stdout = stdout\n    yield stdout\n    sys.stdout = old\n\n\ndef get_colors(result):\n    \"\"\"Get color from results.\"\"\"\n\n    colors = []\n    if isinstance(result, HtmlRow):\n        colors = HtmlRow(\n            [\n                ColorTuple(c.to_string(fit=False), c.clone()) if isinstance(c, Color) else ColorTuple(c, ColorAll(c))\n                for c in result\n            ]\n        )\n    elif isinstance(result, (HtmlSteps, HtmlGradient)):\n        t = type(result)\n        colors = t([c.clone() if isinstance(c, Color) else ColorAll(c) for c in result])\n    elif isinstance(result, Color):\n        colors.append(ColorTuple(result.to_string(fit=False), result.clone()))\n    elif isinstance(result, Interpolator):\n        colors = HtmlGradient(result.steps(steps=5, max_delta_e=2.3))\n    elif isinstance(result, str):\n        try:\n            colors.append(ColorTuple(result, ColorAll(result)))\n        except Exception:\n            pass\n    elif isinstance(result, Sequence):\n        for x in result:\n            if isinstance(x, Color):\n                colors.append(ColorTuple(x.to_string(fit=False), x.clone()))\n            elif isinstance(x, str):\n                try:\n                    colors.append(ColorTuple(x, ColorAll(x)))\n                except Exception:\n                    pass\n    return colors\n\n\ndef find_colors(text):\n    \"\"\"Find colors in text buffer.\"\"\"\n\n    colors = []\n    for m in RE_COLOR_START.finditer(text):\n        start = m.start()\n        mcolor = ColorAll.match(text, start=start)\n        if mcolor is not None:\n            colors.append(ColorTuple(text[mcolor.start:mcolor.end], mcolor.color))\n    return colors\n\n\ndef execute(cmd, no_except=True, inline=False):\n    \"\"\"Execute color commands.\"\"\"\n\n    g = {k: getattr(coloraide, k) for k in coloraide.__all__}\n    g['coloraide'] = coloraide\n    g['Color'] = ColorAll\n    g['HtmlRow'] = HtmlRow\n    g['HtmlSteps'] = HtmlSteps\n    g['HtmlGradient'] = HtmlGradient\n\n    if coloraide_extras is not None:\n        g['coloraide_extras'] = coloraide_extras\n\n    console = ''\n    colors = []\n\n    # Build AST tree\n    src = cmd.strip()\n    lines = src.split('\\n')\n    try:\n        tree = ast.parse(src)\n    except Exception as e:\n        if no_except:\n            if not inline:\n                from pymdownx.superfences import SuperFencesException\n                raise SuperFencesException from e\n            else:\n                from pymdownx.inlinehilite import InlineHiliteException\n                raise InlineHiliteException from e\n        import traceback\n        return '{}'.format(traceback.format_exc()), colors\n\n    for node in tree.body:\n        result = None\n\n        # Format source as Python console statements\n        start = node.lineno\n        end = node.end_lineno\n        stmt = lines[start - 1: end]\n        command = ''\n        for i, line in enumerate(stmt, 0):\n            if i == 0:\n                stmt[i] = '>>> ' + line\n            else:\n                stmt[i] = '... ' + line\n        command += '\\n'.join(stmt)\n        if isinstance(node, AST_BLOCKS):\n            command += '\\n... '\n\n        try:\n            # Capture anything sent to standard out\n            text = ''\n            with std_output() as s:\n                # Execute code\n                if isinstance(node, ast.Expr):\n                    _eval = ast.Expression(node.value)\n                    result = eval(compile(_eval, '<string>', 'eval'), g)\n                else:\n                    _exec = ast.Module([node], [])\n                    exec(compile(_exec, '<string>', 'exec'), g)\n\n                # Execution went well, so append command\n                console += command\n\n                # Output captured standard out after statements\n                text = s.getvalue()\n                if text:\n                    clist = find_colors(text)\n                    if clist:\n                        colors.append(clist)\n                    console += '\\n{}'.format(text)\n                s.flush()\n        except Exception as e:\n            if no_except:\n                if not inline:\n                    from pymdownx.superfences import SuperFencesException\n                    raise SuperFencesException from e\n                else:\n                    from pymdownx.inlinehilite import InlineHiliteException\n                    raise InlineHiliteException from e\n            import traceback\n            console += '{}\\n{}'.format(command, traceback.format_exc())\n            # Failed for some reason, so quit\n            break\n\n        # If we got a result, output it as well\n        if result is not None:\n            clist = get_colors(result)\n            if clist:\n                colors.append(clist)\n            console += '{}{}\\n'.format('\\n' if not text else '', str(result))\n        else:\n            console += '\\n' if not text else ''\n\n    return console, colors\n\n\ndef colorize(src, lang, **options):\n    \"\"\"Colorize.\"\"\"\n\n    HtmlFormatter = find_formatter_class('html')\n    lexer = get_lexer_by_name(lang, **options)\n    formatter = HtmlFormatter(cssclass=\"highlight\", wrapcode=True)\n    return highlight(src, lexer, formatter).strip()\n\n\ndef color_command_validator(language, inputs, options, attrs, md):\n    \"\"\"Color validator.\"\"\"\n\n    valid_inputs = set(['exceptions'])\n\n    for k, v in inputs.items():\n        if k in valid_inputs:\n            options[k] = True\n            continue\n        attrs[k] = v\n    return True\n\n\ndef _color_command_console(colors):\n    \"\"\"Color command formatter.\"\"\"\n\n    el = ''\n    bar = False\n    values = []\n    for item in colors:\n        if isinstance(item, (HtmlGradient, HtmlSteps)):\n            current = total = percent = last = 0\n            if isinstance(item, HtmlSteps):\n                total = len(item)\n                percent = 100 / total\n                current = percent\n\n            if bar:\n                el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                values = []\n            sub_el1 = '<div class=\"swatch-bar\"><span class=\"swatch swatch-gradient\">{}</span></div>'\n            style = \"--swatch-stops: \"\n            stops = []\n            for e, color in enumerate(item):\n                color.fit(WEBSPACE)\n                if current:\n                    stops.append('{} {}%'.format(color.convert(WEBSPACE).to_string(), str(last)))\n                    stops.append('{} {}%'.format(color.convert(WEBSPACE).to_string(), str(current)))\n                    last = current\n                    if e < (total - 1):\n                        current += percent\n                    else:\n                        current = 100\n                else:\n                    stops.append(color.convert(WEBSPACE).to_string())\n            if not stops:\n                stops.extend(['transparent'] * 2)\n            if len(stops) == 1:\n                stops.append(stops[0])\n            style += ','.join(stops)\n            sub_el2 = '<span class=\"swatch-color\" style=\"{}\"></span>'.format(style)\n            el += sub_el1.format(sub_el2)\n            bar = False\n        else:\n            is_row = False\n            if isinstance(item, HtmlRow):\n                is_row = True\n                if bar and values:\n                    el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                    values = []\n                bar = False\n\n            bar = True\n            for color in item:\n                base_classes = \"swatch\"\n                if not color.color.in_gamut(WEBSPACE):\n                    base_classes += \" out-of-gamut\"\n                color.color.fit(WEBSPACE)\n                srgb = color.color.convert(WEBSPACE)\n                value1 = srgb.to_string(alpha=False)\n                value2 = srgb.to_string()\n                style = \"--swatch-stops: {} 50%, {} 50%\".format(value1, value2)\n                title = color.string\n                classes = base_classes\n                c = '<span class=\"swatch-color\" style=\"{style}\"></span>'.format(style=style)\n                c = '<span class=\"{classes}\" title=\"{title}&#013;Copy to clipboard\">{color}</span>'.format(\n                    classes=classes,\n                    color=c,\n                    title=title\n                )\n                values.append(c)\n\n            if is_row and values:\n                el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                values = []\n                bar = False\n    if bar:\n        el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n        values = []\n\n    return el\n\n\ndef color_command_formatter(src=\"\", language=\"\", class_name=None, options=None, md=\"\", **kwargs):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    global code_id\n    from pymdownx.superfences import SuperFencesException\n\n    try:\n        if len(md.preprocessors['fenced_code_block'].extension.stash) == 0:\n            code_id = 0\n\n        # Check if we should allow exceptions\n        exceptions = options.get('exceptions', False) if options is not None else False\n\n        console, colors = execute(src.strip(), not exceptions)\n        el = _color_command_console(colors)\n\n        el += md.preprocessors['fenced_code_block'].extension.superfences[0]['formatter'](\n            src=console,\n            class_name=\"highlight\",\n            language='pycon',\n            md=md,\n            options=options,\n            **kwargs\n        )\n        el = '<div class=\"color-command\">{}</div>'.format(el)\n        el = template.format(el_id=code_id, raw_source=_escape(src), results=el)\n        code_id += 1\n    except SuperFencesException:\n        raise\n    except Exception:\n        from pymdownx import superfences\n        import traceback\n        print(traceback.format_exc())\n        return superfences.fence_code_format(src, 'text', class_name, options, md, **kwargs)\n    return el\n\n\ndef color_formatter(src=\"\", language=\"\", class_name=None, md=\"\", exceptions=True):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    from pymdownx.inlinehilite import InlineHiliteException\n\n    try:\n        result = src.strip()\n\n        try:\n            color = ColorAll(result.strip())\n        except Exception:\n            console, colors = execute(result, exceptions, inline=True)\n            if len(colors) != 1 or len(colors[0]) != 1:\n                if exceptions:\n                    raise InlineHiliteException('Only one color allowed')\n                else:\n                    raise ValueError('Only one color allowed')\n            color = colors[0][0].color\n            result = colors[0][0].string\n\n        el = Etree.Element('span')\n        stops = []\n        if not color.in_gamut(WEBSPACE):\n            color.fit(WEBSPACE)\n            attributes = {'class': \"swatch out-of-gamut\", \"title\": result}\n            sub_el = Etree.SubElement(el, 'span', attributes)\n            stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False))\n            if color[-1] < 1.0:\n                stops[-1] += ' 50%'\n                stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%')\n        else:\n            attributes = {'class': \"swatch\", \"title\": result}\n            sub_el = Etree.SubElement(el, 'span', attributes)\n            stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False))\n            if color[-1] < 1.0:\n                stops[-1] += ' 50%'\n                stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%')\n\n        if not stops:\n            stops.extend(['transparent'] * 2)\n        if len(stops) == 1:\n            stops.append(stops[0])\n\n        Etree.SubElement(\n            sub_el,\n            'span',\n            {\n                \"class\": \"swatch-color\",\n                \"style\": \"--swatch-stops: {};\".format(','.join(stops))\n            }\n        )\n\n        el.append(md.inlinePatterns['backtick'].handle_code('css-color', result))\n    except InlineHiliteException:\n        raise\n    except Exception:\n        import traceback\n        print(traceback.format_exc())\n        el = md.inlinePatterns['backtick'].handle_code('text', src)\n    return el\n\n\n#############################\n# Pyodide specific code\n#############################\ndef live_color_command_formatter(src):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    try:\n        console, colors = execute(src.strip(), False)\n        el = _color_command_console(colors)\n\n        if not colors:\n            el += '<div class=\"swatch-bar\"></div>'\n\n        el += colorize(console, 'pycon', **{'python3': True, 'stripnl': False})\n        el = '<div class=\"color-command\">{}</div>'.format(el)\n    except Exception:\n        return '<div class=\"color-command\"><div class=\"swatch-bar\"></div>{}</div>'.format(colorize('', 'text'))\n    return el\n\n\ndef live_color_command_validator(language, inputs, options, attrs, md):\n    \"\"\"Color validator.\"\"\"\n\n    value = color_command_validator(language, inputs, options, attrs, md)\n    # Live edit, we always allow exceptions so not to crash the service.\n    options['exceptions'] = True\n    return value\n\n\ndef live_color_formatter(src=\"\", language=\"\", class_name=None, md=\"\"):\n    \"\"\"Color formatter for a live environment.\"\"\"\n\n    return color_formatter(src, language, class_name, md, exceptions=False)\n\n\ndef render_console(*args):\n    \"\"\"Render console update.\"\"\"\n\n    from js import document\n\n    try:\n        # Run code\n        inputs = document.getElementById(\"__playground-inputs_{}\".format(globals()['id_num']))\n        results = document.getElementById(\"__playground-results_{}\".format(globals()['id_num']))\n        results.innerHTML = live_color_command_formatter(inputs.value)\n        scrollingElement = results.querySelector('code')\n        scrollingElement.scrollTop = scrollingElement.scrollHeight\n    except Exception as e:\n        print(e)\n\n\ndef render_notebook(*args):\n    \"\"\"Render notebook.\"\"\"\n\n    import markdown\n    from pymdownx import slugs, superfences\n    from js import document\n\n    text = globals().get('content', '')\n    extensions = [\n        'markdown.extensions.toc',\n        'markdown.extensions.admonition',\n        'markdown.extensions.smarty',\n        'pymdownx.betterem',\n        'markdown.extensions.attr_list',\n        'markdown.extensions.def_list',\n        'markdown.extensions.tables',\n        'markdown.extensions.abbr',\n        'markdown.extensions.footnotes',\n        'markdown.extensions.md_in_html',\n        'pymdownx.superfences',\n        'pymdownx.highlight',\n        'pymdownx.inlinehilite',\n        'pymdownx.magiclink',\n        'pymdownx.tilde',\n        'pymdownx.caret',\n        'pymdownx.smartsymbols',\n        'pymdownx.emoji',\n        'pymdownx.escapeall',\n        'pymdownx.tasklist',\n        'pymdownx.striphtml',\n        'pymdownx.snippets',\n        'pymdownx.keys',\n        'pymdownx.details',\n        'pymdownx.saneheaders',\n        'pymdownx.tabbed',\n        'pymdownx.arithmatex'\n    ]\n    extension_configs = {\n        'markdown.extensions.toc': {\n            'slugify': slugs.slugify(case=\"lower\"),\n            'permalink': \"\"\n        },\n        'markdown.extensions.smarty': {\n            \"smart_quotes\": False,\n        },\n        'pymdownx.arithmatex': {\n            'generic': True,\n            'block_tag': 'pre'\n        },\n        'pymdownx.superfences': {\n            'preserve_tabs': True,\n            'custom_fences': [\n                {\n\n                    \"name\": \"diagram\",\n                    \"class\": \"diagram\",\n                    \"format\": superfences.fence_code_format\n                },\n                {\n                    \"name\": 'playground',\n                    \"class\": 'playground',\n                    \"format\": color_command_formatter,\n                    \"validator\": live_color_command_validator\n                }\n            ]\n        },\n        'pymdownx.inlinehilite': {\n            'custom_inline': [\n                {\n                    'name': 'color',\n                    'class': 'color',\n                    'format': live_color_formatter\n                }\n            ]\n        },\n        'pymdownx.magiclink': {\n            'repo_url_shortener': True,\n            'repo_url_shorthand': True,\n            'social_url_shorthand': True,\n            'user': 'facelessuser',\n            'repo': 'coloraide'\n        },\n        'pymdownx.keys': {\n            'separator': \"\\uff0b\"\n        },\n        'pymdownx.tabbed': {\n            'alternate_style': True\n        }\n    }\n\n    try:\n        html = markdown.markdown(text, extensions=extensions, extension_configs=extension_configs)\n    except Exception:\n        html = ''\n    content = document.getElementById(\"__notebook-render\")\n    content.innerHTML = html\n\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\ncallback()\n",v=window.colorNotebook.defaultPlayground,w=function(e){return'\n!!! new "This notebook is powered by [Pyodide](https://github.com/pyodide/pyodide). Learn more [here](?notebook=https://gist.githubusercontent.com/facelessuser/7c819668b5eb248ecb9ac608d91391cf/raw/playground.md). Preview, convert, interpolate, and explore!"\n\n````````playground\n'.concat(e,"\n````````\n")},_=!1,x=!1,b=function(){h=!0,window.document.dispatchEvent(new Event("DOMContentLoaded",{bubbles:!0,cancelable:!0})),window.document$.next()},E=function(e){var t=window.pageXOffset||(document.documentElement||document.body.parentNode||document.body).scrollLeft,n=window.pageYOffset||(document.documentElement||document.body.parentNode||document.body).scrollTop;e.style.height="5px",e.style.height="".concat(e.scrollHeight,"px"),window.scrollTo(t,n)},k=function(e){return encodeURIComponent(e).replace(/[.!'()*]/g,(function(e){return"%".concat(e.charCodeAt(0).toString(16))}))},L=function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return(o=document.getElementById("__playground-inputs_".concat(n))).setAttribute("readonly",""),s.globals.set("id_num",n),s.globals.set("action","playground"),e.next=6,s.runPythonAsync(g);case 6:o.removeAttribute("readonly");case 7:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),S=function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return s.globals.set("content",n),s.globals.set("action","notebook"),e.next=4,s.runPythonAsync(g);case 4:(o=document.getElementById("__notebook-input"))&&(l=n,o.value=n),window.location.hash&&(window.location.href=window.location.href);case 7:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),C=function(){var t=n(e().mark((function t(n){var o,r,a,c,l,u;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(f){e.next=5;break}return f=!0,e.next=4,loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.21.3/full/",fullStdLib:!1});case 4:s=e.sent;case 5:if((_||!n)&&(x||n)){e.next=14;break}o="".concat(window.location.origin,"/").concat(window.location.pathname.split("/")[1],"/playground/"),r=n?window.colorNotebook.notebookWheels:window.colorNotebook.playgroundWheels,a=[],n?_=!0:x=!0,c=i(r);try{for(c.s();!(l=c.n()).done;)u=l.value,a.push(o+u)}catch(e){c.e(e)}finally{c.f()}return e.next=14,s.loadPackage(a);case 14:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),T=function(e,t,n){var o=null==t?"Loading...":t,r=n?"loading relative":"loading",a=document.createElement("template");a.innerHTML='<div class="'.concat(r,'"><div class="loader"></div><div>').concat(o,"</div></div>"),e.appendChild(a.content.firstChild)},P=function(e){e.querySelector(".loading")&&e.removeChild(e.querySelector(".loading"))},A=function(e){if("Tab"===e.key){var t=e.target;if(t.selectionStart!==t.selectionEnd){e.preventDefault();for(var n=t.selectionStart,o=t.selectionEnd,r=t.value;n>0&&"\n"!==r[n-1];)n--;for(;o>0&&"\n"!==r[o-1]&&o<r.length;)o++;for(var a=r.substr(n,o-n).split("\n"),i=0;i<a.length;i++)i===a.length-1&&0===a[i].length||(e.shiftKey?a[i]=a[i].replace(y,""):a[i]="    ".concat(a[i]));a=a.join("\n"),t.value=r.substr(0,n)+a+r.substr(o),t.selectionStart=n,t.selectionEnd=n+a.length}}},I=function(){var t=n(e().mark((function t(o){var r;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:r=document.getElementById("__notebook-source"),document.querySelectorAll(".playground").forEach((function(t){var a=t.id.replace(p,"$1"),i=document.getElementById("__playground-inputs_".concat(a)),s=document.getElementById("__playground-results_".concat(a)),f=document.getElementById("__playground-code_".concat(a)),m=document.querySelector("button#__playground-edit_".concat(a)),h=document.querySelector("button#__playground-share_".concat(a)),y=document.querySelector("button#__playground-run_".concat(a)),g=document.querySelector("button#__playground-cancel_".concat(a));if(i.addEventListener("input",(function(){E(i)})),i.addEventListener("keydown",A),r&&o){var v=document.getElementById("__notebook-input");v.addEventListener("input",(function(e){E(e.target)})),v.addEventListener("keydown",A),document.getElementById("__notebook-edit").addEventListener("click",(function(){d[v.id]=v.value,document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),E(document.getElementById("__notebook-input"))})),document.getElementById("__notebook-md-gist").addEventListener("click",function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:null!==(o=prompt("Please enter link to the Markdown page source:",u))&&(o=k(o),n.preventDefault(),history.pushState({notebook:o},"","?".concat(new URLSearchParams("notebook=".concat(o)).toString())),F(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-py-gist").addEventListener("click",function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:null!==(o=prompt("Please enter the link to the Python code source:",u))&&(o=k(o),n.preventDefault(),history.pushState({source:o},"","?".concat(new URLSearchParams("source=".concat(o)).toString())),F(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-input").value=l,document.getElementById("__notebook-cancel").addEventListener("click",(function(){v.value=d[v.id],delete d[v.id],document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden")})),document.getElementById("__notebook-submit").addEventListener("click",n(e().mark((function t(){var n,o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=document.getElementById("__notebook-render"),l=document.getElementById("__notebook-input").value,n.classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),o=document.querySelector("article"),T(o,"Loading Notebook..."),n.innerHTML="",d={},e.next=10,C(!0);case 10:return e.next=12,S(l);case 12:return e.next=14,I();case 14:P(o),b();case 16:case"end":return e.stop()}}),t)}))))}i.addEventListener("touchmove",(function(e){e.stopPropagation()})),s.addEventListener("click",(function(e){var t=e.target;if(t.matches("span.swatch-color")){var n="",o=t.parentNode;if(!o.matches("span.swatch-gradient")){if(n=(n=o.getAttribute("title").replace("Copy to clipboard","")).replace("\n",""),window.clipboardData&&window.clipboardData.setData)return window.clipboardData.setData("Text",n);if(document.queryCommandSupported&&document.queryCommandSupported("copy")){var r=document.createElement("textarea");r.textContent=n,r.style.position="fixed",document.body.appendChild(r),r.select();try{return document.execCommand("copy")}catch(e){return prompt("Copy to clipboard: Ctrl+C, Enter",n)}finally{document.body.removeChild(r)}}}}})),m.addEventListener("click",n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:d[a]=i.value,f.classList.toggle("hidden"),s.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),E(i),i.focus();case 9:case"end":return e.stop()}}),t)})))),h.addEventListener("click",n(e().mark((function t(){var o,r,a,s,c;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:o=window.location.pathname.split("/")[1],r=k(i.value),a=window.location,s="/playground/",a.pathname.startsWith("/".concat(o,"/"))&&(s="/".concat(o,"/playground/")),(c="".concat(a.protocol,"//").concat(a.host).concat(s,"?code=").concat(r)).length>2e3?alert("Code must be small enough to generate a shareable URL under 2000 characters!"):navigator.clipboard.writeText(c).then(n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:alert("Link copied to clipboard :)");case 1:case"end":return e.stop()}}),t)}))),n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:alert("Failed to copy link clipboard!");case 1:case"end":return e.stop()}}),t)}))));case 7:case"end":return t.stop()}}),t)})))),y.addEventListener("click",n(e().mark((function t(){var n,o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!c){e.next=2;break}return e.abrupt("return");case 2:return c=!0,n=f.querySelector("form"),T(n,null,!0),(o=document.querySelectorAll(".playground .playground-run"))&&o.forEach((function(e){e.setAttribute("disabled","")})),e.next=9,C(!1);case 9:return s.querySelector("code").innerHTML="",e.next=12,L(a);case 12:o&&o.forEach((function(e){e.removeAttribute("disabled")})),P(n),f.classList.toggle("hidden"),s.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),delete d[a],c=!1;case 22:case"end":return e.stop()}}),t)})))),g.addEventListener("click",(function(){i.value=d[a],delete d[a],f.classList.toggle("hidden"),s.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden")}))}));case 3:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),F=function(){var t=n(e().mark((function t(o){var r,a,i,s,c,l,p,f,h;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(d={},!window.location.pathname.endsWith("/playground/")){t.next=32;break}if(r=new URLSearchParams(window.location.search),a="Loading Pyodide...",i="Loading Notebook...",s=r.has("source")?r.get("source"):r.get("notebook"),c=document.querySelector("article"),null===s||!s.trim()){t.next=16;break}return T(c,a),t.next=11,C(!0);case 11:P(c),T(c,i);try{l=r.has("source")?"source":"notebook",m=decodeURIComponent(r.toString()),p="",f=new XMLHttpRequest,u=s,f.open("GET",s,!0),f.onload=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return 4===f.readyState&&200===f.status&&(p=f.responseText),"source"===l&&(p=w(p)),e.next=4,S(p);case 4:return e.next=6,I(o);case 6:P(c),b();case 8:case"end":return e.stop()}}),t)}))),f.send()}catch(e){}t.next=30;break;case 16:return u="",h=w(r.has("code")?r.get("code"):v),m=decodeURIComponent(r.toString()),T(c,a),t.next=22,C(!0);case 22:return P(c),T(c,i),t.next=26,S(h);case 26:return t.next=28,I(o);case 28:P(c),b();case 30:t.next=35;break;case 32:u="",m="",I(o);case 35:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),document.addEventListener("click",(function(e){var t=window.location.pathname.split("/")[1],n=e.target||e.srcElement;if("A"===n.tagName&&F&&n.getAttribute("href")&&n.host===window.location.host&&window.location.pathname==="/".concat(t,'/playground/"')&&window.location.pathname===n.pathname&&window.location.search!==n.search){e.preventDefault();var r,a={},s=i(new URLSearchParams(n.search));try{for(s.s();!(r=s.n()).done;){var c=o(r.value,2),l=c[0],u=c[1];a[l]=u}}catch(e){s.e(e)}finally{s.f()}history.pushState(a,"",n.href),F(!1)}})),window.addEventListener("popstate",(function(){var e=window.location.pathname.split("/")[1];window.location.pathname==="/".concat(e,"/playground/")&&decodeURIComponent(new URLSearchParams(window.location.search).toString())!==m&&F(!1)})),window.addEventListener("unload",(function(){h=!1})),window.document$.subscribe((function(){h?h=!1:F(!0)}))}();
-//# sourceMappingURL=extra-notebook-c353fe5e.js.map
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js b/docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js
new file mode 100644
index 000000000..da1b1e160
--- /dev/null
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js
@@ -0,0 +1,2 @@
+function _typeof(e){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_typeof(e)}var runtime=function(e){"use strict";var t,n=Object.prototype,o=n.hasOwnProperty,r="function"==typeof Symbol?Symbol:{},a=r.iterator||"@@iterator",i=r.asyncIterator||"@@asyncIterator",s=r.toStringTag||"@@toStringTag";function c(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{c({},"")}catch(e){c=function(e,t,n){return e[t]=n}}function l(e,t,n,o){var r=t&&t.prototype instanceof y?t:y,a=Object.create(r.prototype),i=new T(o||[]);return a._invoke=function(e,t,n){var o=d;return function(r,a){if(o===f)throw new Error("Generator is already running");if(o===m){if("throw"===r)throw a;return A()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=L(i,n);if(s){if(s===h)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===d)throw o=m,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=f;var c=u(e,t,n);if("normal"===c.type){if(o=n.done?m:p,c.arg===h)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o=m,n.method="throw",n.arg=c.arg)}}}(e,n,i),a}function u(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}e.wrap=l;var d="suspendedStart",p="suspendedYield",f="executing",m="completed",h={};function y(){}function g(){}function v(){}var w={};c(w,a,(function(){return this}));var _=Object.getPrototypeOf,x=_&&_(_(P([])));x&&x!==n&&o.call(x,a)&&(w=x);var b=v.prototype=y.prototype=Object.create(w);function E(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function k(e,t){function n(r,a,i,s){var c=u(e[r],e,a);if("throw"!==c.type){var l=c.arg,d=l.value;return d&&"object"===_typeof(d)&&o.call(d,"__await")?t.resolve(d.__await).then((function(e){n("next",e,i,s)}),(function(e){n("throw",e,i,s)})):t.resolve(d).then((function(e){l.value=e,i(l)}),(function(e){return n("throw",e,i,s)}))}s(c.arg)}var r;this._invoke=function(e,o){function a(){return new t((function(t,r){n(e,o,t,r)}))}return r=r?r.then(a,a):a()}}function L(e,n){var o=e.iterator[n.method];if(o===t){if(n.delegate=null,"throw"===n.method){if(e.iterator.return&&(n.method="return",n.arg=t,L(e,n),"throw"===n.method))return h;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return h}var r=u(o,e.iterator,n.arg);if("throw"===r.type)return n.method="throw",n.arg=r.arg,n.delegate=null,h;var a=r.arg;return a?a.done?(n[e.resultName]=a.value,n.next=e.nextLoc,"return"!==n.method&&(n.method="next",n.arg=t),n.delegate=null,h):a:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,h)}function S(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function C(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function T(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(S,this),this.reset(!0)}function P(e){if(e){var n=e[a];if(n)return n.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var r=-1,i=function n(){for(;++r<e.length;)if(o.call(e,r))return n.value=e[r],n.done=!1,n;return n.value=t,n.done=!0,n};return i.next=i}}return{next:A}}function A(){return{value:t,done:!0}}return g.prototype=v,c(b,"constructor",v),c(v,"constructor",g),g.displayName=c(v,s,"GeneratorFunction"),e.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===g||"GeneratorFunction"===(t.displayName||t.name))},e.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,v):(e.__proto__=v,c(e,s,"GeneratorFunction")),e.prototype=Object.create(b),e},e.awrap=function(e){return{__await:e}},E(k.prototype),c(k.prototype,i,(function(){return this})),e.AsyncIterator=k,e.async=function(t,n,o,r,a){void 0===a&&(a=Promise);var i=new k(l(t,n,o,r),a);return e.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},E(b),c(b,s,"Generator"),c(b,a,(function(){return this})),c(b,"toString",(function(){return"[object Generator]"})),e.keys=function(e){var t=[];for(var n in e)t.push(n);return t.reverse(),function n(){for(;t.length;){var o=t.pop();if(o in e)return n.value=o,n.done=!1,n}return n.done=!0,n}},e.values=P,T.prototype={constructor:T,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=t,this.done=!1,this.delegate=null,this.method="next",this.arg=t,this.tryEntries.forEach(C),!e)for(var n in this)"t"===n.charAt(0)&&o.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=t)},stop:function(){this.done=!0;var e=this.tryEntries[0].completion;if("throw"===e.type)throw e.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var n=this;function r(o,r){return s.type="throw",s.arg=e,n.next=o,r&&(n.method="next",n.arg=t),!!r}for(var a=this.tryEntries.length-1;a>=0;--a){var i=this.tryEntries[a],s=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var c=o.call(i,"catchLoc"),l=o.call(i,"finallyLoc");if(c&&l){if(this.prev<i.catchLoc)return r(i.catchLoc,!0);if(this.prev<i.finallyLoc)return r(i.finallyLoc)}else if(c){if(this.prev<i.catchLoc)return r(i.catchLoc,!0)}else{if(!l)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return r(i.finallyLoc)}}}},abrupt:function(e,t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev<r.finallyLoc){var a=r;break}}a&&("break"===e||"continue"===e)&&a.tryLoc<=t&&t<=a.finallyLoc&&(a=null);var i=a?a.completion:{};return i.type=e,i.arg=t,a?(this.method="next",this.next=a.finallyLoc,h):this.complete(i)},complete:function(e,t){if("throw"===e.type)throw e.arg;return"break"===e.type||"continue"===e.type?this.next=e.arg:"return"===e.type?(this.rval=this.arg=e.arg,this.method="return",this.next="end"):"normal"===e.type&&t&&(this.next=t),h},finish:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),C(n),h}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var o=n.completion;if("throw"===o.type){var r=o.arg;C(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,n,o){return this.delegate={iterator:P(e),resultName:n,nextLoc:o},"next"===this.method&&(this.arg=t),h}},e}("object"===("undefined"==typeof module?"undefined":_typeof(module))?module.exports:{});try{regeneratorRuntime=runtime}catch(e){"object"===("undefined"==typeof globalThis?"undefined":_typeof(globalThis))?globalThis.regeneratorRuntime=runtime:Function("r","regeneratorRuntime = r")(runtime)}!function(){"use strict";function e(){e=function(){return t};var t={},n=Object.prototype,o=n.hasOwnProperty,r=Object.defineProperty||function(e,t,n){e[t]=n.value},a="function"==typeof Symbol?Symbol:{},i=a.iterator||"@@iterator",s=a.asyncIterator||"@@asyncIterator",c=a.toStringTag||"@@toStringTag";function l(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,n){return e[t]=n}}function u(e,t,n,o){var a=t&&t.prototype instanceof f?t:f,i=Object.create(a.prototype),s=new S(o||[]);return r(i,"_invoke",{value:b(e,n,s)}),i}function d(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=u;var p={};function f(){}function m(){}function h(){}var y={};l(y,i,(function(){return this}));var g=Object.getPrototypeOf,v=g&&g(g(C([])));v&&v!==n&&o.call(v,i)&&(y=v);var w=h.prototype=f.prototype=Object.create(y);function _(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function x(e,t){function n(r,a,i,s){var c=d(e[r],e,a);if("throw"!==c.type){var l=c.arg,u=l.value;return u&&"object"==_typeof(u)&&o.call(u,"__await")?t.resolve(u.__await).then((function(e){n("next",e,i,s)}),(function(e){n("throw",e,i,s)})):t.resolve(u).then((function(e){l.value=e,i(l)}),(function(e){return n("throw",e,i,s)}))}s(c.arg)}var a;r(this,"_invoke",{value:function(e,o){function r(){return new t((function(t,r){n(e,o,t,r)}))}return a=a?a.then(r,r):r()}})}function b(e,t,n){var o="suspendedStart";return function(r,a){if("executing"===o)throw new Error("Generator is already running");if("completed"===o){if("throw"===r)throw a;return T()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=E(i,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===o)throw o="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o="executing";var c=d(e,t,n);if("normal"===c.type){if(o=n.done?"completed":"suspendedYield",c.arg===p)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o="completed",n.method="throw",n.arg=c.arg)}}}function E(e,t){var n=e.iterator[t.method];if(void 0===n){if(t.delegate=null,"throw"===t.method){if(e.iterator.return&&(t.method="return",t.arg=void 0,E(e,t),"throw"===t.method))return p;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return p}var o=d(n,e.iterator,t.arg);if("throw"===o.type)return t.method="throw",t.arg=o.arg,t.delegate=null,p;var r=o.arg;return r?r.done?(t[e.resultName]=r.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,p):r:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,p)}function k(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function L(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function S(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(k,this),this.reset(!0)}function C(e){if(e){var t=e[i];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n<e.length;)if(o.call(e,n))return t.value=e[n],t.done=!1,t;return t.value=void 0,t.done=!0,t};return r.next=r}}return{next:T}}function T(){return{value:void 0,done:!0}}return m.prototype=h,r(w,"constructor",{value:h,configurable:!0}),r(h,"constructor",{value:m,configurable:!0}),m.displayName=l(h,c,"GeneratorFunction"),t.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===m||"GeneratorFunction"===(t.displayName||t.name))},t.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,h):(e.__proto__=h,l(e,c,"GeneratorFunction")),e.prototype=Object.create(w),e},t.awrap=function(e){return{__await:e}},_(x.prototype),l(x.prototype,s,(function(){return this})),t.AsyncIterator=x,t.async=function(e,n,o,r,a){void 0===a&&(a=Promise);var i=new x(u(e,n,o,r),a);return t.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},_(w),l(w,c,"Generator"),l(w,i,(function(){return this})),l(w,"toString",(function(){return"[object Generator]"})),t.keys=function(e){var t=Object(e),n=[];for(var o in t)n.push(o);return n.reverse(),function e(){for(;n.length;){var o=n.pop();if(o in t)return e.value=o,e.done=!1,e}return e.done=!0,e}},t.values=C,S.prototype={constructor:S,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=void 0,this.done=!1,this.delegate=null,this.method="next",this.arg=void 0,this.tryEntries.forEach(L),!e)for(var t in this)"t"===t.charAt(0)&&o.call(this,t)&&!isNaN(+t.slice(1))&&(this[t]=void 0)},stop:function(){this.done=!0;var e=this.tryEntries[0].completion;if("throw"===e.type)throw e.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var t=this;function n(n,o){return i.type="throw",i.arg=e,t.next=n,o&&(t.method="next",t.arg=void 0),!!o}for(var r=this.tryEntries.length-1;r>=0;--r){var a=this.tryEntries[r],i=a.completion;if("root"===a.tryLoc)return n("end");if(a.tryLoc<=this.prev){var s=o.call(a,"catchLoc"),c=o.call(a,"finallyLoc");if(s&&c){if(this.prev<a.catchLoc)return n(a.catchLoc,!0);if(this.prev<a.finallyLoc)return n(a.finallyLoc)}else if(s){if(this.prev<a.catchLoc)return n(a.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return n(a.finallyLoc)}}}},abrupt:function(e,t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev<r.finallyLoc){var a=r;break}}a&&("break"===e||"continue"===e)&&a.tryLoc<=t&&t<=a.finallyLoc&&(a=null);var i=a?a.completion:{};return i.type=e,i.arg=t,a?(this.method="next",this.next=a.finallyLoc,p):this.complete(i)},complete:function(e,t){if("throw"===e.type)throw e.arg;return"break"===e.type||"continue"===e.type?this.next=e.arg:"return"===e.type?(this.rval=this.arg=e.arg,this.method="return",this.next="end"):"normal"===e.type&&t&&(this.next=t),p},finish:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),L(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var o=n.completion;if("throw"===o.type){var r=o.arg;L(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:C(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},t}function t(e,t,n,o,r,a,i){try{var s=e[a](i),c=s.value}catch(e){return void n(e)}s.done?t(c):Promise.resolve(c).then(o,r)}function n(e){return function(){var n=this,o=arguments;return new Promise((function(r,a){var i=e.apply(n,o);function s(e){t(i,r,a,s,c,"next",e)}function c(e){t(i,r,a,s,c,"throw",e)}s(void 0)}))}}function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var o,r,a=[],i=!0,s=!1;try{for(n=n.call(e);!(i=(o=n.next()).done)&&(a.push(o.value),!t||a.length!==t);i=!0);}catch(e){s=!0,r=e}finally{try{i||null==n.return||n.return()}finally{if(s)throw r}}return a}(e,t)||r(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(e,t){if(e){if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?a(e,t):void 0}}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n<t;n++)o[n]=e[n];return o}function i(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=r(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var o=0,a=function(){};return{s:a,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,s=!0,c=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return s=e.done,e},e:function(e){c=!0,i=e},f:function(){try{s||null==n.return||n.return()}finally{if(c)throw i}}}}var s,c,l,u,d,p,f,m,h,y,g,v,w,_,x,b,E,k,L,S,C,T,P,A,I,F;s=null,c=!1,l="",u="",d={},p=/.*?_(\d+)$/,f=!1,m="",h=!1,y=/^( {1,4}|\t)/,g="\n\"\"\"\nExecute Python code in code blocks (color previews added specifically for ColorAide).\n\nThis can be executed in either a Pyodide environment of a normal Python environment.\n3rd party libraries (that are not available directly from Pyodide) are only loaded\nwhen needed so that Pyodide will have a chance to load them if necessary. This is also\nhardcoded to work with ColorAide.\n\nThis is meant to be executed by Pyodide on preformatted HTML to allow for live execution of\ncode snippets using `coloraide`.\n\nTransform Python code by executing it, transforming to a Python console output,\nand finding and outputting color previews.\n\"\"\"\nimport xml.etree.ElementTree as Etree\nfrom collections.abc import Sequence\nfrom collections import namedtuple\nimport ast\nfrom io import StringIO\nimport contextlib\nimport sys\nimport re\nfrom pygments import highlight\nfrom pygments.lexers import get_lexer_by_name\nfrom pygments.formatters import find_formatter_class\nimport coloraide\nfrom coloraide import Color\nfrom coloraide.interpolate import Interpolator, normalize_domain\ntry:\n    from coloraide_extras.everything import ColorAll\nexcept ImportError:\n    from coloraide.everything import ColorAll\ntry:\n    import coloraide_extras\nexcept ImportError:\n    coloraide_extras = None\n\nWEBSPACE = \"srgb\"\nAST_BLOCKS = (ast.If, ast.For, ast.While, ast.Try, ast.With, ast.FunctionDef, ast.ClassDef)\n\nRE_COLOR_START = re.compile(\n    r\"(?i)(?:\\b(?<![-#&$])(?:color|hsla?|lch|lab|hwb|rgba?)\\(|\\b(?<![-#&$])[\\w]{3,}(?![(-])\\b|(?<![&])#)\"\n)\n\ntemplate = '''<div class=\"playground\" id=\"__playground_{el_id}\">\n<div class=\"playground-results\" id=\"__playground-results_{el_id}\">\n{results}\n</div>\n<div class=\"playground-code hidden\" id=\"__playground-code_{el_id}\">\n<form autocomplete=\"off\">\n<textarea class=\"playground-inputs\" id=\"__playground-inputs_{el_id}\" spellcheck=\"false\">{raw_source}</textarea>\n</form>\n</div>\n\n<button id=\"__playground-edit_{el_id}\" class=\"playground-edit\" title=\"Edit the current snippet\">Edit</button>\n<button id=\"__playground-share_{el_id}\" class=\"playground-share\" title=\"Copy URL to current snippet\">Share</button>\n<button id=\"__playground-run_{el_id}\" class=\"playground-run hidden\" title=\"Run code (Ctrl + Enter)\">Run</button>\n<button id=\"__playground-cancel_{el_id}\" class=\"playground-cancel hidden\" title=\"Cancel edit (Escape)\">Cancel</button>\n</div>'''\n\ncode_id = 0\n\n\nclass HtmlGradient(list):\n    \"\"\"HTML color gradient.\"\"\"\n\n\nclass HtmlSteps(list):\n    \"\"\"HTML color steps.\"\"\"\n\n\nclass ColorTuple(namedtuple('ColorTuple', ['string', 'color'])):\n    \"\"\"Color tuple.\"\"\"\n\n\nclass HtmlRow(list):\n    \"\"\"Create a row with the given colors.\"\"\"\n\n\ndef _escape(txt):\n    \"\"\"Basic HTML escaping.\"\"\"\n\n    txt = txt.replace('&', '&amp;')\n    txt = txt.replace('<', '&lt;')\n    txt = txt.replace('>', '&gt;')\n    return txt\n\n\n@contextlib.contextmanager\ndef std_output(stdout=None):\n    \"\"\"Capture standard out.\"\"\"\n    old = sys.stdout\n    if stdout is None:\n        stdout = StringIO()\n    sys.stdout = stdout\n    yield stdout\n    sys.stdout = old\n\n\ndef get_colors(result):\n    \"\"\"Get color from results.\"\"\"\n\n    colors = []\n    domain = []\n    if isinstance(result, HtmlRow):\n        colors = HtmlRow(\n            [\n                ColorTuple(c.to_string(fit=False), c.clone()) if isinstance(c, Color) else ColorTuple(c, ColorAll(c))\n                for c in result\n            ]\n        )\n    elif isinstance(result, (HtmlSteps, HtmlGradient)):\n        t = type(result)\n        colors = t([c.clone() if isinstance(c, Color) else ColorAll(c) for c in result])\n    elif isinstance(result, Color):\n        colors.append(ColorTuple(result.to_string(fit=False), result.clone()))\n    elif isinstance(result, Interpolator):\n        # Since we are auto showing the gradient, we need to scale the domain to something we expect.\n        if result.domain:\n            domain = result.domain\n            result.domain = normalize_domain(result.domain)\n        colors = HtmlGradient(result.steps(steps=5, max_delta_e=2.3))\n        if domain:\n            result.domain = domain\n            domain = []\n    elif isinstance(result, str):\n        try:\n            colors.append(ColorTuple(result, ColorAll(result)))\n        except Exception:\n            pass\n    elif isinstance(result, Sequence):\n        for x in result:\n            if isinstance(x, Color):\n                colors.append(ColorTuple(x.to_string(fit=False), x.clone()))\n            elif isinstance(x, str):\n                try:\n                    colors.append(ColorTuple(x, ColorAll(x)))\n                except Exception:\n                    pass\n    return colors\n\n\ndef find_colors(text):\n    \"\"\"Find colors in text buffer.\"\"\"\n\n    colors = []\n    for m in RE_COLOR_START.finditer(text):\n        start = m.start()\n        mcolor = ColorAll.match(text, start=start)\n        if mcolor is not None:\n            colors.append(ColorTuple(text[mcolor.start:mcolor.end], mcolor.color))\n    return colors\n\n\ndef execute(cmd, no_except=True, inline=False):\n    \"\"\"Execute color commands.\"\"\"\n\n    g = {k: getattr(coloraide, k) for k in coloraide.__all__}\n    g['coloraide'] = coloraide\n    g['Color'] = ColorAll\n    g['HtmlRow'] = HtmlRow\n    g['HtmlSteps'] = HtmlSteps\n    g['HtmlGradient'] = HtmlGradient\n\n    if coloraide_extras is not None:\n        g['coloraide_extras'] = coloraide_extras\n\n    console = ''\n    colors = []\n\n    # Build AST tree\n    src = cmd.strip()\n    lines = src.split('\\n')\n    try:\n        tree = ast.parse(src)\n    except Exception as e:\n        if no_except:\n            if not inline:\n                from pymdownx.superfences import SuperFencesException\n                raise SuperFencesException from e\n            else:\n                from pymdownx.inlinehilite import InlineHiliteException\n                raise InlineHiliteException from e\n        import traceback\n        return '{}'.format(traceback.format_exc()), colors\n\n    for node in tree.body:\n        result = None\n\n        # Format source as Python console statements\n        start = node.lineno\n        end = node.end_lineno\n        stmt = lines[start - 1: end]\n        command = ''\n        for i, line in enumerate(stmt, 0):\n            if i == 0:\n                stmt[i] = '>>> ' + line\n            else:\n                stmt[i] = '... ' + line\n        command += '\\n'.join(stmt)\n        if isinstance(node, AST_BLOCKS):\n            command += '\\n... '\n\n        try:\n            # Capture anything sent to standard out\n            text = ''\n            with std_output() as s:\n                # Execute code\n                if isinstance(node, ast.Expr):\n                    _eval = ast.Expression(node.value)\n                    result = eval(compile(_eval, '<string>', 'eval'), g)\n                else:\n                    _exec = ast.Module([node], [])\n                    exec(compile(_exec, '<string>', 'exec'), g)\n\n                # Execution went well, so append command\n                console += command\n\n                # Output captured standard out after statements\n                text = s.getvalue()\n                if text:\n                    clist = find_colors(text)\n                    if clist:\n                        colors.append(clist)\n                    console += '\\n{}'.format(text)\n                s.flush()\n        except Exception as e:\n            if no_except:\n                if not inline:\n                    from pymdownx.superfences import SuperFencesException\n                    raise SuperFencesException from e\n                else:\n                    from pymdownx.inlinehilite import InlineHiliteException\n                    raise InlineHiliteException from e\n            import traceback\n            console += '{}\\n{}'.format(command, traceback.format_exc())\n            # Failed for some reason, so quit\n            break\n\n        # If we got a result, output it as well\n        if result is not None:\n            clist = get_colors(result)\n            if clist:\n                colors.append(clist)\n            console += '{}{}\\n'.format('\\n' if not text else '', str(result))\n        else:\n            console += '\\n' if not text else ''\n\n    return console, colors\n\n\ndef colorize(src, lang, **options):\n    \"\"\"Colorize.\"\"\"\n\n    HtmlFormatter = find_formatter_class('html')\n    lexer = get_lexer_by_name(lang, **options)\n    formatter = HtmlFormatter(cssclass=\"highlight\", wrapcode=True)\n    return highlight(src, lexer, formatter).strip()\n\n\ndef color_command_validator(language, inputs, options, attrs, md):\n    \"\"\"Color validator.\"\"\"\n\n    valid_inputs = set(['exceptions'])\n\n    for k, v in inputs.items():\n        if k in valid_inputs:\n            options[k] = True\n            continue\n        attrs[k] = v\n    return True\n\n\ndef _color_command_console(colors):\n    \"\"\"Color command formatter.\"\"\"\n\n    el = ''\n    bar = False\n    values = []\n    for item in colors:\n        if isinstance(item, (HtmlGradient, HtmlSteps)):\n            current = total = percent = last = 0\n            if isinstance(item, HtmlSteps):\n                total = len(item)\n                percent = 100 / total\n                current = percent\n\n            if bar:\n                el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                values = []\n            sub_el1 = '<div class=\"swatch-bar\"><span class=\"swatch swatch-gradient\">{}</span></div>'\n            style = \"--swatch-stops: \"\n            stops = []\n            for e, color in enumerate(item):\n                color.fit(WEBSPACE)\n                if current:\n                    stops.append('{} {}%'.format(color.convert(WEBSPACE).to_string(), str(last)))\n                    stops.append('{} {}%'.format(color.convert(WEBSPACE).to_string(), str(current)))\n                    last = current\n                    if e < (total - 1):\n                        current += percent\n                    else:\n                        current = 100\n                else:\n                    stops.append(color.convert(WEBSPACE).to_string())\n            if not stops:\n                stops.extend(['transparent'] * 2)\n            if len(stops) == 1:\n                stops.append(stops[0])\n            style += ','.join(stops)\n            sub_el2 = '<span class=\"swatch-color\" style=\"{}\"></span>'.format(style)\n            el += sub_el1.format(sub_el2)\n            bar = False\n        else:\n            is_row = False\n            if isinstance(item, HtmlRow):\n                is_row = True\n                if bar and values:\n                    el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                    values = []\n                bar = False\n\n            bar = True\n            for color in item:\n                base_classes = \"swatch\"\n                if not color.color.in_gamut(WEBSPACE):\n                    base_classes += \" out-of-gamut\"\n                color.color.fit(WEBSPACE)\n                srgb = color.color.convert(WEBSPACE)\n                value1 = srgb.to_string(alpha=False)\n                value2 = srgb.to_string()\n                style = \"--swatch-stops: {} 50%, {} 50%\".format(value1, value2)\n                title = color.string\n                classes = base_classes\n                c = '<span class=\"swatch-color\" style=\"{style}\"></span>'.format(style=style)\n                c = '<span class=\"{classes}\" title=\"{title}&#013;Copy to clipboard\">{color}</span>'.format(\n                    classes=classes,\n                    color=c,\n                    title=title\n                )\n                values.append(c)\n\n            if is_row and values:\n                el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n                values = []\n                bar = False\n    if bar:\n        el += '<div class=\"swatch-bar\">{}</div>'.format(' '.join(values))\n        values = []\n\n    return el\n\n\ndef color_command_formatter(src=\"\", language=\"\", class_name=None, options=None, md=\"\", **kwargs):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    global code_id\n    from pymdownx.superfences import SuperFencesException\n\n    try:\n        if len(md.preprocessors['fenced_code_block'].extension.stash) == 0:\n            code_id = 0\n\n        # Check if we should allow exceptions\n        exceptions = options.get('exceptions', False) if options is not None else False\n\n        console, colors = execute(src.strip(), not exceptions)\n        el = _color_command_console(colors)\n\n        el += md.preprocessors['fenced_code_block'].extension.superfences[0]['formatter'](\n            src=console,\n            class_name=\"highlight\",\n            language='pycon',\n            md=md,\n            options=options,\n            **kwargs\n        )\n        el = '<div class=\"color-command\">{}</div>'.format(el)\n        el = template.format(el_id=code_id, raw_source=_escape(src), results=el)\n        code_id += 1\n    except SuperFencesException:\n        raise\n    except Exception:\n        from pymdownx import superfences\n        import traceback\n        print(traceback.format_exc())\n        return superfences.fence_code_format(src, 'text', class_name, options, md, **kwargs)\n    return el\n\n\ndef color_formatter(src=\"\", language=\"\", class_name=None, md=\"\", exceptions=True):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    from pymdownx.inlinehilite import InlineHiliteException\n\n    try:\n        result = src.strip()\n\n        try:\n            color = ColorAll(result.strip())\n        except Exception:\n            console, colors = execute(result, exceptions, inline=True)\n            if len(colors) != 1 or len(colors[0]) != 1:\n                if exceptions:\n                    raise InlineHiliteException('Only one color allowed')\n                else:\n                    raise ValueError('Only one color allowed')\n            color = colors[0][0].color\n            result = colors[0][0].string\n\n        el = Etree.Element('span')\n        stops = []\n        if not color.in_gamut(WEBSPACE):\n            color.fit(WEBSPACE)\n            attributes = {'class': \"swatch out-of-gamut\", \"title\": result}\n            sub_el = Etree.SubElement(el, 'span', attributes)\n            stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False))\n            if color[-1] < 1.0:\n                stops[-1] += ' 50%'\n                stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%')\n        else:\n            attributes = {'class': \"swatch\", \"title\": result}\n            sub_el = Etree.SubElement(el, 'span', attributes)\n            stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False))\n            if color[-1] < 1.0:\n                stops[-1] += ' 50%'\n                stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%')\n\n        if not stops:\n            stops.extend(['transparent'] * 2)\n        if len(stops) == 1:\n            stops.append(stops[0])\n\n        Etree.SubElement(\n            sub_el,\n            'span',\n            {\n                \"class\": \"swatch-color\",\n                \"style\": \"--swatch-stops: {};\".format(','.join(stops))\n            }\n        )\n\n        el.append(md.inlinePatterns['backtick'].handle_code('css-color', result))\n    except InlineHiliteException:\n        raise\n    except Exception:\n        import traceback\n        print(traceback.format_exc())\n        el = md.inlinePatterns['backtick'].handle_code('text', src)\n    return el\n\n\n#############################\n# Pyodide specific code\n#############################\ndef live_color_command_formatter(src):\n    \"\"\"Formatter wrapper.\"\"\"\n\n    try:\n        console, colors = execute(src.strip(), False)\n        el = _color_command_console(colors)\n\n        if not colors:\n            el += '<div class=\"swatch-bar\"></div>'\n\n        el += colorize(console, 'pycon', **{'python3': True, 'stripnl': False})\n        el = '<div class=\"color-command\">{}</div>'.format(el)\n    except Exception:\n        return '<div class=\"color-command\"><div class=\"swatch-bar\"></div>{}</div>'.format(colorize('', 'text'))\n    return el\n\n\ndef live_color_command_validator(language, inputs, options, attrs, md):\n    \"\"\"Color validator.\"\"\"\n\n    value = color_command_validator(language, inputs, options, attrs, md)\n    # Live edit, we always allow exceptions so not to crash the service.\n    options['exceptions'] = True\n    return value\n\n\ndef live_color_formatter(src=\"\", language=\"\", class_name=None, md=\"\"):\n    \"\"\"Color formatter for a live environment.\"\"\"\n\n    return color_formatter(src, language, class_name, md, exceptions=False)\n\n\ndef render_console(*args):\n    \"\"\"Render console update.\"\"\"\n\n    from js import document\n\n    try:\n        # Run code\n        inputs = document.getElementById(\"__playground-inputs_{}\".format(globals()['id_num']))\n        results = document.getElementById(\"__playground-results_{}\".format(globals()['id_num']))\n        results.innerHTML = live_color_command_formatter(inputs.value)\n        scrollingElement = results.querySelector('code')\n        scrollingElement.scrollTop = scrollingElement.scrollHeight\n    except Exception as e:\n        print(e)\n\n\ndef render_notebook(*args):\n    \"\"\"Render notebook.\"\"\"\n\n    import markdown\n    from pymdownx import slugs, superfences\n    from js import document\n\n    text = globals().get('content', '')\n    extensions = [\n        'markdown.extensions.toc',\n        'markdown.extensions.admonition',\n        'markdown.extensions.smarty',\n        'pymdownx.betterem',\n        'markdown.extensions.attr_list',\n        'markdown.extensions.def_list',\n        'markdown.extensions.tables',\n        'markdown.extensions.abbr',\n        'markdown.extensions.footnotes',\n        'markdown.extensions.md_in_html',\n        'pymdownx.superfences',\n        'pymdownx.highlight',\n        'pymdownx.inlinehilite',\n        'pymdownx.magiclink',\n        'pymdownx.tilde',\n        'pymdownx.caret',\n        'pymdownx.smartsymbols',\n        'pymdownx.emoji',\n        'pymdownx.escapeall',\n        'pymdownx.tasklist',\n        'pymdownx.striphtml',\n        'pymdownx.snippets',\n        'pymdownx.keys',\n        'pymdownx.details',\n        'pymdownx.saneheaders',\n        'pymdownx.tabbed',\n        'pymdownx.arithmatex'\n    ]\n    extension_configs = {\n        'markdown.extensions.toc': {\n            'slugify': slugs.slugify(case=\"lower\"),\n            'permalink': \"\"\n        },\n        'markdown.extensions.smarty': {\n            \"smart_quotes\": False,\n        },\n        'pymdownx.arithmatex': {\n            'generic': True,\n            'block_tag': 'pre'\n        },\n        'pymdownx.superfences': {\n            'preserve_tabs': True,\n            'custom_fences': [\n                {\n\n                    \"name\": \"diagram\",\n                    \"class\": \"diagram\",\n                    \"format\": superfences.fence_code_format\n                },\n                {\n                    \"name\": 'playground',\n                    \"class\": 'playground',\n                    \"format\": color_command_formatter,\n                    \"validator\": live_color_command_validator\n                }\n            ]\n        },\n        'pymdownx.inlinehilite': {\n            'custom_inline': [\n                {\n                    'name': 'color',\n                    'class': 'color',\n                    'format': live_color_formatter\n                }\n            ]\n        },\n        'pymdownx.magiclink': {\n            'repo_url_shortener': True,\n            'repo_url_shorthand': True,\n            'social_url_shorthand': True,\n            'user': 'facelessuser',\n            'repo': 'coloraide'\n        },\n        'pymdownx.keys': {\n            'separator': \"\\uff0b\"\n        },\n        'pymdownx.tabbed': {\n            'alternate_style': True\n        }\n    }\n\n    try:\n        html = markdown.markdown(text, extensions=extensions, extension_configs=extension_configs)\n    except Exception:\n        html = ''\n    content = document.getElementById(\"__notebook-render\")\n    content.innerHTML = html\n\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\ncallback()\n",v=window.colorNotebook.defaultPlayground,w=function(e){return'\n!!! new "This notebook is powered by [Pyodide](https://github.com/pyodide/pyodide). Learn more [here](?notebook=https://gist.githubusercontent.com/facelessuser/7c819668b5eb248ecb9ac608d91391cf/raw/playground.md). Preview, convert, interpolate, and explore!"\n\n````````playground\n'.concat(e,"\n````````\n")},_=!1,x=!1,b=function(){h=!0,window.document.dispatchEvent(new Event("DOMContentLoaded",{bubbles:!0,cancelable:!0})),window.document$.next()},E=function(e){var t=window.pageXOffset||(document.documentElement||document.body.parentNode||document.body).scrollLeft,n=window.pageYOffset||(document.documentElement||document.body.parentNode||document.body).scrollTop;e.style.height="5px",e.style.height="".concat(e.scrollHeight,"px"),window.scrollTo(t,n)},k=function(e){return encodeURIComponent(e).replace(/[.!'()*]/g,(function(e){return"%".concat(e.charCodeAt(0).toString(16))}))},L=function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return(o=document.getElementById("__playground-inputs_".concat(n))).setAttribute("readonly",""),s.globals.set("id_num",n),s.globals.set("action","playground"),e.next=6,s.runPythonAsync(g);case 6:o.removeAttribute("readonly");case 7:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),S=function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return s.globals.set("content",n),s.globals.set("action","notebook"),e.next=4,s.runPythonAsync(g);case 4:(o=document.getElementById("__notebook-input"))&&(l=n,o.value=n),window.location.hash&&(window.location.href=window.location.href);case 7:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),C=function(){var t=n(e().mark((function t(n){var o,r,a,c,l,u;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(f){e.next=5;break}return f=!0,e.next=4,loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.21.3/full/",fullStdLib:!1});case 4:s=e.sent;case 5:if((_||!n)&&(x||n)){e.next=14;break}o="".concat(window.location.origin,"/").concat(window.location.pathname.split("/")[1],"/playground/"),r=n?window.colorNotebook.notebookWheels:window.colorNotebook.playgroundWheels,a=[],n?_=!0:x=!0,c=i(r);try{for(c.s();!(l=c.n()).done;)u=l.value,a.push(o+u)}catch(e){c.e(e)}finally{c.f()}return e.next=14,s.loadPackage(a);case 14:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),T=function(e,t,n){var o=null==t?"Loading...":t,r=n?"loading relative":"loading",a=document.createElement("template");a.innerHTML='<div class="'.concat(r,'"><div class="loader"></div><div>').concat(o,"</div></div>"),e.appendChild(a.content.firstChild)},P=function(e){e.querySelector(".loading")&&e.removeChild(e.querySelector(".loading"))},A=function(e){if("Tab"===e.key){var t=e.target;if(t.selectionStart!==t.selectionEnd){e.preventDefault();for(var n=t.selectionStart,o=t.selectionEnd,r=t.value;n>0&&"\n"!==r[n-1];)n--;for(;o>0&&"\n"!==r[o-1]&&o<r.length;)o++;for(var a=r.substr(n,o-n).split("\n"),i=0;i<a.length;i++)i===a.length-1&&0===a[i].length||(e.shiftKey?a[i]=a[i].replace(y,""):a[i]="    ".concat(a[i]));a=a.join("\n"),t.value=r.substr(0,n)+a+r.substr(o),t.selectionStart=n,t.selectionEnd=n+a.length}}},I=function(){var t=n(e().mark((function t(o){var r;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:r=document.getElementById("__notebook-source"),document.querySelectorAll(".playground").forEach((function(t){var a=t.id.replace(p,"$1"),i=document.getElementById("__playground-inputs_".concat(a)),s=document.getElementById("__playground-results_".concat(a)),f=document.getElementById("__playground-code_".concat(a)),m=document.querySelector("button#__playground-edit_".concat(a)),h=document.querySelector("button#__playground-share_".concat(a)),y=document.querySelector("button#__playground-run_".concat(a)),g=document.querySelector("button#__playground-cancel_".concat(a));if(i.addEventListener("input",(function(){E(i)})),i.addEventListener("keydown",A),r&&o){var v=document.getElementById("__notebook-input");v.addEventListener("input",(function(e){E(e.target)})),v.addEventListener("keydown",A),document.getElementById("__notebook-edit").addEventListener("click",(function(){d[v.id]=v.value,document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),E(document.getElementById("__notebook-input"))})),document.getElementById("__notebook-md-gist").addEventListener("click",function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:null!==(o=prompt("Please enter link to the Markdown page source:",u))&&(o=k(o),n.preventDefault(),history.pushState({notebook:o},"","?".concat(new URLSearchParams("notebook=".concat(o)).toString())),F(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-py-gist").addEventListener("click",function(){var t=n(e().mark((function t(n){var o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:null!==(o=prompt("Please enter the link to the Python code source:",u))&&(o=k(o),n.preventDefault(),history.pushState({source:o},"","?".concat(new URLSearchParams("source=".concat(o)).toString())),F(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-input").value=l,document.getElementById("__notebook-cancel").addEventListener("click",(function(){v.value=d[v.id],delete d[v.id],document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden")})),document.getElementById("__notebook-submit").addEventListener("click",n(e().mark((function t(){var n,o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=document.getElementById("__notebook-render"),l=document.getElementById("__notebook-input").value,n.classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),o=document.querySelector("article"),T(o,"Loading Notebook..."),n.innerHTML="",d={},e.next=10,C(!0);case 10:return e.next=12,S(l);case 12:return e.next=14,I();case 14:P(o),b();case 16:case"end":return e.stop()}}),t)}))))}i.addEventListener("touchmove",(function(e){e.stopPropagation()})),s.addEventListener("click",(function(e){var t=e.target;if(t.matches("span.swatch-color")){var n="",o=t.parentNode;if(!o.matches("span.swatch-gradient")){if(n=(n=o.getAttribute("title").replace("Copy to clipboard","")).replace("\n",""),window.clipboardData&&window.clipboardData.setData)return window.clipboardData.setData("Text",n);if(document.queryCommandSupported&&document.queryCommandSupported("copy")){var r=document.createElement("textarea");r.textContent=n,r.style.position="fixed",document.body.appendChild(r),r.select();try{return document.execCommand("copy")}catch(e){return prompt("Copy to clipboard: Ctrl+C, Enter",n)}finally{document.body.removeChild(r)}}}}})),m.addEventListener("click",n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:d[a]=i.value,f.classList.toggle("hidden"),s.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),E(i),i.focus();case 9:case"end":return e.stop()}}),t)})))),h.addEventListener("click",n(e().mark((function t(){var o,r,a,s,c;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:o=window.location.pathname.split("/")[1],r=k(i.value),a=window.location,s="/playground/",a.pathname.startsWith("/".concat(o,"/"))&&(s="/".concat(o,"/playground/")),(c="".concat(a.protocol,"//").concat(a.host).concat(s,"?code=").concat(r)).length>2e3?alert("Code must be small enough to generate a shareable URL under 2000 characters!"):navigator.clipboard.writeText(c).then(n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:alert("Link copied to clipboard :)");case 1:case"end":return e.stop()}}),t)}))),n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:alert("Failed to copy link clipboard!");case 1:case"end":return e.stop()}}),t)}))));case 7:case"end":return t.stop()}}),t)})))),y.addEventListener("click",n(e().mark((function t(){var n,o;return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!c){e.next=2;break}return e.abrupt("return");case 2:return c=!0,n=f.querySelector("form"),T(n,null,!0),(o=document.querySelectorAll(".playground .playground-run"))&&o.forEach((function(e){e.setAttribute("disabled","")})),e.next=9,C(!1);case 9:return s.querySelector("code").innerHTML="",e.next=12,L(a);case 12:o&&o.forEach((function(e){e.removeAttribute("disabled")})),P(n),f.classList.toggle("hidden"),s.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),delete d[a],c=!1;case 22:case"end":return e.stop()}}),t)})))),g.addEventListener("click",(function(){i.value=d[a],delete d[a],f.classList.toggle("hidden"),s.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden")}))}));case 3:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),F=function(){var t=n(e().mark((function t(o){var r,a,i,s,c,l,p,f,h;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(d={},!window.location.pathname.endsWith("/playground/")){t.next=32;break}if(r=new URLSearchParams(window.location.search),a="Loading Pyodide...",i="Loading Notebook...",s=r.has("source")?r.get("source"):r.get("notebook"),c=document.querySelector("article"),null===s||!s.trim()){t.next=16;break}return T(c,a),t.next=11,C(!0);case 11:P(c),T(c,i);try{l=r.has("source")?"source":"notebook",m=decodeURIComponent(r.toString()),p="",f=new XMLHttpRequest,u=s,f.open("GET",s,!0),f.onload=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return 4===f.readyState&&200===f.status&&(p=f.responseText),"source"===l&&(p=w(p)),e.next=4,S(p);case 4:return e.next=6,I(o);case 6:P(c),b();case 8:case"end":return e.stop()}}),t)}))),f.send()}catch(e){}t.next=30;break;case 16:return u="",h=w(r.has("code")?r.get("code"):v),m=decodeURIComponent(r.toString()),T(c,a),t.next=22,C(!0);case 22:return P(c),T(c,i),t.next=26,S(h);case 26:return t.next=28,I(o);case 28:P(c),b();case 30:t.next=35;break;case 32:u="",m="",I(o);case 35:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),document.addEventListener("click",(function(e){var t=window.location.pathname.split("/")[1],n=e.target||e.srcElement;if("A"===n.tagName&&F&&n.getAttribute("href")&&n.host===window.location.host&&window.location.pathname==="/".concat(t,'/playground/"')&&window.location.pathname===n.pathname&&window.location.search!==n.search){e.preventDefault();var r,a={},s=i(new URLSearchParams(n.search));try{for(s.s();!(r=s.n()).done;){var c=o(r.value,2),l=c[0],u=c[1];a[l]=u}}catch(e){s.e(e)}finally{s.f()}history.pushState(a,"",n.href),F(!1)}})),window.addEventListener("popstate",(function(){var e=window.location.pathname.split("/")[1];window.location.pathname==="/".concat(e,"/playground/")&&decodeURIComponent(new URLSearchParams(window.location.search).toString())!==m&&F(!1)})),window.addEventListener("unload",(function(){h=!1})),window.document$.subscribe((function(){h?h=!1:F(!0)}))}();
+//# sourceMappingURL=extra-notebook-e52f7bf2.js.map
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js.map b/docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js.map
similarity index 99%
rename from docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js.map
rename to docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js.map
index 4ddb54e41..98010b25d 100644
--- a/docs/theme/assets/coloraide-extras/extra-notebook-c353fe5e.js.map
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-e52f7bf2.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"extra-notebook-c353fe5e.js","sources":["extra-notebook.js"],"sourcesContent":["(() => {\n  let pyodide = null\n  let busy = false\n  let raw = \"\"\n  let gist = \"\"\n  let editTemp = {}\n  const reIdNum = /.*?_(\\d+)$/\n  let initialized = false\n  let lastSearch = \"\"\n  let fake = false\n  const tabStart = /^( {1,4}|\\t)/\n  // This is the Python payload that will be executed when the user\n  // presses the `Run` button. It will execute the code, create a\n  // Python console output, find color references, steps, and interpolation\n  // references and render the appropriate preview.\n  const pycode = `\n{{pycode}}\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\ncallback()\n`\n\n  const defContent = window.colorNotebook.defaultPlayground\n\n  const getContent = content => {\n    return `\n!!! new \"This notebook is powered by [Pyodide](https://github.com/pyodide/pyodide). \\\nLearn more [here](\\\n?notebook=https://gist.githubusercontent.com/facelessuser/7c819668b5eb248ecb9ac608d91391cf/raw/playground.md\\\n). Preview, convert, interpolate, and explore!\"\n\n\\`\\`\\`\\`\\`\\`\\`\\`playground\n${content}\n\\`\\`\\`\\`\\`\\`\\`\\`\n`\n  }\n\n  let notebookInstalled = false\n  let playgroundInstalled = false\n\n  const fakeDOMContentLoaded = () => {\n    // Send a fake `DOMContentLoaded`\n    fake = true\n    window.document.dispatchEvent(new Event(\"DOMContentLoaded\", {\n      bubbles: true,\n      cancelable: true\n    }))\n    window.document$.next()\n  }\n\n  const textResize = inpt => {\n    // Resize inputs based on text height.\n\n    const scrollLeft = window.pageXOffset ||\n      (document.documentElement || document.body.parentNode || document.body).scrollLeft\n\n    const scrollTop  = window.pageYOffset ||\n      (document.documentElement || document.body.parentNode || document.body).scrollTop\n\n    inpt.style.height = \"5px\"\n    inpt.style.height = `${inpt.scrollHeight}px`\n\n    window.scrollTo(scrollLeft, scrollTop)\n  }\n\n  const encodeuri = uri => {\n    // Encode the URI component.\n\n    return encodeURIComponent(uri).replace(/[.!'()*]/g, c => {\n      return `%${c.charCodeAt(0).toString(16)}`\n    })\n  }\n\n  const pyexecute = async currentID => {\n    // Execute Python code inside a playground\n\n    const currentInputs = document.getElementById(`__playground-inputs_${currentID}`)\n    currentInputs.setAttribute(\"readonly\", \"\")\n    pyodide.globals.set(\"id_num\", currentID)\n    pyodide.globals.set(\"action\", \"playground\")\n    await pyodide.runPythonAsync(pycode)\n    currentInputs.removeAttribute(\"readonly\")\n  }\n\n  const pyrender = async text => {\n    // Render an entire notebook page\n\n    pyodide.globals.set(\"content\", text)\n    pyodide.globals.set(\"action\", \"notebook\")\n    await pyodide.runPythonAsync(pycode)\n    const src = document.getElementById(\"__notebook-input\")\n    if (src) {\n      raw = text\n      src.value = text\n    }\n    if (window.location.hash) {\n      // Force jumping to hashes\n      window.location.href = window.location.href // eslint-disable-line no-self-assign\n    }\n  }\n\n  const setupPyodide = async full => {\n    // Load `Pyodide` and the any default packages we can need and can load.\n\n    if (!initialized) {\n      initialized = true\n      pyodide = await loadPyodide({ // eslint-disable-line no-undef\n        indexURL: \"https://cdn.jsdelivr.net/pyodide/v0.21.3/full/\",\n        fullStdLib: false\n      })\n    }\n\n    if ((!notebookInstalled && full) || (!playgroundInstalled && !full)) {\n      const base = `${window.location.origin}/${window.location.pathname.split('/')[1]}/playground/`\n      const packages = (full) ? window.colorNotebook.notebookWheels : window.colorNotebook.playgroundWheels\n      const installs = []\n      if (full) {\n        notebookInstalled = true\n      } else {\n        playgroundInstalled = true\n      }\n      for (const s of packages) {\n        installs.push(base + s)\n      }\n      await pyodide.loadPackage(installs)\n    }\n  }\n\n  const showBusy = (target, label, relative) => {\n    // Show busy indicator\n\n    const loaderLabel = (typeof label === \"undefined\" || label === null) ? \"Loading...\" : label\n    const classes = relative ? \"loading relative\" : \"loading\"\n    const template = document.createElement(\"template\")\n    template.innerHTML = `<div class=\"${classes}\"><div class=\"loader\"></div><div>${loaderLabel}</div></div>`\n    target.appendChild(template.content.firstChild)\n  }\n\n  const hideBusy = target => {\n    // Hide busy indicator\n\n    const loading = target.querySelector(\".loading\")\n    if (loading) {\n      target.removeChild(target.querySelector(\".loading\"))\n    }\n  }\n\n  const popState = () => {\n    // Handle notebook history\n\n    const base = window.location.pathname.split('/')[1]\n    if (\n      window.location.pathname === `/${base}/playground/`\n    ) {\n      const current = decodeURIComponent(new URLSearchParams(window.location.search).toString())\n      if (current !== lastSearch) {\n        main(false) // eslint-disable-line no-use-before-define\n      }\n    }\n  }\n\n  const interceptClickEvent = e => {\n    // Catch links to other notebook pages and handle them\n\n    const base = window.location.pathname.split('/')[1]\n    const target = e.target || e.srcElement\n    if (target.tagName === \"A\" && main) { // eslint-disable-line no-use-before-define\n      if (\n        target.getAttribute(\"href\") &&\n        target.host === window.location.host &&\n        window.location.pathname === `/${base}/playground/\"` &&\n        window.location.pathname === target.pathname &&\n        window.location.search !== target.search\n      ) {\n        e.preventDefault()\n        const search = new URLSearchParams(target.search)\n        const state = {}\n        for (const [key, value] of search) {\n          state[key] = value\n        }\n        history.pushState(state, \"\", target.href)\n        main(false) // eslint-disable-line no-use-before-define\n      }\n    }\n  }\n\n  const handleTab = e => {\n    // Prevent tab from tabbing out.\n\n    if (e.key === 'Tab') {\n      const target = e.target\n\n      if (target.selectionStart !== target.selectionEnd) {\n        e.preventDefault()\n\n        let start = target.selectionStart\n        let end = target.selectionEnd\n\n        const text = target.value\n\n        while (start > 0 && text[start - 1] !== '\\n') {\n          start--\n        }\n        while (end > 0 && text[end - 1] !== '\\n' && end < text.length) {\n          end++\n        }\n\n        let lines = text.substr(start, end - start).split('\\n')\n\n        for (let i = 0; i < lines.length; i++) {\n\n          // Don't indent last line if cursor at start of line\n          if (i === lines.length - 1 && lines[i].length === 0) {\n            continue\n          }\n\n          // Indent or deindent\n          if (e.shiftKey) {\n            lines[i] = lines[i].replace(tabStart, '')\n          } else {\n            lines[i] = `    ${lines[i]}`\n          }\n        }\n        lines = lines.join('\\n')\n\n        // Update the text area\n        target.value = text.substr(0, start) + lines + text.substr(end)\n        target.selectionStart = start\n        target.selectionEnd = start + lines.length\n      }\n    }\n  }\n\n  const init = async first => {\n    // Setup input highlighting and events to run Python code blocks.\n\n    const notebook = document.getElementById(\"__notebook-source\")\n    const playgrounds = document.querySelectorAll(\".playground\")\n    playgrounds.forEach(pg => {\n\n      const currentID = pg.id.replace(reIdNum, \"$1\")\n      const inputs = document.getElementById(`__playground-inputs_${currentID}`)\n      const results = document.getElementById(`__playground-results_${currentID}`)\n      const pgcode = document.getElementById(`__playground-code_${currentID}`)\n      const buttonEdit = document.querySelector(`button#__playground-edit_${currentID}`)\n      const buttonShare = document.querySelector(`button#__playground-share_${currentID}`)\n      const buttonRun = document.querySelector(`button#__playground-run_${currentID}`)\n      const buttonCancel = document.querySelector(`button#__playground-cancel_${currentID}`)\n\n      inputs.addEventListener(\"input\", () => {\n        // Adjust textarea height on text input.\n\n        textResize(inputs)\n      })\n\n      inputs.addEventListener('keydown', handleTab)\n\n      if (notebook && first) {\n        const notebookInput = document.getElementById(\"__notebook-input\")\n\n        notebookInput.addEventListener(\"input\", e => {\n          // Adjust textarea height on text input.\n\n          textResize(e.target)\n        })\n\n        notebookInput.addEventListener('keydown', handleTab)\n\n        const editPage = document.getElementById(\"__notebook-edit\")\n        editPage.addEventListener(\"click\", () => {\n          editTemp[notebookInput.id] = notebookInput.value\n          document.getElementById(\"__notebook-render\").classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n          textResize(document.getElementById(\"__notebook-input\"))\n        })\n\n        document.getElementById(\"__notebook-md-gist\").addEventListener(\"click\", async e => {\n          let uri = prompt(\"Please enter link to the Markdown page source:\", gist) // eslint-disable-line no-alert\n          if (uri !== null) {\n            uri = encodeuri(uri)\n            e.preventDefault()\n            history.pushState({notebook: uri}, \"\", `?${new URLSearchParams(`notebook=${uri}`).toString()}`)\n            main(false) // eslint-disable-line no-use-before-define\n          }\n        })\n\n        document.getElementById(\"__notebook-py-gist\").addEventListener(\"click\", async e => {\n          let uri = prompt(\"Please enter the link to the Python code source:\", gist) // eslint-disable-line no-alert\n          if (uri !== null) {\n            uri = encodeuri(uri)\n            e.preventDefault()\n            history.pushState({source: uri}, \"\", `?${new URLSearchParams(`source=${uri}`).toString()}`)\n            main(false) // eslint-disable-line no-use-before-define\n          }\n        })\n\n        document.getElementById(\"__notebook-input\").value = raw\n        document.getElementById(\"__notebook-cancel\").addEventListener(\"click\", () => {\n          notebookInput.value = editTemp[notebookInput.id]\n          delete editTemp[notebookInput.id]\n          document.getElementById(\"__notebook-render\").classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n        })\n\n        document.getElementById(\"__notebook-submit\").addEventListener(\"click\", async() => {\n          const render = document.getElementById(\"__notebook-render\")\n          raw = document.getElementById(\"__notebook-input\").value\n          render.classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n          const article = document.querySelector(\"article\")\n          showBusy(article, \"Loading Notebook...\")\n          render.innerHTML = \"\"\n          editTemp = {}\n          await setupPyodide(true)\n          await pyrender(raw)\n          await init()\n          hideBusy(article)\n          fakeDOMContentLoaded()\n        })\n      }\n\n      inputs.addEventListener(\"touchmove\", e => {\n        // Stop propagation on \"touchmove\".\n\n        e.stopPropagation()\n      })\n\n      results.addEventListener(\"click\", e => {\n        // Handle clicks on results and copies color from single color swatch when clicked.\n\n        const el = e.target\n        if (el.matches('span.swatch-color')) {\n          let content = ''\n          const parent = el.parentNode\n          if (!parent.matches('span.swatch-gradient')) {\n            content = parent.getAttribute('title').replace('Copy to clipboard', '')\n            content = content.replace('\\n', '')\n            if (window.clipboardData && window.clipboardData.setData) {\n              // Old `IE`` handling, do we really need this?\n              return window.clipboardData.setData(\"Text\", content)\n            } else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n              const textarea = document.createElement(\"textarea\")\n              textarea.textContent = content\n              textarea.style.position = \"fixed\"\n              document.body.appendChild(textarea)\n              textarea.select()\n              try {\n                return document.execCommand(\"copy\")\n              } catch (ex) {\n                return prompt(\"Copy to clipboard: Ctrl+C, Enter\", content) // eslint-disable-line no-alert\n              } finally {\n                document.body.removeChild(textarea)\n              }\n            }\n          }\n        }\n      })\n\n      buttonEdit.addEventListener(\"click\", async() => {\n        // Handle the button click: show source or execute source.\n\n        editTemp[currentID] = inputs.value\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        textResize(inputs)\n        inputs.focus()\n      })\n\n      buttonShare.addEventListener(\"click\", async() => {\n        // Handle the share click: copy URL with code as parameter.\n\n        const base = window.location.pathname.split('/')[1]\n        const uri = encodeuri(inputs.value)\n        const loc = window.location\n        let pathname = \"/playground/\"\n        if (loc.pathname.startsWith(`/${base}/`)) {\n          pathname = `/${base}/playground/`\n        }\n        const path = `${loc.protocol}//${loc.host}${pathname}?code=${uri}`\n        if (path.length > 2000) {\n          alert( // eslint-disable-line no-alert\n            \"Code must be small enough to generate a shareable URL under 2000 characters!\"\n          )\n        } else {\n          navigator.clipboard.writeText(path).then(async() => {\n            alert(\"Link copied to clipboard :)\") // eslint-disable-line no-alert\n          }, async() => {\n            alert(\"Failed to copy link clipboard!\") // eslint-disable-line no-alert\n          })\n        }\n      })\n\n      buttonRun.addEventListener(\"click\", async() => {\n        // Handle the button click: show source or execute source.\n\n        if (busy) {\n          return\n        }\n\n        busy = true\n        // Load Pyodide and related packages.\n        const form = pgcode.querySelector(\"form\")\n        showBusy(form, null, true)\n        const buttons = document.querySelectorAll(\".playground .playground-run\")\n        if (buttons) {\n          buttons.forEach(b => {\n            b.setAttribute(\"disabled\", \"\")\n          })\n        }\n        await setupPyodide(false)\n        results.querySelector(\"code\").innerHTML = \"\"\n        await pyexecute(currentID)\n        if (buttons) {\n          buttons.forEach(b => {\n            b.removeAttribute(\"disabled\")\n          })\n        }\n        hideBusy(form)\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n\n        delete editTemp[currentID]\n        busy = false\n      })\n\n      buttonCancel.addEventListener(\"click\", () => {\n        // Cancel edit.\n\n        inputs.value = editTemp[currentID]\n        delete editTemp[currentID]\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n      })\n    })\n  }\n\n  const main = async first => {\n    // Load external source to render in a playground.\n    // This can be something like a file on a gist we must read in (?source=)\n    // or raw code (?code=).\n\n    editTemp = {}\n\n    if (window.location.pathname.endsWith(\"/playground/\")) {\n      const params = new URLSearchParams(window.location.search)\n      const loadMsg = \"Loading Pyodide...\"\n      const pageMsg = \"Loading Notebook...\"\n      const uri = params.has(\"source\") ? params.get(\"source\") : params.get(\"notebook\")\n      const article = document.querySelector(\"article\")\n      if (uri !== null && uri.trim()) {\n        // A source was specified, so load it.\n        showBusy(article, loadMsg)\n        await setupPyodide(true)\n        hideBusy(article)\n        showBusy(article, pageMsg)\n        try {\n          const gistType = params.has(\"source\") ? \"source\" : \"notebook\"\n          lastSearch = decodeURIComponent(params.toString())\n          let value = \"\"\n          const xhr = new XMLHttpRequest()\n          gist = uri\n          xhr.open(\"GET\", uri, true)\n          xhr.onload = async() => {\n            // Try and load the requested content\n            if (xhr.readyState === 4) {\n              if (xhr.status === 200) {\n                value = xhr.responseText\n              }\n            }\n\n            if (gistType === \"source\") {\n              value = getContent(value)\n            }\n            await pyrender(value)\n            await init(first)\n            hideBusy(article)\n            fakeDOMContentLoaded()\n          }\n          xhr.send()\n        } catch (err) {} // eslint-disable-line no-empty\n      } else {\n        gist = \"\"\n        const content = getContent(params.has(\"code\") ? params.get(\"code\") : defContent)\n        lastSearch = decodeURIComponent(params.toString())\n        showBusy(article, loadMsg)\n        await setupPyodide(true)\n        hideBusy(article)\n        showBusy(article, pageMsg)\n        await pyrender(content)\n        await init(first)\n        hideBusy(article)\n        fakeDOMContentLoaded()\n      }\n    } else {\n      gist = \"\"\n      lastSearch = \"\"\n      init(first)\n    }\n  }\n\n  // Capture links in notebook pages so that we can make playgound links load instantly\n  document.addEventListener(\"click\", interceptClickEvent)\n\n  // Handle history of notebook pages as they are loaded dynamically\n  window.addEventListener(\"popstate\", popState)\n\n  // Before leaving, turn off fake, just in case we navigated away before finished\n  window.addEventListener(\"unload\", () => {\n    fake = false\n  })\n\n  // Attach main via subscribe (subscribes to Materials on page load and instant page loads)\n  window.document$.subscribe(() => {\n    // To get other libraries to reload, we may create a fake `DOMContentLoaded`\n    // No need to process these events.\n    if (fake) {\n      fake = false\n      return\n    }\n    main(true)\n  })\n})()\n"],"names":["pyodide","busy","raw","gist","editTemp","reIdNum","initialized","lastSearch","fake","tabStart","pycode","defContent","getContent","notebookInstalled","playgroundInstalled","fakeDOMContentLoaded","textResize","encodeuri","pyexecute","pyrender","setupPyodide","showBusy","hideBusy","handleTab","init","main","window","colorNotebook","defaultPlayground","content","concat","document","dispatchEvent","Event","bubbles","cancelable","document$","next","inpt","scrollLeft","pageXOffset","documentElement","body","parentNode","scrollTop","pageYOffset","style","height","scrollHeight","scrollTo","uri","encodeURIComponent","replace","c","charCodeAt","toString","_ref","_asyncToGenerator","_regeneratorRuntime","mark","currentID","currentInputs","wrap","_context","prev","getElementById","setAttribute","globals","set","runPythonAsync","removeAttribute","stop","_callee","_x","apply","this","arguments","_ref2","text","src","_context2","value","location","hash","href","_callee2","_x2","_ref3","full","base","packages","installs","_iterator","_step","s","_context3","loadPyodide","indexURL","fullStdLib","sent","origin","pathname","split","notebookWheels","playgroundWheels","_createForOfIteratorHelper","n","done","push","err","e","f","loadPackage","_callee3","_x3","target","label","relative","loaderLabel","classes","template","createElement","innerHTML","appendChild","firstChild","querySelector","removeChild","key","selectionStart","selectionEnd","preventDefault","start","end","length","lines","substr","i","shiftKey","join","_ref4","first","notebook","_context12","querySelectorAll","forEach","pg","id","inputs","results","pgcode","buttonEdit","buttonShare","buttonRun","buttonCancel","addEventListener","notebookInput","classList","toggle","_ref5","_context4","prompt","history","pushState","URLSearchParams","_callee4","_x5","_ref6","_context5","source","_callee5","_x6","_callee6","render","article","_context6","stopPropagation","el","matches","parent","getAttribute","clipboardData","setData","queryCommandSupported","textarea","textContent","position","select","execCommand","ex","_callee7","_context7","focus","_callee10","loc","path","_context10","startsWith","protocol","host","alert","navigator","clipboard","writeText","then","_callee8","_context8","_callee9","_context9","_callee11","form","buttons","_context11","abrupt","b","_callee12","_x4","_ref13","params","loadMsg","pageMsg","gistType","xhr","_context14","endsWith","search","has","get","trim","decodeURIComponent","XMLHttpRequest","open","onload","_callee13","_context13","readyState","status","responseText","send","_callee14","_x7","srcElement","tagName","_step2","state","_iterator2","_step2$value","_slicedToArray","subscribe"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"extra-notebook-e52f7bf2.js","sources":["extra-notebook.js"],"sourcesContent":["(() => {\n  let pyodide = null\n  let busy = false\n  let raw = \"\"\n  let gist = \"\"\n  let editTemp = {}\n  const reIdNum = /.*?_(\\d+)$/\n  let initialized = false\n  let lastSearch = \"\"\n  let fake = false\n  const tabStart = /^( {1,4}|\\t)/\n  // This is the Python payload that will be executed when the user\n  // presses the `Run` button. It will execute the code, create a\n  // Python console output, find color references, steps, and interpolation\n  // references and render the appropriate preview.\n  const pycode = `\n{{pycode}}\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\ncallback()\n`\n\n  const defContent = window.colorNotebook.defaultPlayground\n\n  const getContent = content => {\n    return `\n!!! new \"This notebook is powered by [Pyodide](https://github.com/pyodide/pyodide). \\\nLearn more [here](\\\n?notebook=https://gist.githubusercontent.com/facelessuser/7c819668b5eb248ecb9ac608d91391cf/raw/playground.md\\\n). Preview, convert, interpolate, and explore!\"\n\n\\`\\`\\`\\`\\`\\`\\`\\`playground\n${content}\n\\`\\`\\`\\`\\`\\`\\`\\`\n`\n  }\n\n  let notebookInstalled = false\n  let playgroundInstalled = false\n\n  const fakeDOMContentLoaded = () => {\n    // Send a fake `DOMContentLoaded`\n    fake = true\n    window.document.dispatchEvent(new Event(\"DOMContentLoaded\", {\n      bubbles: true,\n      cancelable: true\n    }))\n    window.document$.next()\n  }\n\n  const textResize = inpt => {\n    // Resize inputs based on text height.\n\n    const scrollLeft = window.pageXOffset ||\n      (document.documentElement || document.body.parentNode || document.body).scrollLeft\n\n    const scrollTop  = window.pageYOffset ||\n      (document.documentElement || document.body.parentNode || document.body).scrollTop\n\n    inpt.style.height = \"5px\"\n    inpt.style.height = `${inpt.scrollHeight}px`\n\n    window.scrollTo(scrollLeft, scrollTop)\n  }\n\n  const encodeuri = uri => {\n    // Encode the URI component.\n\n    return encodeURIComponent(uri).replace(/[.!'()*]/g, c => {\n      return `%${c.charCodeAt(0).toString(16)}`\n    })\n  }\n\n  const pyexecute = async currentID => {\n    // Execute Python code inside a playground\n\n    const currentInputs = document.getElementById(`__playground-inputs_${currentID}`)\n    currentInputs.setAttribute(\"readonly\", \"\")\n    pyodide.globals.set(\"id_num\", currentID)\n    pyodide.globals.set(\"action\", \"playground\")\n    await pyodide.runPythonAsync(pycode)\n    currentInputs.removeAttribute(\"readonly\")\n  }\n\n  const pyrender = async text => {\n    // Render an entire notebook page\n\n    pyodide.globals.set(\"content\", text)\n    pyodide.globals.set(\"action\", \"notebook\")\n    await pyodide.runPythonAsync(pycode)\n    const src = document.getElementById(\"__notebook-input\")\n    if (src) {\n      raw = text\n      src.value = text\n    }\n    if (window.location.hash) {\n      // Force jumping to hashes\n      window.location.href = window.location.href // eslint-disable-line no-self-assign\n    }\n  }\n\n  const setupPyodide = async full => {\n    // Load `Pyodide` and the any default packages we can need and can load.\n\n    if (!initialized) {\n      initialized = true\n      pyodide = await loadPyodide({ // eslint-disable-line no-undef\n        indexURL: \"https://cdn.jsdelivr.net/pyodide/v0.21.3/full/\",\n        fullStdLib: false\n      })\n    }\n\n    if ((!notebookInstalled && full) || (!playgroundInstalled && !full)) {\n      const base = `${window.location.origin}/${window.location.pathname.split('/')[1]}/playground/`\n      const packages = (full) ? window.colorNotebook.notebookWheels : window.colorNotebook.playgroundWheels\n      const installs = []\n      if (full) {\n        notebookInstalled = true\n      } else {\n        playgroundInstalled = true\n      }\n      for (const s of packages) {\n        installs.push(base + s)\n      }\n      await pyodide.loadPackage(installs)\n    }\n  }\n\n  const showBusy = (target, label, relative) => {\n    // Show busy indicator\n\n    const loaderLabel = (typeof label === \"undefined\" || label === null) ? \"Loading...\" : label\n    const classes = relative ? \"loading relative\" : \"loading\"\n    const template = document.createElement(\"template\")\n    template.innerHTML = `<div class=\"${classes}\"><div class=\"loader\"></div><div>${loaderLabel}</div></div>`\n    target.appendChild(template.content.firstChild)\n  }\n\n  const hideBusy = target => {\n    // Hide busy indicator\n\n    const loading = target.querySelector(\".loading\")\n    if (loading) {\n      target.removeChild(target.querySelector(\".loading\"))\n    }\n  }\n\n  const popState = () => {\n    // Handle notebook history\n\n    const base = window.location.pathname.split('/')[1]\n    if (\n      window.location.pathname === `/${base}/playground/`\n    ) {\n      const current = decodeURIComponent(new URLSearchParams(window.location.search).toString())\n      if (current !== lastSearch) {\n        main(false) // eslint-disable-line no-use-before-define\n      }\n    }\n  }\n\n  const interceptClickEvent = e => {\n    // Catch links to other notebook pages and handle them\n\n    const base = window.location.pathname.split('/')[1]\n    const target = e.target || e.srcElement\n    if (target.tagName === \"A\" && main) { // eslint-disable-line no-use-before-define\n      if (\n        target.getAttribute(\"href\") &&\n        target.host === window.location.host &&\n        window.location.pathname === `/${base}/playground/\"` &&\n        window.location.pathname === target.pathname &&\n        window.location.search !== target.search\n      ) {\n        e.preventDefault()\n        const search = new URLSearchParams(target.search)\n        const state = {}\n        for (const [key, value] of search) {\n          state[key] = value\n        }\n        history.pushState(state, \"\", target.href)\n        main(false) // eslint-disable-line no-use-before-define\n      }\n    }\n  }\n\n  const handleTab = e => {\n    // Prevent tab from tabbing out.\n\n    if (e.key === 'Tab') {\n      const target = e.target\n\n      if (target.selectionStart !== target.selectionEnd) {\n        e.preventDefault()\n\n        let start = target.selectionStart\n        let end = target.selectionEnd\n\n        const text = target.value\n\n        while (start > 0 && text[start - 1] !== '\\n') {\n          start--\n        }\n        while (end > 0 && text[end - 1] !== '\\n' && end < text.length) {\n          end++\n        }\n\n        let lines = text.substr(start, end - start).split('\\n')\n\n        for (let i = 0; i < lines.length; i++) {\n\n          // Don't indent last line if cursor at start of line\n          if (i === lines.length - 1 && lines[i].length === 0) {\n            continue\n          }\n\n          // Indent or deindent\n          if (e.shiftKey) {\n            lines[i] = lines[i].replace(tabStart, '')\n          } else {\n            lines[i] = `    ${lines[i]}`\n          }\n        }\n        lines = lines.join('\\n')\n\n        // Update the text area\n        target.value = text.substr(0, start) + lines + text.substr(end)\n        target.selectionStart = start\n        target.selectionEnd = start + lines.length\n      }\n    }\n  }\n\n  const init = async first => {\n    // Setup input highlighting and events to run Python code blocks.\n\n    const notebook = document.getElementById(\"__notebook-source\")\n    const playgrounds = document.querySelectorAll(\".playground\")\n    playgrounds.forEach(pg => {\n\n      const currentID = pg.id.replace(reIdNum, \"$1\")\n      const inputs = document.getElementById(`__playground-inputs_${currentID}`)\n      const results = document.getElementById(`__playground-results_${currentID}`)\n      const pgcode = document.getElementById(`__playground-code_${currentID}`)\n      const buttonEdit = document.querySelector(`button#__playground-edit_${currentID}`)\n      const buttonShare = document.querySelector(`button#__playground-share_${currentID}`)\n      const buttonRun = document.querySelector(`button#__playground-run_${currentID}`)\n      const buttonCancel = document.querySelector(`button#__playground-cancel_${currentID}`)\n\n      inputs.addEventListener(\"input\", () => {\n        // Adjust textarea height on text input.\n\n        textResize(inputs)\n      })\n\n      inputs.addEventListener('keydown', handleTab)\n\n      if (notebook && first) {\n        const notebookInput = document.getElementById(\"__notebook-input\")\n\n        notebookInput.addEventListener(\"input\", e => {\n          // Adjust textarea height on text input.\n\n          textResize(e.target)\n        })\n\n        notebookInput.addEventListener('keydown', handleTab)\n\n        const editPage = document.getElementById(\"__notebook-edit\")\n        editPage.addEventListener(\"click\", () => {\n          editTemp[notebookInput.id] = notebookInput.value\n          document.getElementById(\"__notebook-render\").classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n          textResize(document.getElementById(\"__notebook-input\"))\n        })\n\n        document.getElementById(\"__notebook-md-gist\").addEventListener(\"click\", async e => {\n          let uri = prompt(\"Please enter link to the Markdown page source:\", gist) // eslint-disable-line no-alert\n          if (uri !== null) {\n            uri = encodeuri(uri)\n            e.preventDefault()\n            history.pushState({notebook: uri}, \"\", `?${new URLSearchParams(`notebook=${uri}`).toString()}`)\n            main(false) // eslint-disable-line no-use-before-define\n          }\n        })\n\n        document.getElementById(\"__notebook-py-gist\").addEventListener(\"click\", async e => {\n          let uri = prompt(\"Please enter the link to the Python code source:\", gist) // eslint-disable-line no-alert\n          if (uri !== null) {\n            uri = encodeuri(uri)\n            e.preventDefault()\n            history.pushState({source: uri}, \"\", `?${new URLSearchParams(`source=${uri}`).toString()}`)\n            main(false) // eslint-disable-line no-use-before-define\n          }\n        })\n\n        document.getElementById(\"__notebook-input\").value = raw\n        document.getElementById(\"__notebook-cancel\").addEventListener(\"click\", () => {\n          notebookInput.value = editTemp[notebookInput.id]\n          delete editTemp[notebookInput.id]\n          document.getElementById(\"__notebook-render\").classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n        })\n\n        document.getElementById(\"__notebook-submit\").addEventListener(\"click\", async() => {\n          const render = document.getElementById(\"__notebook-render\")\n          raw = document.getElementById(\"__notebook-input\").value\n          render.classList.toggle(\"hidden\")\n          document.getElementById(\"__notebook-source\").classList.toggle(\"hidden\")\n          const article = document.querySelector(\"article\")\n          showBusy(article, \"Loading Notebook...\")\n          render.innerHTML = \"\"\n          editTemp = {}\n          await setupPyodide(true)\n          await pyrender(raw)\n          await init()\n          hideBusy(article)\n          fakeDOMContentLoaded()\n        })\n      }\n\n      inputs.addEventListener(\"touchmove\", e => {\n        // Stop propagation on \"touchmove\".\n\n        e.stopPropagation()\n      })\n\n      results.addEventListener(\"click\", e => {\n        // Handle clicks on results and copies color from single color swatch when clicked.\n\n        const el = e.target\n        if (el.matches('span.swatch-color')) {\n          let content = ''\n          const parent = el.parentNode\n          if (!parent.matches('span.swatch-gradient')) {\n            content = parent.getAttribute('title').replace('Copy to clipboard', '')\n            content = content.replace('\\n', '')\n            if (window.clipboardData && window.clipboardData.setData) {\n              // Old `IE`` handling, do we really need this?\n              return window.clipboardData.setData(\"Text\", content)\n            } else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n              const textarea = document.createElement(\"textarea\")\n              textarea.textContent = content\n              textarea.style.position = \"fixed\"\n              document.body.appendChild(textarea)\n              textarea.select()\n              try {\n                return document.execCommand(\"copy\")\n              } catch (ex) {\n                return prompt(\"Copy to clipboard: Ctrl+C, Enter\", content) // eslint-disable-line no-alert\n              } finally {\n                document.body.removeChild(textarea)\n              }\n            }\n          }\n        }\n      })\n\n      buttonEdit.addEventListener(\"click\", async() => {\n        // Handle the button click: show source or execute source.\n\n        editTemp[currentID] = inputs.value\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        textResize(inputs)\n        inputs.focus()\n      })\n\n      buttonShare.addEventListener(\"click\", async() => {\n        // Handle the share click: copy URL with code as parameter.\n\n        const base = window.location.pathname.split('/')[1]\n        const uri = encodeuri(inputs.value)\n        const loc = window.location\n        let pathname = \"/playground/\"\n        if (loc.pathname.startsWith(`/${base}/`)) {\n          pathname = `/${base}/playground/`\n        }\n        const path = `${loc.protocol}//${loc.host}${pathname}?code=${uri}`\n        if (path.length > 2000) {\n          alert( // eslint-disable-line no-alert\n            \"Code must be small enough to generate a shareable URL under 2000 characters!\"\n          )\n        } else {\n          navigator.clipboard.writeText(path).then(async() => {\n            alert(\"Link copied to clipboard :)\") // eslint-disable-line no-alert\n          }, async() => {\n            alert(\"Failed to copy link clipboard!\") // eslint-disable-line no-alert\n          })\n        }\n      })\n\n      buttonRun.addEventListener(\"click\", async() => {\n        // Handle the button click: show source or execute source.\n\n        if (busy) {\n          return\n        }\n\n        busy = true\n        // Load Pyodide and related packages.\n        const form = pgcode.querySelector(\"form\")\n        showBusy(form, null, true)\n        const buttons = document.querySelectorAll(\".playground .playground-run\")\n        if (buttons) {\n          buttons.forEach(b => {\n            b.setAttribute(\"disabled\", \"\")\n          })\n        }\n        await setupPyodide(false)\n        results.querySelector(\"code\").innerHTML = \"\"\n        await pyexecute(currentID)\n        if (buttons) {\n          buttons.forEach(b => {\n            b.removeAttribute(\"disabled\")\n          })\n        }\n        hideBusy(form)\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n\n        delete editTemp[currentID]\n        busy = false\n      })\n\n      buttonCancel.addEventListener(\"click\", () => {\n        // Cancel edit.\n\n        inputs.value = editTemp[currentID]\n        delete editTemp[currentID]\n        pgcode.classList.toggle(\"hidden\")\n        results.classList.toggle(\"hidden\")\n        buttonEdit.classList.toggle(\"hidden\")\n        buttonShare.classList.toggle(\"hidden\")\n        buttonRun.classList.toggle(\"hidden\")\n        buttonCancel.classList.toggle(\"hidden\")\n      })\n    })\n  }\n\n  const main = async first => {\n    // Load external source to render in a playground.\n    // This can be something like a file on a gist we must read in (?source=)\n    // or raw code (?code=).\n\n    editTemp = {}\n\n    if (window.location.pathname.endsWith(\"/playground/\")) {\n      const params = new URLSearchParams(window.location.search)\n      const loadMsg = \"Loading Pyodide...\"\n      const pageMsg = \"Loading Notebook...\"\n      const uri = params.has(\"source\") ? params.get(\"source\") : params.get(\"notebook\")\n      const article = document.querySelector(\"article\")\n      if (uri !== null && uri.trim()) {\n        // A source was specified, so load it.\n        showBusy(article, loadMsg)\n        await setupPyodide(true)\n        hideBusy(article)\n        showBusy(article, pageMsg)\n        try {\n          const gistType = params.has(\"source\") ? \"source\" : \"notebook\"\n          lastSearch = decodeURIComponent(params.toString())\n          let value = \"\"\n          const xhr = new XMLHttpRequest()\n          gist = uri\n          xhr.open(\"GET\", uri, true)\n          xhr.onload = async() => {\n            // Try and load the requested content\n            if (xhr.readyState === 4) {\n              if (xhr.status === 200) {\n                value = xhr.responseText\n              }\n            }\n\n            if (gistType === \"source\") {\n              value = getContent(value)\n            }\n            await pyrender(value)\n            await init(first)\n            hideBusy(article)\n            fakeDOMContentLoaded()\n          }\n          xhr.send()\n        } catch (err) {} // eslint-disable-line no-empty\n      } else {\n        gist = \"\"\n        const content = getContent(params.has(\"code\") ? params.get(\"code\") : defContent)\n        lastSearch = decodeURIComponent(params.toString())\n        showBusy(article, loadMsg)\n        await setupPyodide(true)\n        hideBusy(article)\n        showBusy(article, pageMsg)\n        await pyrender(content)\n        await init(first)\n        hideBusy(article)\n        fakeDOMContentLoaded()\n      }\n    } else {\n      gist = \"\"\n      lastSearch = \"\"\n      init(first)\n    }\n  }\n\n  // Capture links in notebook pages so that we can make playgound links load instantly\n  document.addEventListener(\"click\", interceptClickEvent)\n\n  // Handle history of notebook pages as they are loaded dynamically\n  window.addEventListener(\"popstate\", popState)\n\n  // Before leaving, turn off fake, just in case we navigated away before finished\n  window.addEventListener(\"unload\", () => {\n    fake = false\n  })\n\n  // Attach main via subscribe (subscribes to Materials on page load and instant page loads)\n  window.document$.subscribe(() => {\n    // To get other libraries to reload, we may create a fake `DOMContentLoaded`\n    // No need to process these events.\n    if (fake) {\n      fake = false\n      return\n    }\n    main(true)\n  })\n})()\n"],"names":["pyodide","busy","raw","gist","editTemp","reIdNum","initialized","lastSearch","fake","tabStart","pycode","defContent","getContent","notebookInstalled","playgroundInstalled","fakeDOMContentLoaded","textResize","encodeuri","pyexecute","pyrender","setupPyodide","showBusy","hideBusy","handleTab","init","main","window","colorNotebook","defaultPlayground","content","concat","document","dispatchEvent","Event","bubbles","cancelable","document$","next","inpt","scrollLeft","pageXOffset","documentElement","body","parentNode","scrollTop","pageYOffset","style","height","scrollHeight","scrollTo","uri","encodeURIComponent","replace","c","charCodeAt","toString","_ref","_asyncToGenerator","_regeneratorRuntime","mark","currentID","currentInputs","wrap","_context","prev","getElementById","setAttribute","globals","set","runPythonAsync","removeAttribute","stop","_callee","_x","apply","this","arguments","_ref2","text","src","_context2","value","location","hash","href","_callee2","_x2","_ref3","full","base","packages","installs","_iterator","_step","s","_context3","loadPyodide","indexURL","fullStdLib","sent","origin","pathname","split","notebookWheels","playgroundWheels","_createForOfIteratorHelper","n","done","push","err","e","f","loadPackage","_callee3","_x3","target","label","relative","loaderLabel","classes","template","createElement","innerHTML","appendChild","firstChild","querySelector","removeChild","key","selectionStart","selectionEnd","preventDefault","start","end","length","lines","substr","i","shiftKey","join","_ref4","first","notebook","_context12","querySelectorAll","forEach","pg","id","inputs","results","pgcode","buttonEdit","buttonShare","buttonRun","buttonCancel","addEventListener","notebookInput","classList","toggle","_ref5","_context4","prompt","history","pushState","URLSearchParams","_callee4","_x5","_ref6","_context5","source","_callee5","_x6","_callee6","render","article","_context6","stopPropagation","el","matches","parent","getAttribute","clipboardData","setData","queryCommandSupported","textarea","textContent","position","select","execCommand","ex","_callee7","_context7","focus","_callee10","loc","path","_context10","startsWith","protocol","host","alert","navigator","clipboard","writeText","then","_callee8","_context8","_callee9","_context9","_callee11","form","buttons","_context11","abrupt","b","_callee12","_x4","_ref13","params","loadMsg","pageMsg","gistType","xhr","_context14","endsWith","search","has","get","trim","decodeURIComponent","XMLHttpRequest","open","onload","_callee13","_context13","readyState","status","responseText","send","_callee14","_x7","srcElement","tagName","_step2","state","_iterator2","_step2$value","_slicedToArray","subscribe"],"mappings":"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"}
\ No newline at end of file
diff --git a/docs/theme/playground-config-3625007a.js b/docs/theme/playground-config-52c8e5b3.js
similarity index 77%
rename from docs/theme/playground-config-3625007a.js
rename to docs/theme/playground-config-52c8e5b3.js
index 1d787e5fa..868d0d922 100644
--- a/docs/theme/playground-config-3625007a.js
+++ b/docs/theme/playground-config-52c8e5b3.js
@@ -1,5 +1,5 @@
 var colorNotebook = {
-    "playgroundWheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.5.dev0-py3-none-any.whl'],
-    "notebookWheels": ['Markdown-3.3.4-py3-none-any.whl', 'pymdown_extensions-9.5-py3-none-any.whl', 'Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.5.dev0-py3-none-any.whl'],
+    "playgroundWheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.5-py3-none-any.whl'],
+    "notebookWheels": ['Markdown-3.3.4-py3-none-any.whl', 'pymdown_extensions-9.5-py3-none-any.whl', 'Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.5-py3-none-any.whl'],
     "defaultPlayground": "import coloraide\ncoloraide.__version__\nColor('red')"
 }
diff --git a/mkdocs.yml b/mkdocs.yml
index be9c7dd7f..b1da3c2fb 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -208,9 +208,9 @@ extra_css:
   - assets/coloraide-extras/extra-d44d9d3485.css
 extra_javascript:
   - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
-  - playground-config-3625007a.js
+  - playground-config-52c8e5b3.js
   - https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
-  - assets/coloraide-extras/extra-notebook-c353fe5e.js
+  - assets/coloraide-extras/extra-notebook-e52f7bf2.js
 
 extra:
   social:


Test patch:
diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py
index 33194da28..1c073e198 100644
--- a/tests/test_interpolation.py
+++ b/tests/test_interpolation.py
@@ -8,6 +8,67 @@
 class TestInterpolation(util.ColorAsserts, unittest.TestCase):
     """Test interpolation."""
 
+    def test_domain(self):
+        """Test that domains work."""
+
+        i = Color.interpolate(
+            ['blue', 'green', 'yellow', 'orange', 'red'],
+            domain=[-32, 32, 60, 85, 95]
+        )
+
+        self.assertColorEqual(i(-32), Color('blue'))
+        self.assertColorEqual(i(35), Color('rgb(52.558 141.25 0.01039)'))
+        self.assertColorEqual(i(60), Color('yellow'))
+        self.assertColorEqual(i(79), Color('rgb(256.86 187 0.02703)'))
+        self.assertColorEqual(i(95), Color('red'))
+
+    def test_domain_extrapolation(self):
+        """Test extrapolation with custom domain."""
+
+        i = Color.interpolate(['red', 'blue'], extrapolate=True, domain=[-25, 25])
+        self.assertColorEqual(i(-30), Color('rgb(277.88 -56.723 -61.634)'))
+        self.assertColorEqual(i(30), Color('rgb(-22.838 -53.038 273.02)'))
+
+    def test_domain_of_one(self):
+        """
+        Test domain of one.
+
+        This is pointless, but we won't break because of it.
+        """
+
+        i = Color.interpolate(
+            ['blue', 'green', 'yellow', 'orange', 'red'],
+            domain=[1]
+        )
+
+        self.assertColorEqual(i(-1), Color('blue'))
+        self.assertColorEqual(i(0), Color('blue'))
+        self.assertColorEqual(i(1), Color('blue'))
+        self.assertColorEqual(i(2), Color('red'))
+
+    def test_domain_in_step(self):
+        """Test domains work in steps."""
+
+        steps = Color.steps(
+            ['blue', 'green', 'yellow', 'orange', 'red'],
+            steps=11,
+            domain=[-32, 32, 60, 85, 95]
+        )
+
+        self.assertColorEqual(steps[0], Color('blue'))
+        self.assertColorEqual(steps[3], Color('rgb(-53.051 112.52 135.23)'))
+        self.assertColorEqual(steps[5], Color('rgb(-3.6056 127.82 10.348)'))
+        self.assertColorEqual(steps[7], Color('rgb(230.77 240.55 0.01746)'))
+        self.assertColorEqual(steps[10], Color('red'))
+
+    def test_domain_mix(self):
+        """Test domains in mix."""
+
+        self.assertColorEqual(
+            Color('red').mix('blue', 0.75, domain=[0.0, 0.75, 1.0]),
+            Color('red').mix('blue', 0.5)
+        )
+
     def test_mix(self):
         """Test interpolation via mixing."""