Repository: facelessuser/coloraide (language: python)

Issue:
Limit default colors and plugins to just CSS colors?
Should we limit the default registered color spaces to just CSS colors? Or just include them all and let the user subclass and deregister what they don't want? This goes for delta E plugins etc.

Right now we just include them all and the user would need to deregister any spaces they explicitly do not want to include. I have no problem releasing 1.0 this way, but just want to think whether we may prefer the default to be a bit lighter or not.


Discussion / hints:
It is possible we could move non-essential color spaces to a new package.

Things like Luv, Din99o, Okhsl, Okhsv, could all be moved to something that allows people to install and cherry-pick the spaces they want to use. Out of the box, we could ship with all the CSS colors and then just a package with "other" colors. It would probably be a good place for something like HSLuv and HPLuv support (#63). I have no real use for those spaces, but some people may enjoy them. Maybe not worth shipping them by default, but they have their place for some. 
I've created an ["extra"](https://github.com/facelessuser/coloraide-extras) repository for additional color spaces. It currently implements some simple color spaces like:

- xyY
- UCS
- UVW
- naive CMYK
- Prismatic
 
Some are probably more fun to play with than others.

If we decide to only include just the CSS spaces out of the box, we could move the following to the "extras" repository:

- Din99o
- Lch99o
- Okhsl
- Okhsv
- Luv
- Lchuv
- HSLuv
- ICtCp
- Jzazbz
- Jzczhz
- maybe Lab-d65
- maybe Lch-d65

We could also just not register them by default. We could eventually merge in the "extras" repo into the main one and just have the spaces not registered, for now, I'm treating "extras" as a playground for new spaces. Once I clean it up, I plan to push it to PyPI.

I think we are ready to finally look into this. I think we will definitely move things like what is found below, along with any related ∆E methods. Spaces will be either moved to `coloraide_extras`, or we will include them here, but have them deregistered by default. That still needs to be decided.

- Jzazbz
- Jzczhz
- ICtCp
- rec2100pq
- Din99o
- Lch99o
- Luv
- Lchuv
- HSLuv

We might move:

- Okhsl
- Okhsv

We will keep:

- Lab D65
- Lch D65

I think we are going to keep all the spaces in coloraide and deregister the following:

- Jzazbz
- Jzczhz
- ICtCp
- rec2100pq
- Din99o
- Lch99o
- Luv
- Lchuv
- HSLuv
- Okhsl
- Okhsv

Then we are going to pull in all the stable color spaces from `coloraide-extras` and leave `coloraide-extras` as more of an experimental playground. We'll pull in:

- HSI
- IPT
- IgPgTg
- CMY
- CMYK
- xyY
- HunterLab
- Prismatic
- RLAB
- ORGB

`Color` will have mainly the CSS color spaces (plus D65 versions of Lab and D65). `ColorAll` will be derived from `Color` and have everything, though users can derive from `Color` and cherry pick whatever they want.



Fix patch:
diff --git a/README.md b/README.md
index 915e83ad0..e98d98889 100644
--- a/README.md
+++ b/README.md
@@ -9,15 +9,13 @@
 
 # ColorAide
 
-> **This is still a work in progress.**
->
-> While ColorAide is very usable and out of the alpha stage, it is currently in a prerelease > state. This simply means
-> the API could still be in flux to some degree. ColorAide is still technically waiting for > some issues related to CSS
-> color syntax and behavior to be decided by the CSS Working Group.
+> **Currently a Beta**
+> ColorAide is very close to a stable release and is currently in the Beta stage. While minor things could still change,
+> there are no plans for major API changes at this point.
 
 ## Overview
 
-ColorAide is a color library for Python with the intent of providing an easy to use interface to work with colors.
+ColorAide is a pure Python, object oriented approach to colors.
 
 ```python
 >>> from coloraide import Color
@@ -32,16 +30,22 @@ ColorAide is a color library for Python with the intent of providing an easy to
 'rgb(255 65.751 107.47)'
 ```
 
-ColorAide is a continually evolving project, but was created with a number of specific goals in mind:
+ColorAide particularly has a focus on the following:
+
+- Accurate colors.
+
+- Proper round tripping (where reasonable).
 
 - Be generally easy to pick up for the average user.
 
-- Support modern CSS color spaces and syntax as well as a number of popular non-CSS color spaces.
+- Support modern CSS color spaces and syntax.
 
-- Provide a plugin API to extend color spaces and more.
+- Make accessible many new and old non-CSS color spaces.
 
 - Provide a number of useful utilities such as interpolation, color distancing, blending, gamut mapping, etc.
 
+- Provide a plugin API to extend supported color spaces and more.
+
 - Allow users to configure defaults to their liking.
 
 With ColorAide, you can specify a color, convert it to other color spaces, mix it with other colors, output it in
diff --git a/coloraide/__init__.py b/coloraide/__init__.py
index 2c04e9fa8..782c3dd7c 100644
--- a/coloraide/__init__.py
+++ b/coloraide/__init__.py
@@ -1,7 +1,7 @@
 """ColorAide Library."""
 from .__meta__ import __version_info__, __version__  # noqa: F401
-from .color import Color, ColorMatch
+from .color import Color, ColorAll, ColorMatch
 from .interpolate import stop, hint
 from .algebra import NaN
 
-__all__ = ("Color", "ColorMatch", "NaN", "stop", "hint")
+__all__ = ("Color", "ColorAll", "ColorMatch", "NaN", "stop", "hint")
diff --git a/coloraide/__meta__.py b/coloraide/__meta__.py
index 903644226..dd213d5b3 100644
--- a/coloraide/__meta__.py
+++ b/coloraide/__meta__.py
@@ -192,5 +192,5 @@ def parse_version(ver: str) -> Version:
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(1, 0, 0, "alpha", 1)
+__version_info__ = Version(1, 0, 0, "beta", 1)
 __version__ = __version_info__._get_canonical()
diff --git a/coloraide/color.py b/coloraide/color.py
index f0fb3034f..f1bf224d7 100644
--- a/coloraide/color.py
+++ b/coloraide/color.py
@@ -46,6 +46,16 @@
 from .spaces.hsluv import HSLuv
 from .spaces.okhsl import Okhsl
 from .spaces.okhsv import Okhsv
+from .spaces.hsi import HSI
+from .spaces.ipt import IPT
+from .spaces.igpgtg import IgPgTg
+from .spaces.cmy import CMY
+from .spaces.cmyk import CMYK
+from .spaces.xyy import XyY
+from .spaces.hunter_lab import HunterLab
+from .spaces.prismatic import Prismatic
+from .spaces.rlab import RLAB
+from .spaces.orgb import ORGB
 from .distance import DeltaE
 from .distance.delta_e_76 import DE76
 from .distance.delta_e_94 import DE94
@@ -67,15 +77,22 @@
 from typing import overload, Union, Sequence, Dict, List, Optional, Any, cast, Callable, Set, Tuple, Type, Mapping
 
 SUPPORTED_DE = (
-    DE76, DE94, DECMC, DE2000, DEITP, DE99o, DEZ, DEHyAB, DEOK
+    DE76, DE94, DECMC, DE2000, DEHyAB, DEOK
+)
+
+EXTRA_DE = (
+    DEITP, DE99o, DEZ
 )
 
 SUPPORTED_SPACES = (
     XYZD65, XYZD50, SRGB, SRGBLinear, DisplayP3, DisplayP3Linear,
     Oklab, Oklch, Lab, Lch, LabD65, LchD65, HSV, HSL, HWB, Rec2020, Rec2020Linear,
-    A98RGB, A98RGBLinear, ProPhotoRGB, ProPhotoRGBLinear,
-    Rec2100PQ, Jzazbz, JzCzhz, ICtCp, Din99o, Lch99o,
-    Luv, Lchuv, Okhsl, Okhsv, HSLuv
+    A98RGB, A98RGBLinear, ProPhotoRGB, ProPhotoRGBLinear
+)
+
+EXTRA_SPACES = (
+    Rec2100PQ, Jzazbz, JzCzhz, ICtCp, Din99o, Lch99o, Luv, Lchuv, Okhsl, Okhsv, HSLuv,
+    HSI, IPT, IgPgTg, CMY, CMYK, XyY, HunterLab, Prismatic, RLAB, ORGB
 )
 
 SUPPORTED_FIT = (
@@ -891,3 +908,10 @@ def set(  # noqa: A003
 
 
 Color.register(SUPPORTED_SPACES + SUPPORTED_DE + SUPPORTED_FIT + SUPPORTED_CAT + SUPPORTED_FILTERS)
+
+
+class ColorAll(Color):
+    """Color derivative with all extra spaces."""
+
+
+ColorAll.register(EXTRA_DE + EXTRA_SPACES)
diff --git a/coloraide/interpolate/common.py b/coloraide/interpolate/common.py
index 7ab13ea12..478246e71 100644
--- a/coloraide/interpolate/common.py
+++ b/coloraide/interpolate/common.py
@@ -213,13 +213,13 @@ def color_steps(
 
         # If we currently have delta over our limit inject more stops.
         # If inserting between every color would push us over the max_steps, halt.
-        while m_delta > max_delta_e and (len(ret) * 2 - 1 <= max_steps):
+        total = len(ret)
+        while m_delta > max_delta_e and (total * 2 - 1 <= max_steps):
             # Inject stops while measuring again to see if it was sufficient
             m_delta = 0.0
             i = 1
-            offset = 0
-            for i in range(1, len(ret)):
-                index = i + offset
+            index = 1
+            while index < total:
                 prev = ret[index - 1]
                 cur = ret[index]
                 p = (cast(float, cur['p']) + cast(float, prev['p'])) / 2
@@ -230,6 +230,7 @@ def color_steps(
                     color.delta_e(cast('Color', cur['color']), method=delta_e)
                 )
                 ret.insert(index, {'p': p, 'color': color})
-                offset += 1
+                total += 1
+                index += 2
 
     return [cast('Color', i['color']) for i in ret]
diff --git a/coloraide/spaces/cmy.py b/coloraide/spaces/cmy.py
new file mode 100644
index 000000000..1776ee0c3
--- /dev/null
+++ b/coloraide/spaces/cmy.py
@@ -0,0 +1,49 @@
+"""Uncalibrated, naive CMY color space."""
+from ..spaces import Space
+from ..channels import Channel
+from ..cat import WHITES
+from ..types import Vector
+from typing import Tuple
+
+
+def srgb_to_cmy(rgb: Vector) -> Vector:
+    """Convert sRGB to CMY."""
+
+    return [1 - c for c in rgb]
+
+
+def cmy_to_srgb(cmy: Vector) -> Vector:
+    """Convert CMY to sRGB."""
+
+    return [1 - c for c in cmy]
+
+
+class CMY(Space):
+    """The CMY color class."""
+
+    BASE = "srgb"
+    NAME = "cmy"
+    SERIALIZE = ("--cmy",)  # type: Tuple[str, ...]
+    CHANNELS = (
+        Channel("c", 0.0, 1.0, bound=True),
+        Channel("m", 0.0, 1.0, bound=True),
+        Channel("y", 0.0, 1.0, bound=True)
+    )
+    CHANNEL_ALIASES = {
+        "cyan": 'c',
+        "magenta": 'm',
+        "yellow": 'y'
+    }
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To sRGB."""
+
+        return cmy_to_srgb(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From sRGB."""
+
+        return srgb_to_cmy(coords)
diff --git a/coloraide/spaces/cmyk.py b/coloraide/spaces/cmyk.py
new file mode 100644
index 000000000..38c43da0c
--- /dev/null
+++ b/coloraide/spaces/cmyk.py
@@ -0,0 +1,68 @@
+"""
+Uncalibrated, naive CMYK color space.
+
+https://www.w3.org/TR/css-color-5/#cmyk-rgb
+"""
+from ..spaces import Space
+from ..channels import Channel
+from ..cat import WHITES
+from ..types import Vector
+from typing import Tuple
+
+
+def srgb_to_cmyk(rgb: Vector) -> Vector:
+    """Convert sRGB to CMYK."""
+
+    k = 1.0 - max(rgb)
+    c = m = y = 0.0
+    if k != 1:
+        r, g, b = rgb
+        c = (1.0 - r - k) / (1.0 - k)
+        m = (1.0 - g - k) / (1.0 - k)
+        y = (1.0 - b - k) / (1.0 - k)
+
+    return [c, m, y, k]
+
+
+def cmyk_to_srgb(cmyk: Vector) -> Vector:
+    """Convert CMYK to sRGB."""
+
+    c, m, y, k = cmyk
+    return [
+        1.0 - min(1.0, c * (1.0 - k) + k),
+        1.0 - min(1.0, m * (1.0 - k) + k),
+        1.0 - min(1.0, y * (1.0 - k) + k)
+    ]
+
+
+class CMYK(Space):
+    """The CMYK color class."""
+
+    BASE = "srgb"
+    NAME = "cmyk"
+    SERIALIZE = ("--cmyk",)  # type: Tuple[str, ...]
+    CHANNELS = (
+        Channel("c", 0.0, 1.0, bound=True),
+        Channel("m", 0.0, 1.0, bound=True),
+        Channel("y", 0.0, 1.0, bound=True),
+        Channel("k", 0.0, 1.0, bound=True)
+    )
+    CHANNEL_ALIASES = {
+        "cyan": 'c',
+        "magenta": 'm',
+        "yellow": 'y',
+        "black": 'k'
+    }
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To sRGB."""
+
+        return cmyk_to_srgb(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From sRGB."""
+
+        return srgb_to_cmyk(coords)
diff --git a/coloraide/spaces/hsi.py b/coloraide/spaces/hsi.py
new file mode 100644
index 000000000..3bbfd022d
--- /dev/null
+++ b/coloraide/spaces/hsi.py
@@ -0,0 +1,119 @@
+"""
+HSI class.
+
+https://en.wikipedia.org/wiki/HSL_and_HSV#Saturation
+"""
+from ..spaces import Space, Cylindrical, WHITES
+from ..channels import Channel, FLG_ANGLE
+from .. import algebra as alg
+from .. import util
+from ..types import Vector
+
+
+def srgb_to_hsi(rgb: Vector) -> Vector:
+    """SRGB to HSI."""
+
+    r, g, b = rgb
+    h = alg.NaN
+    s = 0.0
+    mx = max(rgb)
+    mn = min(rgb)
+    i = sum(rgb) * 1 / 3
+    s = 0 if i == 0 else 1 - (mn / i)
+    c = mx - mn
+
+    if c != 0.0:
+        if mx == r:
+            h = (g - b) / c
+        elif mx == g:
+            h = (b - r) / c + 2.0
+        else:
+            h = (r - g) / c + 4.0
+        h *= 60.0
+
+    return [util.constrain_hue(h), s, i]
+
+
+def hsi_to_srgb(hsi: Vector) -> Vector:
+    """HSI to RGB."""
+
+    h, s, i = hsi
+    h = h % 360
+    h /= 60
+    z = 1 - abs(h % 2 - 1)
+    c = (3 * i * s) / (1 + z)
+    x = c * z
+
+    if alg.is_nan(h):  # pragma: no cover
+        # In our current setup, this will not occur. If colors are naturally achromatic,
+        # they will resolve to zeros automatically even without this check. This case
+        # would be a shortcut normally.
+        #
+        # Unnatural cases, such as explicitly setting of hue to undefined, could cause this,
+        # but the conversion pipeline converts all undefined values to zero. We'd have to
+        # encounter a natural case due to conversion to or from HSI mid pipeline to trigger
+        # this, and we are not currently in a position where that would occur with sRGB being
+        # the only pass-through.
+        rgb = [0.0] * 3
+    elif 0 <= h <= 1:
+        rgb = [c, x, 0]
+    elif 1 <= h <= 2:
+        rgb = [x, c, 0]
+    elif 2 <= h <= 3:
+        rgb = [0, c, x]
+    elif 3 <= h <= 4:
+        rgb = [0, x, c]
+    elif 4 <= h <= 5:
+        rgb = [x, 0, c]
+    else:
+        rgb = [c, 0, x]
+    m = i * (1 - s)
+
+    return [chan + m for chan in rgb]
+
+
+class HSI(Cylindrical, Space):
+    """HSI class."""
+
+    BASE = "srgb"
+    NAME = "hsi"
+    SERIALIZE = ("--hsi",)
+    CHANNELS = (
+        Channel("h", 0.0, 360.0, bound=True, flags=FLG_ANGLE),
+        Channel("s", 0.0, 1.0, bound=True),
+        Channel("i", 0.0, 1.0, bound=True)
+    )
+    CHANNEL_ALIASES = {
+        "hue": "h",
+        "saturation": "s",
+        "intensity": "i"
+    }
+    WHITE = WHITES['2deg']['D65']
+    GAMUT_CHECK = "srgb"
+
+    @classmethod
+    def normalize(cls, coords: Vector) -> Vector:
+        """On color update."""
+
+        h, s, i = alg.no_nans(coords[:-1])
+        h = h % 360
+        h /= 60
+        z = 1 - abs(h % 2 - 1)
+        c = (3 * i * s) / (1 + z)
+
+        if c == 0:
+            coords[0] = alg.NaN
+
+        return coords
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To sRGB from HSI."""
+
+        return hsi_to_srgb(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From sRGB to HSI."""
+
+        return srgb_to_hsi(coords)
diff --git a/coloraide/spaces/hunter_lab.py b/coloraide/spaces/hunter_lab.py
new file mode 100644
index 000000000..adc450c6a
--- /dev/null
+++ b/coloraide/spaces/hunter_lab.py
@@ -0,0 +1,74 @@
+"""
+Hunter Lab class.
+
+https://support.hunterlab.com/hc/en-us/articles/203997095-Hunter-Lab-Color-Scale-an08-96a2
+"""
+from coloraide.cat import WHITES
+from coloraide.spaces.lab import Lab
+from coloraide import algebra as alg
+from coloraide import util
+from coloraide.types import Vector, VectorLike
+from coloraide.channels import Channel, FLG_MIRROR_PERCENT
+
+# Values for the original Hunter Lab with illuminant C.
+# Used to calculate an appropriate `Ka` and `Kb` for whatever white point we are using.
+CXN = 98.04
+CYN = 100.0
+CZN = 118.11
+CKA = 175.0
+CKB = 70.0
+
+
+def xyz_to_hlab(xyz: Vector, white: VectorLike) -> Vector:
+    """Convert XYZ to Hunter Lab."""
+
+    xn, yn, zn = alg.multiply(util.xy_to_xyz(white), 100, dims=alg.D1_SC)
+    ka = CKA * alg.nth_root(xn / CXN, 2)
+    kb = CKB * alg.nth_root(zn / CZN, 2)
+    x, y, z = alg.multiply(xyz, 100, dims=alg.D1_SC)
+    l = alg.nth_root(y / yn, 2)
+    a = b = 0.0
+    if l != 0:
+        a = ka * (x / xn - y / yn) / l
+        b = kb * (y / yn - z / zn) / l
+    return [l * 100, a, b]
+
+
+def hlab_to_xyz(hlab: Vector, white: VectorLike) -> Vector:
+    """Convert Hunter Lab to XYZ."""
+
+    xn, yn, zn = alg.multiply(util.xy_to_xyz(white), 100, dims=alg.D1_SC)
+    ka = CKA * alg.nth_root(xn / CXN, 2)
+    kb = CKB * alg.nth_root(zn / CZN, 2)
+    l, a, b = hlab
+    l /= 100
+    y = (l ** 2) * yn
+    x = (((a * l) / ka) + (y / yn)) * xn
+    z = (((b * l) / kb) - (y / yn)) * -zn
+    return alg.divide([x, y, z], 100, dims=alg.D1_SC)
+
+
+class HunterLab(Lab):
+    """Hunter Lab class."""
+
+    BASE = 'xyz-d65'
+    NAME = "hunter-lab"
+    SERIALIZE = ("--hunter-lab",)
+    WHITE = WHITES['2deg']['D65']
+    CHANNELS = (
+        Channel("l", 0.0, 100.0),
+        Channel("a", -210.0, 210.0, flags=FLG_MIRROR_PERCENT),
+        Channel("b", -210.0, 210.0, flags=FLG_MIRROR_PERCENT)
+    )
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Hunter Lab."""
+
+        return hlab_to_xyz(coords, cls.white())
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Hunter Lab."""
+
+        return xyz_to_hlab(coords, cls.white())
diff --git a/coloraide/spaces/igpgtg.py b/coloraide/spaces/igpgtg.py
new file mode 100644
index 000000000..22ace159c
--- /dev/null
+++ b/coloraide/spaces/igpgtg.py
@@ -0,0 +1,85 @@
+"""
+The IgPgTg color space.
+
+https://www.ingentaconnect.com/content/ist/jpi/2020/00000003/00000002/art00002#
+"""
+from ..spaces import Space, Labish
+from ..channels import Channel, FLG_MIRROR_PERCENT
+from ..cat import WHITES
+from .. import algebra as alg
+from ..types import Vector
+from typing import Tuple
+
+XYZ_TO_LMS = [
+    [2.968, 2.741, -0.649],
+    [1.237, 5.969, -0.173],
+    [-0.318, 0.387, 2.311]
+]
+
+LMS_TO_XYZ = [
+    [0.4343486855574634, -0.20636237011428418, 0.10653033617352772],
+    [-0.08785463778363381, 0.20846346647992345, -0.009066845616854866],
+    [0.07447971736457795, -0.06330532030466152, 0.44889031421761344]
+]
+
+LMS_TO_IGPGTG = [
+    [0.117, 1.464, 0.13],
+    [8.285, -8.361, 21.4],
+    [-1.208, 2.412, -36.53]
+]
+
+IGPGTG_TO_LMS = [
+    [0.5818464618992484, 0.1233185479390782, 0.07431308420320765],
+    [0.6345481937914158, -0.009437923746683553, -0.003270744675229782],
+    [0.022656986516578225, -0.0047011518748263665, -0.030048158824914562]
+]
+
+
+def xyz_to_igpgtg(xyz: Vector) -> Vector:
+    """XYZ to IgPgTg."""
+
+    lms_in = alg.dot(XYZ_TO_LMS, xyz, dims=alg.D2_D1)
+    lms = [
+        alg.npow(lms_in[0] / 18.36, 0.427),
+        alg.npow(lms_in[1] / 21.46, 0.427),
+        alg.npow(lms_in[2] / 19435, 0.427)
+    ]
+    return alg.dot(LMS_TO_IGPGTG, lms, dims=alg.D2_D1)
+
+
+def igpgtg_to_xyz(itp: Vector) -> Vector:
+    """IgPgTg to XYZ."""
+
+    lms = alg.dot(IGPGTG_TO_LMS, itp, dims=alg.D2_D1)
+    lms_in = [
+        alg.nth_root(lms[0], 0.427) * 18.36,
+        alg.nth_root(lms[1], 0.427) * 21.46,
+        alg.nth_root(lms[2], 0.427) * 19435
+    ]
+    return alg.dot(LMS_TO_XYZ, lms_in, dims=alg.D2_D1)
+
+
+class IgPgTg(Labish, Space):
+    """The IgPgTg class."""
+
+    BASE = "xyz-d65"
+    NAME = "igpgtg"
+    SERIALIZE = ("--igpgtg",)  # type: Tuple[str, ...]
+    CHANNELS = (
+        Channel("ig", 0.0, 1.0),
+        Channel("pg", -1.0, 1.0, flags=FLG_MIRROR_PERCENT),
+        Channel("tg", -1.0, 1.0, flags=FLG_MIRROR_PERCENT)
+    )
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ."""
+
+        return igpgtg_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ."""
+
+        return xyz_to_igpgtg(coords)
diff --git a/coloraide/spaces/ipt.py b/coloraide/spaces/ipt.py
new file mode 100644
index 000000000..ad7379381
--- /dev/null
+++ b/coloraide/spaces/ipt.py
@@ -0,0 +1,93 @@
+"""
+The IPT color space.
+
+https://www.researchgate.net/publication/\
+221677980_Development_and_Testing_of_a_Color_Space_IPT_with_Improved_Hue_Uniformity.
+"""
+from ..spaces import Space, Labish
+from ..channels import Channel, FLG_MIRROR_PERCENT
+from ..cat import WHITES
+from .. import algebra as alg
+from ..types import Vector
+from typing import Tuple
+
+# The IPT algorithm requires the use of the Hunt-Pointer-Estevez matrix,
+# but it was originally calculated with the assumption of a slightly different
+# D65 white point than what we use.
+#
+# - Theirs: [0.9504, 1.0, 1.0889] -> xy chromaticity points (0.3127035830618893, 0.32902313032606195)
+# - Ours: [0.9504559270516716, 1, 1.0890577507598784] -> calculated from xy chromaticity points [0.31270, 0.32900]
+#
+# For a good conversion, our options were to either set the color space to a slightly different D65 white point,
+# or adjust the algorithm such that it accounted for the difference in white point. We chose the latter.
+#
+# ```
+# theirs = alg.diag([0.9504, 1.0, 1.0889])
+# ours = alg.diag(white_d65)
+# return alg.multi_dot([MHPE, theirs, alg.inv(ours)])
+# ```
+#
+# Below is the Hunter-Pointer-Estevez matrix combined with our white point compensation.
+XYZ_TO_LMS = [
+    [0.4001764512951712, 0.7075, -0.08068831054981859],
+    [-0.2279865839462744, 1.15, 0.061191135138152386],
+    [0.0, 0.0, 0.9182669691320122]
+]
+
+LMS_TO_XYZ = [
+    [1.8503518239760197, -1.1383686221417688, 0.23844898940542367],
+    [0.36683077517134854, 0.6438845448402356, -0.01067344358438],
+    [0.0, 0.0, 1.089007917757562]
+]
+
+LMS_P_TO_IPT = [
+    [0.4, 0.4, 0.2],
+    [4.455, -4.851, 0.396],
+    [0.8056, 0.3572, -1.1628]
+]
+
+IPT_TO_LMS_P = [
+    [1.0000000000000004, 0.0975689305146139, 0.2052264331645916],
+    [0.9999999999999997, -0.1138764854731471, 0.13321715836999803],
+    [1.0, 0.0326151099170664, -0.6768871830691793]
+]
+
+
+def xyz_to_ipt(xyz: Vector) -> Vector:
+    """XYZ to IPT."""
+
+    lms_p = [alg.npow(c, 0.43) for c in alg.dot(XYZ_TO_LMS, xyz, dims=alg.D2_D1)]
+    return alg.dot(LMS_P_TO_IPT, lms_p, dims=alg.D2_D1)
+
+
+def ipt_to_xyz(ipt: Vector) -> Vector:
+    """IPT to XYZ."""
+
+    lms = [alg.nth_root(c, 0.43) for c in alg.dot(IPT_TO_LMS_P, ipt, dims=alg.D2_D1)]
+    return alg.dot(LMS_TO_XYZ, lms, dims=alg.D2_D1)
+
+
+class IPT(Labish, Space):
+    """The IPT class."""
+
+    BASE = "xyz-d65"
+    NAME = "ipt"
+    SERIALIZE = ("--ipt",)  # type: Tuple[str, ...]
+    CHANNELS = (
+        Channel("i", 0.0, 1.0, bound=True),
+        Channel("p", -1.0, 1.0, bound=True, flags=FLG_MIRROR_PERCENT),
+        Channel("t", -1.0, 1.0, bound=True, flags=FLG_MIRROR_PERCENT)
+    )
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ."""
+
+        return ipt_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ."""
+
+        return xyz_to_ipt(coords)
diff --git a/coloraide/spaces/orgb.py b/coloraide/spaces/orgb.py
new file mode 100644
index 000000000..6ca672fa0
--- /dev/null
+++ b/coloraide/spaces/orgb.py
@@ -0,0 +1,87 @@
+"""
+ORGB color space.
+
+https://graphics.stanford.edu/~boulos/papers/orgb_sig.pdf
+"""
+import math
+from .. import algebra as alg
+from ..spaces import Space, Labish
+from ..types import Vector
+from ..cat import WHITES
+from ..channels import Channel, FLG_MIRROR_PERCENT
+
+RGB_TO_LC1C2 = [
+    [0.2990, 0.5870, 0.1140],
+    [0.5000, 0.5000, -1.0000],
+    [0.8660, -0.8660, 0.0000]
+]
+
+LC1C2_TO_RGB = alg.inv(RGB_TO_LC1C2)
+
+
+def rotate(v: Vector, d: float) -> Vector:
+    """Rotate the vector."""
+
+    m = alg.identity(3)
+    m[1][1:] = math.cos(d), -math.sin(d)
+    m[2][1:] = math.sin(d), math.cos(d)
+    return alg.dot(m, v, dims=alg.D2_D1)
+
+
+def srgb_to_orgb(rgb: Vector) -> Vector:
+    """SRGB to ORGB."""
+
+    lcc = alg.dot(RGB_TO_LC1C2, rgb, dims=alg.D2_D1)
+    theta = math.atan2(lcc[2], lcc[1])
+    theta0 = theta
+    atheta = abs(theta)
+    if atheta < (math.pi / 3):
+        theta0 = (3 / 2) * theta
+    elif (math.pi / 3) <= atheta <= math.pi:
+        theta0 = math.copysign((math.pi / 2) + (3 / 4) * (atheta - math.pi / 3), theta)
+
+    return rotate(lcc, theta0 - theta)
+
+
+def orgb_to_srgb(lcc: Vector) -> Vector:
+    """ORGB to sRGB."""
+
+    theta0 = math.atan2(lcc[2], lcc[1])
+    theta = theta0
+    atheta0 = abs(theta0)
+    if atheta0 < (math.pi / 2):
+        theta = (2 / 3) * theta0
+    elif (math.pi / 2) <= atheta0 <= math.pi:
+        theta = math.copysign((math.pi / 3) + (4 / 3) * (atheta0 - math.pi / 2), theta0)
+
+    return alg.dot(LC1C2_TO_RGB, rotate(lcc, theta - theta0))
+
+
+class ORGB(Labish, Space):
+    """ORGB color class."""
+
+    BASE = 'srgb'
+    NAME = "orgb"
+    SERIALIZE = ("--orgb",)
+    WHITE = WHITES['2deg']['D65']
+    EXTENDED_RANGE = True
+    CHANNELS = (
+        Channel("l", 0.0, 1.0, bound=True),
+        Channel("cyb", -1.0, 1.0, bound=True, flags=FLG_MIRROR_PERCENT),
+        Channel("crg", -1.0, 1.0, bound=True, flags=FLG_MIRROR_PERCENT)
+    )
+    CHANNEL_ALIASES = {
+        "luma": "l"
+    }
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To base from oRGB."""
+
+        return orgb_to_srgb(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From base to oRGB."""
+
+        return srgb_to_orgb(coords)
diff --git a/coloraide/spaces/prismatic.py b/coloraide/spaces/prismatic.py
new file mode 100644
index 000000000..6b8703cc5
--- /dev/null
+++ b/coloraide/spaces/prismatic.py
@@ -0,0 +1,64 @@
+"""
+Prismatic color space.
+
+Creates a Maxwell color triangle with a lightness component.
+
+http://psgraphics.blogspot.com/2015/10/prismatic-color-model.html
+https://studylib.net/doc/14656976/the-prismatic-color-space-for-rgb-computations
+"""
+from ..spaces import Space
+from ..channels import Channel
+from ..cat import WHITES
+from ..types import Vector
+from typing import Tuple
+
+
+def srgb_to_lrgb(rgb: Vector) -> Vector:
+    """Convert sRGB to Prismatic."""
+
+    l = max(rgb)
+    s = sum(rgb)
+    return [l] + ([(c / s) for c in rgb] if s != 0 else [0, 0, 0])
+
+
+def lrgb_to_srgb(lrgb: Vector) -> Vector:
+    """Convert Prismatic to sRGB."""
+
+    rgb = lrgb[1:]
+    l = lrgb[0]
+    mx = max(rgb)
+    return [(l * c) / mx for c in rgb] if mx != 0 else [0, 0, 0]
+
+
+class Prismatic(Space):
+    """The Prismatic color class."""
+
+    BASE = "srgb"
+    NAME = "prismatic"
+    SERIALIZE = ("--prismatic",)  # type: Tuple[str, ...]
+    EXTENDED_RANGE = False
+    CHANNELS = (
+        Channel("l", 0.0, 1.0, bound=True),
+        Channel("r", 0.0, 1.0, bound=True),
+        Channel("g", 0.0, 1.0, bound=True),
+        Channel("b", 0.0, 1.0, bound=True)
+    )
+    CHANNEL_ALIASES = {
+        "lightness": 'l',
+        "red": 'r',
+        "green": 'g',
+        "blue": 'b'
+    }
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To sRGB."""
+
+        return lrgb_to_srgb(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From sRGB."""
+
+        return srgb_to_lrgb(coords)
diff --git a/coloraide/spaces/rlab.py b/coloraide/spaces/rlab.py
new file mode 100644
index 000000000..cad7e5d2d
--- /dev/null
+++ b/coloraide/spaces/rlab.py
@@ -0,0 +1,72 @@
+"""
+RLAB.
+
+https://scholarworks.rit.edu/cgi/viewcontent.cgi?article=1153&context=article
+https://www.imaging.org/site/PDFS/Papers/1997/RP-0-67/2368.pdf
+"""
+from ..cat import WHITES
+from ..spaces.lab import Lab
+from .. import algebra as alg
+from ..types import Vector
+from ..channels import Channel, FLG_MIRROR_PERCENT
+
+XYZ_TO_XYZ_REF = [
+    [1.0521266389510715, 2.220446049250313e-16, 0.0],
+    [0.0, 1.0, 2.414043899674756e-19],
+    [0.0, 0.0, 0.9182249511582473]
+]
+
+XYZ_REF_TO_XYZ = [
+    [0.9504559270516716, -2.110436108208428e-16, 5.548406636355788e-35],
+    [0.0, 1.0, -2.629033219615395e-19],
+    [0.0, 0.0, 1.0890577507598784]
+]
+
+EXP = 2.3
+
+
+def rlab_to_xyz(rlab: Vector) -> Vector:
+    """RLAB to XYZ."""
+
+    l, a, b = rlab
+    yr = l / 100
+    xr = alg.npow((a / 430) + yr, EXP)
+    zr = alg.npow(yr - (b / 170), EXP)
+    return alg.dot(XYZ_REF_TO_XYZ, [xr, alg.npow(yr, EXP), zr], dims=alg.D2_D1)
+
+
+def xyz_to_rlab(xyz: Vector) -> Vector:
+    """XYZ to RLAB."""
+
+    xyz_ref = alg.dot(XYZ_TO_XYZ_REF, xyz, dims=alg.D2_D1)
+    xr, yr, zr = [alg.nth_root(c, EXP) for c in xyz_ref]
+    l = 100 * yr
+    a = 430 * (xr - yr)
+    b = 170 * (yr - zr)
+    return [l, a, b]
+
+
+class RLAB(Lab):
+    """RLAB class."""
+
+    BASE = 'xyz-d65'
+    NAME = "rlab"
+    SERIALIZE = ("--rlab",)
+    WHITE = WHITES['2deg']['D65']
+    CHANNELS = (
+        Channel("l", 0.0, 100.0),
+        Channel("a", -125.0, 125.0, flags=FLG_MIRROR_PERCENT),
+        Channel("b", -125.0, 125.0, flags=FLG_MIRROR_PERCENT)
+    )
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Hunter Lab."""
+
+        return rlab_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Hunter Lab."""
+
+        return xyz_to_rlab(coords)
diff --git a/coloraide/spaces/xyy.py b/coloraide/spaces/xyy.py
new file mode 100644
index 000000000..da8ab95d9
--- /dev/null
+++ b/coloraide/spaces/xyy.py
@@ -0,0 +1,37 @@
+"""
+The xyY color space.
+
+https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
+"""
+from ..spaces import Space
+from ..channels import Channel
+from ..cat import WHITES
+from .. import util
+from ..types import Vector
+from typing import Tuple
+
+
+class XyY(Space):
+    """The xyY class."""
+
+    BASE = "xyz-d65"
+    NAME = "xyy"
+    SERIALIZE = ("--xyy",)  # type: Tuple[str, ...]
+    CHANNELS = (
+        Channel("x", 0.0, 1.0),
+        Channel("y", 0.0, 1.0),
+        Channel("Y", 0.0, 1.0)
+    )
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ."""
+
+        return util.xy_to_xyz(coords[0:2], coords[2])
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ."""
+
+        return util.xyz_to_xyY(coords, cls.white())
diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt
index 8d4188478..83b6d24d0 100644
--- a/docs/src/dictionary/en-custom.txt
+++ b/docs/src/dictionary/en-custom.txt
@@ -5,6 +5,7 @@ Achromatopsia
 Alvy
 Az
 BT
+Berns
 Bezier
 Björn
 Boronine
@@ -19,10 +20,12 @@ CIELAB's
 CIELCH
 CIELCH's
 CIELUV
+CIEXYZ
 CMC
 CMCCAT
 CMF
 CMFs
+CMY
 CMYK
 CSS
 CVD
@@ -56,8 +59,9 @@ Florian
 Formatter
 HDR
 HLG
+HPE
 HSB
-HSL
+HSI
 HSL
 HSLuv
 HSV
@@ -69,6 +73,7 @@ ICtCp
 IPT
 ITP
 ITU
+IgPgTg
 Interpolator
 Iz
 Izazbz
@@ -117,6 +122,7 @@ Protanopia
 PyPI
 Pyodide
 RGB
+RLAB
 ROMM
 RYB
 SDR
@@ -132,19 +138,23 @@ Tritanopia
 Twemoji
 UCS
 UHDTV
+Uncalibrated
 Verou
 Viénot
 Von
 WCAG
 WCG
+XD
 XYZ
 YCbCr
+ZD
 accessor
 accessors
 achromatomaly
 achromatopsia
 async
 az
+barycentric
 bezier
 boolean
 broadcasted
@@ -152,6 +162,7 @@ bz
 cd
 chroma
 chromaticity
+colorimetrically
 colorimetry
 config
 coroutine
@@ -183,6 +194,8 @@ luminance
 macOS
 memoization
 monochromacy
+oRGB
+oRGB's
 opRGB
 opto
 performant
diff --git a/docs/src/markdown/.snippets/abbr.md b/docs/src/markdown/.snippets/abbr.md
index 0f3a7e1d9..d0e43c9be 100644
--- a/docs/src/markdown/.snippets/abbr.md
+++ b/docs/src/markdown/.snippets/abbr.md
@@ -9,3 +9,4 @@
 *[CVDs]: color vision deficiency
 *[HDR]: High Dynamic Range
 *[SDR]: Standard Dynamic Range
+*[HPE]: Hunt-Pointer-Estevez
diff --git a/docs/src/markdown/.snippets/links.md b/docs/src/markdown/.snippets/links.md
index fe2de5205..6d17ba26b 100644
--- a/docs/src/markdown/.snippets/links.md
+++ b/docs/src/markdown/.snippets/links.md
@@ -1,6 +1,7 @@
 [brettel]: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.496.7153&rep=rep1&type=pdf
 [colorio]: https://github.com/nschloe/colorio
 [colorjs]: https://github.com/LeaVerou/color.js
+[color-math]: https://github.com/gtaylor/python-colormath
 [colour-science]: https://github.com/colour-science/colour
 [compositing-level-1]: https://www.w3.org/TR/compositing-1/
 [css-spec-convert]: https://drafts.csswg.org/css-color/#color-conversion-code
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index afc8f08b5..a6cab7b00 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,6 +1,17 @@
 # Changelog
 
-## 1.0a1
+## 1.0b1
+
+!!! warning "Breaking Changes"
+    1.0b1 introduces a number of breaking changes. As we are very close to releasing the first stable release, we've
+    taken opportunity to address any issues related to speed and usability. While this is unfortunate for early
+    adopters, we feel that in the long run that these changes will make ColorAide a better library. We've also added new
+    a new Bezier interpolation method and added many more color spaces!
+
+- **BREAK**: The `coloraide.Color` object now only registers a subset of the available color spaces and ∆E algorithms in
+  order to create a lighter default color object. `coloraide.ColorAll` has been provided for a quick way to get access
+  to all available color spaces and plugins. Generally, it is recommend to subclass `Color` and register just what is
+  desired.
 
 - **BREAK**: Reworked interpolation:
 
@@ -47,30 +58,38 @@
 - **BREAK**: Remove deprecated dynamic properties which helps to increase speed by removing overhead on class property
   access.
 
+- **BREAK**: Remove deprecated dynamic properties which helps to increase speed by removing overhead on class property
+  access. Use indexing instead: `color['red']` or `color[0]`.
+
+- **BREAK**: Remove deprecated `coords()` method. Use indexing and slices instead: `color[:-1]`.
+
 - **NEW**: Update `lch()`, `lab()`, `oklch()`, and `oklab()` to optionally support percentages for lightness, chroma, a,
   and b. Lightness is no longer enforced to be a percentage in the CSS syntax and these spaces will serialize as a
   number by default instead. Optionally, these forms can force a percentage output via the `to_string` method when using
   the `percentage` option. Percent ranges roughly correspond with the Display P3 gamut per the CSS specification.
 
-  Additionally, CSS color spaces using the `color()` format as an input will translate using these same ranges if the
-  channels are percentages. `hue` will also be respected and treated as 0 - 360 when using a percentage.
+    Additionally, CSS color spaces using the `color()` format as an input will translate using these same ranges if the
+    channels are percentages. `hue` will also be respected and treated as 0 - 360 when using a percentage.
 
-  Non-CSS color spaces will also respect their defined ranges when using percentages in the `color()` form.
+    Non-CSS color spaces will also respect their defined ranges when using percentages in the `color()` form.
 
-- **BREAK**: Remove deprecated dynamic properties which helps to increase speed by removing overhead on class property
-  access. Use indexing instead: `color['red']` or `color[0]`.
-- **BREAK**: Remove deprecated `coords()` method. Use indexing and slices instead: `color[:-1]`.
 - **NEW**: Add `silent` option to `deregister` so that if a proper category is specified, and the plugin does not exit,
   the operation will not throw an error.
+
 - **NEW**: Add new color spaces: `display-p3-linear`, `a98-rgb-linear`, `rec2020-linear`, `prophoto-rgb-linear`, and
-  `rec2100pq`.
+  `rec2100pq`, `hsi`, `rlab`, `hunter-lab`, `xyy`, `prismatic`, `orgb`, `cmy`, `cmyk`, `ipt`, and `igpgtg`.
+
 - **NEW**: Monochromatic color harmony must also be performed in a cylindrical color space to make achromatic detection
   easier. This means all color harmonies now must be performed under a cylindrical color space.
+
 - **NEW**: Use Lab D65 for ∆E 2000, ∆E 76, ∆E HyAB, Euclidean distance, and Lch D65 for Lch Chroma gamut mapping. Lab
   D65 is far more commonly used for the aforementioned ∆E methods. Lch Chroma gamut mapping, which uses ∆E 2000 needs to
   use the same D65 white point to avoid wasting conversion time.
+
 - **FIX**: Better handling of monochromatic harmonies that are near white or black.
 
+- **FIX**: Small fix to `steps` ∆E logic.
+
 ## 0.18.1
 
 - **FIX**: Fix issue where when generating steps with a `max_delta_e`, the ∆E was reduced too much causing additional,
diff --git a/docs/src/markdown/api/index.md b/docs/src/markdown/api/index.md
index 790de97ea..0261a57e2 100644
--- a/docs/src/markdown/api/index.md
+++ b/docs/src/markdown/api/index.md
@@ -84,9 +84,10 @@ class Color:
 
 Description
 : 
-    The `Color` class object is a wrapper around the internal color space objects. It provides an API interface to allow
-    users to specify and manipulate colors. Color objects can be iterated, which will return each color channel. They
-    are also indexable by channel name or by channel index.
+    The `Color` class object is a wrapper around the internal color space objects. `Color` is the base Color object
+    and only registers a select number of color spaces. It provides an API interface to allow users to specify and
+    manipulate colors. Color objects can be iterated, which will return each color channel. They are also indexable by
+    channel name or by channel index.
 
 Import path
 : 
@@ -105,6 +106,43 @@ Parameters
     `alpha`    | `#!py3 1`    | `alpha` accepts a number specifying the `alpha` channel. Must be used in conjunction with `data` or it will be ignored.
     `filters`  | `#!py3 None` | `filters` accepts a list of color spaces to allow. When `#!py3 None` is provided (the default) all supported color spaces are accepted.
 
+## `coloraide.ColorAll` {#colorall}
+
+```py3
+class ColorAll(Color):
+    def __init__(
+        self,
+        color,
+        data=None,
+        alpha=util.DEF_ALPHA,
+        *,
+        filters=None, **kwargs
+    ):
+```
+
+Description
+: 
+    The `ColorAll` class object is derived from [`Color`](#color) and extends the registered color spaces to include all
+    offered by ColorAide.
+
+Import path
+: 
+    `ColorAll` is imported from the `coloraide` library:
+
+    ```py3
+    from coloraide import ColorAll
+    ```
+
+Parameters
+: 
+    Parameters | Defaults     | Description
+    ---------- | -------------| -----------
+    `color`    |              | A color string, a dictionary describing the color, or another `ColorAll` class object. If given `data`, a string must be used and should represent the color space to use.
+    `data`     | `#!py3 None` | `data` accepts a list of numbers representing the coordinates of the color. If provided, `color` must be a string specifying the color space.
+    `alpha`    | `#!py3 1`    | `alpha` accepts a number specifying the `alpha` channel. Must be used in conjunction with `data` or it will be ignored.
+    `filters`  | `#!py3 None` | `filters` accepts a list of color spaces to allow. When `#!py3 None` is provided (the default) all supported color spaces are accepted.
+
+
 ## `Color.register` {#register}
 
 ```py
diff --git a/docs/src/markdown/color.md b/docs/src/markdown/color.md
index 5d36a2412..92aac97d8 100644
--- a/docs/src/markdown/color.md
+++ b/docs/src/markdown/color.md
@@ -4,7 +4,7 @@ The `Color` object is where all the magic of ColorAide happens. In order to mani
 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.
 
-## Creating Colors
+## Importing
 
 The `Color` object contains all the logic to create and manipulate colors. It can be imported from `coloraide`.
 
@@ -12,8 +12,26 @@ The `Color` object contains all the logic to create and manipulate colors. It ca
 from coloraide import Color
 ```
 
-Afterwards, colors can be created using various, valid CSS syntax. Syntax includes legacy formats as defined in CSS
-Level 3, but also contains CSS Level 4!
+By default, the `Color` object contains only a subset of the available color spaces and related distancing algorithms in
+order to keep the 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`.
+
+```py3
+from coloraide import ColorAll as Color
+```
+
+!!! tip "Custom Color Objects"
+    To add more plugins or tweak color defaults, see [Custom Color Classes](#custom-color-classes) for more.
+
+## 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.
+
+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!
 
 ```playground
 Color("red")
@@ -23,15 +41,15 @@ Color("rgb(0 0 255 / 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, there may be changes.
+    syntax as browsers begin to implement the spec.
 
 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.
 
-Essentially, we've adopted the `#!css-color color()` function as the universal way in which to serialize colors. If the
-CSS spec does not formally recognize a color in this form, the color identifier will use the two dashes as a prefix
+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.
 
@@ -47,8 +65,8 @@ Color("srgb", [0.5, 0, 1], 0.3)
 ```
 
 Colors can also be exported to and receive input from simple dictionaries. These can be useful when serializing to JSON
-or various other reasons. The `space` key and all relevant color channels must be specified when constructing a color
-object from a dictionary, `alpha` is the only optional channel and will be assumed as `1` if omitted.
+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.
 
 ```playground
 d = Color('red').to_dict()
@@ -73,9 +91,9 @@ color1
 color1.new("blue")
 ```
 
-If desired, all creation methods can be have a color space filter list passed in. The filter list will prevent an input
-which specifies a color space found in our list to not be accepted. Use a filter will constrain inputs to supported
-color spaces not found in the list.
+If desired, all creation methods can have a color space filter list passed in. The filter list will prevent an input
+which specifies a color space found in our list to not be accepted. Using a filter will constrain inputs to only the
+color spaces in the list.
 
 ```playground
 try:
@@ -89,7 +107,7 @@ Color("hsl(130 30% 75%)", filters=["hsl"])
 
 The `clone` method is an easy way to duplicate the current color object.
 
-Here we clone the `#!color green` object, giving us two.
+Here we clone the `#!color green` color object, giving us two.
 
 ```playground
 c1 = Color("green")
@@ -181,7 +199,7 @@ Color('yellow').convert("lab")
         What is more a problem is not that chroma doesn't resolve to `#!py3 0`, but that you can get nonsensical hues as
         chroma gets very close to zero. Because of this, we simply set hue to undefined when chroma is deemed very close
         for the color model. But this small change can affect the outcome slightly when doing a round trip back to the
-        original color, though, not usually enough to impact the value in any significantly meaningful way.
+        original color, though, not usually enough to impact the value in any significant, meaningful way.
 
         The definition of very close to `#!py3 0` can be different from color space to color space, so the impact of
         such a change can be more significant for certain color spaces.
@@ -286,16 +304,26 @@ for m in RE_COLOR_START.finditer(text):
 
 ## Custom Color Classes
 
-In general, it is always recommended to subclass the [`Color`](#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 [`Color`](#color) is subclassed, it is safe to then update global overrides or register and deregister
-plugins without the worry of affecting the base class.
+The `Color` object was created to be extensible and has implemented various functionalities as plugins. Things like
+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
+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.
+
+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
+`Color` is subclassed, it is safe to then update global overrides or register and deregister plugins without the worry
+of affecting the base class.
 
 ### Override Default Settings
 
 ColorAide has a number of preferences that can be altered in the `Color` class. Most of these options can be configured
-on demand when calling into a function that uses theme, but it may be useful to setup a new defaults so that the `Color`
-object behaves how you prefer.
+on demand when calling into a related function that uses them, but it may be useful to set them up one time on a new
+`Color` object.
 
 ```playground
 class Color2(Color):
@@ -307,7 +335,7 @@ Color2('rgb(128.12345 0 128.12345)').to_string()
 
 Properties             | Defaults            | Description
 ---------------------- | ------------------- | -----------
-`FIT`                  | `#!py "lch-chroma"` | The default gamut mapping method used by the [`Color`](#color) object.
+`FIT`                  | `#!py "lch-chroma"` | The default gamut mapping method used by the `Color` object.
 `INTERPOLATE`          | `#!py "oklab"`      | The default color space used for interpolation.
 `DELTA_E`              | `#!py "76"`         | The default ∆E algorithm used. This applies to when [`delta_e()`](./distance.md#delta-e) is called without specifying a method or when using color distancing to separate color when using the interpolation method called [`steps`](./interpolation.md#steps).
 `PRECISION`            | `#!py 5`            | The default precision for string outputs.
@@ -316,22 +344,12 @@ Properties             | Defaults            | Description
 
 ### Plugins
 
-Currently, only color spaces, delta E methods, chromatic adaptation, filters, and gamut mapping methods are exposed as
-plugins.
-
-Some potential, useful cases: register new color spaces that are not supported directly in ColorAide, test out a new ∆E
-algorithm, experiment with a potentially better gamut mapping algorithm, tweak an existing color space to accept and
-output color strings in a new format, create a variant of a color space that uses a different white point than is
-currently supported, etc.
+Currently, color spaces, delta E methods, chromatic adaptation, filters, 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.
 
-If you wanted a more lightweight [`Color`](#color) object, you could deregister color spaces you don't need. Though,
-keep in mind that some color spaces are essential, like XYZ D65 which is used in many color conversions. Removing some
-colors could also break functionality of certain features that are reliant on a specific color space, such as CIELAB
-which is used for ∆E^\*^~2000~ color distancing or CIELCH which is used in the the LCH Chroma gamut mapping, but both
-could also be deregistered to avoid issues.
-
-While we will not go into 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).
+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).
 
 Registration is performed by the `register` method. It can take a single plugin or a list of plugins. Based on the
 plugin's type, The Color object will determine how to properly register the plugin. If the plugin attempts to overwrite
@@ -339,35 +357,40 @@ 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.
 
-Here is an example creating a custom XYZ color space using the D55 white point.
+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.cat import WHITES
-from coloraide.spaces.xyz_d65 import XYZD65
+from coloraide import Color
+from coloraide.spaces.xyy import XyY
 
-class XYZD55(XYZD65):
-    BASE = "xyz-d65"
-    NAME = "xyz-d55"
-    SERIALIZE = ("xyz-d55",)
-    WHITE = WHITES['2deg']['D55']
+try:
+    Color('red').convert('xyy')
+except:
+    print('Nope')
 
 class Custom(Color): ...
-Custom.register(XYZD55)
-Custom('red').convert('xyz-d55')
+
+Custom.register(XyY)
+
+Custom('red').convert('xyy')
 ```
 
 If a deregistration was 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`. 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.
+deregister: `category:name`.
+
+Valid categories are `space`, `delta-e`, `cat`, `filter`, 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.
 
 ```playground
 class Custom(Color): ...
-Custom.deregister('space:jzazbz')
+Custom.deregister('space:lab-d65')
 try:
-    Custom('red').convert('jzazbz')
+    Custom('red').convert('lab-d65')
 except ValueError:
-    print('Could not convert to Jzazbz as it is no longer registered')
+    print('Could not convert to Lab D65 as it is no longer registered')
 ```
 
 Use of `*` with `deregister` will remove all plugins. Use of `category:*` will remove all plugins of that category.
diff --git a/docs/src/markdown/colors/index.md b/docs/src/markdown/colors/index.md
index 9df6825c3..fdb453160 100644
--- a/docs/src/markdown/colors/index.md
+++ b/docs/src/markdown/colors/index.md
@@ -1,22 +1,117 @@
 # Supported Colors
 
 ColorAide aims to support all the color spaces and models currently offered in modern CSS, such as sRGB, Display P3,
-CIELAB, Oklab, etc. We also try to include a number of color spaces that are not available in CSS, but are generally
-popular.
-
-It is doubtful that we'll ever include all available color spaces that exist, but ColorAide is extensible and allows for
-new color spaces and models to be added by anyone, even 3rd parties.
-
-Currently, we also offer a separate package called [ColorAide Extras][extras] that provides some more color spaces:
-Hunter Lab, xyY, IPT, etc. We will most likely keep less common and experimental spaces/models over there and be very
-selective as to what we bring over here.
-
-!!! warning "Possible Relocation of Some Color Spaces for 1.0.0"
-    It is possible that before the 1.0.0 release that we may move some color spaces from ColorAide over to
-    [ColorAide Extras][extras]. Any color spaces and associated ∆E methods not considered part of the CSS spec could
-    could be considered for relocation. HSLuv, Jzazbz, DIN99o, CIELUV, and ICtCp are all examples of spaces that could
-    be moved to ColorAide Extras in the future. This would provide a leaner package with the option for users to install
-    the additional package and cherry pick the spaces of interest.
+CIELAB, Oklab, etc. We also include a number of color spaces that are not available in CSS.
+
+While ColorAide supports a lot of color spaces, it is rare that a user would ever need every color space implemented by
+ColorAide available at all times, so to keep the Color object lighter, and color matching logic quicker, the
+`coloraide.Color` object does not register all color spaces by default.
+
+The following color spaces are registered by default:
+
+&nbsp;                                | &nbsp;                                      | Default Color\ Spaces       | &nbsp;                                   | &nbsp;
+------------------------------------- | ------------------------------------------- | --------------------------- | ---------------------------------------- | -----
+[XYZ\ D65](#xyz-d65)                  | [XYZ\ D50](#xyz-d50)                        | [Linear sRGB](#linear-srgb) | [Linear Display\ P3](#linear-display-p3) | [Linear A98\ RGB](#linear-a98-rgb)
+[Linear Rec.\ 2020](#linear-rec-2020) | [Linear ProPhoto\ RGB](#linear-prohoto-rgb) | [sRGB](#srgb)               | [Display\ P3](#display-p3)               | [A98\ RGB](#a98-rgb)
+[Rec.\ 2020](#rec-2020)               | [ProPhoto\ RGB](#prophoto-rgb)              | [HSL](#hsl)                 | [HSV](#hsv)                              | [HWB](#hwb)
+[Lab](#cielab-d50)                    | [Lch](#cielch-d50)                          | [Lab\ D65](#cielab-d65)     | [Lch\ D65](#cielch-d65)                  | [Oklab](#oklab)
+[Oklch](#oklch)                       |                                             |                             |                                          |
+
+Normally, it is suggested that a user cherry pick any additional color spaces they need by subclassing the
+`coloraide.Color` object and registering any additional color spaces that are needed (and any other plugins that are
+desired). With that said, ColorAide does also provide `coloraide.ColorAll` which includes every implemented color space,
+∆E method, and other relevant plugins. In our live examples, it is the default color object we use to demonstrate
+features. Below we've provided a diagram of all the color spaces and how they translate to one another.
+
+```diagram
+flowchart TB;
+
+    oklch --- oklab ----- xyz-d65
+        okhsl --- oklab
+        okhsv --- oklab
+
+    display-p3 --- display-p3-linear --- xyz-d65;
+
+    a98-rgb --- a98-rgb-linear --- xyz-d65;
+
+    hwb --- hsv --- hsl --- srgb --- srgb-linear ----- xyz-d65;
+        orgb --- srgb;
+        prismatic --- srgb;
+        hsi --- srgb;
+        cmy --- srgb;
+        cmyk --- srgb;
+
+    rec2020 --- rec2020-linear --- xyz-d65;
+        rec2100pq --- rec2020-linear;
+
+    prophoto-rgb --- prophoto-rgb-linear --- xyz-d50 ----- xyz-d65;
+        lch --- lab --- xyz-d50;
+
+    xyz-d65 --- lab-d65 --- lch-d65;
+
+    xyz-d65 --- jzazbz --- jzczhz;
+
+    xyz-d65 --- ipt;
+
+    xyz-d65 --- ictcp;
+
+    xyz-d65 --- igpgtg
+
+    xyz-d65 --- din99o --- lch99o;
+
+    xyz-d65 --- hunter-lab;
+
+    xyz-d65 --- rlab;
+
+    xyz-d65 --- luv --- lchuv;
+
+        luv --- hsluv;
+
+    xyz-d65 --- xyy
+
+    xyz-d65(XYZ D65);
+    xyz-d50(XYZ D50);
+    rec2020(Rec. 2020);
+    rec2020-linear(Linear Rec. 2020);
+    rec2100pq(Rec. 2100 PQ);
+    srgb-linear(Linear sRGB);
+    srgb(sRGB);
+    hsl(HSL);
+    hsv(HSV);
+    hwb(HWB);
+    display-p3-linear(Linear Display P3);
+    display-p3(Display P3);
+    a98-rgb-linear(Linear A98 RGB);
+    a98-rgb(A98 RGB);
+    prophoto-rgb-linear(Linear ProPhoto RGB);
+    prophoto-rgb(ProPhoto RGB);
+    lab(Lab);
+    lch(Lch);
+    lab-d65(Lab D65);
+    lch-d65(Lch D65);
+    oklab(Oklab);
+    oklch(Oklch);
+    okhsl(Okhsl);
+    okhsv(Okhsv);
+    luv(Luv);
+    lchuv(LCHuv);
+    hsluv(HSLuv);
+    din99o(DIN99o);
+    lch99o(DIN99o Lch);
+    jzazbz(Jzazbz);
+    jzczhz(JzCzhz);
+    ictcp(ICtCp);
+    orgb(oRGB);
+    ipt(IPT);
+    igpgtg(IgPgTg);
+    hunter-lab(Hunter Lab);
+    rlab(RLAB);
+    hsi(HSI);
+    cmy(CMY);
+    cmyk(CMYK);
+    xyy(xyY);
+    prismatic(Prismatic);
+```
 
 ## RGB
 
@@ -100,7 +195,7 @@ _[Learn about sRGB](https://en.wikipedia.org/wiki/SRGB)_
         Color("srgb", [0, 0, 0], 1).to_string()
         ```
 
-### sRGB Linear
+### Linear sRGB
 
 <div class="info-container" markdown="1">
 !!! info inline end "Properties"
@@ -235,7 +330,7 @@ _[Learn about Display P3](https://www.color.org/chardata/rgb/DisplayP3.xalter)_
         Color("display-p3", [0, 0, 0], 1).to_string()
         ```
 
-### Display P3 Linear
+### Linear Display P3
 
 <div class="info-container" markdown="1">
 !!! info inline end "Properties"
@@ -374,7 +469,7 @@ _[Learn about A98 RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space)_
         Color("a98-rgb", [0, 0, 0], 1).to_string()
         ```
 
-### A98 RGB Linear
+### Linear A98 RGB
 
 <div class="info-container" markdown="1">
 
@@ -514,7 +609,7 @@ _[Learn about REC.2020](https://en.wikipedia.org/wiki/Rec._2020)_
         Color("rec2020", [0, 0, 0], 1).to_string()
         ```
 
-### REC. 2020 Linear
+### Linear REC. 2020
 
 <div class="info-container" markdown="1">
 !!! info inline end "Properties"
@@ -652,7 +747,7 @@ _[Learn about ProPhoto](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space)_
         Color("prophoto-rgb", [0, 0, 0], 1).to_string()
         ```
 
-### ProPhoto Linear
+### Linear ProPhoto
 
 <div class="info-container" markdown="1">
 !!! info inline end "Properties"
@@ -1216,6 +1311,205 @@ _[Learn about HSLuv](https://www.hsluv.org/)_
         Color("hsluv", [0, 0, 0], 1).to_string()
         ```
 
+### HSI
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `hsi`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    `h`  | [0, 360)
+    `s`  | [0, 1]
+    `i`  | [0, 1]
+
+<figure markdown="1">
+
+![HSI](../images/hsi-3d.png)
+
+<figcaption>The sRGB gamut represented within the HSI color space.</figcaption>
+</figure>
+
+The HSI model is similar to models like HSL and HSV except that it uses I for intensity instead of Lightness or Value.
+It does not attempt to "fill" a cylinder by its definition of saturation leading to a very different look when we plot
+it.
+
+![HSI Slice](../images/hsi-slice.png)
+
+[Learn more](https://en.wikipedia.org/wiki/HSL_and_HSV#HSI_to_RGB).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `h`      | `hue`
+        `s`      | `saturation`
+        `i`      | `intensity`
+
+    **Inputs**
+    : 
+
+        The HSI space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --hsi`:
+
+        ```css-color
+        color(--hsi h s i / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--hsi h s i / a)` form.
+
+        ```playground
+        Color("hsi", [0, 0, 0], 1)
+        Color("hsi", [0, 0, 0], 1).to_string()
+        ```
+
+## CMY(K)
+
+CMY and CMYK are subtractive color models. The CMY color model itself does not define what is meant by cyan, magenta and
+yellow colorimetrically, and so the results of mixing them are not specified as absolute. As far as ColorAide Extra is
+concerned, it has defined its primaries the same as sRGB making it an absolute color space.
+
+There are many places in which CMY or CMYK are used, often in device dependent applications. CMYK is used in all sorts
+of printing applications, and the exact definition of cyan, magenta, yellow, and black will differ depending on how the
+device has implemented it. Unless they are calibrated to the sRGB color space primaries, it is almost certain this will
+not match such implementations.
+
+### CMY
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `cmy`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    `c`  | [0, 1]
+    `m`  | [0, 1]
+    `y`  | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![CMY](../images/cmy-3d.png)
+
+<figcaption>The sRGB gamut represented within the CMY color space.</figcaption>
+</figure>
+
+The CMY color model is a subtractive color model in which cyan, magenta and yellow pigments or dyes are added together
+in various ways to reproduce a broad array of colors. The name of the model comes from the initials of the three
+subtractive primary colors: cyan, magenta, and yellow.
+
+The CMY color space, as ColorAide Extras has chosen to implement it, is directly calculated from the sRGB color space,
+and as such, is based off the sRGB primaries.
+
+[Learn more](https://en.wikipedia.org/wiki/CMY_color_model).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `c`      | `cyan`
+        `m`      | `magenta`
+        `y`      | `yellow`
+
+    **Inputs**
+    : 
+
+        CMY is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --cmy`:
+
+        ```css-color
+        color(--cmy c m y / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--cmy c m y / a)` form.
+
+        ```playground
+        Color("cmy", [0, 0, 0], 1)
+        Color("cmy", [0, 0, 0], 1).to_string()
+        ```
+
+### CMYK
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `cmyk`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    `c`  | [0, 1]
+    `m`  | [0, 1]
+    `y`  | [0, 1]
+    `k`  | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+The CMYK color model is a just like [CMY](#cmy) except that it adds an additional channel `k` to control blackness.
+
+The CMYK color space, as ColorAide Extras has chosen to implement it, is directly calculated from the sRGB color space,
+and as such, is based off the sRGB primaries.
+
+[Learn more](https://en.wikipedia.org/wiki/CMY_color_model).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `c`      | `cyan`
+        `m`      | `magenta`
+        `y`      | `yellow`
+        `k`      | `black`
+
+    **Inputs**
+    : 
+
+        CMY is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --cmyk`:
+
+        ```css-color
+        color(--cmyk c m y k / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--cmyk c m y k / a)` form.
+
+        ```playground
+        Color("cmyk", [0, 0, 0, 0], 1)
+        Color("cmyk", [0, 0, 0, 0], 1).to_string()
+        ```
+
 ## XYZ
 
 The 1931 CIE XYZ color space encompasses all colors that are visible to a person with average eyesight. It also contains
@@ -1253,7 +1547,7 @@ can be represented with other white points as well. CSS actually allows using ei
     y          | [0.0, 1.0]
     z          | [0.0, 1.0]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs.
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs.
 
 <figure markdown="1">
 
@@ -1326,7 +1620,7 @@ _[Learn about XYZ](https://en.wikipedia.org/wiki/CIE_1931_color_space)_
     y          | [0.0, 1.0]
     z          | [0.0, 1.0]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs.
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs.
 
 <figure markdown="1">
 
@@ -1375,6 +1669,68 @@ _[Learn about XYZ](https://en.wikipedia.org/wiki/CIE_1931_color_space)_
         Color("xyz-d50", [0, 0, 0], 1).to_string()
         ```
 
+## CIE xyY
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `xyy`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    `x`  | [0, 1]
+    `y`  | [0, 1]
+    `Y`  | [0, 1]
+
+    ^\*^ Space is not bound to the range and is used to define percentage inputs/outputs.
+
+<figure markdown="1">
+
+![xyY](../images/xyy-3d.png)
+
+<figcaption>The sRGB gamut represented within the xyY color space.</figcaption>
+</figure>
+
+A derivative of the CIE 1931 XYZ space, the CIE xyY color space, is often used as a way to graphically present the
+chromaticity of colors.
+
+[Learn more](https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `x`      |
+        `y`      |
+        `Y`      |
+
+    **Inputs**
+    : 
+
+        The xyY space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --xyy`:
+
+        ```css-color
+        color(--xyy x y Y / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--xyy x y Y / a)` form.
+
+        ```playground
+        Color("xyy", [0, 0, 0], 1)
+        Color("xyy", [0, 0, 0], 1).to_string()
+        ```
+
 ## CIELAB
 
 CIELAB -- also referred to as L\*a\*b\* -- is another CIE color space. it was created as a perceptually uniform color
@@ -1400,7 +1756,7 @@ also included variants with D65 white points as well.
     a    | [-125, 125]
     b    | [-125, 125]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1473,7 +1829,7 @@ _[Learn about CIELAB](https://en.wikipedia.org/wiki/CIELAB_color_space)_
     a    | [-130, 130]
     b    | [-130, 130]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1548,7 +1904,7 @@ D65 white point.
     c    | [0, 150]
     h    | [0, 360)
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1619,7 +1975,7 @@ _[Learn about CIELCH](https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindric
     c    | [0, 160]
     h    | [0, 360)
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1687,7 +2043,7 @@ _[Learn about CIELCH](https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindric
     a    | [-0.4, 0.4]
     b    | [-0.4, 0.4]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1758,7 +2114,7 @@ _[Learn about Oklab](https://bottosson.github.io/posts/oklab/)_
     c    | [0, 0.4]
     h    | [0, 360)
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1822,7 +2178,7 @@ _[Learn about Oklch](https://bottosson.github.io/posts/oklab/)_
     u    | [-215, 215]
     v    | [-215, 215]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1898,7 +2254,7 @@ _[Learn about CIELUV](https://en.wikipedia.org/wiki/CIELUV)_
     c    | [0, 220]
     h    | [0, 360)
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -1969,7 +2325,7 @@ _[Learn about CIELCH~uv~](https://en.wikipedia.org/wiki/CIELUV)_
     bz   | [-0.5, 0.5]
 
     ^\*^ Space is not bound to the range but is specified to enclose the full range of an HDR BT.2020 gamut and is used
-    to quantify percentage inputs/outputs.
+    to define percentage inputs/outputs.
 
 <figure markdown="1">
 
@@ -2043,7 +2399,7 @@ _[Learn about Jzazbz](https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13
     hz   | [0, 360)
 
     ^\*^ Space is not bound to the range but is specified to enclose the full range of an HDR BT.2020 gamut and is used
-    to quantify percentage inputs/outputs.
+    to define percentage inputs/outputs.
 
 <figure markdown="1">
 
@@ -2111,7 +2467,7 @@ _[Learn about JzCzhz](https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13
     cp         | [-0.5, 0.5]
 
     ^\*^ Space is not bound to the range but is specified to enclose the full range of an HDR BT.2020 gamut and is used
-    to quantify percentage inputs/outputs.
+    to define percentage inputs/outputs.
 
 <figure markdown="1">
 
@@ -2181,7 +2537,7 @@ _[Learn about ICtCp](https://en.wikipedia.org/wiki/ICtCp)_
     a    | [-55, 55]
     b    | [-55, 55]
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -2253,7 +2609,7 @@ _[Learn about DIN99o](https://de.wikipedia.org/wiki/DIN99-Farbraum)_
     c    | [0, 60]
     h    | [0, 360)
 
-    ^\*^ Space is not bound to the range and is only used as a reference to quantify percentage inputs/outputs in
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
     relation to the Display P3 color space.
 
 <figure markdown="1">
@@ -2304,6 +2660,394 @@ _[Learn about DIN99o Lch](https://de.wikipedia.org/wiki/DIN99-Farbraum)_
         Color("lch99o", [0, 0, 0], 1).to_string()
         ```
 
+## oRGB
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `orgb`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name  | Range^\*^
+    ----- | -----
+    `l`   | [0, 1]
+    `cyb` | [-1, 1]
+    `crg` | [-1, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![oRGB](../images/orgb-3d.png)
+
+<figcaption>The sRGB gamut represented within the oRGB color space.</figcaption>
+</figure>
+
+A new color model that is based on opponent color theory. Like HSV, it is designed specifically for computer graphics.
+However, it is also designed to work well for computational applications such as color transfer, where HSV falters.
+Despite being geared towards computation, oRGB's natural axes facilitate HSV-style color selection and manipulation.
+oRGB also allows for new applications such as a quantitative cool-to-warm metric, intuitive color manipulations and
+variations, and simple gamut mapping. This new color model strikes a balance between simplicity and the computational
+qualities of color spaces such as CIELAB.
+
+[Learn more](https://graphics.stanford.edu/~boulos/papers/orgb_sig.pdf).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `l`      | `luma`
+        `cyb`    |
+        `crb`    |
+
+    **Inputs**
+    : 
+
+        The oRGB space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --orgb`:
+
+        ```css-color
+        color(--orgb l cyb crb / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--orgb l cyb crg / a)` form.
+
+        ```playground
+        Color("orgb", [0, 0, 0], 1)
+        Color("orgb", [0, 0, 0], 1).to_string()
+        ```
+
+## Hunter Lab
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `hunter-lab`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    `l`  | [0.0, 100]
+    `a`  | [-210, 210]
+    `b`  | [-210, 210]
+
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
+    relation to the Display P3 color space.
+
+<figure markdown="1">
+
+![oRGB](../images/hunter-lab-3d.png)
+
+<figcaption>The sRGB gamut represented within the Hunter Lab color space.</figcaption>
+</figure>
+
+The Hunter Lab color space, defined in 1948 by Richard S. Hunter, is another color space referred to as "Lab". Like
+CIELAB, it was also designed to be computed via simple formulas from the CIEXYZ space, but to be more perceptually
+uniform than CIEXYZ. Hunter named his coordinates L, a, and b. The CIE named the coordinates for CIELAB as L*, a*, b* to
+distinguish them from Hunter's coordinates.
+
+[Learn more](https://support.hunterlab.com/hc/en-us/articles/203997095-Hunter-Lab-Color-Scale-an08-96a2).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `l`      | `lightness`
+        `a`      |
+        `b`      |
+
+    **Inputs**
+    : 
+
+        The Hunter Lab space is not currently supported in the CSS spec, the parsed input and string output formats use
+        the `#!css-color color()` function format using the custom name `#!css-color --hunter-lab`:
+
+        ```css-color
+        color(--hunter-lab l a b / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--hunter-lab l a b / a)` form.
+
+        ```playground
+        Color("hunter-lab", [0, 0, 0], 1)
+        Color("hunter-lab", [0, 0, 0], 1).to_string()
+        ```
+
+## RLAB
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `rlab`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    `l`  | [0.0, 100.0]
+    `a`  | [-125, 125]
+    `b`  | [-125, 125]
+
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs in
+    relation to the Display P3 color space.
+
+<figure markdown="1">
+
+![RLAB](../images/rlab-3d.png)
+
+<figcaption>The sRGB gamut represented within the RLAB color space.</figcaption>
+</figure>
+
+The RLAB color-appearance space was developed by Fairchild and Berns for cross-media color reproduction applications in
+which images are reproduced with differing white points, luminance levels, and/or surrounds.
+
+[Learn more](https://scholarworks.rit.edu/cgi/viewcontent.cgi?article=1153&context=article).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `l`      | `lightness`
+        `a`      |
+        `b`      |
+
+    **Inputs**
+    : 
+
+        The RLAB space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --rlab`:
+
+        ```css-color
+        color(--rlab l a b / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--rlab l a b / a)` form.
+
+        ```playground
+        Color("rlab", [0, 0, 0], 1)
+        Color("rlab", [0, 0, 0], 1).to_string()
+        ```
+
+## IPT
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `ipt`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    `i`  | [0.0, 1.0]
+    `p`  | [-1, 1]
+    `t`  | [-1, 1]
+
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs.
+
+<figure markdown="1">
+
+![IPT](../images/ipt-3d.png)
+
+<figcaption>The sRGB gamut represented within the IPT color space.</figcaption>
+</figure>
+
+Ebner and Fairchild addressed the issue of non-constant lines of hue in their color space dubbed IPT. The IPT color
+space converts D65-adapted XYZ data (XD65, YD65, ZD65) to long-medium-short cone response data (LMS) using an adapted
+form of the Hunt-Pointer-Estevez matrix (M~HPE~(D65)).
+
+The IPT color appearance model excels at providing a formulation for hue where a constant hue value equals a constant
+perceived hue independent of the values of lightness and chroma (which is the general ideal for any color appearance
+model, but hard to achieve). It is therefore well-suited for gamut mapping implementations.
+
+[Learn more](https://www.researchgate.net/publication/21677980_Development_and_Testing_of_a_Color_Space_IPT_with_Improved_Hue_Uniformity.).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `i`      |
+        `p`      |
+        `t`      |
+
+    **Inputs**
+    : 
+
+        The IPT space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --ipt`:
+
+        ```css-color
+        color(--ipt i p t / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--ipt i p t / a)` form.
+
+        ```playground
+        Color("ipt", [0, 0, 0], 1)
+        Color("ipt", [0, 0, 0], 1).to_string()
+        ```
+
+## IgPgTg
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `ipt`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    `ig` | [0.0, 1]
+    `pg` | [-1, 1]
+    `tg` | [-1, 1]
+
+    ^\*^ Space is not bound to the range and is only used as a reference to define percentage inputs/outputs.
+
+<figure markdown="1">
+
+![IgPgTg](../images/igpgtg-3d.png)
+
+<figcaption>The sRGB gamut represented within the IgPgTg color space.</figcaption>
+</figure>
+
+IgPgTg uses the same structure as IPT, an established hue-uniform color space utilized in gamut mapping applications.
+While IPT was fit to visual data on the perceived hue, IGPGTG was optimized based on evidence linking the peak
+wavelength of Gaussian-shaped light spectra to their perceived hues.
+
+[Learn more](https://www.researchgate.net/publication/21677980_Development_and_Testing_of_a_Color_Space_IPT_with_Improved_Hue_Uniformity.).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `ig`     |
+        `pg`     |
+        `tg`     |
+
+    **Inputs**
+    : 
+
+        The IgPgTg space is not currently supported in the CSS spec, the parsed input and string output formats use the
+        `#!css-color color()` function format using the custom name `#!css-color --igpgtg`:
+
+        ```css-color
+        color(--igpgtg ig pg tg / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--igpgtg ig pg tg / a)` form.
+
+        ```playground
+        Color("igpgtg", [0, 0, 0], 1)
+        Color("igpgtg", [0, 0, 0], 1).to_string()
+        ```
+
+## Prismatic
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `prismatic`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    `l`  | [0, 1]
+    `r`  | [0, 1]
+    `g`  | [0, 1]
+    `b`  | [0, 1]
+
+<figure markdown="1">
+
+![Prismatic](../images/prismatic.png)
+
+<figcaption>Prismatic Illustrations</figcaption>
+</figure>
+
+The Prismatic model introduces a simple transform of the RGB color cube into a light/dark dimension and a 2D hue. The
+hue is a normalized (barycentric)triangle with pure red, green, and blue at the vertices, often called the Maxwell Color
+Triangle.  Each cross section of the space is the same barycentric triangle, and the light/dark dimension runs zero to
+one for each hue so the whole color volume takes the form of a prism.
+
+[Learn more](http://psgraphics.blogspot.com/2015/10/prismatic-color-model.html).
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `l`      | `lightness`
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs**
+    : 
+
+        The Prismatic space is not currently supported in the CSS spec, the parsed input and string output formats use
+        the `#!css-color color()` function format using the custom name `#!css-color --prismatic`:
+
+        ```css-color
+        color(--prismatic l r g b / a)  // Color function
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output use the
+        `#!css-color color(--prismatic l r g b / a)` form.
+
+        ```playground
+        Color("prismatic", [0, 0, 0, 0], 1)
+        Color("prismatic", [0, 0, 0, 0], 1).to_string()
+        ```
+
 <style>
 .info-container {display: inline-block;}
 </style>
diff --git a/docs/src/markdown/contrast.md b/docs/src/markdown/contrast.md
index 4d181c45b..fe3c0eda1 100644
--- a/docs/src/markdown/contrast.md
+++ b/docs/src/markdown/contrast.md
@@ -24,7 +24,7 @@ print(l)
 Color('purple').convert('xyz-d65')['y']
 ```
 
-For convenience, the `luminance` method exposes access to this value to make it quick and easy query the relative
+For convenience, the `luminance` method exposes access to this value to make it quick and easy to query the relative
 luminance, or Y parameter from XYZ D65, for a given color.
 
 ```playground
diff --git a/docs/src/markdown/distance.md b/docs/src/markdown/distance.md
index 55fd9d9c9..fddb6b265 100644
--- a/docs/src/markdown/distance.md
+++ b/docs/src/markdown/distance.md
@@ -41,8 +41,8 @@ being a little slower.
 Color("red").delta_e("blue", method="2000")
 ```
 
-Below are all the supported ∆E methods. Follow relevant links to read the specs and find out more about a given ∆E
-method.
+Below are all the supported ∆E methods that are included in the default `coloraide.Color` object. Follow relevant links
+to read the specs and find out more about a given ∆E method.
 
 Delta\ E                                 | Name            | Parameters
 ---------------------------------------- | --------------- | --------------------
@@ -53,8 +53,9 @@ Delta\ E                                 | Name            | Parameters
 [∆E~HyAB~][dehyab]\ (HyAB)               | `hyab`          | `space="lab-d65"`
 ∆E~ok~                                   | `ok`            | `scalar=1`
 
-The following are also available, but they must be manually registered, or can be accessed by use `coloraide.ColorAll`
-instead of `coloraide.Color`. The associated color space must be registered as well.
+The following are also available, but they must be manually registered by creating a
+[custom class](./color.md#custom-color-classes), or can be accessed by use `coloraide.ColorAll` instead of
+`coloraide.Color`. The associated color space must be registered as well.
 
 Delta\ E                                 | Name            | Parameters
 ---------------------------------------- | --------------- | --------------------
@@ -74,3 +75,9 @@ the colors, the closest ends up being `#!color maroon`.
 ```playground
 Color('red').closest(['pink', 'yellow', 'green', 'blue', 'purple', 'maroon'])
 ```
+
+The default distancing method is used if one is not supplied, but others can be used:
+
+```playground
+Color('red').closest(['pink', 'yellow', 'green', 'blue', 'purple', 'maroon'], method='2000')
+```
diff --git a/docs/src/markdown/gamut.md b/docs/src/markdown/gamut.md
index 93c6b9257..b5afb87f6 100644
--- a/docs/src/markdown/gamut.md
+++ b/docs/src/markdown/gamut.md
@@ -238,7 +238,7 @@ HtmlRow(Color.steps([yellow, lightness_mask], steps=10, space='lch'))
 # Clipping
 yellow = ColorClip('color(display-p3 1 1 0)')
 lightness_mask = Color('lch(0% none none)')
-HtmlRow(Color.steps([yellow, lightness_mask], steps=10, space='lch'))
+HtmlRow(ColorClip.steps([yellow, lightness_mask], steps=10, space='lch'))
 ```
 
 There are times when clipping is simply preferred. It is fast, and if you are just trimming noise off channels, it is
diff --git a/docs/src/markdown/images/cmy-3d.png b/docs/src/markdown/images/cmy-3d.png
new file mode 100644
index 000000000..4bacc2571
Binary files /dev/null and b/docs/src/markdown/images/cmy-3d.png differ
diff --git a/docs/src/markdown/images/hsi-3d.png b/docs/src/markdown/images/hsi-3d.png
new file mode 100644
index 000000000..a546ec6df
Binary files /dev/null and b/docs/src/markdown/images/hsi-3d.png differ
diff --git a/docs/src/markdown/images/hsi-slice.png b/docs/src/markdown/images/hsi-slice.png
new file mode 100644
index 000000000..ed6ba6fb7
Binary files /dev/null and b/docs/src/markdown/images/hsi-slice.png differ
diff --git a/docs/src/markdown/images/hunter-lab-3d.png b/docs/src/markdown/images/hunter-lab-3d.png
new file mode 100644
index 000000000..c54abc83e
Binary files /dev/null and b/docs/src/markdown/images/hunter-lab-3d.png differ
diff --git a/docs/src/markdown/images/igpgtg-3d.png b/docs/src/markdown/images/igpgtg-3d.png
new file mode 100644
index 000000000..5fcd40e77
Binary files /dev/null and b/docs/src/markdown/images/igpgtg-3d.png differ
diff --git a/docs/src/markdown/images/ipt-3d.png b/docs/src/markdown/images/ipt-3d.png
new file mode 100644
index 000000000..774a154da
Binary files /dev/null and b/docs/src/markdown/images/ipt-3d.png differ
diff --git a/docs/src/markdown/images/orgb-3d.png b/docs/src/markdown/images/orgb-3d.png
new file mode 100644
index 000000000..9eff4382a
Binary files /dev/null and b/docs/src/markdown/images/orgb-3d.png differ
diff --git a/docs/src/markdown/images/prismatic.png b/docs/src/markdown/images/prismatic.png
new file mode 100644
index 000000000..2f1d8a821
Binary files /dev/null and b/docs/src/markdown/images/prismatic.png differ
diff --git a/docs/src/markdown/images/rlab-3d.png b/docs/src/markdown/images/rlab-3d.png
new file mode 100644
index 000000000..2dc4ce88f
Binary files /dev/null and b/docs/src/markdown/images/rlab-3d.png differ
diff --git a/docs/src/markdown/images/xyy-3d.png b/docs/src/markdown/images/xyy-3d.png
new file mode 100644
index 000000000..2efd030d1
Binary files /dev/null and b/docs/src/markdown/images/xyy-3d.png differ
diff --git a/docs/src/markdown/index.md b/docs/src/markdown/index.md
index 97b0b31d3..105ae0a5b 100644
--- a/docs/src/markdown/index.md
+++ b/docs/src/markdown/index.md
@@ -1,20 +1,19 @@
 # Introduction
 
-!!! warning "Currently a Prerelease"
-    While ColorAide is very usable and out of the alpha stage, it is currently in a prerelease state. This simply means
-    the API could still be in flux to some degree. ColorAide is still technically waiting for some issues related to CSS
-    color syntax and behavior to be decided by the CSS Working Group.
+!!! warning "Currently a Beta"
+    ColorAide is very close to a stable release and is currently in the Beta stage. While minor things could still
+    change, there are no plans for major API changes at this point.
 
 ## What is ColorAide?
 
-ColorAide is a color library for Python with the intent of providing an easy to use interface to work with colors.
+ColorAide is a pure Python, object oriented approach to colors.
 
 ```playground
 from coloraide import Color
 Color.steps(['lch(75% 50 0)', 'lch(75% 50 300)'], steps=8, space='lch', hue='longer')
 ```
 
-ColorAide is a continually evolving project, but was created with a number of specific goals in mind:
+ColorAide particularly has a focus on the following:
 
 - [x] Accurate colors.
 
@@ -22,7 +21,9 @@ ColorAide is a continually evolving project, but was created with a number of sp
 
 - [x] Be generally easy to pick up for the average user.
 
-- [x] Support modern CSS color spaces and syntax as well as a number of popular non-CSS color spaces.
+- [x] Support modern CSS color spaces and syntax.
+
+- [x] Make accessible many new and old non-CSS color spaces.
 
 - [x] Provide a number of useful utilities such as interpolation, color distancing, blending, gamut mapping, etc.
 
@@ -30,21 +31,16 @@ ColorAide is a continually evolving project, but was created with a number of sp
 
 - [x] Allow users to configure defaults to their liking.
 
-There are many color libraries out there, and ColorAide is not meant to be the _one_ library to replace all other color
-libraries. ColorAide focuses on not only trying to be accurate with its colors, but also to be easy to use. Not all
-color libraries are easy to pick up and use and can require a user to manage a color's illuminant, be aware of and
-manage the full conversion chain to get one color converted to another, and other various intricacies. ColorAide tries
-to handle a lot of this so you can go straight to using the colors.
-
-There are some great scientific libraries out there for working with colors. Two libraries that stand out are
-[Colour Science][colour-science] and [Colorio][colorio], both of which are fantastic libraries, but are definitely more
-geared towards scientific endeavors. They provide a wealth of various spaces, illuminants, access to complex color space
-visualizers, and numerous esoteric tools. They use Numpy to greatly speed up calculations as well. If you want to learn
-more about colors, these are great libraries to play with, but they can be a little more cumbersome to use for the
-average user.
-
-While ColorAide could certainly be used to analyze colors, it does not aim to provide scientific tools and mainly
-focuses on making color usage easy and accessible in projects using colors.
+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
+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.
+
+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
+advantages as well. ColorAide might not always be the tool for **every** job, but hopefully it is a great tool all the
+same.
 
 ## Installation
 
diff --git a/docs/src/markdown/interpolation.md b/docs/src/markdown/interpolation.md
index 2f0c1dbfc..2bd9e000d 100644
--- a/docs/src/markdown/interpolation.md
+++ b/docs/src/markdown/interpolation.md
@@ -498,7 +498,7 @@ degrees, ColorAide will accept it, but the hue will not affect the color in any
 
 During conversions, such context is lost, and if an achromatic color is converted to the color space like HSL, the
 resultant color will have a hue that is noted as undefined. This is simply because there is no good hue for achromatic
-colors as they play no part in the color. All hues actually incorrect as achromatic colors have no real hue. Instead,
+colors as they play no part in the color. Any hue is actually incorrect as achromatic colors have no real hue. Instead,
 colors will be returned with a value that represents that the hue is missing or undefined, or maybe better worded, could
 not be defined.
 
@@ -513,8 +513,22 @@ ColorAide also uses `NaN`, or in Python `#!py3 float('nan')`, to represent undef
 when a hue is deemed undefined, the hue value will be set to `coloraide.NaN`, which is just a constant containing
 `#!py3 float('nan')`.
 
-When interpolating, if one color's channel has a `NaN`, the other color's channel will be used as the result. If both
-colors have a `NaN` for the same channel, then `NaN` will be returned.
+When performing linear interpolation, where only two color's channels are ever being evaluated together at a given time,
+if one color's channel has a `NaN`, the other color's channel will be used as the result. If both colors have a `NaN`
+for the same channel, then `NaN` will be returned.
+
+!!! tip "NaN Handling in Bezier Interpolation"
+    This logic is similar for things like Bezier interpolation, but will be extended as Bezier interpolation can
+    evaluate multiple colors at a time. For instance if interpolating a single channel across three colors, `NaN` must
+    be resolved simultaneously across all three colors.
+
+    Here are some example resolutions for 3 values from one channel from three separate colors:
+
+    - `#!py3 [NaN, NaN, 1]` --> `#!py3 [1, 1, 1]`
+    - `#!py3 [NaN, 0.5, 1]` --> `#!py3 [0.5, 0.5, 1]`
+    - `#!py3 [1, 0.5, NaN]` --> `#!py3 [1, 0.5, 0.5]`
+    - `#!py3 [1, NaN, NaN]` --> `#!py3 [1, 1, 1]`.
+    - `#!py3 [NaN, NaN, NaN]` --> `#!py3 [NaN, NaN, NaN]`
 
 Notice that in this example, because white's saturation is zero, the hue is undefined. Because the hue is undefined,
 when the color is mixed with a second color (`#!color green`), the hue of the second color is used.
diff --git a/docs/src/markdown/manipulation.md b/docs/src/markdown/manipulation.md
index 389f597ae..cfa71f41e 100644
--- a/docs/src/markdown/manipulation.md
+++ b/docs/src/markdown/manipulation.md
@@ -93,39 +93,40 @@ As with coordinate retrieval, a number of ways can be used set coordinates as we
     c.set('hsl.hue', lambda x: x + 180)
     ```
 
-## Modifying Coordinates in Other Spaces
-
-As previously mentioned, coordinates in other spaces can be modified with the `set` function. Here we alter the color
-`#!color blue` by editing the `hue` channel in the CIELCH color space and get `#!color Color("blue").set('lch.hue', 130)`.
-Keep in mind that the colors are being converted to the specified space under the hood, set, and then converted back, so
-if you have multiple operations to apply in a given color space, it may be more efficient to convert to that space,
-apply the set operations directly, and then convert back.
-
-```playground
-Color("blue").set('lch.hue', 130)
-```
-
-When setting a color in another color space, the final value is subject to any rounding errors that may occur in the
-round trip to and from the specified color space. Also, depending on the transform functions of the spaces involved, and
-whether the original color is on the edge of its own gamut, this can lead to a color going slightly out of gamut, and if
-one of the spaces involved in the conversion doesn't handle out of gamut colors with sensible values, you may get
-something unexpected back.
-
-Consider the following example comparing the modification of an HSL color in HWB vs Oklab.
-
-```playground
-Color('hsl(0 0% 50%)').set('hwb.blackness', 0).set('hwb.whiteness', 100)
-Color('hsl(0 0% 50%)').set('oklab.lightness', 1)
-Color('hsl(0 0% 50%)').set('oklab.lightness', 1).convert('srgb')[:]
-```
-
-The above example cleanly converts between HSL and HWB as the conversion between these two is much more precise, but the
-Oklab example is not quite as precise and returns a color with a saturation that is way out of bounds. This is partly
-because the Oklab max whiteness isn't exactly `1`, but more like `~0.999...`, The HSL model doesn't really represent out
-of gamut colors in a logical way and, in this case, creates a color with a negative saturation. It looks worse than it
-really is as when we convert it to sRGB, we see it is barely off. None of this is a bug, it is just the nature of the
-algorithms we are using to convert, the precision of the floats, and the slight rounding errors that occur when
-using [floating-point arithmetic][floating-point], etc.
+    ??? note "Notes on Modifying Coordinates in Other Spaces"
+
+        As previously mentioned, coordinates in other spaces can be modified with the `set` function. Here we alter the
+        color `#!color blue` by editing the `hue` channel in the CIELCH color space and get
+        `#!color Color("blue").set('lch.hue', 130)`. Keep in mind that the colors are being converted to the specified
+        space under the hood, set, and then converted back, so if you have multiple operations to apply in a given color
+        space, it may be more efficient to convert to that space, apply the set operations directly, and then convert
+        back.
+
+        ```playground
+        Color("blue").set('lch.hue', 130)
+        ```
+
+        When setting a color in another color space, the final value is subject to any rounding errors that may occur in
+        the round trip to and from the specified color space. Also, depending on the transform functions of the spaces
+        involved, and whether the original color is on the edge of its own gamut, this can lead to a color going
+        slightly out of gamut, and if one of the spaces involved in the conversion doesn't handle out of gamut colors
+        with sensible values, you may get something unexpected back.
+
+        Consider the following example comparing the modification of an HSL color in HWB vs Oklab.
+
+        ```playground
+        Color('hsl(0 0% 50%)').set('hwb.blackness', 0).set('hwb.whiteness', 100)
+        Color('hsl(0 0% 50%)').set('oklab.lightness', 1)
+        Color('hsl(0 0% 50%)').set('oklab.lightness', 1).convert('srgb')[:]
+        ```
+
+        The above example cleanly converts between HSL and HWB as the conversion between these two is much more precise,
+        but the Oklab example is not quite as precise and returns a color with a saturation that is way out of bounds.
+        This is partly because the Oklab max whiteness isn't exactly `1`, but more like `~0.999...`, The HSL model
+        doesn't really represent out of gamut colors in a logical way and, in this case, creates a color with a negative
+        saturation. It looks worse than it really is as when we convert it to sRGB, we see it is barely off. None of
+        this is a bug, it is just the nature of the algorithms we are using to convert, the precision of the floats, and
+        the slight rounding errors that occur when using [floating-point arithmetic][floating-point], etc.
 
 ## Undefined Values
 
@@ -135,10 +136,12 @@ Colors in general can sometimes have undefined channels. This can actually happe
 with hues will have powerless hues when the color is achromatic. This can occur if saturation or chroma is zero, or
 when a color has no lightness, etc.
 
-    Normally, if a hue is defined manually defined on an achromatic color they are considered defined even if the
-    defined value is powerless, but during the conversion process, the algorithm won't know what to assign as a hue. For
-    instance, if saturation is zero, one could argue the hue should be `0`, but that is actually a red hue, and
-    achromatic colors have no hue. In the end, no hue is actually satisfactory, so an undefined hue is applied.
+    If a hue is manually defined to an achromatic color hue is considered defined, though that value will still be
+    powerless during conversion. But lets considered an achromatic color in a rectangular color space being converted to
+    a cylindrical color space with hue. During the conversion process, there is nothing to suggest to the algorithm what
+    the hue should be. For instance, if saturation is zero, one could argue the hue should be `0`, but that is actually
+    a red hue, and achromatic colors have no hue. In the end, no hue is actually satisfactory, so an undefined hue is
+    applied.
 
     ```playground
     color = Color('white').convert('hsl')
@@ -146,7 +149,7 @@ when a color has no lightness, etc.
     ```
 
 2. When specifying raw data, and an insufficient amount of channel data is provided, the missing channels will be
-assumed as undefined, the exception is the `alpha` channel which is assumed to be `1` unless explicitly defined or
+assumed as undefined, the exception is that `alpha` channel which is assumed to be `1` unless explicitly defined or
 explicitly set as undefined.
 
     ```playground
@@ -154,9 +157,9 @@ explicitly set as undefined.
     Color('srgb', [1, 0, 0], NaN)[:]
     ```
 
-3. Undefined values can also occur when a user specifies a channel with the `none` keyword. This can also be done in raw
-color data by directly passing `#!py3 float('nan')` -- the provided `NaN` constant is essentially an alias for
-`#!py3 float('nan')`.
+3. Undefined values can also occur when a user specifies a channel with the `none` keyword in CSS syntax. This can also
+be done in raw color data by directly passing `#!py3 float('nan')` -- the provided `NaN` constant is essentially an
+alias for this.
 
     One may question why such a thing would ever be desired, but this can be quite useful when interpolating as
     undefined channels will not be interpolated. It can be thought of as a way to mask off channels. Checkout the
diff --git a/docs/src/markdown/plugins/index.md b/docs/src/markdown/plugins/index.md
index 72168323b..66c688caa 100644
--- a/docs/src/markdown/plugins/index.md
+++ b/docs/src/markdown/plugins/index.md
@@ -151,7 +151,8 @@ All color spaces supported by ColorAide are specified via color space plugins. T
 channel properties, gamut bounds, input matching/parsing logic, string output logic, conversion to and from a specified
 base color, etc.
 
-Color space plugins are a little more complex compared to [Delta E](#delta-e) and [Fit](#fitgamut-mapping) plugins.
+Color space plugins are a little more complex compared to [Delta E](#delta-e), [Fit](#fitgamut-mapping), and other
+plugins.
 
 ### Plugin Class
 
@@ -206,39 +207,48 @@ class XYZD65(Space):
     #
     # - `name`: The name of the channel.
     # - `low`: Lower limit of the channel, for unbound channels, the value will be arbitrary.
-    # - `low`: Upper limit of the channel, for unbound channels, the value will be arbitrary.
+    # - `high`: Upper limit of the channel, for unbound channels, the value will be arbitrary.
     # - `bound`: Whether the channel enforces the gamut range.
     # - `limit`: Optional upper and lower limit. Used to define a hard limit for the channel that is clamped
     #            when the channel is set. This differs from gamut boundaries which can be exceeded until gamut
     #            mapping occurs. For instance, `chroma` often enforces no values below zero as these values
-    #            do not naturally occur, not even with normal out of gamut colors.
+    #            do not naturally occur, not even with normal out of gamut colors. So, we could clamp the lower
+    #            bound: `(0, None)`.
     # - `flags`: Flags used to provide additional context for the channel.
     #
     # The following flags are supported:
     # - FLG_ANGLE: denotes that channel is a angle or degree value.
-    # - FLG_PERCENT: denotes the value is considered a percent.
-    #                Channels with this expect the value to be between 0 - 100
+    # - FLG_PERCENT: denotes the value is considered a percent input. This is usually used in named CSS functions
+    #                like `hsl()` which require string inputs for saturation and lightness to always be in a
+    #                percentage format. The CSS `color()` function ignores this flags as no channels are always
+    #                required to be percentages. Percentage range will be determined by `high` and `low`.
     # - FLG_OPT_PERCENT: denotes the value can optionally be considered as a percent.
-    #                    Channels with this are expected to be between 0 - 1.
-    #
-    # NOTE: Behavior of percent flags may change depending on how CSS Level 4 plans to handle non rectangular
-    #       colors in the `color(space ...)` format (if they handle them at all).
+    #                    This is also only used for CSS string input and output. CSS `oklab`, `lab()`, `oklch`,
+    #                    `lch()`, and `srgb()` allow for channels to be provided as percentages or normal
+    #                    numbers in certain cases. This tells the parser and serializer which channels allow this.
+    #                    Percentage range will be determined by `high` and `low`.
+    # - FLG_MIRROR_PERCENT: The channel, when importing or exporting to a percent should mirror the percentage
+    #                       for negative values. This is used mainly in Lab and Lab like spaces which have `a`
+    #                       `b` channels that allow for both negative and positive values. If set, `high` and `low`
+    #                       should fulfill `abs(low) == high`.
     CHANNELS = (
         Channel("x", 0.0, 1.0),
         Channel("y", 0.0, 1.0),
         Channel("z", 0.0, 1.0)
     )
 
-    # A dictionary containing a mapping of aliases to `CHANNEL_NAMES` found above.
+    # A dictionary containing a mapping of aliases to `name` attribute of `CHANNELS` found above.
     CHANNEL_ALIASES = {}
 
-    # If you'd like this color space to parse as a `color(space ...)` format.
+    # If you'd like this color space to parse as and export a `color(space ...)` format.
     # If set to `False` the space will not recognize the color format as an input.
-    # To override output of the color format, you will also need to override the `to_string` method.
+    # This only affects input matching. To override output of the color format, you will also
+    # need to override the `to_string` method.
     COLOR_FORMAT = True
 
     # Specify the white point that the color space uses
     # White point should be a `tuple` containing the x and y chromaticity points.
+    # Some basic ones are provided in the `cat` module for both 2 degree and 10 degree observer.
     WHITE = cat.WHITES['2deg']['D65']
 
     # If `GAMUT_CHECK` is set to a color space name, the provided color space will be used to verify the an "in gamut"
@@ -253,9 +263,9 @@ class XYZD65(Space):
     GAMUT_CHECK = None
     # When set to `True`, this denotes that the color space has the ability to represent out of gamut in colors in an
     # extended range. When interpolation is done, if colors are interpolated in a smaller gamut than the colors being
-    # interpolated, the colors will usually be gamut mapped, but if the interpolation space happens to support extended
-    # ranges, then the colors will not be gamut mapped even if their gamut is larger than the target interpolation
-    # space.
+    # interpolated, and that color is "bound" to a gamut, the colors will usually be gamut mapped, but if the
+    # interpolation space happens to support extended ranges, then the colors will not be gamut mapped even if their
+    # gamut is larger than the target interpolation space.
     EXTENDED_RANGE = False
 
     ############################
@@ -283,10 +293,16 @@ class XYZD65(Space):
 ````
 
 In addition to the above methods, some color spaces, such as cylindrical spaces, have some additional logic that
-determines when a `hue` is undefined. This function provides access to this logic in case `normalize` is called from the
-the `Color` object. In the case of such color spaces, it may be necessary to define
-`normalize` as well. Below is an example from HSL that sets `hue` to undefined when `saturation` is `#!py3 0` or
-`lightness` is equal to `#!py3 0` or `#!py3 1`. This logic should mirror what is performed in a normal conversion.
+determines when a `hue` is undefined. During conversion, undefined channels are usually thrown away, and a color may be
+returned with undefined hues if the color is achromatic.a
+
+The `Space.normalize` function is not used during conversion, but provides logic for specifically normalizing an exiting
+color when `Color.normalize` is called. Logic should generally match whatever occurs during conversion.conversion
+
+Usually, for rectangular spaces, it simply eliminates undefined channels. For cylindrical spaces, it will also set the
+hue to undefined if the color meets the criteria of the color space. This may be when chroma zero, or maybe even every
+close to zero, maybe even when lightness is equal to `#!py3 0` or `#!py3 1`. This can vary from color space to color
+space.
 
 ```py
     @classmethod
@@ -433,10 +449,11 @@ from coloraide.css import parse
 class SRGB(base.SRGB):
     """SRGB class."""
 
-    # This color class should opt into the generic `color(space ...)` format.
+    # This color class should opt into the generic `color(space ...)` input format.
     # This is `True` by default, but shown for demonstration purposes.
     COLOR_FORMAT: True
 
+    # If the color format above is not found, continue with our custom match to handle all other formats.
     @classmethod
     def match(
         cls,
diff --git a/docs/src/mkdocs.yml b/docs/src/mkdocs.yml
index ff3d1ef66..364b75f8d 100644
--- a/docs/src/mkdocs.yml
+++ b/docs/src/mkdocs.yml
@@ -70,6 +70,10 @@ markdown_extensions:
   - pymdownx.superfences:
       preserve_tabs: true
       custom_fences:
+        # Mermaid diagrams
+        - name: diagram
+          class: diagram
+          format: !!python/name:pymdownx.superfences.fence_code_format
         - name: playground
           class: playground
           format: !!python/name:docs.src.py.notebook.color_command_formatter
@@ -124,7 +128,8 @@ markdown_extensions:
 extra_css:
   - assets/coloraide-extras/extra.css
 extra_javascript:
-  - playground-config-e4040a76.js
+  - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
+  - playground-config-50c86242.js
   - https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js
   - assets/coloraide-extras/extra-notebook.js
 
diff --git a/docs/src/py/notebook.py b/docs/src/py/notebook.py
index aff45b49f..eb9ffc730 100644
--- a/docs/src/py/notebook.py
+++ b/docs/src/py/notebook.py
@@ -86,12 +86,12 @@ def std_output(stdout=None):
 def get_colors(result):
     """Get color from results."""
 
-    from coloraide import Color
+    from coloraide import Color, ColorAll
     from coloraide.interpolate import Interpolator
     try:
         from coloraide_extras import Color as Color2
     except ImportError:
-        Color2 = Color
+        Color2 = ColorAll
 
     colors = []
     if isinstance(result, HtmlRow):
@@ -131,7 +131,7 @@ def find_colors(text):
     try:
         from coloraide_extras import Color
     except ImportError:
-        from coloraide import Color
+        from coloraide import ColorAll as Color
 
     colors = []
     for m in RE_COLOR_START.finditer(text):
@@ -152,7 +152,7 @@ def execute(cmd, no_except=True, inline=False):
         coloraide_extras = None
 
     g = {
-        'Color': coloraide.Color,
+        'Color': coloraide.ColorAll,
         'coloraide': coloraide,
         'NaN': coloraide.NaN,
         'stop': coloraide.stop,
@@ -400,7 +400,7 @@ def color_formatter(src="", language="", class_name=None, md="", exceptions=True
     try:
         from coloraide_extras import Color
     except ImportError:
-        from coloraide import Color
+        from coloraide import ColorAll as Color
 
     try:
         result = src.strip()
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js b/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js
new file mode 100644
index 000000000..5f45e2674
--- /dev/null
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js
@@ -0,0 +1,4 @@
+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 P()}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=_&&_(_(I([])));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 I(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:P}}function P(){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=I,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:I(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(){
+/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
+e=function(){return t};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 p?t:p,a=Object.create(r.prototype),i=new k(o||[]);return a._invoke=function(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 S()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=x(i,n);if(s){if(s===d)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=u(e,t,n);if("normal"===c.type){if(o=n.done?"completed":"suspendedYield",c.arg===d)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o="completed",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}}}t.wrap=l;var d={};function p(){}function f(){}function m(){}var h={};c(h,a,(function(){return this}));var y=Object.getPrototypeOf,g=y&&y(y(L([])));g&&g!==n&&o.call(g,a)&&(h=g);var v=m.prototype=p.prototype=Object.create(h);function w(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function _(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 x(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,x(e,t),"throw"===t.method))return d;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var o=u(n,e.iterator,t.arg);if("throw"===o.type)return t.method="throw",t.arg=o.arg,t.delegate=null,d;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,d):r:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,d)}function b(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 E(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function k(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(b,this),this.reset(!0)}function L(e){if(e){var t=e[a];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:S}}function S(){return{value:void 0,done:!0}}return f.prototype=m,c(v,"constructor",m),c(m,"constructor",f),f.displayName=c(m,s,"GeneratorFunction"),t.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===f||"GeneratorFunction"===(t.displayName||t.name))},t.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,m):(e.__proto__=m,c(e,s,"GeneratorFunction")),e.prototype=Object.create(v),e},t.awrap=function(e){return{__await:e}},w(_.prototype),c(_.prototype,i,(function(){return this})),t.AsyncIterator=_,t.async=function(e,n,o,r,a){void 0===a&&(a=Promise);var i=new _(l(e,n,o,r),a);return t.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},w(v),c(v,s,"Generator"),c(v,a,(function(){return this})),c(v,"toString",(function(){return"[object Generator]"})),t.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}},t.values=L,k.prototype={constructor:k,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(E),!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,d):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),d},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),E(n),d}},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;E(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:L(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),d}},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}var i,s,c,l,u,d,p,f,m,h,y,g,v,w,_,x,b,E,k,L,S,C,T,I;i=null,s=!1,c="",l="",u={},d=/.*?_(\d+)$/,p=!1,f="",m=!1,h=/^( {1,4}|\t)/,y="\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\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    from coloraide import Color, ColorAll\n    from coloraide.interpolate import Interpolator\n    try:\n        from coloraide_extras import Color as Color2\n    except ImportError:\n        Color2 = ColorAll\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, Color2(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 Color2(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, Color2(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, Color2(x)))\n                except Exception:\n                    pass\n    return colors\n\n\ndef find_colors(text):\n    \"\"\"Find colors in text buffer.\"\"\"\n\n    try:\n        from coloraide_extras import Color\n    except ImportError:\n        from coloraide import ColorAll as Color\n\n    colors = []\n    for m in RE_COLOR_START.finditer(text):\n        start = m.start()\n        mcolor = Color.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    import coloraide\n    try:\n        import coloraide_extras\n    except ImportError:\n        coloraide_extras = None\n\n    g = {\n        'Color': coloraide.ColorAll,\n        'coloraide': coloraide,\n        'NaN': coloraide.NaN,\n        'stop': coloraide.stop,\n        'hint': coloraide.hint,\n        'HtmlRow': HtmlRow,\n        'HtmlSteps': HtmlSteps,\n        'HtmlGradient': HtmlGradient\n    }\n\n    if coloraide_extras is not None:\n        g['coloraide_extras'] = coloraide_extras\n        g['Color'] = coloraide_extras.Color\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    from pygments import highlight\n    from pygments.lexers import get_lexer_by_name\n    from pygments.formatters import find_formatter_class\n    HtmlFormatter = find_formatter_class('html')\n\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            base_classes = \"swatch\"\n            for color in item:\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    try:\n        from coloraide_extras import Color\n    except ImportError:\n        from coloraide import ColorAll as Color\n\n    try:\n        result = src.strip()\n\n        try:\n            color = Color(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\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    ]\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.superfences': {\n            'preserve_tabs': True,\n            'custom_fences': [\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    }\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\nimport micropip\nfrom js import location\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\nbase = location.pathname.lstrip('/').split('/')[0]\nawait micropip.install([location.origin + '/{}/playground/{}'.format(base, w) for w in wheels])\ncallback()\n",g=window.color_notebook.default_playground,v=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")},w=function(){m=!0,window.document.dispatchEvent(new Event("DOMContentLoaded",{bubbles:!0,cancelable:!0}))},_=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)},x=function(e){return encodeURIComponent(e).replace(/[.!'()*]/g,(function(e){return"%".concat(e.charCodeAt(0).toString(16))}))},b=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",""),i.globals.set("id_num",n),i.globals.set("action","playground"),i.globals.set("wheels",window.color_notebook.playground_wheels),e.next=7,i.runPythonAsync(y);case 7:o.removeAttribute("readonly");case 8:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),E=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 i.globals.set("content",n),i.globals.set("action","notebook"),i.globals.set("wheels",window.color_notebook.notebook_wheels),e.next=5,i.runPythonAsync(y);case 5:(o=document.getElementById("__notebook-input"))&&(c=n,o.value=n),window.location.hash&&(window.location.href=window.location.href);case 8:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),k=function(){var t=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(p){e.next=7;break}return p=!0,e.next=4,loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.20.0/full/",fullStdLib:!1});case 4:return i=e.sent,e.next=7,i.loadPackage(["micropip"]);case 7:case"end":return e.stop()}}),t)})));return function(){return t.apply(this,arguments)}}(),L=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)},S=function(e){e.querySelector(".loading")&&e.removeChild(e.querySelector(".loading"))},C=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(h,""):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}}},T=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(d,"$1"),i=document.getElementById("__playground-inputs_".concat(a)),p=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(){_(i)})),i.addEventListener("keydown",C),r&&o){var v=document.getElementById("__notebook-input");v.addEventListener("input",(function(e){_(e.target)})),v.addEventListener("keydown",C),document.getElementById("__notebook-edit").addEventListener("click",(function(){u[v.id]=v.value,document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),_(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:",l))&&(o=x(o),n.preventDefault(),history.pushState({notebook:o},"","?".concat(new URLSearchParams("notebook=".concat(o)).toString())),I(!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:",l))&&(o=x(o),n.preventDefault(),history.pushState({source:o},"","?".concat(new URLSearchParams("source=".concat(o)).toString())),I(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-input").value=c,document.getElementById("__notebook-cancel").addEventListener("click",(function(){v.value=u[v.id],delete u[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"),c=document.getElementById("__notebook-input").value,n.classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),o=document.querySelector("article"),L(o,"Loading Notebook..."),n.innerHTML="",u={},e.next=10,k();case 10:return e.next=12,E(c);case 12:return e.next=14,T();case 14:S(o);case 15:case"end":return e.stop()}}),t)}))))}i.addEventListener("touchmove",(function(e){e.stopPropagation()})),p.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:u[a]=i.value,f.classList.toggle("hidden"),p.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),_(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=x(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),r.length>1e3?alert("Code must be under a 1000 characters to generate a URL!"):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(!s){e.next=2;break}return e.abrupt("return");case 2:return s=!0,n=f.querySelector("form"),L(n,null,!0),(o=document.querySelectorAll(".playground .playground-run"))&&o.forEach((function(e){e.setAttribute("disabled","")})),e.next=9,k();case 9:return p.querySelector("code").innerHTML="",e.next=12,b(a);case 12:o&&o.forEach((function(e){e.removeAttribute("disabled")})),S(n),f.classList.toggle("hidden"),p.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),delete u[a],s=!1;case 22:case"end":return e.stop()}}),t)})))),g.addEventListener("click",(function(){i.value=u[a],delete u[a],f.classList.toggle("hidden"),p.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)}}(),I=function(){var t=n(e().mark((function t(o){var r,a,i,s,c,d,p,m,h;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(u={},!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 L(c,a),t.next=11,k();case 11:S(c),L(c,i);try{d=r.has("source")?"source":"notebook",f=decodeURIComponent(r.toString()),p="",m=new XMLHttpRequest,l=s,m.open("GET",s,!0),m.onload=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return 4===m.readyState&&200===m.status&&(p=m.responseText),"source"===d&&(p=v(p)),e.next=4,E(p);case 4:return e.next=6,T(o);case 6:S(c),w();case 8:case"end":return e.stop()}}),t)}))),m.send()}catch(e){}t.next=30;break;case 16:return l="",h=v(r.has("code")?r.get("code"):g),f=decodeURIComponent(r.toString()),L(c,a),t.next=22,k();case 22:return S(c),L(c,i),t.next=26,E(h);case 26:return t.next=28,T(o);case 28:S(c),w();case 30:t.next=35;break;case 32:l="",f="",T(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&&I&&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 a,i={},s=function(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}}}}(new URLSearchParams(n.search));try{for(s.s();!(a=s.n()).done;){var c=o(a.value,2),l=c[0],u=c[1];i[l]=u}}catch(e){s.e(e)}finally{s.f()}history.pushState(i,"",n.href),I(!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())!==f&&I(!1)})),window.addEventListener("unload",(function(){m=!0})),window.document$.subscribe((function(){m?m=!1:I(!0)}))}();
+//# sourceMappingURL=extra-notebook-1d173e7b.js.map
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js.map b/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map
similarity index 99%
rename from docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js.map
rename to docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map
index 78e3c9ab7..73a31f163 100644
--- a/docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js.map
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"extra-notebook-bb8e4037.js","sources":["../../../src/js/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\nimport micropip\nfrom js import location\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\nbase = location.pathname.lstrip('/').split('/')[0]\nawait micropip.install([location.origin + '/{}/playground/{}'.format(base, w) for w in wheels])\ncallback()\n`\n\n  const defContent = window.color_notebook.default_playground\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  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  }\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    pyodide.globals.set('wheels', window.color_notebook.playground_wheels)\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    pyodide.globals.set('wheels', window.color_notebook.notebook_wheels)\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() => {\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.20.0/full/\",\n        fullStdLib: false\n      })\n      await pyodide.loadPackage([\"micropip\"])\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()\n          await pyrender(raw)\n          await init()\n          hideBusy(article)\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 (uri.length > 1000) {\n          alert(\"Code must be under a 1000 characters to generate a URL!\") // eslint-disable-line no-alert\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()\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()\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()\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 = true\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 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","fakeDOMContentLoaded","textResize","encodeuri","pyexecute","pyrender","setupPyodide","showBusy","hideBusy","handleTab","init","main","window","color_notebook","default_playground","content","concat","document","dispatchEvent","Event","bubbles","cancelable","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","next","getElementById","setAttribute","globals","set","playground_wheels","runPythonAsync","removeAttribute","stop","_callee","_x","apply","this","arguments","_ref2","text","src","_context2","notebook_wheels","value","location","hash","href","_callee2","_x2","_ref3","_callee3","_context3","loadPyodide","indexURL","fullStdLib","sent","loadPackage","target","label","relative","loaderLabel","classes","template","createElement","innerHTML","appendChild","firstChild","querySelector","removeChild","e","key","selectionStart","selectionEnd","preventDefault","start","end","length","lines","substr","split","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","_x4","_ref6","_context5","source","_callee5","_x5","_callee6","render","article","_context6","stopPropagation","el","matches","parent","getAttribute","clipboardData","setData","queryCommandSupported","textarea","textContent","position","select","execCommand","ex","_callee7","_context7","focus","_callee10","base","loc","pathname","path","_context10","startsWith","protocol","host","alert","navigator","clipboard","writeText","then","_callee8","_context8","_callee9","_context9","_callee11","form","buttons","_context11","abrupt","b","_callee12","_x3","_ref13","params","loadMsg","pageMsg","gistType","xhr","_context14","endsWith","search","has","get","trim","decodeURIComponent","XMLHttpRequest","open","onload","_callee13","_context13","readyState","status","responseText","send","err","_callee14","_x6","srcElement","tagName","_step","state","_iterator","_createForOfIteratorHelper","s","n","done","_step$value","_slicedToArray","f","document$","subscribe"],"mappings":";;giPAAA,IACMA,EACAC,EACAC,EACAC,EACAC,EACEC,EACFC,EACAC,EACAC,EACEC,EAKAC,EAiBAC,EAEAC,EAaAC,EASAC,EAeAC,EAQAC,EAYAC,EAkBAC,EAaAC,EAUAC,EAgDAC,EA+CAC,EAoNAC,EAtbFvB,EAAU,KACVC,GAAO,EACPC,EAAM,GACNC,EAAO,GACPC,EAAW,GACTC,EAAU,aACZC,GAAc,EACdC,EAAa,GACbC,GAAO,EACLC,EAAW,eAKXC,EAAN,6poBAiBMC,EAAaa,OAAOC,eAAeC,mBAEnCd,EAAa,SAAAe,GACjB,MAAA,8RAAAC,OAOFD,EAPE,iBAYId,EAAuB,WAE3BL,GAAO,EACPgB,OAAOK,SAASC,cAAc,IAAIC,MAAM,mBAAoB,CAC1DC,SAAS,EACTC,YAAY,MAIVnB,EAAa,SAAAoB,GAGjB,IAAMC,EAAaX,OAAOY,cACvBP,SAASQ,iBAAmBR,SAASS,KAAKC,YAAcV,SAASS,MAAMH,WAEpEK,EAAahB,OAAOiB,cACvBZ,SAASQ,iBAAmBR,SAASS,KAAKC,YAAcV,SAASS,MAAME,UAE1EN,EAAKQ,MAAMC,OAAS,MACpBT,EAAKQ,MAAMC,OAAYT,GAAAA,OAAAA,EAAKU,aAA5B,MAEApB,OAAOqB,SAASV,EAAYK,IAGxBzB,EAAY,SAAA+B,GAGhB,OAAOC,mBAAmBD,GAAKE,QAAQ,aAAa,SAAAC,GAClD,MAAWA,IAAAA,OAAAA,EAAEC,WAAW,GAAGC,SAAS,SAIlCnC,EAAS,WAAA,IAAAoC,EAAAC,EAAAC,IAAAC,MAAG,WAAMC,GAAN,IAAAC,EAAA,OAAAH,IAAAI,MAAA,SAAAC,GAAA,OAAA,OAAAA,EAAAC,KAAAD,EAAAE,MAAA,KAAA,EAAA,OAGVJ,EAAgB5B,SAASiC,eAAT,uBAAAlC,OAA+C4B,KACvDO,aAAa,WAAY,IACvC/D,EAAQgE,QAAQC,IAAI,SAAUT,GAC9BxD,EAAQgE,QAAQC,IAAI,SAAU,cAC9BjE,EAAQgE,QAAQC,IAAI,SAAUzC,OAAOC,eAAeyC,mBAPpCP,EAAAE,KAAA,EAQV7D,EAAQmE,eAAezD,GARb,KAAA,EAShB+C,EAAcW,gBAAgB,YATd,KAAA,EAAA,IAAA,MAAA,OAAAT,EAAAU,UAAAC,OAAH,OAAA,SAAAC,GAAA,OAAAnB,EAAAoB,MAAAC,KAAAC,YAAA,GAYTzD,EAAQ,WAAA,IAAA0D,EAAAtB,EAAAC,IAAAC,MAAG,WAAMqB,GAAN,IAAAC,EAAA,OAAAvB,IAAAI,MAAA,SAAAoB,GAAA,OAAA,OAAAA,EAAAlB,KAAAkB,EAAAjB,MAAA,KAAA,EAAA,OAGf7D,EAAQgE,QAAQC,IAAI,UAAWW,GAC/B5E,EAAQgE,QAAQC,IAAI,SAAU,YAC9BjE,EAAQgE,QAAQC,IAAI,SAAUzC,OAAOC,eAAesD,iBALrCD,EAAAjB,KAAA,EAMT7D,EAAQmE,eAAezD,GANd,KAAA,GAOTmE,EAAMhD,SAASiC,eAAe,uBAElC5D,EAAM0E,EACNC,EAAIG,MAAQJ,GAEVpD,OAAOyD,SAASC,OAElB1D,OAAOyD,SAASE,KAAO3D,OAAOyD,SAASE,MAd1B,KAAA,EAAA,IAAA,MAAA,OAAAL,EAAAT,UAAAe,OAAH,OAAA,SAAAC,GAAA,OAAAV,EAAAH,MAAAC,KAAAC,YAAA,GAkBRxD,EAAY,WAAA,IAAGoE,EAAAjC,EAAAC,IAAAC,MAAA,SAAAgC,IAAA,OAAAjC,IAAAI,MAAA,SAAA8B,GAAA,OAAA,OAAAA,EAAA5B,KAAA4B,EAAA3B,MAAA,KAAA,EAAA,GAGdvD,EAHc,CAAAkF,EAAA3B,KAAA,EAAA,MAAA,OAIjBvD,GAAc,EAJGkF,EAAA3B,KAAA,EAKD4B,YAAY,CAC1BC,SAAU,iDACVC,YAAY,IAPG,KAAA,EAAA,OAKjB3F,EALiBwF,EAAAI,KAAAJ,EAAA3B,KAAA,EASX7D,EAAQ6F,YAAY,CAAC,aATV,KAAA,EAAA,IAAA,MAAA,OAAAL,EAAAnB,UAAAkB,OAAH,OAAA,WAAA,OAAAD,EAAAd,MAAAC,KAAAC,YAAA,GAaZvD,EAAW,SAAC2E,EAAQC,EAAOC,GAG/B,IAAMC,EAAe,MAAOF,EAA2C,aAAeA,EAChFG,EAAUF,EAAW,mBAAqB,UAC1CG,EAAWtE,SAASuE,cAAc,YACxCD,EAASE,UAA2BH,eAAAA,OAAAA,8CAA2CD,EAA/E,gBACAH,EAAOQ,YAAYH,EAASxE,QAAQ4E,aAGhCnF,EAAW,SAAA0E,GAGCA,EAAOU,cAAc,aAEnCV,EAAOW,YAAYX,EAAOU,cAAc,cA2CtCnF,EAAY,SAAAqF,GAGhB,GAAc,QAAVA,EAAEC,IAAe,CACnB,IAAMb,EAASY,EAAEZ,OAEjB,GAAIA,EAAOc,iBAAmBd,EAAOe,aAAc,CACjDH,EAAEI,iBAOF,IALA,IAAIC,EAAQjB,EAAOc,eACfI,EAAMlB,EAAOe,aAEXjC,EAAOkB,EAAOd,MAEb+B,EAAQ,GAAyB,OAApBnC,EAAKmC,EAAQ,IAC/BA,IAEF,KAAOC,EAAM,GAAuB,OAAlBpC,EAAKoC,EAAM,IAAeA,EAAMpC,EAAKqC,QACrDD,IAKF,IAFA,IAAIE,EAAQtC,EAAKuC,OAAOJ,EAAOC,EAAMD,GAAOK,MAAM,MAEzCC,EAAI,EAAGA,EAAIH,EAAMD,OAAQI,IAG5BA,IAAMH,EAAMD,OAAS,GAAyB,IAApBC,EAAMG,GAAGJ,SAKnCP,EAAEY,SACJJ,EAAMG,GAAKH,EAAMG,GAAGrE,QAAQvC,EAAU,IAEtCyG,EAAMG,iBAAYH,EAAMG,KAG5BH,EAAQA,EAAMK,KAAK,MAGnBzB,EAAOd,MAAQJ,EAAKuC,OAAO,EAAGJ,GAASG,EAAQtC,EAAKuC,OAAOH,GAC3DlB,EAAOc,eAAiBG,EACxBjB,EAAOe,aAAeE,EAAQG,EAAMD,UAKpC3F,EAAI,WAAA,IAAAkG,EAAAnE,EAAAC,IAAAC,MAAG,WAAMkE,GAAN,IAAAC,EAAA,OAAApE,IAAAI,MAAA,SAAAiE,GAAA,OAAA,OAAAA,EAAA/D,KAAA+D,EAAA9D,MAAA,KAAA,EAGL6D,EAAW7F,SAASiC,eAAe,qBACrBjC,SAAS+F,iBAAiB,eAClCC,SAAQ,SAAAC,GAElB,IAAMtE,EAAYsE,EAAGC,GAAG/E,QAAQ3C,EAAS,MACnC2H,EAASnG,SAASiC,eAAT,uBAAAlC,OAA+C4B,IACxDyE,EAAUpG,SAASiC,eAAT,wBAAAlC,OAAgD4B,IAC1D0E,EAASrG,SAASiC,eAAT,qBAAAlC,OAA6C4B,IACtD2E,EAAatG,SAAS2E,cAAT,4BAAA5E,OAAmD4B,IAChE4E,EAAcvG,SAAS2E,cAAT,6BAAA5E,OAAoD4B,IAClE6E,EAAYxG,SAAS2E,cAAT,2BAAA5E,OAAkD4B,IAC9D8E,EAAezG,SAAS2E,cAAT,8BAAA5E,OAAqD4B,IAU1E,GARAwE,EAAOO,iBAAiB,SAAS,WAG/BzH,EAAWkH,MAGbA,EAAOO,iBAAiB,UAAWlH,GAE/BqG,GAAYD,EAAO,CACrB,IAAMe,EAAgB3G,SAASiC,eAAe,oBAE9C0E,EAAcD,iBAAiB,SAAS,SAAA7B,GAGtC5F,EAAW4F,EAAEZ,WAGf0C,EAAcD,iBAAiB,UAAWlH,GAEzBQ,SAASiC,eAAe,mBAChCyE,iBAAiB,SAAS,WACjCnI,EAASoI,EAAcT,IAAMS,EAAcxD,MAC3CnD,SAASiC,eAAe,qBAAqB2E,UAAUC,OAAO,UAC9D7G,SAASiC,eAAe,qBAAqB2E,UAAUC,OAAO,UAC9D5H,EAAWe,SAASiC,eAAe,wBAGrCjC,SAASiC,eAAe,sBAAsByE,iBAAiB,QAA/D,WAAA,IAAAI,EAAAtF,EAAAC,IAAAC,MAAwE,WAAMmD,GAAN,IAAA5D,EAAA,OAAAQ,IAAAI,MAAA,SAAAkF,GAAA,OAAA,OAAAA,EAAAhF,KAAAgF,EAAA/E,MAAA,KAAA,EAE1D,QADRf,EAAM+F,OAAO,iDAAkD1I,MAEjE2C,EAAM/B,EAAU+B,GAChB4D,EAAEI,iBACFgC,QAAQC,UAAU,CAACrB,SAAU5E,GAAM,GAAQ,IAAAlB,OAAA,IAAIoH,gBAA4BlG,YAAAA,OAAAA,IAAOK,aAClF5B,GAAK,IAN+D,KAAA,EAAA,IAAA,MAAA,OAAAqH,EAAAvE,UAAA4E,OAAxE,OAAA,SAAAC,GAAA,OAAAP,EAAAnE,MAAAC,KAAAC,YAAA,IAUA7C,SAASiC,eAAe,sBAAsByE,iBAAiB,QAA/D,WAAA,IAAAY,EAAA9F,EAAAC,IAAAC,MAAwE,WAAMmD,GAAN,IAAA5D,EAAA,OAAAQ,IAAAI,MAAA,SAAA0F,GAAA,OAAA,OAAAA,EAAAxF,KAAAwF,EAAAvF,MAAA,KAAA,EAE1D,QADRf,EAAM+F,OAAO,mDAAoD1I,MAEnE2C,EAAM/B,EAAU+B,GAChB4D,EAAEI,iBACFgC,QAAQC,UAAU,CAACM,OAAQvG,GAAM,GAAQ,IAAAlB,OAAA,IAAIoH,gBAA0BlG,UAAAA,OAAAA,IAAOK,aAC9E5B,GAAK,IAN+D,KAAA,EAAA,IAAA,MAAA,OAAA6H,EAAA/E,UAAAiF,OAAxE,OAAA,SAAAC,GAAA,OAAAJ,EAAA3E,MAAAC,KAAAC,YAAA,IAUA7C,SAASiC,eAAe,oBAAoBkB,MAAQ9E,EACpD2B,SAASiC,eAAe,qBAAqByE,iBAAiB,SAAS,WACrEC,EAAcxD,MAAQ5E,EAASoI,EAAcT,WACtC3H,EAASoI,EAAcT,IAC9BlG,SAASiC,eAAe,qBAAqB2E,UAAUC,OAAO,UAC9D7G,SAASiC,eAAe,qBAAqB2E,UAAUC,OAAO,aAGhE7G,SAASiC,eAAe,qBAAqByE,iBAAiB,QAASlF,EAAAC,IAAAC,MAAA,SAAAiG,IAAA,IAAAC,EAAAC,EAAA,OAAApG,IAAAI,MAAA,SAAAiG,GAAA,OAAA,OAAAA,EAAA/F,KAAA+F,EAAA9F,MAAA,KAAA,EAAA,OAC/D4F,EAAS5H,SAASiC,eAAe,qBACvC5D,EAAM2B,SAASiC,eAAe,oBAAoBkB,MAClDyE,EAAOhB,UAAUC,OAAO,UACxB7G,SAASiC,eAAe,qBAAqB2E,UAAUC,OAAO,UACxDgB,EAAU7H,SAAS2E,cAAc,WACvCrF,EAASuI,EAAS,uBAClBD,EAAOpD,UAAY,GACnBjG,EAAW,GAR0DuJ,EAAA9F,KAAA,GAS/D3C,IAT+D,KAAA,GAAA,OAAAyI,EAAA9F,KAAA,GAU/D5C,EAASf,GAVsD,KAAA,GAAA,OAAAyJ,EAAA9F,KAAA,GAW/DvC,IAX+D,KAAA,GAYrEF,EAASsI,GAZ4D,KAAA,GAAA,IAAA,MAAA,OAAAC,EAAAtF,UAAAmF,QAgBzExB,EAAOO,iBAAiB,aAAa,SAAA7B,GAGnCA,EAAEkD,qBAGJ3B,EAAQM,iBAAiB,SAAS,SAAA7B,GAGhC,IAAMmD,EAAKnD,EAAEZ,OACb,GAAI+D,EAAGC,QAAQ,qBAAsB,CACnC,IAAInI,EAAU,GACRoI,EAASF,EAAGtH,WAClB,IAAKwH,EAAOD,QAAQ,wBAAyB,CAG3C,GADAnI,GADAA,EAAUoI,EAAOC,aAAa,SAAShH,QAAQ,oBAAqB,KAClDA,QAAQ,KAAM,IAC5BxB,OAAOyI,eAAiBzI,OAAOyI,cAAcC,QAE/C,OAAO1I,OAAOyI,cAAcC,QAAQ,OAAQvI,GACvC,GAAIE,SAASsI,uBAAyBtI,SAASsI,sBAAsB,QAAS,CACnF,IAAMC,EAAWvI,SAASuE,cAAc,YACxCgE,EAASC,YAAc1I,EACvByI,EAAS1H,MAAM4H,SAAW,QAC1BzI,SAASS,KAAKgE,YAAY8D,GAC1BA,EAASG,SACT,IACE,OAAO1I,SAAS2I,YAAY,QAC5B,MAAOC,GACP,OAAO5B,OAAO,mCAAoClH,GAC1C,QACRE,SAASS,KAAKmE,YAAY2D,UAOpCjC,EAAWI,iBAAiB,QAASlF,EAAAC,IAAAC,MAAA,SAAAmH,IAAA,OAAApH,IAAAI,MAAA,SAAAiH,GAAA,OAAA,OAAAA,EAAA/G,KAAA+G,EAAA9G,MAAA,KAAA,EAGnCzD,EAASoD,GAAawE,EAAOhD,MAC7BkD,EAAOO,UAAUC,OAAO,UACxBT,EAAQQ,UAAUC,OAAO,UACzBL,EAAUI,UAAUC,OAAO,UAC3BJ,EAAaG,UAAUC,OAAO,UAC9BP,EAAWM,UAAUC,OAAO,UAC5BN,EAAYK,UAAUC,OAAO,UAC7B5H,EAAWkH,GACXA,EAAO4C,QAX4B,KAAA,EAAA,IAAA,MAAA,OAAAD,EAAAtG,UAAAqG,QAcrCtC,EAAYG,iBAAiB,QAASlF,EAAAC,IAAAC,MAAA,SAAAsH,IAAA,IAAAC,EAAAhI,EAAAiI,EAAAC,EAAAC,EAAA,OAAA3H,IAAAI,MAAA,SAAAwH,GAAA,OAAA,OAAAA,EAAAtH,KAAAsH,EAAArH,MAAA,KAAA,EAG9BiH,EAAOtJ,OAAOyD,SAAS+F,SAAS5D,MAAM,KAAK,GAC3CtE,EAAM/B,EAAUiH,EAAOhD,OACvB+F,EAAMvJ,OAAOyD,SACf+F,EAAW,eACXD,EAAIC,SAASG,WAAeL,IAAAA,OAAAA,UAC9BE,EAAQ,IAAApJ,OAAOkJ,EAAf,iBAEIG,EAV8B,GAAArJ,OAUpBmJ,EAAIK,SAVgB,MAAAxJ,OAUHmJ,EAAIM,MAVDzJ,OAUQoJ,EAVR,UAAApJ,OAUyBkB,GACzDA,EAAImE,OAAS,IACfqE,MAAM,2DAENC,UAAUC,UAAUC,UAAUR,GAAMS,KAAKrI,EAAAC,IAAAC,MAAA,SAAAoI,IAAA,OAAArI,IAAAI,MAAA,SAAAkI,GAAA,OAAA,OAAAA,EAAAhI,KAAAgI,EAAA/H,MAAA,KAAA,EACvCyH,MAAM,+BADiC,KAAA,EAAA,IAAA,MAAA,OAAAM,EAAAvH,UAAAsH,OAEtCtI,EAAAC,IAAAC,MAAA,SAAAsI,IAAA,OAAAvI,IAAAI,MAAA,SAAAoI,GAAA,OAAA,OAAAA,EAAAlI,KAAAkI,EAAAjI,MAAA,KAAA,EACDyH,MAAM,kCADL,KAAA,EAAA,IAAA,MAAA,OAAAQ,EAAAzH,UAAAwH,QAhB+B,KAAA,EAAA,IAAA,MAAA,OAAAX,EAAA7G,UAAAwG,QAsBtCxC,EAAUE,iBAAiB,QAASlF,EAAAC,IAAAC,MAAA,SAAAwI,IAAA,IAAAC,EAAAC,EAAA,OAAA3I,IAAAI,MAAA,SAAAwI,GAAA,OAAA,OAAAA,EAAAtI,KAAAsI,EAAArI,MAAA,KAAA,EAAA,IAG9B5D,EAH8B,CAAAiM,EAAArI,KAAA,EAAA,MAAA,OAAAqI,EAAAC,OAAA,UAAA,KAAA,EAAA,OAOlClM,GAAO,EAED+L,EAAO9D,EAAO1B,cAAc,QAClCrF,EAAS6K,EAAM,MAAM,IACfC,EAAUpK,SAAS+F,iBAAiB,iCAExCqE,EAAQpE,SAAQ,SAAAuE,GACdA,EAAErI,aAAa,WAAY,OAdGmI,EAAArI,KAAA,EAiB5B3C,IAjB4B,KAAA,EAAA,OAkBlC+G,EAAQzB,cAAc,QAAQH,UAAY,GAlBR6F,EAAArI,KAAA,GAmB5B7C,EAAUwC,GAnBkB,KAAA,GAoB9ByI,GACFA,EAAQpE,SAAQ,SAAAuE,GACdA,EAAEhI,gBAAgB,eAGtBhD,EAAS4K,GACT9D,EAAOO,UAAUC,OAAO,UACxBT,EAAQQ,UAAUC,OAAO,UACzBP,EAAWM,UAAUC,OAAO,UAC5BN,EAAYK,UAAUC,OAAO,UAC7BL,EAAUI,UAAUC,OAAO,UAC3BJ,EAAaG,UAAUC,OAAO,iBAEvBtI,EAASoD,GAChBvD,GAAO,EAlC2B,KAAA,GAAA,IAAA,MAAA,OAAAiM,EAAA7H,UAAA0H,QAqCpCzD,EAAaC,iBAAiB,SAAS,WAGrCP,EAAOhD,MAAQ5E,EAASoD,UACjBpD,EAASoD,GAChB0E,EAAOO,UAAUC,OAAO,UACxBT,EAAQQ,UAAUC,OAAO,UACzBP,EAAWM,UAAUC,OAAO,UAC5BN,EAAYK,UAAUC,OAAO,UAC7BL,EAAUI,UAAUC,OAAO,UAC3BJ,EAAaG,UAAUC,OAAO,gBA/MvB,KAAA,EAAA,IAAA,MAAA,OAAAf,EAAAtD,UAAAgI,OAAH,OAAA,SAAAC,GAAA,OAAA9E,EAAAhD,MAAAC,KAAAC,YAAA,GAoNJnD,EAAI,WAAA,IAAAgL,EAAAlJ,EAAAC,IAAAC,MAAG,WAAMkE,GAAN,IAAA+E,EAAAC,EAAAC,EAAA5J,EAAA4G,EAAAiD,EAAA3H,EAAA4H,EAAAjL,EAAA,OAAA2B,IAAAI,MAAA,SAAAmJ,GAAA,OAAA,OAAAA,EAAAjJ,KAAAiJ,EAAAhJ,MAAA,KAAA,EAAA,GAKXzD,EAAW,IAEPoB,OAAOyD,SAAS+F,SAAS8B,SAAS,gBAP3B,CAAAD,EAAAhJ,KAAA,GAAA,MAAA,GAQH2I,EAAS,IAAIxD,gBAAgBxH,OAAOyD,SAAS8H,QAC7CN,EAAU,qBACVC,EAAU,sBACV5J,EAAM0J,EAAOQ,IAAI,UAAYR,EAAOS,IAAI,UAAYT,EAAOS,IAAI,YAC/DvD,EAAU7H,SAAS2E,cAAc,WAC3B,OAAR1D,IAAgBA,EAAIoK,OAbf,CAAAL,EAAAhJ,KAAA,GAAA,MAAA,OAeP1C,EAASuI,EAAS+C,GAfXI,EAAAhJ,KAAA,GAgBD3C,IAhBC,KAAA,GAiBPE,EAASsI,GACTvI,EAASuI,EAASgD,GAClB,IACQC,EAAWH,EAAOQ,IAAI,UAAY,SAAW,WACnDzM,EAAa4M,mBAAmBX,EAAOrJ,YACnC6B,EAAQ,GACN4H,EAAM,IAAIQ,eAChBjN,EAAO2C,EACP8J,EAAIS,KAAK,MAAOvK,GAAK,GACrB8J,EAAIU,OAASjK,EAAAC,IAAAC,MAAA,SAAAgK,IAAA,OAAAjK,IAAAI,MAAA,SAAA8J,GAAA,OAAA,OAAAA,EAAA5J,KAAA4J,EAAA3J,MAAA,KAAA,EAAA,OAEY,IAAnB+I,EAAIa,YACa,MAAfb,EAAIc,SACN1I,EAAQ4H,EAAIe,cAIC,WAAbhB,IACF3H,EAAQpE,EAAWoE,IATVwI,EAAA3J,KAAA,EAWL5C,EAAS+D,GAXJ,KAAA,EAAA,OAAAwI,EAAA3J,KAAA,EAYLvC,EAAKmG,GAZA,KAAA,EAaXrG,EAASsI,GACT7I,IAdW,KAAA,EAAA,IAAA,MAAA,OAAA2M,EAAAnJ,UAAAkJ,OAgBbX,EAAIgB,OACJ,MAAOC,IA3CFhB,EAAAhJ,KAAA,GAAA,MAAA,KAAA,GAAA,OA6CP1D,EAAO,GACDwB,EAAUf,EAAW4L,EAAOQ,IAAI,QAAUR,EAAOS,IAAI,QAAUtM,GACrEJ,EAAa4M,mBAAmBX,EAAOrJ,YACvChC,EAASuI,EAAS+C,GAhDXI,EAAAhJ,KAAA,GAiDD3C,IAjDC,KAAA,GAAA,OAkDPE,EAASsI,GACTvI,EAASuI,EAASgD,GAnDXG,EAAAhJ,KAAA,GAoDD5C,EAASU,GApDR,KAAA,GAAA,OAAAkL,EAAAhJ,KAAA,GAqDDvC,EAAKmG,GArDJ,KAAA,GAsDPrG,EAASsI,GACT7I,IAvDO,KAAA,GAAAgM,EAAAhJ,KAAA,GAAA,MAAA,KAAA,GA0DT1D,EAAO,GACPI,EAAa,GACbe,EAAKmG,GA5DI,KAAA,GAAA,IAAA,MAAA,OAAAoF,EAAAxI,UAAAyJ,OAAH,OAAA,SAAAC,GAAA,OAAAxB,EAAA/H,MAAAC,KAAAC,YAAA,GAiEV7C,SAAS0G,iBAAiB,SA7VE,SAAA7B,GAG1B,IAAMoE,EAAOtJ,OAAOyD,SAAS+F,SAAS5D,MAAM,KAAK,GAC3CtB,EAASY,EAAEZ,QAAUY,EAAEsH,WAC7B,GAAuB,MAAnBlI,EAAOmI,SAAmB1M,GAE1BuE,EAAOkE,aAAa,SACpBlE,EAAOuF,OAAS7J,OAAOyD,SAASoG,MAChC7J,OAAOyD,SAAS+F,WAAhB,IAAApJ,OAAiCkJ,EAFjC,kBAGAtJ,OAAOyD,SAAS+F,WAAalF,EAAOkF,UACpCxJ,OAAOyD,SAAS8H,SAAWjH,EAAOiH,OAClC,CACArG,EAAEI,iBACF,IAFAoH,EAGMC,EAAQ,GAHdC,koBAAAC,CAEe,IAAIrF,gBAAgBlD,EAAOiH,SAF1C,IAIA,IAAmCqB,EAAAE,MAAAJ,EAAAE,EAAAG,KAAAC,MAAA,CAAA,IAAAC,EAAAC,EAAAR,EAAAlJ,MAAA,GAAvB2B,EAAuB8H,EAAA,GAAlBzJ,EAAkByJ,EAAA,GACjCN,EAAMxH,GAAO3B,GALf,MAAA6I,GAAAO,EAAA1H,EAAAmH,GAAA,QAAAO,EAAAO,IAOA7F,QAAQC,UAAUoF,EAAO,GAAIrI,EAAOX,MACpC5D,GAAK,OA4UXC,OAAO+G,iBAAiB,YA9WP,WAGf,IAAMuC,EAAOtJ,OAAOyD,SAAS+F,SAAS5D,MAAM,KAAK,GAE/C5F,OAAOyD,SAAS+F,WAAiBF,IAAAA,OAAAA,mBAEjBqC,mBAAmB,IAAInE,gBAAgBxH,OAAOyD,SAAS8H,QAAQ5J,cAC/D5C,GACdgB,GAAK,MAwWXC,OAAO+G,iBAAiB,UAAU,WAChC/H,GAAO,KAITgB,OAAOoN,UAAUC,WAAU,WAGrBrO,EACFA,GAAO,EAGTe,GAAK"}
\ No newline at end of file
+{"version":3,"file":"extra-notebook-1d173e7b.js","sources":["../../../src/js/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\nimport micropip\nfrom js import location\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\nbase = location.pathname.lstrip('/').split('/')[0]\nawait micropip.install([location.origin + '/{}/playground/{}'.format(base, w) for w in wheels])\ncallback()\n`\n\n  const defContent = window.color_notebook.default_playground\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  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  }\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    pyodide.globals.set('wheels', window.color_notebook.playground_wheels)\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    pyodide.globals.set('wheels', window.color_notebook.notebook_wheels)\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() => {\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.20.0/full/\",\n        fullStdLib: false\n      })\n      await pyodide.loadPackage([\"micropip\"])\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()\n          await pyrender(raw)\n          await init()\n          hideBusy(article)\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 (uri.length > 1000) {\n          alert(\"Code must be under a 1000 characters to generate a URL!\") // eslint-disable-line no-alert\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()\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()\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()\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 = true\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 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","fakeDOMContentLoaded","textResize","encodeuri","pyexecute","pyrender","setupPyodide","showBusy","hideBusy","handleTab","init","main","window","color_notebook","default_playground","content","concat","document","dispatchEvent","Event","bubbles","cancelable","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","next","getElementById","setAttribute","globals","set","playground_wheels","runPythonAsync","removeAttribute","stop","_callee","_x","apply","this","arguments","_ref2","text","src","_context2","notebook_wheels","value","location","hash","href","_callee2","_x2","_ref3","_callee3","_context3","loadPyodide","indexURL","fullStdLib","sent","loadPackage","target","label","relative","loaderLabel","classes","template","createElement","innerHTML","appendChild","firstChild","querySelector","removeChild","e","key","selectionStart","selectionEnd","preventDefault","start","end","length","lines","substr","split","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","_x4","_ref6","_context5","source","_callee5","_x5","_callee6","render","article","_context6","stopPropagation","el","matches","parent","getAttribute","clipboardData","setData","queryCommandSupported","textarea","textContent","position","select","execCommand","ex","_callee7","_context7","focus","_callee10","base","loc","pathname","path","_context10","startsWith","protocol","host","alert","navigator","clipboard","writeText","then","_callee8","_context8","_callee9","_context9","_callee11","form","buttons","_context11","abrupt","b","_callee12","_x3","_ref13","params","loadMsg","pageMsg","gistType","xhr","_context14","endsWith","search","has","get","trim","decodeURIComponent","XMLHttpRequest","open","onload","_callee13","_context13","readyState","status","responseText","send","err","_callee14","_x6","srcElement","tagName","_step","state","_iterator","_createForOfIteratorHelper","s","n","done","_step$value","_slicedToArray","f","document$","subscribe"],"mappings":"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"}
\ No newline at end of file
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js b/docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js
deleted file mode 100644
index 80f50d4c6..000000000
--- a/docs/theme/assets/coloraide-extras/extra-notebook-bb8e4037.js
+++ /dev/null
@@ -1,4 +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 P()}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=_&&_(_(I([])));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 I(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:P}}function P(){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=I,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:I(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(){
-/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
-e=function(){return t};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 p?t:p,a=Object.create(r.prototype),i=new k(o||[]);return a._invoke=function(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 S()}for(n.method=r,n.arg=a;;){var i=n.delegate;if(i){var s=x(i,n);if(s){if(s===d)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=u(e,t,n);if("normal"===c.type){if(o=n.done?"completed":"suspendedYield",c.arg===d)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(o="completed",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}}}t.wrap=l;var d={};function p(){}function f(){}function m(){}var h={};c(h,a,(function(){return this}));var y=Object.getPrototypeOf,g=y&&y(y(L([])));g&&g!==n&&o.call(g,a)&&(h=g);var v=m.prototype=p.prototype=Object.create(h);function w(e){["next","throw","return"].forEach((function(t){c(e,t,(function(e){return this._invoke(t,e)}))}))}function _(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 x(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,x(e,t),"throw"===t.method))return d;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var o=u(n,e.iterator,t.arg);if("throw"===o.type)return t.method="throw",t.arg=o.arg,t.delegate=null,d;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,d):r:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,d)}function b(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 E(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function k(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(b,this),this.reset(!0)}function L(e){if(e){var t=e[a];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:S}}function S(){return{value:void 0,done:!0}}return f.prototype=m,c(v,"constructor",m),c(m,"constructor",f),f.displayName=c(m,s,"GeneratorFunction"),t.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===f||"GeneratorFunction"===(t.displayName||t.name))},t.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,m):(e.__proto__=m,c(e,s,"GeneratorFunction")),e.prototype=Object.create(v),e},t.awrap=function(e){return{__await:e}},w(_.prototype),c(_.prototype,i,(function(){return this})),t.AsyncIterator=_,t.async=function(e,n,o,r,a){void 0===a&&(a=Promise);var i=new _(l(e,n,o,r),a);return t.isGeneratorFunction(n)?i:i.next().then((function(e){return e.done?e.value:i.next()}))},w(v),c(v,s,"Generator"),c(v,a,(function(){return this})),c(v,"toString",(function(){return"[object Generator]"})),t.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}},t.values=L,k.prototype={constructor:k,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(E),!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,d):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),d},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),E(n),d}},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;E(n)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:L(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),d}},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}var i,s,c,l,u,d,p,f,m,h,y,g,v,w,_,x,b,E,k,L,S,C,T,I;i=null,s=!1,c="",l="",u={},d=/.*?_(\d+)$/,p=!1,f="",m=!1,h=/^( {1,4}|\t)/,y="\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\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    from coloraide import Color\n    from coloraide.interpolate import Interpolator\n    try:\n        from coloraide_extras import Color as Color2\n    except ImportError:\n        Color2 = Color\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, Color2(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 Color2(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, Color2(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, Color2(x)))\n                except Exception:\n                    pass\n    return colors\n\n\ndef find_colors(text):\n    \"\"\"Find colors in text buffer.\"\"\"\n\n    try:\n        from coloraide_extras import Color\n    except ImportError:\n        from coloraide import Color\n\n    colors = []\n    for m in RE_COLOR_START.finditer(text):\n        start = m.start()\n        mcolor = Color.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    import coloraide\n    try:\n        import coloraide_extras\n    except ImportError:\n        coloraide_extras = None\n\n    g = {\n        'Color': coloraide.Color,\n        'coloraide': coloraide,\n        'NaN': coloraide.NaN,\n        'stop': coloraide.stop,\n        'hint': coloraide.hint,\n        'HtmlRow': HtmlRow,\n        'HtmlSteps': HtmlSteps,\n        'HtmlGradient': HtmlGradient\n    }\n\n    if coloraide_extras is not None:\n        g['coloraide_extras'] = coloraide_extras\n        g['Color'] = coloraide_extras.Color\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    from pygments import highlight\n    from pygments.lexers import get_lexer_by_name\n    from pygments.formatters import find_formatter_class\n    HtmlFormatter = find_formatter_class('html')\n\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            base_classes = \"swatch\"\n            for color in item:\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    try:\n        from coloraide_extras import Color\n    except ImportError:\n        from coloraide import Color\n\n    try:\n        result = src.strip()\n\n        try:\n            color = Color(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\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    ]\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.superfences': {\n            'preserve_tabs': True,\n            'custom_fences': [\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    }\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\nimport micropip\nfrom js import location\n\naction = globals().get('action')\nif action == 'notebook':\n    callback = render_notebook\nelse:\n    callback = render_console\n\nbase = location.pathname.lstrip('/').split('/')[0]\nawait micropip.install([location.origin + '/{}/playground/{}'.format(base, w) for w in wheels])\ncallback()\n",g=window.color_notebook.default_playground,v=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")},w=function(){m=!0,window.document.dispatchEvent(new Event("DOMContentLoaded",{bubbles:!0,cancelable:!0}))},_=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)},x=function(e){return encodeURIComponent(e).replace(/[.!'()*]/g,(function(e){return"%".concat(e.charCodeAt(0).toString(16))}))},b=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",""),i.globals.set("id_num",n),i.globals.set("action","playground"),i.globals.set("wheels",window.color_notebook.playground_wheels),e.next=7,i.runPythonAsync(y);case 7:o.removeAttribute("readonly");case 8:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),E=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 i.globals.set("content",n),i.globals.set("action","notebook"),i.globals.set("wheels",window.color_notebook.notebook_wheels),e.next=5,i.runPythonAsync(y);case 5:(o=document.getElementById("__notebook-input"))&&(c=n,o.value=n),window.location.hash&&(window.location.href=window.location.href);case 8:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),k=function(){var t=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(p){e.next=7;break}return p=!0,e.next=4,loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.20.0/full/",fullStdLib:!1});case 4:return i=e.sent,e.next=7,i.loadPackage(["micropip"]);case 7:case"end":return e.stop()}}),t)})));return function(){return t.apply(this,arguments)}}(),L=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)},S=function(e){e.querySelector(".loading")&&e.removeChild(e.querySelector(".loading"))},C=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(h,""):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}}},T=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(d,"$1"),i=document.getElementById("__playground-inputs_".concat(a)),p=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(){_(i)})),i.addEventListener("keydown",C),r&&o){var v=document.getElementById("__notebook-input");v.addEventListener("input",(function(e){_(e.target)})),v.addEventListener("keydown",C),document.getElementById("__notebook-edit").addEventListener("click",(function(){u[v.id]=v.value,document.getElementById("__notebook-render").classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),_(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:",l))&&(o=x(o),n.preventDefault(),history.pushState({notebook:o},"","?".concat(new URLSearchParams("notebook=".concat(o)).toString())),I(!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:",l))&&(o=x(o),n.preventDefault(),history.pushState({source:o},"","?".concat(new URLSearchParams("source=".concat(o)).toString())),I(!1));case 2:case"end":return e.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),document.getElementById("__notebook-input").value=c,document.getElementById("__notebook-cancel").addEventListener("click",(function(){v.value=u[v.id],delete u[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"),c=document.getElementById("__notebook-input").value,n.classList.toggle("hidden"),document.getElementById("__notebook-source").classList.toggle("hidden"),o=document.querySelector("article"),L(o,"Loading Notebook..."),n.innerHTML="",u={},e.next=10,k();case 10:return e.next=12,E(c);case 12:return e.next=14,T();case 14:S(o);case 15:case"end":return e.stop()}}),t)}))))}i.addEventListener("touchmove",(function(e){e.stopPropagation()})),p.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:u[a]=i.value,f.classList.toggle("hidden"),p.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),_(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=x(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),r.length>1e3?alert("Code must be under a 1000 characters to generate a URL!"):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(!s){e.next=2;break}return e.abrupt("return");case 2:return s=!0,n=f.querySelector("form"),L(n,null,!0),(o=document.querySelectorAll(".playground .playground-run"))&&o.forEach((function(e){e.setAttribute("disabled","")})),e.next=9,k();case 9:return p.querySelector("code").innerHTML="",e.next=12,b(a);case 12:o&&o.forEach((function(e){e.removeAttribute("disabled")})),S(n),f.classList.toggle("hidden"),p.classList.toggle("hidden"),m.classList.toggle("hidden"),h.classList.toggle("hidden"),y.classList.toggle("hidden"),g.classList.toggle("hidden"),delete u[a],s=!1;case 22:case"end":return e.stop()}}),t)})))),g.addEventListener("click",(function(){i.value=u[a],delete u[a],f.classList.toggle("hidden"),p.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)}}(),I=function(){var t=n(e().mark((function t(o){var r,a,i,s,c,d,p,m,h;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(u={},!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 L(c,a),t.next=11,k();case 11:S(c),L(c,i);try{d=r.has("source")?"source":"notebook",f=decodeURIComponent(r.toString()),p="",m=new XMLHttpRequest,l=s,m.open("GET",s,!0),m.onload=n(e().mark((function t(){return e().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return 4===m.readyState&&200===m.status&&(p=m.responseText),"source"===d&&(p=v(p)),e.next=4,E(p);case 4:return e.next=6,T(o);case 6:S(c),w();case 8:case"end":return e.stop()}}),t)}))),m.send()}catch(e){}t.next=30;break;case 16:return l="",h=v(r.has("code")?r.get("code"):g),f=decodeURIComponent(r.toString()),L(c,a),t.next=22,k();case 22:return S(c),L(c,i),t.next=26,E(h);case 26:return t.next=28,T(o);case 28:S(c),w();case 30:t.next=35;break;case 32:l="",f="",T(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&&I&&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 a,i={},s=function(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}}}}(new URLSearchParams(n.search));try{for(s.s();!(a=s.n()).done;){var c=o(a.value,2),l=c[0],u=c[1];i[l]=u}}catch(e){s.e(e)}finally{s.f()}history.pushState(i,"",n.href),I(!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())!==f&&I(!1)})),window.addEventListener("unload",(function(){m=!0})),window.document$.subscribe((function(){m?m=!1:I(!0)}))}();
-//# sourceMappingURL=extra-notebook-bb8e4037.js.map
diff --git a/docs/theme/playground-config-e4040a76.js b/docs/theme/playground-config-50c86242.js
similarity index 78%
rename from docs/theme/playground-config-e4040a76.js
rename to docs/theme/playground-config-50c86242.js
index 2da3fa92e..2f753d2a7 100644
--- a/docs/theme/playground-config-e4040a76.js
+++ b/docs/theme/playground-config-50c86242.js
@@ -1,5 +1,5 @@
 var color_notebook = {
-    "playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.0a1-py3-none-any.whl'],
-    "notebook_wheels": ['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.0a1-py3-none-any.whl'],
+    "playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.0b1-py3-none-any.whl'],
+    "notebook_wheels": ['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.0b1-py3-none-any.whl'],
     "default_playground": "import coloraide\ncoloraide.__version__\nColor('red')"
 }
diff --git a/mkdocs.yml b/mkdocs.yml
index 6346e2512..694c5406e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -70,6 +70,10 @@ markdown_extensions:
   - pymdownx.superfences:
       preserve_tabs: true
       custom_fences:
+        # Mermaid diagrams
+        - name: diagram
+          class: diagram
+          format: !!python/name:pymdownx.superfences.fence_code_format
         - name: playground
           class: playground
           format: !!python/name:docs.src.py.notebook.color_command_formatter
@@ -124,9 +128,10 @@ markdown_extensions:
 extra_css:
   - assets/coloraide-extras/extra-753860a807.css
 extra_javascript:
-  - playground-config-e4040a76.js
+  - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
+  - playground-config-50c86242.js
   - https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js
-  - assets/coloraide-extras/extra-notebook-bb8e4037.js
+  - assets/coloraide-extras/extra-notebook-1d173e7b.js
 
 extra:
   social:
diff --git a/tools/calc_igpgtg_matrices.py b/tools/calc_igpgtg_matrices.py
new file mode 100644
index 000000000..f9b2ac26c
--- /dev/null
+++ b/tools/calc_igpgtg_matrices.py
@@ -0,0 +1,47 @@
+"""
+Calculate IgPgTg matrices.
+
+https://www.ingentaconnect.com/content/ist/jpi/2020/00000003/00000002/art00002#
+"""
+import sys
+import os
+
+sys.path.insert(0, os.getcwd())
+
+from coloraide import algebra as alg  # noqa: E402
+
+m1 = [
+    [2.968, 2.741, -0.649],
+    [1.237, 5.969, -0.173],
+    [-0.318, 0.387, 2.311]
+]
+
+m2 = [
+    [0.117, 1.464, 0.130],
+    [8.285, -8.361, 21.40],
+    [-1.208, 2.412, -36.53]
+]
+
+
+def pprint(value):
+    """Print the matrix."""
+    print('[', end='')
+    first = True
+    for v in value:
+        if first:
+            first = False
+        else:
+            print(',\n ', end='')
+        print(v, end='')
+    print(']')
+
+
+if __name__ == "__main__":
+    print('===== XYZ to LMS =====')
+    pprint(m1)
+    print('===== LMS to XYZ =====')
+    pprint(alg.inv(m1))
+    print('===== LMS to IgPgTg =====')
+    pprint(m2)
+    print('===== IgPgTg to LMS =====')
+    pprint(alg.inv(m2))
diff --git a/tools/calc_ipt_matrices.py b/tools/calc_ipt_matrices.py
new file mode 100644
index 000000000..560c94754
--- /dev/null
+++ b/tools/calc_ipt_matrices.py
@@ -0,0 +1,96 @@
+"""
+Calculate IPT matrices.
+
+https://scholarworks.rit.edu/cgi/viewcontent.cgi?article=3862&context=theses
+"""
+import sys
+import os
+
+sys.path.insert(0, os.getcwd())
+
+from coloraide import algebra as alg  # noqa: E402
+from coloraide import util  # noqa: E402
+
+
+white_d65 = util.xy_to_xyz((0.31270, 0.32900))
+
+
+def white_space_fixup(m):
+    """
+    Fix up the IPT white space.
+
+    The IPT paper noted that the D65 white point that they use was [0.9504, 1.0, 1.0889].
+    Our D65 white point is slightly different, so actual precision of the color space breaks down
+    and doesn't match implementations that do use the above white point.
+
+    If we look at Colour Science, we can see that when using the above white point, that
+    precision around white is much better:
+
+    ```
+    >>> import colour
+    >>> import numpy as np
+    >>> XYZ = np.array([0.9504, 1.0, 1.0889])
+    >>> colour.XYZ_to_IPT(XYZ).tolist()
+    [0.9999910919149724, 6.691134620062655e-05, -3.9005477081299755e-05]
+    ```
+
+    But what we got was:
+
+    ```
+    >>> from coloraide_extras import Color
+    >>> Color('white').convert('ipt')[:-1]
+    [1.0000046779854483, 0.00011652905964981697, -0.00010857262923669175]
+    ```
+
+    Clearly, this is because of a different white point. By simply removing the
+    previous white point and adding ours in, we get comparable results.
+
+    ```
+    >>> from coloraide_extras import Color
+    >>> Color('white').convert('ipt')[:-1]
+    [0.9999910919149725, 6.69113462008486e-05, -3.900547708157731e-05]
+    ```
+    """
+
+    theirs = alg.diag([0.9504, 1.0, 1.0889])
+    ours = alg.diag(white_d65)
+    return alg.multi_dot([m, theirs, alg.inv(ours)])
+
+
+m1 = [
+    [0.4002, 0.7075, -0.0807],
+    [-0.2280, 1.1500, 0.0612],
+    [0.0, 0.0, 0.9184]
+]
+
+m1 = white_space_fixup(m1)
+
+m2 = [
+    [0.4000, 0.4000, 0.2000],
+    [4.4550, -4.8510, 0.3960],
+    [0.8056, 0.3572, -1.1628]
+]
+
+
+def pprint(value):
+    """Print the matrix."""
+    print('[', end='')
+    first = True
+    for v in value:
+        if first:
+            first = False
+        else:
+            print(',\n ', end='')
+        print(v, end='')
+    print(']')
+
+
+if __name__ == "__main__":
+    print('===== XYZ to LMS =====')
+    pprint(m1)
+    print('===== LMS to XYZ =====')
+    pprint(alg.inv(m1))
+    print('===== LMS P to IPT =====')
+    pprint(m2)
+    print('===== IPT to LMS P =====')
+    pprint(alg.inv(m2))
diff --git a/tools/calc_lch_threshold.py b/tools/calc_lch_threshold.py
index f0a593e72..2652779cb 100644
--- a/tools/calc_lch_threshold.py
+++ b/tools/calc_lch_threshold.py
@@ -34,7 +34,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 
 RE_LEAD_ZERO = re.compile(r'^0\.0+')
 
@@ -42,6 +42,10 @@
 def run(lch, lab):
     """Run the calculation."""
 
+    max_chroma = 0.0
+    max_a = 0.0
+    max_b = 0.0
+
     for space in ('srgb', 'display-p3', 'rec2020', 'a98-rgb', 'prophoto-rgb'):
         max_chroma = 0.0
         max_a = 0.0
@@ -49,7 +53,7 @@ def run(lch, lab):
 
         for x in range(-0x600, 0x600):
             # Create an achromatic RGB color
-            color = Color('color({space} {num:f} {num:f} {num:f})'.format(space=space, num=x / 255))
+            color = Color(space, [x / 100] * 3)
             if lab:
                 labish = color.convert(lab)
                 a_name, b_name = labish._space.labish_names()[1:]
diff --git a/tools/calc_range_rgb.py b/tools/calc_range_rgb.py
index f2463cdfa..17c9e72a4 100644
--- a/tools/calc_range_rgb.py
+++ b/tools/calc_range_rgb.py
@@ -8,7 +8,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from coloraide import algebra as alg  # noqa: E402
 
 
diff --git a/tools/calc_rlab_matrices.py b/tools/calc_rlab_matrices.py
new file mode 100644
index 000000000..7845f8839
--- /dev/null
+++ b/tools/calc_rlab_matrices.py
@@ -0,0 +1,84 @@
+"""
+RLAB.
+
+https://scholarworks.rit.edu/cgi/viewcontent.cgi?article=1153&context=article
+https://www.imaging.org/site/PDFS/Papers/1997/RP-0-67/2368.pdf
+
+There was some conflicting info between the two papers. One cited M one way, and one cited the inverse.
+One was clearly a mistake. It seems that the `imaging.org` one is the correct M. We ended up implementing
+the "Refinement of the RLAB Color Space" algorithm and just replaced M with the correct one.
+
+We did recalculate R for our white point to get a nice, clean transform.
+"""
+
+import sys
+import os
+
+sys.path.insert(0, os.getcwd())
+
+from coloraide import algebra as alg  # noqa: E402
+from coloraide import util  # noqa: E402
+from coloraide.cat import WHITES  # noqa: E402
+
+
+white_d65 = util.xy_to_xyz((0.31270, 0.32900))
+
+R = [
+    [1.9569, -1.1882, 0.2313],
+    [0.3612, 0.6388, 0.0],
+    [0.0, 0.0, 1.0000]
+]
+
+M = [
+    [0.4002, 0.7076, -0.0808],
+    [-0.2263, 1.1653, 0.0457],
+    [0.0, 0.0, 0.9182]
+]
+
+YN = 318.0  # D65 `318 cd / m^2`
+D = 0.0
+
+
+def calc_a_lms(lms):
+    """Calculate the various matrix diagonal values."""
+
+    a = []
+    s = sum(lms)
+    for c in lms:
+        l = (3.0 * c) / s
+        p = (1.0 + alg.nth_root(YN, 3) + l) / (1.0 + alg.nth_root(YN, 3) + 1.0 / l)
+        a.append((p + D * (1.0 - p)) / c)
+    return a
+
+
+def calc_ram(white):
+    """Calculate RAM."""
+
+    xyz_w = util.xy_to_xyz(white)
+    lms = alg.dot(M, xyz_w)
+    a_lms = calc_a_lms(lms)
+    A = alg.diag(a_lms)
+    R = alg.multi_dot([alg.inv(alg.diag(xyz_w)), alg.inv(M), alg.inv(A)])
+    ram = alg.dot(R, alg.dot(A, M))
+    return ram
+
+
+def pprint(value):
+    """Print the matrix."""
+    print('[', end='')
+    first = True
+    for v in value:
+        if first:
+            first = False
+        else:
+            print(',\n ', end='')
+        print(v, end='')
+    print(']')
+
+
+if __name__ == "__main__":
+    RAM = calc_ram(WHITES['2deg']['D65'])
+    print('===== XYZ to XYZref =====')
+    pprint(RAM)
+    print('===== XYZref to XYZ =====')
+    pprint(alg.inv(RAM))
diff --git a/tools/cie_diagrams.py b/tools/cie_diagrams.py
index 40c688731..9f3748fd6 100644
--- a/tools/cie_diagrams.py
+++ b/tools/cie_diagrams.py
@@ -14,7 +14,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from coloraide import util  # noqa: E402
 from coloraide.cat import WHITES  # noqa: E402
 
diff --git a/tools/compose_imgs.py b/tools/compose_imgs.py
index 9a908eecf..8d03d6881 100644
--- a/tools/compose_imgs.py
+++ b/tools/compose_imgs.py
@@ -12,7 +12,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 
 
 def printt(t):
diff --git a/tools/filter_img.py b/tools/filter_img.py
index e6c8553ef..3fb8e3ac3 100644
--- a/tools/filter_img.py
+++ b/tools/filter_img.py
@@ -12,7 +12,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 
 
 def printt(t):
diff --git a/tools/gamut_3d_diagrams.py b/tools/gamut_3d_diagrams.py
index 9a96ff7ae..d53cc4a5b 100644
--- a/tools/gamut_3d_diagrams.py
+++ b/tools/gamut_3d_diagrams.py
@@ -20,7 +20,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from coloraide.spaces import Cylindrical, Lchish, Labish  # noqa: E402
 from coloraide.algebra import is_nan  # noqa: E402
 
diff --git a/tools/gamut_diagram.py b/tools/gamut_diagram.py
index 97a6a0b01..50d183009 100644
--- a/tools/gamut_diagram.py
+++ b/tools/gamut_diagram.py
@@ -9,7 +9,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from tools.slice_diagram import plot_slice  # noqa: E402
 from coloraide.util import fmt_float  # noqa: E402
 
diff --git a/tools/harmony_diagram.py b/tools/harmony_diagram.py
index c6a378907..e71be7e81 100644
--- a/tools/harmony_diagram.py
+++ b/tools/harmony_diagram.py
@@ -12,7 +12,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from tools.slice_diagram import plot_slice  # noqa: E402
 
 
diff --git a/tools/interp_diagram.py b/tools/interp_diagram.py
index 972cc036d..796218c08 100644
--- a/tools/interp_diagram.py
+++ b/tools/interp_diagram.py
@@ -16,7 +16,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from tools.slice_diagram import plot_slice  # noqa: E402
 from coloraide.util import fmt_float  # noqa: E402
 from coloraide.spaces import Cylindrical  # noqa: E402
diff --git a/tools/slice_diagram.py b/tools/slice_diagram.py
index c7306de7a..01d8330cf 100644
--- a/tools/slice_diagram.py
+++ b/tools/slice_diagram.py
@@ -24,7 +24,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import Color
+    from coloraide import ColorAll as Color
 from coloraide import NaN  # noqa: E402
 from coloraide.util import fmt_float  # noqa: E402
 from coloraide.spaces import Cylindrical  # noqa: E402


Test patch:
diff --git a/tests/test_cmy.py b/tests/test_cmy.py
new file mode 100644
index 000000000..5fae1934f
--- /dev/null
+++ b/tests/test_cmy.py
@@ -0,0 +1,64 @@
+"""Test CMY."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestCMY(util.ColorAssertsPyTest):
+    """Test CMY."""
+
+    COLORS = [
+        ('red', 'color(--cmy 0 1 1)'),
+        ('orange', 'color(--cmy 0 0.35294 1)'),
+        ('yellow', 'color(--cmy 0 0 1)'),
+        ('green', 'color(--cmy 1 0.49804 1)'),
+        ('blue', 'color(--cmy 1 1 0)'),
+        ('indigo', 'color(--cmy 0.70588 1 0.4902)'),
+        ('violet', 'color(--cmy 0.06667 0.4902 0.06667)'),
+        ('white', 'color(--cmy 0 0 0)'),
+        ('gray', 'color(--cmy 0.49804 0.49804 0.49804)'),
+        ('black', 'color(--cmy 1 1 1)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_cmy_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('cmy'), Color(color2))
+
+
+class TestCMYPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test CMY."""
+
+    def test_c(self):
+        """Test `c`."""
+
+        c = Color('color(--cmy 0 0 1)')
+        self.assertEqual(c['c'], 0)
+        c['c'] = 0.2
+        self.assertEqual(c['c'], 0.2)
+
+    def test_m(self):
+        """Test `m`."""
+
+        c = Color('color(--cmy 0 0 1)')
+        self.assertEqual(c['m'], 0)
+        c['m'] = 0.1
+        self.assertEqual(c['m'], 0.1)
+
+    def test_y(self):
+        """Test `y`."""
+
+        c = Color('color(--cmy 0 0 1)')
+        self.assertEqual(c['y'], 1)
+        c['y'] = 0.1
+        self.assertEqual(c['y'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--cmy 0 0 1)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_cmyk.py b/tests/test_cmyk.py
new file mode 100644
index 000000000..5881d41d8
--- /dev/null
+++ b/tests/test_cmyk.py
@@ -0,0 +1,72 @@
+"""Test CMYK."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestCMYK(util.ColorAssertsPyTest):
+    """Test CMYK."""
+
+    COLORS = [
+        ('red', 'color(--cmyk 0 1 1 0)'),
+        ('orange', 'color(--cmyk 0 0.35294 1 0)'),
+        ('yellow', 'color(--cmyk 0 0 1 0)'),
+        ('green', 'color(--cmyk 1 0 1 0.49804)'),
+        ('blue', 'color(--cmyk 1 1 0 0)'),
+        ('indigo', 'color(--cmyk 0.42308 1 0 0.4902)'),
+        ('violet', 'color(--cmyk 0 0.45378 0 0.06667)'),
+        ('white', 'color(--cmyk 0 0 0 0)'),
+        ('gray', 'color(--cmyk 0 0 0 0.49804)'),
+        ('black', 'color(--cmyk 0 0 0 1)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_cmyk_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('cmyk'), Color(color2))
+
+
+class TestCMYKPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test CMYK."""
+
+    def test_c(self):
+        """Test `c`."""
+
+        c = Color('color(--cmyk 0 0 1 0)')
+        self.assertEqual(c['c'], 0)
+        c['c'] = 0.2
+        self.assertEqual(c['c'], 0.2)
+
+    def test_m(self):
+        """Test `m`."""
+
+        c = Color('color(--cmyk 0 0 1 0)')
+        self.assertEqual(c['m'], 0)
+        c['m'] = 0.1
+        self.assertEqual(c['m'], 0.1)
+
+    def test_y(self):
+        """Test `y`."""
+
+        c = Color('color(--cmyk 0 0 1 0)')
+        self.assertEqual(c['y'], 1)
+        c['y'] = 0.1
+        self.assertEqual(c['y'], 0.1)
+
+    def test_k(self):
+        """Test `k`."""
+
+        c = Color('color(--cmyk 0 0 1 0)')
+        self.assertEqual(c['k'], 0)
+        c['k'] = 0.1
+        self.assertEqual(c['k'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--cmyk 0 0 1 0)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_din99o.py b/tests/test_din99o.py
index 4b15bea1d..69aacda1a 100644
--- a/tests/test_din99o.py
+++ b/tests/test_din99o.py
@@ -1,7 +1,7 @@
 """Test DIN99o library."""
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 
 
 class TestDIN99oInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_distance.py b/tests/test_distance.py
index d0b30114e..0886d99ca 100644
--- a/tests/test_distance.py
+++ b/tests/test_distance.py
@@ -1,5 +1,5 @@
 """Test distance methods."""
-from coloraide import Color
+from coloraide import ColorAll as Color
 from . import util
 import pytest
 import unittest
diff --git a/tests/test_hsi.py b/tests/test_hsi.py
new file mode 100644
index 000000000..f150b88a7
--- /dev/null
+++ b/tests/test_hsi.py
@@ -0,0 +1,99 @@
+"""Test HSI."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+from coloraide import NaN
+import pytest
+
+
+class TestHSI(util.ColorAssertsPyTest):
+    """Test HSI."""
+
+    COLORS = [
+        ('red', 'color(--hsi 0 1 0.33333)'),
+        ('orange', 'color(--hsi 38.824 1 0.54902)'),
+        ('yellow', 'color(--hsi 60 1 0.66667)'),
+        ('green', 'color(--hsi 120 1 0.16732)'),
+        ('blue', 'color(--hsi 240 1 0.33333)'),
+        ('indigo', 'color(--hsi 274.62 1 0.26797)'),
+        ('violet', 'color(--hsi 300 0.35644 0.79216)'),
+        ('white', 'color(--hsi 0 0 1)'),
+        ('gray', 'color(--hsi 0 0 0.50196)'),
+        ('black', 'color(--hsi 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_hsi_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('hsi'), Color(color2))
+
+
+class TestHSIPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test HSI."""
+
+    def test_h(self):
+        """Test `h`."""
+
+        c = Color('color(--hsi 60 1 0.66667)')
+        self.assertEqual(c['h'], 60)
+        c['h'] = 0.2
+        self.assertEqual(c['h'], 0.2)
+
+    def test_s(self):
+        """Test `s`."""
+
+        c = Color('color(--hsi 60 1 0.66667)')
+        self.assertEqual(c['s'], 1)
+        c['s'] = 0.1
+        self.assertEqual(c['s'], 0.1)
+
+    def test_i(self):
+        """Test `i`."""
+
+        c = Color('color(--hsi 60 1 0.66667)')
+        self.assertEqual(c['i'], 0.66667)
+        c['i'] = 0.1
+        self.assertEqual(c['i'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--hsi 60 1 0.66667)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
+
+
+class TestNull(util.ColorAsserts, unittest.TestCase):
+    """Test Null cases."""
+
+    def test_null_input(self):
+        """Test null input."""
+
+        c = Color('hsi', [NaN, 0.5, 1], 1)
+        self.assertTrue(c.is_nan('hue'))
+
+    def test_none_input(self):
+        """Test `none` null."""
+
+        c = Color('color(--hsi none 0% 75% / 1)')
+        self.assertTrue(c.is_nan('hue'))
+
+    def test_null_normalization_min_sat(self):
+        """Test minimum saturation."""
+
+        c = Color('color(--hsi 270 0% 75% / 1)').normalize()
+        self.assertTrue(c.is_nan('hue'))
+
+    def test_null_normalization_min_intensity(self):
+        """Test minimum intensity."""
+
+        c = Color('color(--hsi 270 20% 0% / 1)').normalize()
+        self.assertTrue(c.is_nan('hue'))
+
+    def test_corner_case_null(self):
+        """Test corner case that produces null."""
+
+        c = Color('color(srgb -2 0 2)').convert('hsl')
+        self.assertTrue(c.is_nan('hue'))
diff --git a/tests/test_hsluv/test_hsluv.py b/tests/test_hsluv/test_hsluv.py
index cad526244..88c1352c6 100644
--- a/tests/test_hsluv/test_hsluv.py
+++ b/tests/test_hsluv/test_hsluv.py
@@ -2,7 +2,7 @@
 from coloraide import NaN
 import unittest
 from .. import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 import json
 import os
 
diff --git a/tests/test_hunter_lab.py b/tests/test_hunter_lab.py
new file mode 100644
index 000000000..31bd9479f
--- /dev/null
+++ b/tests/test_hunter_lab.py
@@ -0,0 +1,64 @@
+"""Test Hunter Lab."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestHunterLab(util.ColorAssertsPyTest):
+    """Test Hunter Lab."""
+
+    COLORS = [
+        ('red', 'color(--hunter-lab 46.113 82.672 28.408)'),
+        ('orange', 'color(--hunter-lab 69.407 23.266 40.946)'),
+        ('yellow', 'color(--hunter-lab 96.323 -21.054 55.869)'),
+        ('green', 'color(--hunter-lab 39.291 -32.086 22.368)'),
+        ('blue', 'color(--hunter-lab 26.869 75.478 -200.29)'),
+        ('indigo', 'color(--hunter-lab 17.629 40.896 -62.916)'),
+        ('violet', 'color(--hunter-lab 63.496 58.109 -40.51)'),
+        ('white', 'color(--hunter-lab 100 0 0)'),
+        ('gray', 'color(--hunter-lab 46.461 0 0)'),
+        ('black', 'color(--hunter-lab 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_hunter_lab_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('hunter-lab'), Color(color2))
+
+
+class TestHunterLabPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test Hunter Lab."""
+
+    def test_l(self):
+        """Test `l`."""
+
+        c = Color('color(--hunter-lab 96.323 -21.06 55.728)')
+        self.assertEqual(c['l'], 96.323)
+        c['l'] = 0.2
+        self.assertEqual(c['l'], 0.2)
+
+    def test_a(self):
+        """Test `a`."""
+
+        c = Color('color(--hunter-lab 96.323 -21.06 55.728)')
+        self.assertEqual(c['a'], -21.06)
+        c['a'] = 0.1
+        self.assertEqual(c['a'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--hunter-lab 96.323 -21.06 55.728)')
+        self.assertEqual(c['b'], 55.728)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--hunter-lab 96.323 -21.06 55.728)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_ictcp.py b/tests/test_ictcp.py
index ab887dbf0..73f21e4e6 100644
--- a/tests/test_ictcp.py
+++ b/tests/test_ictcp.py
@@ -1,7 +1,7 @@
 """Test ICtCp library."""
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 
 
 class TestICtCpInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_igpgtg.py b/tests/test_igpgtg.py
new file mode 100644
index 000000000..8e70efc3d
--- /dev/null
+++ b/tests/test_igpgtg.py
@@ -0,0 +1,64 @@
+"""Test IgPgTg."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestIgPgTg(util.ColorAssertsPyTest):
+    """Test IgPgTg."""
+
+    COLORS = [
+        ('red', 'color(--igpgtg 0.54834 0.15366 0.43674)'),
+        ('orange', 'color(--igpgtg 0.73238 0.0397 0.32108)'),
+        ('yellow', 'color(--igpgtg 0.94415 -0.1422 0.39035)'),
+        ('green', 'color(--igpgtg 0.42594 -0.18386 0.17055)'),
+        ('blue', 'color(--igpgtg 0.3076 -0.26988 -0.40879)'),
+        ('indigo', 'color(--igpgtg 0.22925 0.08712 -0.20068)'),
+        ('violet', 'color(--igpgtg 0.68117 0.22553 -0.17411)'),
+        ('white', 'color(--igpgtg 0.97415 0.00139 -0.00404)'),
+        ('gray', 'color(--igpgtg 0.50619 0.00072 -0.0021)'),
+        ('black', 'color(--igpgtg 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_igpgtg_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('igpgtg'), Color(color2))
+
+
+class TestIgPgTgPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test IgPgTg."""
+
+    def test_ig(self):
+        """Test `ig`."""
+
+        c = Color('color(--igpgtg 0.94415 -0.1422 0.39035)')
+        self.assertEqual(c['ig'], 0.94415)
+        c['ig'] = 0.2
+        self.assertEqual(c['ig'], 0.2)
+
+    def test_pg(self):
+        """Test `pg`."""
+
+        c = Color('color(--igpgtg 0.94415 -0.1422 0.39035)')
+        self.assertEqual(c['pg'], -0.1422)
+        c['pg'] = 0.1
+        self.assertEqual(c['pg'], 0.1)
+
+    def test_tg(self):
+        """Test `tg`."""
+
+        c = Color('color(--igpgtg 0.94415 -0.1422 0.39035)')
+        self.assertEqual(c['tg'], 0.39035)
+        c['tg'] = 0.1
+        self.assertEqual(c['tg'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--igpgtg 0.94415 -0.1422 0.39035)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py
index 2090f4b6d..8bbe12bb5 100644
--- a/tests/test_interpolation.py
+++ b/tests/test_interpolation.py
@@ -672,17 +672,17 @@ def test_steps_multi_max_delta_e(self):
     def test_steps_custom_delta_e(self):
         """Test a custom delta E input."""
 
-        colors = Color.steps(['red', 'green', 'blue'], space="srgb", max_delta_e=10, delta_e='99o')
+        colors = Color.steps(['red', 'green', 'blue'], space="srgb", max_delta_e=10, delta_e='2000')
         for index, color in enumerate(colors, 0):
             if not index:
                 continue
-            self.assertTrue(color.delta_e(colors[index - 1]) <= 10)
+            self.assertTrue(color.delta_e(colors[index - 1], method='2000') <= 10)
 
-        colors = Color.steps(['red', 'green', 'blue'], space="srgb", max_delta_e=3, delta_e='99o')
+        colors = Color.steps(['red', 'green', 'blue'], space="srgb", max_delta_e=3, delta_e='2000')
         for index, color in enumerate(colors, 0):
             if not index:
                 continue
-            self.assertTrue(color.delta_e(colors[index - 1]) <= 3)
+            self.assertTrue(color.delta_e(colors[index - 1], method='2000') <= 3)
 
     def test_steps_custom_delta_compare(self):
         """Test a custom delta E input."""
diff --git a/tests/test_ipt.py b/tests/test_ipt.py
new file mode 100644
index 000000000..6fd0fa113
--- /dev/null
+++ b/tests/test_ipt.py
@@ -0,0 +1,64 @@
+"""Test IPT."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestIPT(util.ColorAssertsPyTest):
+    """Test IPT."""
+
+    COLORS = [
+        ('red', 'color(--ipt 0.45616 0.62086 0.44282)'),
+        ('orange', 'color(--ipt 0.64877 0.189 0.5303)'),
+        ('yellow', 'color(--ipt 0.85654 -0.10831 0.65148)'),
+        ('green', 'color(--ipt 0.3933 -0.23454 0.27459)'),
+        ('blue', 'color(--ipt 0.4443 -0.23713 -0.74847)'),
+        ('indigo', 'color(--ipt 0.28318 0.09827 -0.31015)'),
+        ('violet', 'color(--ipt 0.72886 0.31223 -0.24012)'),
+        ('white', 'color(--ipt 0.99999 0.00007 -0.00004)'),
+        ('gray', 'color(--ipt 0.51724 0.00003 -0.00002)'),
+        ('black', 'color(--ipt 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_ipt_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('ipt'), Color(color2))
+
+
+class TestIPTPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test IPT."""
+
+    def test_i(self):
+        """Test `i`."""
+
+        c = Color('color(--ipt 0.85654 -0.10831 0.65148)')
+        self.assertEqual(c['i'], 0.85654)
+        c['i'] = 0.2
+        self.assertEqual(c['i'], 0.2)
+
+    def test_p(self):
+        """Test `p`."""
+
+        c = Color('color(--ipt 0.85654 -0.10831 0.65148)')
+        self.assertEqual(c['p'], -0.10831)
+        c['p'] = 0.1
+        self.assertEqual(c['p'], 0.1)
+
+    def test_t(self):
+        """Test `t`."""
+
+        c = Color('color(--ipt 0.85654 -0.10831 0.65148)')
+        self.assertEqual(c['t'], 0.65148)
+        c['t'] = 0.1
+        self.assertEqual(c['t'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--ipt 0.85654 -0.10831 0.65148)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_jzCzhz.py b/tests/test_jzCzhz.py
index dd71c9864..572edbc55 100644
--- a/tests/test_jzCzhz.py
+++ b/tests/test_jzCzhz.py
@@ -1,7 +1,8 @@
 """Test JzCzhz library."""
 import unittest
 from . import util
-from coloraide import Color, NaN
+from coloraide import ColorAll as Color
+from coloraide import NaN
 
 
 class TestJzCzhzInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_jzazbz.py b/tests/test_jzazbz.py
index 37885cc2a..c6fcaa7a9 100644
--- a/tests/test_jzazbz.py
+++ b/tests/test_jzazbz.py
@@ -1,7 +1,7 @@
 """Test Jzazbz library."""
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 
 
 class TestJzazbzInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_lch99o.py b/tests/test_lch99o.py
index e92b72b68..f06905bbb 100644
--- a/tests/test_lch99o.py
+++ b/tests/test_lch99o.py
@@ -1,7 +1,8 @@
 """Test DIN99o LCH library."""
 import unittest
 from . import util
-from coloraide import Color, NaN
+from coloraide import ColorAll as Color
+from coloraide import NaN
 
 
 class TestLch99oInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_lchuv.py b/tests/test_lchuv.py
index e63363369..773a2cc41 100644
--- a/tests/test_lchuv.py
+++ b/tests/test_lchuv.py
@@ -1,7 +1,8 @@
 """Test LCHuv library."""
 import unittest
 from . import util
-from coloraide import Color, NaN
+from coloraide import ColorAll as Color
+from coloraide import NaN
 
 
 class TestLCHuvD65InputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_luv.py b/tests/test_luv.py
index e908c8aec..b852d842e 100644
--- a/tests/test_luv.py
+++ b/tests/test_luv.py
@@ -1,7 +1,7 @@
 """Test Luv library."""
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 
 
 class TestLuvInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_okhsl.py b/tests/test_okhsl.py
index 1c6819a92..49c96fd02 100644
--- a/tests/test_okhsl.py
+++ b/tests/test_okhsl.py
@@ -2,7 +2,7 @@
 from coloraide import NaN
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 
 
 class TestOkhslInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_okhsv.py b/tests/test_okhsv.py
index 35141aca8..ba9251ac4 100644
--- a/tests/test_okhsv.py
+++ b/tests/test_okhsv.py
@@ -1,7 +1,8 @@
 """Test Okhsv library."""
 import unittest
 from . import util
-from coloraide import Color, NaN
+from coloraide import ColorAll as Color
+from coloraide import NaN
 
 
 class TestOkhsvInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_orgb.py b/tests/test_orgb.py
new file mode 100644
index 000000000..d5d7836f2
--- /dev/null
+++ b/tests/test_orgb.py
@@ -0,0 +1,64 @@
+"""Test oRGB."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestORGB(util.ColorAssertsPyTest):
+    """Test oRGB."""
+
+    COLORS = [
+        ('red', 'color(--orgb 0.299 0.00002 0.99998)'),
+        ('orange', 'color(--orgb 0.67882 0.75654 0.4464)'),
+        ('yellow', 'color(--orgb 0.886 1 0)'),
+        ('green', 'color(--orgb 0.29465 0.00001 -0.50195)'),
+        ('blue', 'color(--orgb 0.114 -1 0)'),
+        ('indigo', 'color(--orgb 0.14606 -0.39733 0.19643)'),
+        ('violet', 'color(--orgb 0.68472 -0.29948 0.29947)'),
+        ('white', 'color(--orgb 1 0 0)'),
+        ('gray', 'color(--orgb 0.50196 0 0)'),
+        ('black', 'color(--orgb 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_orgb_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('orgb'), Color(color2))
+
+
+class TestORGBPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test oRGB."""
+
+    def test_l(self):
+        """Test `l`."""
+
+        c = Color('color(--orgb 1 0.5 0.5)')
+        self.assertEqual(c['l'], 1)
+        c['l'] = 0.2
+        self.assertEqual(c['l'], 0.2)
+
+    def test_cyb(self):
+        """Test `cyb`."""
+
+        c = Color('color(--orgb 1 0.5 0.5)')
+        self.assertEqual(c['cyb'], 0.5)
+        c['cyb'] = 0.1
+        self.assertEqual(c['cyb'], 0.1)
+
+    def test_crg(self):
+        """Test `crg`."""
+
+        c = Color('color(--orgb 1 0.5 0.5)')
+        self.assertEqual(c['crg'], 0.5)
+        c['crg'] = 0.1
+        self.assertEqual(c['crg'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--orgb 1 0.5 0.5)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 6224b5844..48a3a5cfd 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -50,7 +50,7 @@ def test_override_interpolate(self):
         class Color2(Color):
             """Color."""
 
-            INTERPOLATE = "din99o"
+            INTERPOLATE = "lab"
 
         self.assertEqual(
             Color('red').mix('green').to_string(),
@@ -58,7 +58,7 @@ class Color2(Color):
         )
         self.assertEqual(
             Color2('red').mix('green').to_string(),
-            Color2('red').mix('green', space='din99o').to_string()
+            Color2('red').mix('green', space='lab').to_string()
         )
 
     def test_override_harmony(self):
@@ -99,46 +99,46 @@ class Color2(Color):
     def test_plugin_registration_space(self):
         """Test plugin registration of `Space`."""
 
-        from coloraide.spaces import jzazbz
+        from coloraide.spaces import lab_d65
 
-        expected = Color('red').convert('jzazbz').to_string()
+        expected = Color('red').convert('lab-d65').to_string()
 
         # Deregistration should have taken place
         class Custom(Color):
             pass
 
-        Custom.deregister('space:jzazbz')
+        Custom.deregister('space:lab-d65')
         with self.assertRaises(ValueError):
-            Custom('red').convert('jzazbz')
+            Custom('red').convert('lab-d65')
 
         # But it should not affect the base class
-        self.assertEqual(Color('red').convert('jzazbz').to_string(), expected)
+        self.assertEqual(Color('red').convert('lab-d65').to_string(), expected)
 
         # Now it is registered again
-        Custom.register(jzazbz.Jzazbz)
-        self.assertEqual(Custom('red').convert('jzazbz').to_string(), expected)
+        Custom.register(lab_d65.LabD65)
+        self.assertEqual(Custom('red').convert('lab-d65').to_string(), expected)
 
     def test_plugin_registration_delta_e(self):
         """Test plugin registration of `DeltaE`."""
 
-        from coloraide.distance import delta_e_z
+        from coloraide.distance import delta_e_2000
 
-        expected = Color('red').delta_e('green', method='jz')
+        expected = Color('red').delta_e('green', method='2000')
 
         # Deregistration should have taken place
         class Custom(Color):
             pass
 
-        Custom.deregister('delta-e:jz')
+        Custom.deregister('delta-e:2000')
         with self.assertRaises(ValueError):
-            Custom('red').delta_e('green', method='jz')
+            Custom('red').delta_e('green', method='2000')
 
         # But it should not affect the base class
-        self.assertEqual(Color('red').delta_e('green', method='jz'), expected)
+        self.assertEqual(Color('red').delta_e('green', method='2000'), expected)
 
         # Now it is registered again
-        Custom.register(delta_e_z.DEZ)
-        self.assertEqual(Custom('red').delta_e('green', method='jz'), expected)
+        Custom.register(delta_e_2000.DE2000)
+        self.assertEqual(Custom('red').delta_e('green', method='2000'), expected)
 
     def test_plugin_registration_fit(self):
         """Test plugin registration of `Fit`."""
@@ -300,15 +300,15 @@ def distance(color, sample, **kwargs):
     def test_bad_registration_exists(self):
         """Test bad registration of plugin that exists."""
 
-        from coloraide.spaces.jzazbz import Jzazbz
+        from coloraide.spaces.lab_d65 import LabD65
 
         class Custom(Color):
             pass
 
         with self.assertRaises(ValueError):
-            Custom.register(Jzazbz)
+            Custom.register(LabD65)
 
-        Custom.register(Jzazbz, overwrite=True)
+        Custom.register(LabD65, overwrite=True)
 
     def test_silent_registration_exists(self):
         """Test silent handling of already registered plugin."""
diff --git a/tests/test_prismatic.py b/tests/test_prismatic.py
new file mode 100644
index 000000000..0ac2a5924
--- /dev/null
+++ b/tests/test_prismatic.py
@@ -0,0 +1,72 @@
+"""Test Prismatic."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestPrismatic(util.ColorAssertsPyTest):
+    """Test Prismatic."""
+
+    COLORS = [
+        ('red', 'color(--prismatic 1 1 0 0)'),
+        ('orange', 'color(--prismatic 1 0.60714 0.39286 0)'),
+        ('yellow', 'color(--prismatic 1 0.5 0.5 0)'),
+        ('green', 'color(--prismatic 0.50196 0 1 0)'),
+        ('blue', 'color(--prismatic 1 0 0 1)'),
+        ('indigo', 'color(--prismatic 0.5098 0.36585 0 0.63415)'),
+        ('violet', 'color(--prismatic 0.93333 0.39274 0.21452 0.39274)'),
+        ('white', 'color(--prismatic 1 0.33333 0.33333 0.33333)'),
+        ('gray', 'color(--prismatic 0.50196 0.33333 0.33333 0.33333)'),
+        ('black', 'color(--prismatic 0 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_prismatic_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('prismatic'), Color(color2))
+
+
+class TestPrismaticPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test Prismatic."""
+
+    def test_l(self):
+        """Test `l`."""
+
+        c = Color('color(--prismatic 1 0.5 0.5 0)')
+        self.assertEqual(c['l'], 1)
+        c['l'] = 0.2
+        self.assertEqual(c['l'], 0.2)
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--prismatic 1 0.5 0.5 0)')
+        self.assertEqual(c['r'], 0.5)
+        c['r'] = 0.1
+        self.assertEqual(c['r'], 0.1)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--prismatic 1 0.5 0.5 0)')
+        self.assertEqual(c['g'], 0.5)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--prismatic 1 0.5 0.5 0)')
+        self.assertEqual(c['b'], 0)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--prismatic 1 0.5 0.5 0)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_rec2100pq.py b/tests/test_rec2100pq.py
index 36b05a835..51e4644f6 100644
--- a/tests/test_rec2100pq.py
+++ b/tests/test_rec2100pq.py
@@ -1,7 +1,7 @@
 """Test Rec. 2100 PQ."""
 import unittest
 from . import util
-from coloraide import Color
+from coloraide import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_rlab.py b/tests/test_rlab.py
new file mode 100644
index 000000000..1174c45ce
--- /dev/null
+++ b/tests/test_rlab.py
@@ -0,0 +1,64 @@
+"""Test RLAB."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestRLAB(util.ColorAssertsPyTest):
+    """Test RLAB."""
+
+    COLORS = [
+        ('red', 'color(--rlab 51.012 79.742 57.26)'),
+        ('orange', 'color(--rlab 72.793 25.151 74.11)'),
+        ('yellow', 'color(--rlab 96.795 -23.838 95.194)'),
+        ('green', 'color(--rlab 44.382 -46.503 42.09)'),
+        ('blue', 'color(--rlab 31.893 71.681 -106.02)'),
+        ('indigo', 'color(--rlab 22.107 42.674 -46.135)'),
+        ('violet', 'color(--rlab 67.371 58.947 -38.558)'),
+        ('white', 'color(--rlab 100 0 0)'),
+        ('gray', 'color(--rlab 51.346 0 0)'),
+        ('black', 'color(--rlab 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_hunter_lab_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('rlab'), Color(color2))
+
+
+class TestRLABPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test RLAB."""
+
+    def test_l(self):
+        """Test `l`."""
+
+        c = Color('color(--rlab 96.323 -21.06 55.728)')
+        self.assertEqual(c['l'], 96.323)
+        c['l'] = 0.2
+        self.assertEqual(c['l'], 0.2)
+
+    def test_a(self):
+        """Test `a`."""
+
+        c = Color('color(--rlab 96.323 -21.06 55.728)')
+        self.assertEqual(c['a'], -21.06)
+        c['a'] = 0.1
+        self.assertEqual(c['a'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--rlab 96.323 -21.06 55.728)')
+        self.assertEqual(c['b'], 55.728)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--rlab 96.323 -21.06 55.728)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py
index 17612e292..ddec3bcd9 100644
--- a/tests/test_roundtrip.py
+++ b/tests/test_roundtrip.py
@@ -1,5 +1,5 @@
 """Sanity check that ensures all colors round trip back."""
-from coloraide import Color
+from coloraide import ColorAll as Color
 from coloraide.spaces import Cylindrical
 from coloraide import algebra as alg
 import pytest
diff --git a/tests/test_xyy.py b/tests/test_xyy.py
new file mode 100644
index 000000000..6cc32ca4d
--- /dev/null
+++ b/tests/test_xyy.py
@@ -0,0 +1,64 @@
+"""Test xyY."""
+import unittest
+from . import util
+from coloraide import ColorAll as Color
+import pytest
+
+
+class TestXyY(util.ColorAssertsPyTest):
+    """Test xyY."""
+
+    COLORS = [
+        ('red', 'color(--xyy 0.64 0.33 0.21264)'),
+        ('orange', 'color(--xyy 0.50047 0.4408 0.48173)'),
+        ('yellow', 'color(--xyy 0.41931 0.50526 0.92781)'),
+        ('green', 'color(--xyy 0.3 0.6 0.15438)'),
+        ('blue', 'color(--xyy 0.15 0.06 0.07219)'),
+        ('indigo', 'color(--xyy 0.22077 0.09899 0.03108)'),
+        ('violet', 'color(--xyy 0.31788 0.21844 0.40317)'),
+        ('white', 'color(--xyy 0.3127 0.329 1)'),
+        ('gray', 'color(--xyy 0.3127 0.329 0.21586)'),
+        ('black', 'color(--xyy 0.3127 0.329 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_xyy_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('xyy'), Color(color2))
+
+
+class TestXyYPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test xyY."""
+
+    def test_x(self):
+        """Test `x`."""
+
+        c = Color('color(--xyy 0.41931 0.50526 0.92781)')
+        self.assertEqual(c['x'], 0.41931)
+        c['x'] = 0.2
+        self.assertEqual(c['x'], 0.2)
+
+    def test_y(self):
+        """Test `y`."""
+
+        c = Color('color(--xyy 0.41931 0.50526 0.92781)')
+        self.assertEqual(c['y'], 0.50526)
+        c['y'] = 0.1
+        self.assertEqual(c['y'], 0.1)
+
+    def test_Y(self):
+        """Test `Y`."""
+
+        c = Color('color(--xyy 0.41931 0.50526 0.92781)')
+        self.assertEqual(c['Y'], 0.92781)
+        c['Y'] = 0.1
+        self.assertEqual(c['Y'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--xyy 0.41931 0.50526 0.92781)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)