Repository: facelessuser/coloraide (language: python)

Issue:
Plugins should be instances
With the experiments regarding CAM16, it has become apparent that some color spaces need some initializing based on viewing conditions.  Also, there are some plugins that allow passing in parameters (some delta E methods, etc.) and you can set them to default, but you cannot set default input parameters.  If we allow each plugin to be an instant, you overwrite a plugin with one initialized with different defaults and set it to the default for your class.

I think this will allow more flexibility. In all honesty, our static cast approach is probably not considered very Pythonic either. 


Discussion / hints:
I think this is going to make a lot more sense and allows for not only specifying a new default for ∆E but instantiating the ∆E algorithm with new options. As far as color spaces go, users should use caution and try to fundamentally change a color space while treating it like the same color space. I made a note about that in #212. we'll have to note this in the docs as well.

Fix patch:
diff --git a/coloraide/__init__.py b/coloraide/__init__.py
index 782c3dd7c..2c04e9fa8 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, ColorAll, ColorMatch
+from .color import Color, ColorMatch
 from .interpolate import stop, hint
 from .algebra import NaN
 
-__all__ = ("Color", "ColorAll", "ColorMatch", "NaN", "stop", "hint")
+__all__ = ("Color", "ColorMatch", "NaN", "stop", "hint")
diff --git a/coloraide/__meta__.py b/coloraide/__meta__.py
index f5f5853cb..191e4515c 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, "beta", 3)
+__version_info__ = Version(1, 0, 0, "beta", 4)
 __version__ = __version_info__._get_canonical()
diff --git a/coloraide/cat.py b/coloraide/cat.py
index 34e66ddc6..722718bd7 100644
--- a/coloraide/cat.py
+++ b/coloraide/cat.py
@@ -89,9 +89,8 @@ class CAT(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def adapt(cls, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
+    def adapt(self, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
         """Adapt a given XYZ color using the provided white points."""
 
 
@@ -111,8 +110,7 @@ class VonKries(CAT, metaclass=VonKriesMeta):
         [0.0000000, 0.0000000, 0.9182200]
     ]  # type: Matrix
 
-    @classmethod
-    def adapt(cls, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
+    def adapt(self, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
         """Adapt a given XYZ color using the provided white points."""
 
         # We are already using the correct white point
@@ -120,7 +118,7 @@ def adapt(cls, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike
             return list(xyz)
 
         a, b = sorted([w1, w2])
-        m, mi = cls.get_adaptation_matrices(a, b)
+        m, mi = cast(Type['VonKries'], self).get_adaptation_matrices(a, b)
         return alg.dot(mi if a != w2 else m, xyz, dims=alg.D2_D1)
 
 
diff --git a/coloraide/color.py b/coloraide/color.py
index 0cfb4d55f..75f811be3 100644
--- a/coloraide/color.py
+++ b/coloraide/color.py
@@ -33,44 +33,15 @@
 from .spaces.prophoto_rgb_linear import ProPhotoRGBLinear
 from .spaces.rec2020 import Rec2020
 from .spaces.rec2020_linear import Rec2020Linear
-from .spaces.rec2100pq import Rec2100PQ
 from .spaces.xyz_d65 import XYZD65
 from .spaces.xyz_d50 import XYZD50
 from .spaces.oklab.css import Oklab
 from .spaces.oklch.css import Oklch
-from .spaces.jzazbz import Jzazbz
-from .spaces.jzczhz import JzCzhz
-from .spaces.ictcp import ICtCp
-from .spaces.din99o import Din99o
-from .spaces.lch99o import Lch99o
-from .spaces.luv import Luv
-from .spaces.lchuv import Lchuv
-from .spaces.hsluv import HSLuv
-from .spaces.hpluv import HPLuv
-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 .spaces.aces2065_1 import ACES20651
-from .spaces.acescg import ACEScg
-from .spaces.acescc import ACEScc
-from .spaces.acescct import ACEScct
 from .distance import DeltaE
 from .distance.delta_e_76 import DE76
 from .distance.delta_e_94 import DE94
 from .distance.delta_e_cmc import DECMC
 from .distance.delta_e_2000 import DE2000
-from .distance.delta_e_itp import DEITP
-from .distance.delta_e_99o import DE99o
-from .distance.delta_e_z import DEZ
 from .distance.delta_e_hyab import DEHyAB
 from .distance.delta_e_ok import DEOK
 from .contrast import ColorContrast
@@ -78,47 +49,13 @@
 from .gamut import Fit
 from .gamut.fit_lch_chroma import LchChroma
 from .gamut.fit_oklch_chroma import OklchChroma
-from .cat import CAT, Bradford, VonKries, XYZScaling, CAT02, CMCCAT97, Sharp, CMCCAT2000, CAT16
+from .cat import CAT, Bradford
 from .filters import Filter
 from .filters.w3c_filter_effects import Sepia, Brightness, Contrast, Saturate, Opacity, HueRotate, Grayscale, Invert
 from .filters.cvd import Protan, Deutan, Tritan
 from .types import Plugin
 from typing import overload, Union, Sequence, Dict, List, Optional, Any, cast, Callable, Tuple, Type, Mapping
 
-SUPPORTED_DE = (
-    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
-)
-
-EXTRA_SPACES = (
-    Rec2100PQ, Jzazbz, JzCzhz, ICtCp, Din99o, Lch99o, Luv, Lchuv, Okhsl, Okhsv,
-    HSLuv, HPLuv, HSI, IPT, IgPgTg, CMY, CMYK, XyY, HunterLab, Prismatic, RLAB, ORGB,
-    ACES20651, ACEScg, ACEScc, ACEScct
-)
-
-SUPPORTED_FIT = (
-    LchChroma, OklchChroma
-)
-
-SUPPORTED_CAT = (Bradford, VonKries, XYZScaling, CAT02, CMCCAT97, Sharp, CMCCAT2000, CAT16)
-
-SUPPORTED_FILTERS = (
-    Sepia, Brightness, Contrast, Saturate, Opacity, HueRotate, Grayscale, Invert, Protan, Deutan, Tritan
-)
-
-SUPPORTED_CONTRAST = (
-    WCAG21Contrast,
-)
-
 
 class ColorMatch:
     """Color match object."""
@@ -146,12 +83,12 @@ def __init__(cls, name: str, bases: Tuple[object, ...], clsdict: Dict[str, Any])
 
         # Ensure subclassed Color objects do not use the same plugin mappings
         if len(cls.mro()) > 2:
-            cls.CS_MAP = cls.CS_MAP.copy()  # type: Dict[str, Type[Space]]
-            cls.DE_MAP = cls.DE_MAP.copy()  # type: Dict[str, Type[DeltaE]]
-            cls.FIT_MAP = cls.FIT_MAP.copy()  # type: Dict[str, Type[Fit]]
-            cls.CAT_MAP = cls.CAT_MAP.copy()  # type: Dict[str, Type[CAT]]
-            cls.FILTER_MAP = cls.FILTER_MAP.copy()  # type: Dict[str, Type[Filter]]
-            cls.CONTRAST_MAP = cls.CONTRAST_MAP.copy()  # type: Dict[str, Type[ColorContrast]]
+            cls.CS_MAP = cls.CS_MAP.copy()  # type: Dict[str, Space]
+            cls.DE_MAP = cls.DE_MAP.copy()  # type: Dict[str, DeltaE]
+            cls.FIT_MAP = cls.FIT_MAP.copy()  # type: Dict[str, Fit]
+            cls.CAT_MAP = cls.CAT_MAP.copy()  # type: Dict[str, CAT]
+            cls.FILTER_MAP = cls.FILTER_MAP.copy()  # type: Dict[str, Filter]
+            cls.CONTRAST_MAP = cls.CONTRAST_MAP.copy()  # type: Dict[str, ColorContrast]
 
         # Ensure each derived class tracks its own conversion paths for color spaces
         # relative to the installed color space plugins.
@@ -159,9 +96,9 @@ def __init__(cls, name: str, bases: Tuple[object, ...], clsdict: Dict[str, Any])
         @functools.lru_cache(maxsize=256)
         def _get_convert_chain(
             cls: Type['Color'],
-            space: Type['Space'],
+            space: 'Space',
             target: str
-        ) -> List[Tuple[Type['Space'], Type['Space'], int, bool]]:
+        ) -> List[Tuple['Space', 'Space', int, bool]]:
             """Resolve a conversion chain, cache it for speed."""
 
             return convert.get_convert_chain(cls, space, target)
@@ -172,12 +109,12 @@ def _get_convert_chain(
 class Color(metaclass=ColorMeta):
     """Color class object which provides access and manipulation of color spaces."""
 
-    CS_MAP = {}  # type: Dict[str, Type[Space]]
-    DE_MAP = {}  # type: Dict[str, Type[DeltaE]]
-    FIT_MAP = {}  # type: Dict[str, Type[Fit]]
-    CAT_MAP = {}  # type: Dict[str, Type[CAT]]
-    CONTRAST_MAP = {}  # type: Dict[str, Type[ColorContrast]]
-    FILTER_MAP = {}  # type: Dict[str, Type[Filter]]
+    CS_MAP = {}  # type: Dict[str, Space]
+    DE_MAP = {}  # type: Dict[str, DeltaE]
+    FIT_MAP = {}  # type: Dict[str, Fit]
+    CAT_MAP = {}  # type: Dict[str, CAT]
+    CONTRAST_MAP = {}  # type: Dict[str, ColorContrast]
+    FILTER_MAP = {}  # type: Dict[str, Filter]
     PRECISION = util.DEF_PREC
     FIT = util.DEF_FIT
     INTERPOLATE = util.DEF_INTERPOLATE
@@ -239,10 +176,10 @@ def __setitem__(self, i: Union[str, int, slice], v: Union[float, Vector]) -> Non
         space = self._space
         if isinstance(i, slice):
             for index, value in zip(range(len(self._coords))[i], cast(Vector, v)):
-                self._coords[index] = alg.clamp(float(value), *space.get_channel(index).limit)
+                self._coords[index] = alg.clamp(float(value), *space.channels[index].limit)
         else:
             index = space.get_channel_index(i) if isinstance(i, str) else i
-            self._coords[index] = alg.clamp(float(cast(float, v)), *space.get_channel(index).limit)
+            self._coords[index] = alg.clamp(float(cast(float, v)), *space.channels[index].limit)
 
     def __eq__(self, other: Any) -> bool:
         """Compare equal."""
@@ -260,7 +197,7 @@ def _parse(
         data: Optional[VectorLike] = None,
         alpha: float = util.DEF_ALPHA,
         **kwargs: Any
-    ) -> Tuple[Type[Space], List[float]]:
+    ) -> Tuple[Space, List[float]]:
         """Parse the color."""
 
         if isinstance(color, str):
@@ -275,7 +212,7 @@ def _parse(
                 if len(data) < num_channels:
                     data = list(data) + [alg.NaN] * (num_channels - len(data))
                 coords = [alg.clamp(float(v), *c.limit) for c, v in zipl(space_class.CHANNELS, data)]
-                coords.append(alg.clamp(float(alpha), *space_class.get_channel(-1).limit))
+                coords.append(alg.clamp(float(alpha), *space_class.channels[-1].limit))
                 obj = space_class, coords
             # Parse a CSS string
             else:
@@ -283,7 +220,7 @@ def _parse(
                 if m is None:
                     raise ValueError("'{}' is not a valid color".format(color))
                 coords = [alg.clamp(float(v), *c.limit) for c, v in zipl(m[0].CHANNELS, m[1])]
-                coords.append(alg.clamp(float(m[2]), *m[0].get_channel(-1).limit))
+                coords.append(alg.clamp(float(m[2]), *m[0].channels[-1].limit))
                 obj = m[0], coords
         elif isinstance(color, Color):
             # Handle a color instance
@@ -308,7 +245,7 @@ def _match(
         string: str,
         start: int = 0,
         fullmatch: bool = False
-    ) -> Optional[Tuple[Type['Space'], Vector, float, int, int]]:
+    ) -> Optional[Tuple['Space', Vector, float, int, int]]:
         """
         Match a color in a buffer and return a color object.
 
@@ -356,7 +293,7 @@ def _is_color(cls, obj: Any) -> bool:
     @classmethod
     def register(
         cls,
-        plugin: Union[Type[Plugin], Sequence[Type[Plugin]]],
+        plugin: Union[Plugin, Sequence[Plugin]],
         *,
         overwrite: bool = False,
         silent: bool = False
@@ -368,20 +305,20 @@ def register(
         if not isinstance(plugin, Sequence):
             plugin = [plugin]
 
-        mapping = None  # type: Optional[Dict[str, Type[Any]]]
+        mapping = None  # type: Optional[Dict[str, Any]]
         for p in plugin:
-            if issubclass(p, Space):
+            if isinstance(p, Space):
                 mapping = cls.CS_MAP
                 reset_convert_cache = True
-            elif issubclass(p, DeltaE):
+            elif isinstance(p, DeltaE):
                 mapping = cls.DE_MAP
-            elif issubclass(p, CAT):
+            elif isinstance(p, CAT):
                 mapping = cls.CAT_MAP
-            elif issubclass(p, Filter):
+            elif isinstance(p, Filter):
                 mapping = cls.FILTER_MAP
-            elif issubclass(p, ColorContrast):
+            elif isinstance(p, ColorContrast):
                 mapping = cls.CONTRAST_MAP
-            elif issubclass(p, Fit):
+            elif isinstance(p, Fit):
                 mapping = cls.FIT_MAP
                 if p.NAME == 'clip':
                     if reset_convert_cache:  # pragma: no cover
@@ -398,7 +335,7 @@ def register(
             value = p
 
             if name != "*" and name not in mapping or overwrite:
-                cast(Dict[str, Type[Plugin]], mapping)[name] = value
+                cast(Dict[str, Plugin], mapping)[name] = value
             elif not silent:
                 if reset_convert_cache:  # pragma: no cover
                     cls._get_convert_chain.cache_clear()
@@ -416,7 +353,7 @@ def deregister(cls, plugin: Union[str, Sequence[str]], *, silent: bool = False)
         if isinstance(plugin, str):
             plugin = [plugin]
 
-        mapping = None  # type: Optional[Dict[str, Type[Any]]]
+        mapping = None  # type: Optional[Dict[str, Any]]
         for p in plugin:
             if p == '*':
                 cls.CS_MAP.clear()
@@ -481,7 +418,7 @@ def random(cls, space: str, *, limits: Optional[Sequence[Optional[Sequence[float
         for i in range(num_chan):
             chan = limits[i] if i < length else None  # type: Any
             if chan is None:
-                chan = cs.get_channel(i)
+                chan = cs.channels[i]
                 a, b = chan.low, chan.high
             else:
                 a, b = chan
@@ -658,7 +595,7 @@ def clip(self, space: Optional[str] = None) -> 'Color':
 
         # If we are perfectly in gamut, don't waste time clipping.
         if c.in_gamut(tolerance=0.0):
-            if issubclass(c._space, Cylindrical):
+            if isinstance(c._space, Cylindrical):
                 name = c._space.hue_name()
                 c.set(name, util.constrain_hue(c[name]))
         else:
@@ -700,7 +637,7 @@ def fit(
         # If we are perfectly in gamut, don't waste time fitting, just normalize hues.
         # If out of gamut, apply mapping/clipping/etc.
         if c.in_gamut(tolerance=0.0):
-            if issubclass(c._space, Cylindrical):
+            if isinstance(c._space, Cylindrical):
                 name = c._space.hue_name()
                 c.set(name, util.constrain_hue(c[name]))
         else:
@@ -739,7 +676,7 @@ def mask(self, channel: Union[str, Sequence[str]], *, invert: bool = False, in_p
         masks = set(
             [aliases.get(channel, channel)] if isinstance(channel, str) else [aliases.get(c, c) for c in channel]
         )
-        for name in self._space.get_all_channels():
+        for name in self._space.channels:
             if (not invert and name in masks) or (invert and name not in masks):
                 this[name] = alg.NaN
         return this
@@ -936,11 +873,60 @@ def set(  # noqa: A003
         return self
 
 
-Color.register(SUPPORTED_SPACES + SUPPORTED_DE + SUPPORTED_FIT + SUPPORTED_CAT + SUPPORTED_FILTERS + SUPPORTED_CONTRAST)
-
-
-class ColorAll(Color):
-    """Color derivative with all extra spaces."""
-
-
-ColorAll.register(EXTRA_DE + EXTRA_SPACES)
+Color.register(
+    [
+        # Spaces
+        XYZD65(),
+        XYZD50(),
+        SRGB(),
+        SRGBLinear(),
+        DisplayP3(),
+        DisplayP3Linear(),
+        Oklab(),
+        Oklch(),
+        Lab(),
+        Lch(),
+        LabD65(),
+        LchD65(),
+        HSV(),
+        HSL(),
+        HWB(),
+        Rec2020(),
+        Rec2020Linear(),
+        A98RGB(),
+        A98RGBLinear(),
+        ProPhotoRGB(),
+        ProPhotoRGBLinear(),
+
+        # CAT
+        Bradford(),
+
+        # Delta E
+        DE76(),
+        DE94(),
+        DECMC(),
+        DE2000(),
+        DEHyAB(),
+        DEOK(),
+
+        # Fit
+        LchChroma(),
+        OklchChroma(),
+
+        # Filters
+        Sepia(),
+        Brightness(),
+        Contrast(),
+        Saturate(),
+        Opacity(),
+        HueRotate(),
+        Grayscale(),
+        Invert(),
+        Protan(),
+        Deutan(),
+        Tritan(),
+
+        # Contrast
+        WCAG21Contrast()
+    ]
+)
diff --git a/coloraide/compositing/__init__.py b/coloraide/compositing/__init__.py
index 81f9173c4..870fe8d6a 100644
--- a/coloraide/compositing/__init__.py
+++ b/coloraide/compositing/__init__.py
@@ -7,7 +7,7 @@
 from . import blend_modes
 from .. import algebra as alg
 from ..channels import Channel
-from typing import Optional, Union, List, Type, TYPE_CHECKING
+from typing import Optional, Union, List, TYPE_CHECKING
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -33,7 +33,7 @@ def clip_channel(coord: float, channel: Channel) -> float:
 def apply_compositing(
     color1: 'Color',
     color2: 'Color',
-    blender: Optional[Type[blend_modes.Blend]],
+    blender: Optional[blend_modes.Blend],
     operator: Union[str, bool]
 ) -> 'Color':
     """Perform the actual blending."""
@@ -84,7 +84,7 @@ def compose(
         blender = blend_modes.get_blender(blend)
     elif blend is True:
         blender = blend_modes.get_blender('normal')
-    is_seperable = blender is not None and issubclass(blender, blend_modes.NonSeperableBlend)
+    is_seperable = blender is not None and isinstance(blender, blend_modes.NonSeperableBlend)
 
     # If we are doing non-separable, we are converting to a special space that
     # can only be done from sRGB, so we have to force sRGB anyway.
diff --git a/coloraide/compositing/blend_modes.py b/coloraide/compositing/blend_modes.py
index cbe55510f..2f7abc707 100644
--- a/coloraide/compositing/blend_modes.py
+++ b/coloraide/compositing/blend_modes.py
@@ -2,7 +2,7 @@
 import math
 from abc import ABCMeta, abstractmethod
 from operator import itemgetter
-from typing import Dict, Type
+from typing import Dict
 from ..types import Vector
 
 
@@ -66,9 +66,8 @@ def set_sat(rgb: Vector, s: float) -> Vector:
 class Blend(metaclass=ABCMeta):
     """Blend base class."""
 
-    @classmethod
     @abstractmethod
-    def blend(cls, coords1: Vector, coords2: Vector) -> Vector:  # pragma: no cover
+    def blend(self, coords1: Vector, coords2: Vector) -> Vector:  # pragma: no cover
         """Blend coordinates."""
 
         raise NotImplementedError('blend is not implemented')
@@ -77,42 +76,37 @@ def blend(cls, coords1: Vector, coords2: Vector) -> Vector:  # pragma: no cover
 class SeperableBlend(Blend):
     """Blend coordinates."""
 
-    @classmethod
     @abstractmethod
-    def apply(cls, cb: float, cs: float) -> float:  # pragma: no cover
+    def apply(self, cb: float, cs: float) -> float:  # pragma: no cover
         """Blend two values."""
 
         raise NotImplementedError('apply is not implemented')
 
-    @classmethod
-    def blend(cls, coords1: Vector, coords2: Vector) -> Vector:
+    def blend(self, coords1: Vector, coords2: Vector) -> Vector:
         """Apply blending logic."""
 
-        return [cls.apply(cb, cs) for cb, cs in zip(coords1, coords2)]
+        return [self.apply(cb, cs) for cb, cs in zip(coords1, coords2)]
 
 
 class NonSeperableBlend(Blend):
     """Non seperable blend method."""
 
-    @classmethod
     @abstractmethod
-    def apply(cls, cb: Vector, cs: Vector) -> Vector:  # pragma: no cover
+    def apply(self, cb: Vector, cs: Vector) -> Vector:  # pragma: no cover
         """Blend two vectors."""
 
         raise NotImplementedError('apply is not implemented')
 
-    @classmethod
-    def blend(cls, coords_backgrond: Vector, coords_source: Vector) -> Vector:
+    def blend(self, coords_backgrond: Vector, coords_source: Vector) -> Vector:
         """Apply blending logic."""
 
-        return cls.apply(coords_backgrond, coords_source)
+        return self.apply(coords_backgrond, coords_source)
 
 
 class BlendNormal(SeperableBlend):
     """Normal blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return cs
@@ -121,8 +115,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendMultiply(SeperableBlend):
     """Multiply blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return cb * cs
@@ -131,8 +124,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendScreen(SeperableBlend):
     """Screen blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return cb + cs - (cb * cs)
@@ -141,8 +133,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendDarken(SeperableBlend):
     """Darken blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return min(cb, cs)
@@ -151,8 +142,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendLighten(SeperableBlend):
     """Lighten blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return max(cb, cs)
@@ -161,8 +151,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendColorDodge(SeperableBlend):
     """Color dodge blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         if cb == 0:
@@ -176,8 +165,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendColorBurn(SeperableBlend):
     """Color Burn blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         if cb == 1:
@@ -191,21 +179,25 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendOverlay(SeperableBlend):
     """Overlay blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def __init__(self) -> None:
+        """Initialize."""
+
+        self.screen = BlendScreen()
+        self.multiply = BlendMultiply()
+
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         if cb >= 0.5:
-            return BlendScreen.apply(cb, 2 * cs - 1)
+            return self.screen.apply(cb, 2 * cs - 1)
         else:
-            return BlendMultiply.apply(cb, cs * 2)
+            return self.multiply.apply(cb, cs * 2)
 
 
 class BlendDifference(SeperableBlend):
     """Difference blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return abs(cb - cs)
@@ -214,8 +206,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendExclusion(SeperableBlend):
     """Exclusion blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         return cb + cs - 2 * cb * cs
@@ -224,21 +215,25 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendHardLight(SeperableBlend):
     """Hard light blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def __init__(self) -> None:
+        """Initialize."""
+
+        self.screen = BlendScreen()
+        self.multiply = BlendMultiply()
+
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         if cs <= 0.5:
-            return BlendMultiply.apply(cb, cs * 2)
+            return self.multiply.apply(cb, cs * 2)
         else:
-            return BlendScreen.apply(cb, 2 * cs - 1)
+            return self.screen.apply(cb, 2 * cs - 1)
 
 
 class BlendSoftLight(SeperableBlend):
     """Soft light blend mode."""
 
-    @classmethod
-    def apply(cls, cb: float, cs: float) -> float:
+    def apply(self, cb: float, cs: float) -> float:
         """Blend two values."""
 
         if cs <= 0.5:
@@ -254,8 +249,7 @@ def apply(cls, cb: float, cs: float) -> float:
 class BlendHue(NonSeperableBlend):
     """Hue blend mode."""
 
-    @classmethod
-    def apply(cls, cb: Vector, cs: Vector) -> Vector:
+    def apply(self, cb: Vector, cs: Vector) -> Vector:
         """Blend two vectors."""
 
         return set_lum(set_sat(cs, sat(cb)), lum(cb))
@@ -264,8 +258,7 @@ def apply(cls, cb: Vector, cs: Vector) -> Vector:
 class BlendSaturation(NonSeperableBlend):
     """Saturation blend mode."""
 
-    @classmethod
-    def apply(cls, cb: Vector, cs: Vector) -> Vector:
+    def apply(self, cb: Vector, cs: Vector) -> Vector:
         """Blend two vectors."""
 
         return set_lum(set_sat(cb, sat(cs)), lum(cb))
@@ -274,8 +267,7 @@ def apply(cls, cb: Vector, cs: Vector) -> Vector:
 class BlendLuminosity(NonSeperableBlend):
     """Luminosity blend mode."""
 
-    @classmethod
-    def apply(cls, cb: Vector, cs: Vector) -> Vector:
+    def apply(self, cb: Vector, cs: Vector) -> Vector:
         """Blend two vectors."""
         return set_lum(cb, lum(cs))
 
@@ -283,34 +275,33 @@ def apply(cls, cb: Vector, cs: Vector) -> Vector:
 class BlendColor(NonSeperableBlend):
     """Color blend mode."""
 
-    @classmethod
-    def apply(cls, cb: Vector, cs: Vector) -> Vector:
+    def apply(self, cb: Vector, cs: Vector) -> Vector:
         """Blend two vectors."""
 
         return set_lum(cs, lum(cb))
 
 
 SUPPORTED = {
-    "normal": BlendNormal,
-    "multiply": BlendMultiply,
-    "screen": BlendScreen,
-    "darken": BlendDarken,
-    "lighten": BlendLighten,
-    "color-dodge": BlendColorDodge,
-    "color-burn": BlendColorBurn,
-    "overlay": BlendOverlay,
-    "difference": BlendDifference,
-    "exclusion": BlendExclusion,
-    "hard-light": BlendHardLight,
-    "soft-light": BlendSoftLight,
-    "hue": BlendHue,
-    "saturation": BlendSaturation,
-    "luminosity": BlendLuminosity,
-    "color": BlendColor,
-}  # type: Dict[str, Type[Blend]]
-
-
-def get_blender(blend: str) -> Type[Blend]:
+    "normal": BlendNormal(),
+    "multiply": BlendMultiply(),
+    "screen": BlendScreen(),
+    "darken": BlendDarken(),
+    "lighten": BlendLighten(),
+    "color-dodge": BlendColorDodge(),
+    "color-burn": BlendColorBurn(),
+    "overlay": BlendOverlay(),
+    "difference": BlendDifference(),
+    "exclusion": BlendExclusion(),
+    "hard-light": BlendHardLight(),
+    "soft-light": BlendSoftLight(),
+    "hue": BlendHue(),
+    "saturation": BlendSaturation(),
+    "luminosity": BlendLuminosity(),
+    "color": BlendColor(),
+}  # type: Dict[str, Blend]
+
+
+def get_blender(blend: str) -> Blend:
     """Get desired blend mode."""
 
     try:
diff --git a/coloraide/contrast/__init__.py b/coloraide/contrast/__init__.py
index ece8e9466..1a30c4f9b 100644
--- a/coloraide/contrast/__init__.py
+++ b/coloraide/contrast/__init__.py
@@ -12,9 +12,8 @@ class ColorContrast(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def contrast(cls, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
+    def contrast(self, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
         """Get the contrast of the two provided colors."""
 
 
diff --git a/coloraide/contrast/wcag21.py b/coloraide/contrast/wcag21.py
index 9aff0705a..e6e2e4743 100644
--- a/coloraide/contrast/wcag21.py
+++ b/coloraide/contrast/wcag21.py
@@ -15,8 +15,7 @@ class WCAG21Contrast(ColorContrast):
 
     NAME = "wcag21"
 
-    @classmethod
-    def contrast(cls, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
+    def contrast(self, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
         """Contrast."""
 
         lum1 = color1.luminance()
diff --git a/coloraide/convert.py b/coloraide/convert.py
index 3a13e4deb..ae0a3dbf4 100644
--- a/coloraide/convert.py
+++ b/coloraide/convert.py
@@ -15,7 +15,7 @@
 def calc_path_to_xyz(
     color: Type['Color'],
     space: str
-) -> Tuple[List[Type['Space']], Dict[str, int]]:
+) -> Tuple[List['Space'], Dict[str, int]]:
     """
     Calculate the conversion path between a given color space and XYZ D65.
 
@@ -54,9 +54,9 @@ def calc_path_to_xyz(
 
 def get_convert_chain(
     color: Type['Color'],
-    space: Type['Space'],
+    space: 'Space',
     target: str
-) -> List[Tuple[Type['Space'], Type['Space'], int, bool]]:
+) -> List[Tuple['Space', 'Space', int, bool]]:
     """
     Create a conversion chain.
 
@@ -74,7 +74,7 @@ def get_convert_chain(
     # If the color space we are converting to is not between
     # the current space and XYZ D65, nothing will get added.
     current = space
-    chain = []  # type: List[Tuple[Type['Space'], Type['Space'], int, bool]]
+    chain = []  # type: List[Tuple['Space', 'Space', int, bool]]
     if current.NAME != ABSOLUTE_BASE:
         count = 0
         while current.NAME not in from_color_index:
@@ -119,7 +119,7 @@ def get_convert_chain(
     return chain
 
 
-def convert(color: 'Color', space: str) -> Tuple[Type['Space'], Vector]:
+def convert(color: 'Color', space: str) -> Tuple['Space', Vector]:
     """Convert the color coordinates to the specified space."""
 
     # Grab the convert for the current space to the desired space
diff --git a/coloraide/css/parse.py b/coloraide/css/parse.py
index 0398f2cae..699f34c35 100644
--- a/coloraide/css/parse.py
+++ b/coloraide/css/parse.py
@@ -6,7 +6,7 @@
 from . import color_names
 from ..channels import Channel, FLG_ANGLE
 from typing import Optional, Tuple
-from typing import Dict, Type, TYPE_CHECKING
+from typing import Dict, TYPE_CHECKING
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..spaces import Space
@@ -281,10 +281,10 @@ def parse_channels(color: str, boundry: Tuple[Channel, ...]) -> Tuple[Vector, fl
 
 def parse_color(
     string: str,
-    spaces: Dict[str, Type['Space']],
+    spaces: Dict[str, 'Space'],
     start: int,
     fullmatch: bool = False
-) -> Optional[Tuple[Type['Space'], Tuple[Vector, float], int]]:
+) -> Optional[Tuple['Space', Tuple[Vector, float], int]]:
     """Perform default color matching."""
 
     m = RE_COLOR_MATCH.match(string, start)
@@ -322,7 +322,7 @@ def parse_color(
 
 
 def parse_css(
-    cspace: Type['Space'],
+    cspace: 'Space',
     string: str,
     start: int = 0,
     fullmatch: bool = True,
diff --git a/coloraide/distance/__init__.py b/coloraide/distance/__init__.py
index b2554f0ee..7f6ff6077 100644
--- a/coloraide/distance/__init__.py
+++ b/coloraide/distance/__init__.py
@@ -53,7 +53,6 @@ class DeltaE(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def distance(cls, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
+    def distance(self, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
         """Get distance between color and sample."""
diff --git a/coloraide/distance/delta_e_76.py b/coloraide/distance/delta_e_76.py
index 17c2c2517..c818050c9 100644
--- a/coloraide/distance/delta_e_76.py
+++ b/coloraide/distance/delta_e_76.py
@@ -12,8 +12,7 @@ class DE76(DeltaE):
     NAME = "76"
     SPACE = "lab-d65"
 
-    @classmethod
-    def distance(cls, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
+    def distance(self, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
         """
         Delta E 1976 color distance formula.
 
@@ -23,4 +22,4 @@ def distance(cls, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
         """
 
         # Equation (1)
-        return distance_euclidean(color, sample, space=cls.SPACE)
+        return distance_euclidean(color, sample, space=self.SPACE)
diff --git a/coloraide/distance/delta_e_94.py b/coloraide/distance/delta_e_94.py
index 8afb44bed..e4ae7273b 100644
--- a/coloraide/distance/delta_e_94.py
+++ b/coloraide/distance/delta_e_94.py
@@ -2,7 +2,7 @@
 from ..distance import DeltaE
 import math
 from .. import algebra as alg
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Optional, Any
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -13,14 +13,25 @@ class DE94(DeltaE):
 
     NAME = "94"
 
-    @classmethod
+    def __init__(
+        self,
+        kl: float = 1,
+        k1: float = 0.045,
+        k2: float = 0.015
+    ):
+        """Initialize."""
+
+        self.kl = kl
+        self.k1 = k1
+        self.k2 = k2
+
     def distance(
-        cls,
+        self,
         color: 'Color',
         sample: 'Color',
-        kl: float = 1,
-        k1: float = 0.045,
-        k2: float = 0.015,
+        kl: Optional[float] = None,
+        k1: Optional[float] = None,
+        k2: Optional[float] = None,
         **kwargs: Any
     ) -> float:
         """
@@ -29,6 +40,15 @@ def distance(
         http://www.brucelindbloom.com/Eqn_DeltaE_CIE94.html
         """
 
+        if kl is None:
+            kl = self.kl
+
+        if k1 is None:
+            k1 = self.k1
+
+        if k2 is None:
+            k2 = self.k2
+
         l1, a1, b1 = alg.no_nans(color.convert("lab")[:-1])
         l2, a2, b2 = alg.no_nans(sample.convert("lab")[:-1])
 
diff --git a/coloraide/distance/delta_e_cmc.py b/coloraide/distance/delta_e_cmc.py
index ff47b001f..c24844ef7 100644
--- a/coloraide/distance/delta_e_cmc.py
+++ b/coloraide/distance/delta_e_cmc.py
@@ -2,7 +2,7 @@
 from ..distance import DeltaE
 import math
 from .. import algebra as alg
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Optional
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -13,13 +13,22 @@ class DECMC(DeltaE):
 
     NAME = "cmc"
 
-    @classmethod
+    def __init__(
+        self,
+        l: float = 2,
+        c: float = 1
+    ):
+        """Initialize."""
+
+        self.l = l
+        self.c = c
+
     def distance(
-        cls,
+        self,
         color: 'Color',
         sample: 'Color',
-        l: float = 2,
-        c: float = 1,
+        l: Optional[float] = None,
+        c: Optional[float] = None,
         **kwargs: Any
     ) -> float:
         """
@@ -28,6 +37,12 @@ def distance(
         http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
         """
 
+        if l is None:
+            l = self.l
+
+        if c is None:
+            c = self.c
+
         l1, a1, b1 = alg.no_nans(color.convert("lab")[:-1])
         l2, a2, b2 = alg.no_nans(sample.convert("lab")[:-1])
 
diff --git a/coloraide/distance/delta_e_hyab.py b/coloraide/distance/delta_e_hyab.py
index 3c7d1da35..62ec70500 100644
--- a/coloraide/distance/delta_e_hyab.py
+++ b/coloraide/distance/delta_e_hyab.py
@@ -3,7 +3,7 @@
 import math
 from .. import algebra as alg
 from ..spaces import Labish
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Optional
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -14,18 +14,25 @@ class DEHyAB(DeltaE):
 
     NAME = "hyab"
 
-    @classmethod
-    def distance(cls, color: 'Color', sample: 'Color', space: str = "lab-d65", **kwargs: Any) -> float:
+    def __init__(self, space: str = "lab-d65") -> None:
+        """Initialize."""
+
+        self.space = space
+
+    def distance(self, color: 'Color', sample: 'Color', space: Optional[str] = None, **kwargs: Any) -> float:
         """
         HyAB distance for Lab-ish spaces.
 
         http://markfairchild.org/PDFs/PAP40.pdf.
         """
 
+        if space is None:
+            space = self.space
+
         color = color.convert(space)
         sample = sample.convert(space)
 
-        if not issubclass(color._space, Labish):
+        if not isinstance(color._space, Labish):
             raise ValueError("The space '{}' is not a 'lab-ish' color space and cannot use HyAB".format(space))
 
         names = color._space.labish_names()
diff --git a/coloraide/distance/delta_e_itp.py b/coloraide/distance/delta_e_itp.py
index da09bb8da..8fbf8c772 100644
--- a/coloraide/distance/delta_e_itp.py
+++ b/coloraide/distance/delta_e_itp.py
@@ -6,7 +6,7 @@
 from ..distance import DeltaE
 import math
 from .. import algebra as alg
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Optional
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -17,10 +17,17 @@ class DEITP(DeltaE):
 
     NAME = "itp"
 
-    @classmethod
-    def distance(cls, color: 'Color', sample: 'Color', scalar: float = 720, **kwargs: Any) -> float:
+    def __init__(self, scalar: float = 720) -> None:
+        """Initialize."""
+
+        self.scalar = scalar
+
+    def distance(self, color: 'Color', sample: 'Color', scalar: Optional[float] = None, **kwargs: Any) -> float:
         """Delta E ITP color distance formula."""
 
+        if scalar is None:
+            scalar = self.scalar
+
         i1, t1, p1 = alg.no_nans(color.convert('ictcp')[:-1])
         i2, t2, p2 = alg.no_nans(sample.convert('ictcp')[:-1])
 
diff --git a/coloraide/distance/delta_e_ok.py b/coloraide/distance/delta_e_ok.py
index 4d9b8cce6..9186d4224 100644
--- a/coloraide/distance/delta_e_ok.py
+++ b/coloraide/distance/delta_e_ok.py
@@ -1,6 +1,6 @@
 """Delta E OK."""
 from .delta_e_76 import DE76
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Optional
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -12,13 +12,20 @@ class DEOK(DE76):
     NAME = "ok"
     SPACE = "oklab"
 
-    @classmethod
-    def distance(cls, color: 'Color', sample: 'Color', scalar: float = 1, **kwargs: Any) -> float:
+    def __init__(self, scalar: float = 1) -> None:
+        """Initialize."""
+
+        self.scalar = scalar
+
+    def distance(self, color: 'Color', sample: 'Color', scalar: Optional[float] = None, **kwargs: Any) -> float:
         """
         Delta E OK color distance formula.
 
         This just uses simple Euclidean distance in the Oklab color space.
         """
 
+        if scalar is None:
+            scalar = self.scalar
+
         # Equation (1)
         return scalar * super().distance(color, sample)
diff --git a/coloraide/distance/delta_e_z.py b/coloraide/distance/delta_e_z.py
index e349ca575..631e45ffb 100644
--- a/coloraide/distance/delta_e_z.py
+++ b/coloraide/distance/delta_e_z.py
@@ -17,8 +17,7 @@ class DEZ(DeltaE):
 
     NAME = "jz"
 
-    @classmethod
-    def distance(cls, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
+    def distance(self, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
         """Delta E z color distance formula."""
 
         jz1, az1, bz1 = alg.no_nans(color.convert('jzazbz')[:-1])
diff --git a/coloraide/everything.py b/coloraide/everything.py
new file mode 100644
index 000000000..ca932f314
--- /dev/null
+++ b/coloraide/everything.py
@@ -0,0 +1,88 @@
+"""Everything and the kitchen sink."""
+from .color import Color as Base
+from .spaces.rec2100pq import Rec2100PQ
+from .spaces.jzazbz import Jzazbz
+from .spaces.jzczhz import JzCzhz
+from .spaces.ictcp import ICtCp
+from .spaces.din99o import Din99o
+from .spaces.lch99o import Lch99o
+from .spaces.luv import Luv
+from .spaces.lchuv import Lchuv
+from .spaces.hsluv import HSLuv
+from .spaces.hpluv import HPLuv
+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 .spaces.aces2065_1 import ACES20651
+from .spaces.acescg import ACEScg
+from .spaces.acescc import ACEScc
+from .spaces.acescct import ACEScct
+from .distance.delta_e_itp import DEITP
+from .distance.delta_e_99o import DE99o
+from .distance.delta_e_z import DEZ
+from .cat import VonKries, XYZScaling, CAT02, CMCCAT97, Sharp, CMCCAT2000, CAT16
+from .color import ColorMatch
+from .interpolate import stop, hint
+from .algebra import NaN
+
+__all__ = ('ColorAll', 'ColorMatch', 'stop', 'hint', 'NaN')
+
+
+class ColorAll(Base):
+    """Color with all plugins."""
+
+
+ColorAll.register(
+    [
+        # Spaces
+        Rec2100PQ(),
+        Jzazbz(),
+        JzCzhz(),
+        ICtCp(),
+        Din99o(),
+        Lch99o(),
+        Luv(),
+        Lchuv(),
+        Okhsl(),
+        Okhsv(),
+        HSLuv(),
+        HPLuv(),
+        HSI(),
+        IPT(),
+        IgPgTg(),
+        CMY(),
+        CMYK(),
+        XyY(),
+        HunterLab(),
+        Prismatic(),
+        RLAB(),
+        ORGB(),
+        ACES20651(),
+        ACEScg(),
+        ACEScc(),
+        ACEScct(),
+
+        # Delta E
+        DEITP(),
+        DE99o(),
+        DEZ(),
+
+        # CAT
+        VonKries(),
+        XYZScaling(),
+        CAT02(),
+        CMCCAT97(),
+        Sharp(),
+        CMCCAT2000(),
+        CAT16(),
+    ]
+)
diff --git a/coloraide/filters/__init__.py b/coloraide/filters/__init__.py
index 78a13d143..8f6ffd3db 100644
--- a/coloraide/filters/__init__.py
+++ b/coloraide/filters/__init__.py
@@ -14,9 +14,8 @@ class Filter(Plugin, metaclass=ABCMeta):
     DEFAULT_SPACE = 'srgb-linear'
     ALLOWED_SPACES = ('srgb-linear',)  # type: Tuple[str, ...]
 
-    @classmethod
     @abstractmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Filter the given color."""
 
 
diff --git a/coloraide/filters/cvd.py b/coloraide/filters/cvd.py
index 7ec664195..c32c0864e 100644
--- a/coloraide/filters/cvd.py
+++ b/coloraide/filters/cvd.py
@@ -222,52 +222,46 @@ class Protan(Filter):
     VIENOT = VIENOT_PROTAN
     MACHADO = MACHADO_PROTAN
 
-    @classmethod
-    def brettel(cls, color: 'Color', severity: float) -> None:
+    def brettel(self, color: 'Color', severity: float) -> None:
         """Tritanopia vision deficiency using Brettel method."""
 
-        brettel(color, severity, cls.BRETTEL)
+        brettel(color, severity, self.BRETTEL)
 
-    @classmethod
-    def vienot(cls, color: 'Color', severity: float) -> None:
+    def vienot(self, color: 'Color', severity: float) -> None:
         """Tritanopia vision deficiency using Viénot method."""
 
-        vienot(color, severity, cls.VIENOT)
+        vienot(color, severity, self.VIENOT)
 
-    @classmethod
-    def machado(cls, color: 'Color', severity: float) -> None:
+    def machado(self, color: 'Color', severity: float) -> None:
         """Tritanopia vision deficiency using Machado method."""
 
-        machado(color, severity, cls.MACHADO)
+        machado(color, severity, self.MACHADO)
 
-    @classmethod
-    def select_filter(cls, method: str) -> Callable[..., None]:
+    def select_filter(self, method: str) -> Callable[..., None]:
         """Select the best filter."""
 
         if method == 'brettel':
-            return cls.brettel
+            return self.brettel
         elif method == 'vienot':
-            return cls.vienot
+            return self.vienot
         elif method == 'machado':
-            return cls.machado
+            return self.machado
         else:
             raise ValueError("Unrecognized CVD filter method '{}'".format(method))
 
-    @classmethod
-    def get_best_filter(cls, method: Optional[str], max_severity: bool) -> Callable[..., None]:
+    def get_best_filter(self, method: Optional[str], max_severity: bool) -> Callable[..., None]:
         """Get the best filter based on the situation."""
 
         if method is None:
             method = 'vienot' if max_severity else 'machado'
-        return cls.select_filter(method)
+        return self.select_filter(method)
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float] = None, **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float] = None, **kwargs: Any) -> None:  # noqa: A003
         """Filter the color."""
 
         method = kwargs.get('method')  # type: Optional[str]
         amount = alg.clamp(1 if amount is None else amount, 0, 1)
-        cls.get_best_filter(method, amount == 1)(color, amount)
+        self.get_best_filter(method, amount == 1)(color, amount)
 
 
 class Deutan(Protan):
@@ -289,8 +283,7 @@ class Tritan(Protan):
     VIENOT = VIENOT_TRITAN
     MACHADO = MACHADO_TRITAN
 
-    @classmethod
-    def get_best_filter(cls, method: Optional[str], amount: float) -> Callable[..., None]:
+    def get_best_filter(self, method: Optional[str], amount: float) -> Callable[..., None]:
         """Get the best filter based on the situation."""
 
-        return cls.select_filter('brettel' if method is None else method)
+        return self.select_filter('brettel' if method is None else method)
diff --git a/coloraide/filters/w3c_filter_effects.py b/coloraide/filters/w3c_filter_effects.py
index 39c1cd8d9..927db031e 100644
--- a/coloraide/filters/w3c_filter_effects.py
+++ b/coloraide/filters/w3c_filter_effects.py
@@ -24,8 +24,7 @@ class Sepia(Filter):
     NAME = 'sepia'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a sepia filter to the color."""
 
         amount = 1 - alg.clamp(1 if amount is None else amount, 0, 1)
@@ -45,8 +44,7 @@ class Grayscale(Filter):
     NAME = 'grayscale'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a grayscale filter to the color."""
 
         amount = 1 - alg.clamp(1 if amount is None else amount, 0, 1)
@@ -66,8 +64,7 @@ class Saturate(Filter):
     NAME = 'saturate'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a saturation filter to the color."""
 
         amount = alg.clamp(1 if amount is None else amount, 0)
@@ -87,8 +84,7 @@ class Invert(Filter):
     NAME = 'invert'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply an invert filter."""
 
         amount = alg.clamp(1 if amount is None else amount, 0, 1)
@@ -102,8 +98,7 @@ class Opacity(Filter):
     NAME = 'opacity'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply an opacity filter."""
 
         amount = alg.clamp(1 if amount is None else amount, 0, 1)
@@ -116,8 +111,7 @@ class Brightness(Filter):
     NAME = 'brightness'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a brightness filter."""
 
         amount = alg.clamp(1 if amount is None else amount, 0)
@@ -131,8 +125,7 @@ class Contrast(Filter):
     NAME = 'contrast'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a contrast filter."""
 
         amount = alg.clamp(1 if amount is None else amount, 0)
@@ -146,8 +139,7 @@ class HueRotate(Filter):
     NAME = 'hue-rotate'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
-    def filter(cls, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:  # noqa: A003
         """Apply a hue rotation filter."""
 
         rad = math.radians(0 if amount is None else amount)
diff --git a/coloraide/gamut/__init__.py b/coloraide/gamut/__init__.py
index bcadd761e..c80f4550a 100644
--- a/coloraide/gamut/__init__.py
+++ b/coloraide/gamut/__init__.py
@@ -62,7 +62,6 @@ class Fit(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def fit(cls, color: 'Color', **kwargs: Any) -> None:
+    def fit(self, color: 'Color', **kwargs: Any) -> None:
         """Get coordinates of the new gamut mapped color."""
diff --git a/coloraide/gamut/fit_lch_chroma.py b/coloraide/gamut/fit_lch_chroma.py
index 26717b4f2..e1597b715 100644
--- a/coloraide/gamut/fit_lch_chroma.py
+++ b/coloraide/gamut/fit_lch_chroma.py
@@ -39,16 +39,15 @@ class LchChroma(Fit):
     MIN_LIGHTNESS = 0
     MAX_LIGHTNESS = 100
 
-    @classmethod
-    def fit(cls, color: 'Color', **kwargs: Any) -> None:
+    def fit(self, color: 'Color', **kwargs: Any) -> None:
         """Gamut mapping via CIELCH chroma."""
 
         space = color.space()
-        mapcolor = color.convert(cls.SPACE)
+        mapcolor = color.convert(self.SPACE)
         lightness = mapcolor['lightness']
 
         # Return white or black if lightness is out of range
-        if lightness >= cls.MAX_LIGHTNESS or lightness <= cls.MIN_LIGHTNESS:
+        if lightness >= self.MAX_LIGHTNESS or lightness <= self.MIN_LIGHTNESS:
             mapcolor['chroma'] = 0
             mapcolor['hue'] = NaN
             clip_channels(color.update(mapcolor))
@@ -60,7 +59,7 @@ def fit(cls, color: 'Color', **kwargs: Any) -> None:
         clip_channels(color.update(mapcolor))
 
         # Adjust chroma if we are not under the JND yet.
-        if mapcolor.delta_e(color, method=cls.DE) >= cls.LIMIT:
+        if mapcolor.delta_e(color, method=self.DE) >= self.LIMIT:
             # Perform "in gamut" checks until we know our lower bound is no longer in gamut.
             lower_in_gamut = True
 
@@ -72,11 +71,11 @@ def fit(cls, color: 'Color', **kwargs: Any) -> None:
                     low = mapcolor['chroma']
                 else:
                     clip_channels(color.update(mapcolor))
-                    de = mapcolor.delta_e(color, method=cls.DE)
-                    if de < cls.LIMIT:
+                    de = mapcolor.delta_e(color, method=self.DE)
+                    if de < self.LIMIT:
                         # Kick out as soon as we are close enough to the JND.
                         # Too far below and we may reduce chroma too aggressively.
-                        if (cls.LIMIT - de) < cls.EPSILON:
+                        if (self.LIMIT - de) < self.EPSILON:
                             break
 
                         # Our lower bound is now out of gamut, so all future searches are
diff --git a/coloraide/harmonies.py b/coloraide/harmonies.py
index bd9541900..75d203f1f 100644
--- a/coloraide/harmonies.py
+++ b/coloraide/harmonies.py
@@ -1,7 +1,7 @@
 """Color harmonies."""
 from . import algebra as alg
 from .spaces import Cylindrical
-from typing import Optional, Type, Dict, List, cast, TYPE_CHECKING
+from typing import Optional, Dict, List, cast, TYPE_CHECKING
 
 if TYPE_CHECKING:  # pragma: no cover
     from .color import Color
@@ -10,8 +10,7 @@
 class Harmony:
     """Color harmony."""
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
 
@@ -42,8 +41,7 @@ class Monochromatic(Harmony):
     RANGE = 12
     STEPS = 5
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
         if space is None:
@@ -52,7 +50,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         orig_space = color.space()
         color0 = color.convert(space).normalize()
 
-        if not issubclass(color0._space, Cylindrical):
+        if not isinstance(color0._space, Cylindrical):
             raise ValueError('Color space must be cylindrical')
 
         # Trim off black and white unless the color is achromatic,
@@ -68,17 +66,17 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         b = color.new('color(srgb 0 0 0 / none)').convert(space, in_place=True).mask(['hue', 'alpha'], in_place=True)
 
         # Calculate how many tints and shades we need to generate
-        db = b.delta_e(color0, method=cls.DELTA_E)
-        dw = w.delta_e(color0, method=cls.DELTA_E)
-        steps_w = int(alg.round_half_up((dw / (db + dw)) * cls.RANGE))
-        steps_b = cls.RANGE - steps_w
+        db = b.delta_e(color0, method=self.DELTA_E)
+        dw = w.delta_e(color0, method=self.DELTA_E)
+        steps_w = int(alg.round_half_up((dw / (db + dw)) * self.RANGE))
+        steps_b = self.RANGE - steps_w
 
         # Very close to black or is black, no need to interpolate from black to current color
         if steps_b <= 1:
             left = []
             if steps_b == 1:
                 left.extend(color.steps([b, color], steps=steps_b, space=space, out_space=orig_space))
-            steps = min(cls.RANGE - (1 + steps_b), steps_w)
+            steps = min(self.RANGE - (1 + steps_b), steps_w)
             right = color.steps([color0, w], steps=steps, space=space, out_space=orig_space)[rtrim]
 
         # Very close to white or is white, no need to interpolate from current color to white
@@ -86,7 +84,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
             right = []
             if steps_w == 1:
                 right.extend(color.steps([color0, w], steps=steps_w, space=space, out_space=orig_space))
-            steps = min(cls.RANGE - (1 + steps_w), steps_b)
+            steps = min(self.RANGE - (1 + steps_w), steps_b)
             right.insert(0, color.clone())
             left = color.steps([b, color], steps=steps, space=space, out_space=orig_space)[ltrim]
 
@@ -98,12 +96,12 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         # Extract a subset of the results
         len_l = len(left)
         len_r = len(right)
-        l = int(cls.STEPS // 2)
-        r = l + (1 if cls.STEPS % 2 else 0)
+        l = int(self.STEPS // 2)
+        r = l + (1 if self.STEPS % 2 else 0)
         if len_r < r:
-            return left[-cls.STEPS + len_r:] + right
+            return left[-self.STEPS + len_r:] + right
         elif len_l < l:
-            return left + right[:cls.STEPS - len_l]
+            return left + right[:self.STEPS - len_l]
         return left[-l:] + right[:r]
 
 
@@ -112,8 +110,7 @@ class Geometric(Harmony):
 
     COUNT = 0
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
         if space is None:
@@ -122,14 +119,14 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         orig_space = color.space()
         color0 = color.convert(space)
 
-        if not issubclass(color0._space, Cylindrical):
+        if not isinstance(color0._space, Cylindrical):
             raise ValueError('Color space must be cylindrical')
 
         name = color0._space.hue_name()
 
-        degree = current = 360 / cls.COUNT
+        degree = current = 360 / self.COUNT
         colors = [color]
-        for r in range(cls.COUNT - 1):
+        for r in range(self.COUNT - 1):
             colors.append(
                 color0.clone().set(
                     name,
@@ -161,8 +158,7 @@ class TetradicSquare(Geometric):
 class SplitComplementary(Harmony):
     """Split Complementary colors."""
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
         if space is None:
@@ -171,7 +167,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         orig_space = color.space()
         color0 = color.convert(space)
 
-        if not issubclass(color0._space, Cylindrical):
+        if not isinstance(color0._space, Cylindrical):
             raise ValueError('Color space must be cylindrical')
 
         name = color0._space.hue_name()
@@ -190,8 +186,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
 class Analogous(Harmony):
     """Analogous colors."""
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
         if space is None:
@@ -200,7 +195,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         orig_space = color.space()
         color0 = color.convert(space)
 
-        if not issubclass(color0._space, Cylindrical):
+        if not isinstance(color0._space, Cylindrical):
             raise ValueError('Color space must be cylindrical')
 
         name = color0._space.hue_name()
@@ -219,8 +214,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
 class TetradicRect(Harmony):
     """Tetradic (rectangular) colors."""
 
-    @classmethod
-    def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
+    def harmonize(self, color: 'Color', space: Optional[str]) -> List['Color']:
         """Get color harmonies."""
 
         if space is None:
@@ -229,7 +223,7 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
         orig_space = color.space()
         color0 = color.convert(space)
 
-        if not issubclass(color0._space, Cylindrical):
+        if not isinstance(color0._space, Cylindrical):
             raise ValueError('Color space must be cylindrical')
 
         name = color0._space.hue_name()
@@ -249,14 +243,14 @@ def harmonize(cls, color: 'Color', space: Optional[str]) -> List['Color']:
 
 
 SUPPORTED = {
-    'complement': Complementary,
-    'split': SplitComplementary,
-    'triad': Triadic,
-    'square': TetradicSquare,
-    'rectangle': TetradicRect,
-    'analogous': Analogous,
-    'mono': Monochromatic
-}  # type: Dict[str, Type[Harmony]]
+    'complement': Complementary(),
+    'split': SplitComplementary(),
+    'triad': Triadic(),
+    'square': TetradicSquare(),
+    'rectangle': TetradicRect(),
+    'analogous': Analogous(),
+    'mono': Monochromatic()
+}  # type: Dict[str, Harmony]
 
 
 def harmonize(color: 'Color', name: str, space: Optional[str]) -> List['Color']:
diff --git a/coloraide/interpolate/bezier.py b/coloraide/interpolate/bezier.py
index a19b4bd1e..a0b4e90a0 100644
--- a/coloraide/interpolate/bezier.py
+++ b/coloraide/interpolate/bezier.py
@@ -158,8 +158,8 @@ def normalize_color(color: 'Color', space: str, premultiplied: bool) -> None:
         premultiply(color)
 
     # Normalize hue
-    if issubclass(color._space, Cylindrical):
-        name = cast(Type[Cylindrical], color._space).hue_name()
+    if isinstance(color._space, Cylindrical):
+        name = cast(Cylindrical, color._space).hue_name()
         color.set(name, lambda h: cast(float, h % 360))
 
 
@@ -237,7 +237,7 @@ def color_bezier_lerp(
     # Send the interpolation list along with the stop map to the Piecewise interpolator
     return InterpolateBezier(
         coords,
-        [str(c) for c in current._space.get_all_channels()],
+        current._space.channels,
         create,
         easings,
         stops,
diff --git a/coloraide/interpolate/piecewise.py b/coloraide/interpolate/piecewise.py
index 04827ed6f..3bd2b8477 100644
--- a/coloraide/interpolate/piecewise.py
+++ b/coloraide/interpolate/piecewise.py
@@ -15,7 +15,7 @@ def adjust_hues(color1: 'Color', color2: 'Color', hue: str) -> None:
     if hue == "specified":
         return
 
-    name = cast(Type[Cylindrical], color1._space).hue_name()
+    name = cast(Cylindrical, color1._space).hue_name()
     c1 = color1.get(name)
     c2 = color2.get(name)
 
@@ -214,7 +214,7 @@ def color_piecewise_lerp(
 
         # Adjust hues if we have two valid hues
         color2 = color.clone()
-        if issubclass(current._space, Cylindrical):
+        if isinstance(current._space, Cylindrical):
             adjust_hues(current, color2, hue)
 
         # Create an entry interpolating the current color and the next color
@@ -235,7 +235,7 @@ def color_piecewise_lerp(
     # Send the interpolation list along with the stop map to the Piecewise interpolator
     return InterpolatePiecewise(
         color_map,
-        [str(c) for c in current._space.get_all_channels()],
+        current._space.channels,
         create,
         easings,
         stops,
diff --git a/coloraide/spaces/__init__.py b/coloraide/spaces/__init__.py
index fb77a0271..9a3f9c695 100644
--- a/coloraide/spaces/__init__.py
+++ b/coloraide/spaces/__init__.py
@@ -6,7 +6,7 @@
 from ..css import serialize
 from .. import algebra as alg
 from ..types import VectorLike, Vector, Plugin
-from typing import Tuple, Dict, Optional, Union, Any, List, cast, Type, Iterator, TYPE_CHECKING
+from typing import Tuple, Dict, Optional, Union, Any, List, cast, TYPE_CHECKING
 
 if TYPE_CHECKING:  # pragma: no cover
     from ..color import Color
@@ -20,51 +20,43 @@
 class Cylindrical:
     """Cylindrical space."""
 
-    @classmethod
-    def hue_name(cls) -> str:
+    def hue_name(self) -> str:
         """Hue channel name."""
 
         return "h"
 
-    @classmethod
-    def hue_index(cls) -> int:  # pragma: no cover
+    def hue_index(self) -> int:  # pragma: no cover
         """Get hue index."""
 
-        return cast(Type['Space'], cls).get_channel_index(cls.hue_name())
+        return cast('Space', self).get_channel_index(self.hue_name())
 
 
 class Labish:
     """Lab-ish color spaces."""
 
-    @classmethod
-    def labish_names(cls) -> Tuple[str, ...]:
+    def labish_names(self) -> Tuple[str, ...]:
         """Return Lab-ish names in the order L a b."""
 
-        return cast(Type['Space'], cls).CHANNELS
+        return cast('Space', self).channels[:-1]
 
-    @classmethod
-    def labish_indexes(cls) -> List[int]:  # pragma: no cover
+    def labish_indexes(self) -> List[int]:  # pragma: no cover
         """Return the index of the Lab-ish channels."""
 
-        names = cls.labish_names()
-        return [cast(Type['Space'], cls).get_channel_index(name) for name in names]
+        return [cast('Space', self).get_channel_index(name) for name in self.labish_names()]
 
 
 class Lchish(Cylindrical):
     """Lch-ish color spaces."""
 
-    @classmethod
-    def lchish_names(cls) -> Tuple[str, ...]:  # pragma: no cover
+    def lchish_names(self) -> Tuple[str, ...]:  # pragma: no cover
         """Return Lch-ish names in the order L c h."""
 
-        return cast(Type['Space'], cls).CHANNELS
+        return cast('Space', self).channels[:-1]
 
-    @classmethod
-    def lchish_indexes(cls) -> List[int]:  # pragma: no cover
+    def lchish_indexes(self) -> List[int]:  # pragma: no cover
         """Return the index of the Lab-ish channels."""
 
-        names = cls.lchish_names()
-        return [cast(Type['Space'], cls).get_channel_index(name) for name in names]
+        return [cast('Space', self).get_channel_index(name) for name in self.lchish_names()]
 
 
 alpha_channel = Channel('alpha', 0.0, 1.0, bound=True, limit=(0.0, 1.0))
@@ -87,9 +79,9 @@ class Space(Plugin, metaclass=SpaceMeta):
     # Color space name
     NAME = ""
     # Serialized name
-    SERIALIZE = tuple()  # type: Tuple[str, ...]
+    SERIALIZE = ()  # type: Tuple[str, ...]
     # Channel names
-    CHANNELS = tuple()  # type: Tuple[Channel, ...]
+    CHANNELS = ()  # type: Tuple[Channel, ...]
     # Channel aliases
     CHANNEL_ALIASES = {}  # type: Dict[str, str]
     # Enable or disable default color format parsing and serialization.
@@ -112,32 +104,21 @@ class Space(Plugin, metaclass=SpaceMeta):
     # White point
     WHITE = (0.0, 0.0)
 
-    @classmethod
-    def get_channel_index(cls, name: str) -> int:
-        """Get channel index."""
+    def __init__(self, **kwargs: Any) -> None:
+        """Initialize."""
 
-        if name == 'alpha':
-            return len(cls.CHANNELS)
-        return cls.CHANNELS.index(cls.CHANNEL_ALIASES.get(name, name))
+        self.channels = self.CHANNELS + (alpha_channel,)
+        self._color_ids = (self.NAME,) if not self.SERIALIZE else self.SERIALIZE
 
-    @classmethod
-    def get_channel(cls, index: int) -> Channel:
+    def get_channel_index(self, name: str) -> int:
         """Get channel index."""
 
-        return (cls.CHANNELS + (alpha_channel,))[index]
+        return self.channels.index(self.CHANNEL_ALIASES.get(name, name))
 
-    @classmethod
-    def get_all_channels(cls) -> Iterator[Channel]:
-        """Get all channels."""
-
-        yield from cls.CHANNELS
-        yield alpha_channel
-
-    @classmethod
-    def _serialize(cls) -> Tuple[str, ...]:
+    def _serialize(self) -> Tuple[str, ...]:
         """Get the serialized name."""
 
-        return (cls.NAME,) if not cls.SERIALIZE else cls.SERIALIZE
+        return self._color_ids
 
     @classmethod
     def white(cls) -> VectorLike:
@@ -145,19 +126,16 @@ def white(cls) -> VectorLike:
 
         return cls.WHITE
 
-    @classmethod
     @abstractmethod
-    def to_base(cls, coords: Vector) -> Vector:  # pragma: no cover
+    def to_base(self, coords: Vector) -> Vector:  # pragma: no cover
         """To base color."""
 
-    @classmethod
     @abstractmethod
-    def from_base(cls, coords: Vector) -> Vector:  # pragma: no cover
+    def from_base(self, coords: Vector) -> Vector:  # pragma: no cover
         """From base color."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
@@ -177,15 +155,13 @@ def to_string(
             none=none
         )
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """Process coordinates and adjust any channels to null/NaN if required."""
 
         return alg.no_nans(coords)
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
diff --git a/coloraide/spaces/a98_rgb.py b/coloraide/spaces/a98_rgb.py
index a156a8495..98e56ad8d 100644
--- a/coloraide/spaces/a98_rgb.py
+++ b/coloraide/spaces/a98_rgb.py
@@ -24,14 +24,12 @@ class A98RGB(SRGB):
     NAME = "a98-rgb"
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from A98 RGB."""
 
         return lin_a98rgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to A98 RGB."""
 
         return gam_a98rgb(coords)
diff --git a/coloraide/spaces/a98_rgb_linear.py b/coloraide/spaces/a98_rgb_linear.py
index 77f37eb7e..99a81d8b6 100644
--- a/coloraide/spaces/a98_rgb_linear.py
+++ b/coloraide/spaces/a98_rgb_linear.py
@@ -44,14 +44,12 @@ class A98RGBLinear(SRGB):
     SERIALIZE = ('--a98-rgb-linear',)
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from A98 RGB."""
 
         return lin_a98rgb_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to A98 RGB."""
 
         return xyz_to_lin_a98rgb(coords)
diff --git a/coloraide/spaces/aces2065_1.py b/coloraide/spaces/aces2065_1.py
index 3b0d7e024..ad05e3f07 100644
--- a/coloraide/spaces/aces2065_1.py
+++ b/coloraide/spaces/aces2065_1.py
@@ -50,14 +50,12 @@ class ACES20651(SRGB):
         Channel("b", 0.0, 65504.0, bound=True)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return aces_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return xyz_to_aces(coords)
diff --git a/coloraide/spaces/acescc.py b/coloraide/spaces/acescc.py
index 591330d9c..fb938d084 100644
--- a/coloraide/spaces/acescc.py
+++ b/coloraide/spaces/acescc.py
@@ -63,14 +63,12 @@ class ACEScc(SRGB):
         Channel("b", CC_MIN, CC_MAX, bound=True)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return acescc_to_acescg(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return acescg_to_acescc(coords)
diff --git a/coloraide/spaces/acescct.py b/coloraide/spaces/acescct.py
index 8709fdd99..9401b6dd2 100644
--- a/coloraide/spaces/acescct.py
+++ b/coloraide/spaces/acescct.py
@@ -58,14 +58,12 @@ class ACEScct(SRGB):
         Channel("b", CCT_MIN, CCT_MAX, bound=True)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return acescct_to_acescg(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return acescg_to_acescct(coords)
diff --git a/coloraide/spaces/acescg.py b/coloraide/spaces/acescg.py
index a349bf20a..91fbd64f0 100644
--- a/coloraide/spaces/acescg.py
+++ b/coloraide/spaces/acescg.py
@@ -47,14 +47,12 @@ class ACEScg(SRGB):
         Channel("b", 0.0, 65504.0, bound=True)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return acescg_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return xyz_to_acescg(coords)
diff --git a/coloraide/spaces/cmy.py b/coloraide/spaces/cmy.py
index 1776ee0c3..810a8e418 100644
--- a/coloraide/spaces/cmy.py
+++ b/coloraide/spaces/cmy.py
@@ -36,14 +36,12 @@ class CMY(Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB."""
 
         return cmy_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB."""
 
         return srgb_to_cmy(coords)
diff --git a/coloraide/spaces/cmyk.py b/coloraide/spaces/cmyk.py
index 38c43da0c..053e906f0 100644
--- a/coloraide/spaces/cmyk.py
+++ b/coloraide/spaces/cmyk.py
@@ -55,14 +55,12 @@ class CMYK(Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB."""
 
         return cmyk_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB."""
 
         return srgb_to_cmyk(coords)
diff --git a/coloraide/spaces/din99o.py b/coloraide/spaces/din99o.py
index 7ca49e49d..eca461aaa 100644
--- a/coloraide/spaces/din99o.py
+++ b/coloraide/spaces/din99o.py
@@ -103,14 +103,12 @@ class Din99o(Lab):
         Channel("b", -55.0, 55.0, flags=FLG_MIRROR_PERCENT)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Din99o."""
 
         return super().to_base(din99o_to_lab(coords))
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Din99o."""
 
         return lab_to_din99o(super().from_base(coords))
diff --git a/coloraide/spaces/display_p3.py b/coloraide/spaces/display_p3.py
index 4970a5695..5e6efa8f0 100644
--- a/coloraide/spaces/display_p3.py
+++ b/coloraide/spaces/display_p3.py
@@ -11,14 +11,12 @@ class DisplayP3(SRGB):
     NAME = "display-p3"
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Display P3."""
 
         return lin_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Display P3."""
 
         return gam_srgb(coords)
diff --git a/coloraide/spaces/display_p3_linear.py b/coloraide/spaces/display_p3_linear.py
index 31c61b5ac..0a2ada262 100644
--- a/coloraide/spaces/display_p3_linear.py
+++ b/coloraide/spaces/display_p3_linear.py
@@ -42,14 +42,12 @@ class DisplayP3Linear(SRGB):
     SERIALIZE = ('--display-p3-linear',)
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Linear Display P3."""
 
         return lin_p3_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Linear Display P3."""
 
         return xyz_to_lin_p3(coords)
diff --git a/coloraide/spaces/hpluv.py b/coloraide/spaces/hpluv.py
index e6c955e46..676c4ea97 100644
--- a/coloraide/spaces/hpluv.py
+++ b/coloraide/spaces/hpluv.py
@@ -124,8 +124,7 @@ class HPLuv(Cylindrical, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -133,14 +132,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[0] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To LCHuv from HPLuv."""
 
         return hpluv_to_lch(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From LCHuv to HPLuv."""
 
         return lch_to_hpluv(coords)
diff --git a/coloraide/spaces/hsi.py b/coloraide/spaces/hsi.py
index 3bbfd022d..dcdb9c22f 100644
--- a/coloraide/spaces/hsi.py
+++ b/coloraide/spaces/hsi.py
@@ -91,8 +91,7 @@ class HSI(Cylindrical, Space):
     WHITE = WHITES['2deg']['D65']
     GAMUT_CHECK = "srgb"
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         h, s, i = alg.no_nans(coords[:-1])
@@ -106,14 +105,12 @@ def normalize(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB from HSI."""
 
         return hsi_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB to HSI."""
 
         return srgb_to_hsi(coords)
diff --git a/coloraide/spaces/hsl/__init__.py b/coloraide/spaces/hsl/__init__.py
index 552fb2e16..7ec1acefc 100644
--- a/coloraide/spaces/hsl/__init__.py
+++ b/coloraide/spaces/hsl/__init__.py
@@ -71,8 +71,7 @@ class HSL(Cylindrical, Space):
     WHITE = WHITES['2deg']['D65']
     GAMUT_CHECK = "srgb"
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -81,14 +80,12 @@ def normalize(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB from HSL."""
 
         return hsl_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB to HSL."""
 
         return srgb_to_hsl(coords)
diff --git a/coloraide/spaces/hsl/css.py b/coloraide/spaces/hsl/css.py
index bef88a27a..cfefe09eb 100644
--- a/coloraide/spaces/hsl/css.py
+++ b/coloraide/spaces/hsl/css.py
@@ -12,15 +12,16 @@
 class HSL(base.HSL):
     """HSL class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
+        comma: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,17 +33,16 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            legacy=kwargs.get('comma', False)
+            color=color,
+            legacy=comma
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/hsluv.py b/coloraide/spaces/hsluv.py
index fd428d231..f10f8a571 100644
--- a/coloraide/spaces/hsluv.py
+++ b/coloraide/spaces/hsluv.py
@@ -127,8 +127,7 @@ class HSLuv(Cylindrical, Space):
     WHITE = WHITES['2deg']['D65']
     GAMUT_CHECK = "srgb"
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -136,14 +135,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[0] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To LCHuv from HSLuv."""
 
         return hsluv_to_lch(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From LCHuv to HSLuv."""
 
         return lch_to_hsluv(coords)
diff --git a/coloraide/spaces/hsv.py b/coloraide/spaces/hsv.py
index 26b5b7476..019bd8fec 100644
--- a/coloraide/spaces/hsv.py
+++ b/coloraide/spaces/hsv.py
@@ -61,8 +61,7 @@ class HSV(Cylindrical, Space):
     GAMUT_CHECK = "srgb"
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -71,14 +70,12 @@ def normalize(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To HSL from HSV."""
 
         return hsv_to_hsl(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From HSL to HSV."""
 
         return hsl_to_hsv(coords)
diff --git a/coloraide/spaces/hunter_lab.py b/coloraide/spaces/hunter_lab.py
index 0854be223..e9a80cb4a 100644
--- a/coloraide/spaces/hunter_lab.py
+++ b/coloraide/spaces/hunter_lab.py
@@ -61,14 +61,12 @@ class HunterLab(Lab):
         Channel("b", -210.0, 210.0, flags=FLG_MIRROR_PERCENT)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Hunter Lab."""
 
-        return hlab_to_xyz(coords, cls.white())
+        return hlab_to_xyz(coords, self.white())
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Hunter Lab."""
 
-        return xyz_to_hlab(coords, cls.white())
+        return xyz_to_hlab(coords, self.white())
diff --git a/coloraide/spaces/hwb/__init__.py b/coloraide/spaces/hwb/__init__.py
index f9e1f3c21..b9db6c5f1 100644
--- a/coloraide/spaces/hwb/__init__.py
+++ b/coloraide/spaces/hwb/__init__.py
@@ -51,8 +51,7 @@ class HWB(Cylindrical, Space):
     GAMUT_CHECK = "srgb"
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -60,14 +59,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[0] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To HSV from HWB."""
 
         return hwb_to_hsv(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From HSV to HWB."""
 
         return hsv_to_hwb(coords)
diff --git a/coloraide/spaces/hwb/css.py b/coloraide/spaces/hwb/css.py
index 91f412e59..05c4c06c0 100644
--- a/coloraide/spaces/hwb/css.py
+++ b/coloraide/spaces/hwb/css.py
@@ -12,15 +12,15 @@
 class HWB(base.HWB):
     """HWB class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,16 +32,15 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False)
+            color=color
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/ictcp.py b/coloraide/spaces/ictcp.py
index a8afa35da..01c94d954 100644
--- a/coloraide/spaces/ictcp.py
+++ b/coloraide/spaces/ictcp.py
@@ -95,14 +95,12 @@ class ICtCp(Labish, Space):
     )
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from ICtCp."""
 
         return ictcp_to_xyz_d65(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to ICtCp."""
 
         return xyz_d65_to_ictcp(coords)
diff --git a/coloraide/spaces/igpgtg.py b/coloraide/spaces/igpgtg.py
index 22ace159c..38cc73101 100644
--- a/coloraide/spaces/igpgtg.py
+++ b/coloraide/spaces/igpgtg.py
@@ -72,14 +72,12 @@ class IgPgTg(Labish, Space):
     )
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return igpgtg_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return xyz_to_igpgtg(coords)
diff --git a/coloraide/spaces/ipt.py b/coloraide/spaces/ipt.py
index ad7379381..14a1a29ad 100644
--- a/coloraide/spaces/ipt.py
+++ b/coloraide/spaces/ipt.py
@@ -80,14 +80,12 @@ class IPT(Labish, Space):
     )
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return ipt_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
         return xyz_to_ipt(coords)
diff --git a/coloraide/spaces/jzazbz.py b/coloraide/spaces/jzazbz.py
index 54a74a946..1ce221265 100644
--- a/coloraide/spaces/jzazbz.py
+++ b/coloraide/spaces/jzazbz.py
@@ -135,14 +135,12 @@ class Jzazbz(Labish, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Jzazbz."""
 
         return jzazbz_to_xyz_d65(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Jzazbz."""
 
         return xyz_d65_to_jzazbz(coords)
diff --git a/coloraide/spaces/jzczhz.py b/coloraide/spaces/jzczhz.py
index 9301a3579..0b8326220 100644
--- a/coloraide/spaces/jzczhz.py
+++ b/coloraide/spaces/jzczhz.py
@@ -66,8 +66,7 @@ class JzCzhz(Lchish, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -76,20 +75,17 @@ def normalize(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def hue_name(cls) -> str:
+    def hue_name(self) -> str:
         """Hue name."""
 
         return "hz"
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To Jzazbz from JzCzhz."""
 
         return jzczhz_to_jzazbz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From Jzazbz to JzCzhz."""
 
         return jzazbz_to_jzczhz(coords)
diff --git a/coloraide/spaces/lab/__init__.py b/coloraide/spaces/lab/__init__.py
index 9b29ed22b..3ae753cb5 100644
--- a/coloraide/spaces/lab/__init__.py
+++ b/coloraide/spaces/lab/__init__.py
@@ -78,14 +78,12 @@ class Lab(Labish, Space):
     }
     WHITE = WHITES['2deg']['D50']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ D50 from Lab."""
 
-        return lab_to_xyz(coords, cls.white())
+        return lab_to_xyz(coords, self.white())
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ D50 to Lab."""
 
-        return xyz_to_lab(coords, cls.white())
+        return xyz_to_lab(coords, self.white())
diff --git a/coloraide/spaces/lab/css.py b/coloraide/spaces/lab/css.py
index d70b48922..9fea02048 100644
--- a/coloraide/spaces/lab/css.py
+++ b/coloraide/spaces/lab/css.py
@@ -12,15 +12,16 @@
 class Lab(base.Lab):
     """Lab class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
+        percent: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,17 +33,16 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            percent=kwargs.get('percent', False),
+            color=color,
+            percent=percent
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/lch/__init__.py b/coloraide/spaces/lch/__init__.py
index 02ebeca50..9a2d41dbb 100644
--- a/coloraide/spaces/lch/__init__.py
+++ b/coloraide/spaces/lch/__init__.py
@@ -58,8 +58,7 @@ class Lch(Lchish, Space):
     }
     WHITE = WHITES['2deg']['D50']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -67,14 +66,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[2] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To Lab from Lch."""
 
         return lch_to_lab(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From Lab to Lch."""
 
         return lab_to_lch(coords)
diff --git a/coloraide/spaces/lch/css.py b/coloraide/spaces/lch/css.py
index 4a9188fc0..88e38854e 100644
--- a/coloraide/spaces/lch/css.py
+++ b/coloraide/spaces/lch/css.py
@@ -12,15 +12,16 @@
 class Lch(base.Lch):
     """Lch class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
+        percent: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,17 +33,16 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            percent=kwargs.get('percent', False)
+            color=color,
+            percent=percent
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/lch99o.py b/coloraide/spaces/lch99o.py
index 5a1227781..f119ba906 100644
--- a/coloraide/spaces/lch99o.py
+++ b/coloraide/spaces/lch99o.py
@@ -52,14 +52,12 @@ class Lch99o(Lch):
         Channel("h", 0.0, 360.0, flags=FLG_ANGLE)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To Din99o from Din99o Lch."""
 
         return lch_to_lab(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From Din99o to Din99o Lch."""
 
         return lab_to_lch(coords)
diff --git a/coloraide/spaces/lchuv.py b/coloraide/spaces/lchuv.py
index 26927235b..beab2cc1e 100644
--- a/coloraide/spaces/lchuv.py
+++ b/coloraide/spaces/lchuv.py
@@ -52,14 +52,12 @@ class Lchuv(Lch, Space):
         Channel("h", 0.0, 360.0, flags=FLG_ANGLE)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To Luv from Lch(uv)."""
 
         return lchuv_to_luv(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From Luv to Lch(uv)."""
 
         return luv_to_lchuv(coords)
diff --git a/coloraide/spaces/luv.py b/coloraide/spaces/luv.py
index 4779c2d73..56919c10a 100644
--- a/coloraide/spaces/luv.py
+++ b/coloraide/spaces/luv.py
@@ -70,14 +70,12 @@ class Luv(Labish, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ D50 from Luv."""
 
-        return luv_to_xyz(coords, cls.WHITE)
+        return luv_to_xyz(coords, self.WHITE)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ D50 to Luv."""
 
-        return xyz_to_luv(coords, cls.WHITE)
+        return xyz_to_luv(coords, self.WHITE)
diff --git a/coloraide/spaces/okhsl.py b/coloraide/spaces/okhsl.py
index b3bf7fc43..6b472f45e 100644
--- a/coloraide/spaces/okhsl.py
+++ b/coloraide/spaces/okhsl.py
@@ -431,8 +431,7 @@ class Okhsl(Cylindrical, Space):
     WHITE = WHITES['2deg']['D65']
     GAMUT_CHECK = "srgb"
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -440,14 +439,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[0] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To Oklab from Okhsl."""
 
         return okhsl_to_oklab(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From Oklab to Okhsl."""
 
         return oklab_to_okhsl(coords)
diff --git a/coloraide/spaces/okhsv.py b/coloraide/spaces/okhsv.py
index 6a84de812..ea356d65b 100644
--- a/coloraide/spaces/okhsv.py
+++ b/coloraide/spaces/okhsv.py
@@ -154,8 +154,7 @@ class Okhsv(Cylindrical, Space):
     WHITE = WHITES['2deg']['D65']
     GAMUT_CHECK = "srgb"
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -163,14 +162,12 @@ def normalize(cls, coords: Vector) -> Vector:
             coords[0] = alg.NaN
         return coords
 
-    @classmethod
-    def to_base(cls, okhsv: Vector) -> Vector:
+    def to_base(self, okhsv: Vector) -> Vector:
         """To Oklab from Okhsv."""
 
         return okhsv_to_oklab(okhsv)
 
-    @classmethod
-    def from_base(cls, oklab: Vector) -> Vector:
+    def from_base(self, oklab: Vector) -> Vector:
         """From Oklab to Okhsv."""
 
         return oklab_to_okhsv(oklab)
diff --git a/coloraide/spaces/oklab/__init__.py b/coloraide/spaces/oklab/__init__.py
index e4486fefb..a95ab9ed0 100644
--- a/coloraide/spaces/oklab/__init__.py
+++ b/coloraide/spaces/oklab/__init__.py
@@ -130,14 +130,12 @@ class Oklab(Labish, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, oklab: Vector) -> Vector:
+    def to_base(self, oklab: Vector) -> Vector:
         """To XYZ."""
 
         return oklab_to_xyz_d65(oklab)
 
-    @classmethod
-    def from_base(cls, xyz: Vector) -> Vector:
+    def from_base(self, xyz: Vector) -> Vector:
         """From XYZ."""
 
         return xyz_d65_to_oklab(xyz)
diff --git a/coloraide/spaces/oklab/css.py b/coloraide/spaces/oklab/css.py
index d0a56e36f..613927ca7 100644
--- a/coloraide/spaces/oklab/css.py
+++ b/coloraide/spaces/oklab/css.py
@@ -12,15 +12,16 @@
 class Oklab(base.Oklab):
     """Oklab class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
+        percent: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,17 +33,16 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            percent=kwargs.get('percent', False)
+            color=color,
+            percent=percent
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/oklch/__init__.py b/coloraide/spaces/oklch/__init__.py
index 173488762..3c83d8402 100644
--- a/coloraide/spaces/oklch/__init__.py
+++ b/coloraide/spaces/oklch/__init__.py
@@ -82,8 +82,7 @@ class Oklch(Lchish, Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
+    def normalize(self, coords: Vector) -> Vector:
         """On color update."""
 
         coords = alg.no_nans(coords)
@@ -92,14 +91,12 @@ def normalize(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def to_base(cls, oklch: Vector) -> Vector:
+    def to_base(self, oklch: Vector) -> Vector:
         """To Lab."""
 
         return oklch_to_oklab(oklch)
 
-    @classmethod
-    def from_base(cls, oklab: Vector) -> Vector:
+    def from_base(self, oklab: Vector) -> Vector:
         """To Lab."""
 
         return oklab_to_oklch(oklab)
diff --git a/coloraide/spaces/oklch/css.py b/coloraide/spaces/oklch/css.py
index 3d0145ea3..72983221f 100644
--- a/coloraide/spaces/oklch/css.py
+++ b/coloraide/spaces/oklch/css.py
@@ -12,15 +12,16 @@
 class Oklch(base.Oklch):
     """Oklch class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[str, bool] = True,
         none: bool = False,
+        color: bool = False,
+        percent: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,17 +33,16 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            percent=kwargs.get('percent', False)
+            color=color,
+            percent=percent
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/orgb.py b/coloraide/spaces/orgb.py
index 6ca672fa0..b1e404878 100644
--- a/coloraide/spaces/orgb.py
+++ b/coloraide/spaces/orgb.py
@@ -74,14 +74,12 @@ class ORGB(Labish, Space):
         "luma": "l"
     }
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To base from oRGB."""
 
         return orgb_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From base to oRGB."""
 
         return srgb_to_orgb(coords)
diff --git a/coloraide/spaces/prismatic.py b/coloraide/spaces/prismatic.py
index 6b8703cc5..c8b6bc399 100644
--- a/coloraide/spaces/prismatic.py
+++ b/coloraide/spaces/prismatic.py
@@ -51,14 +51,12 @@ class Prismatic(Space):
     }
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB."""
 
         return lrgb_to_srgb(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB."""
 
         return srgb_to_lrgb(coords)
diff --git a/coloraide/spaces/prophoto_rgb.py b/coloraide/spaces/prophoto_rgb.py
index 88b8fa423..31ba287b8 100644
--- a/coloraide/spaces/prophoto_rgb.py
+++ b/coloraide/spaces/prophoto_rgb.py
@@ -53,14 +53,12 @@ class ProPhotoRGB(SRGB):
     NAME = "prophoto-rgb"
     WHITE = WHITES['2deg']['D50']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Pro Photo RGB."""
 
         return lin_prophoto(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Pro Photo RGB."""
 
         return gam_prophoto(coords)
diff --git a/coloraide/spaces/prophoto_rgb_linear.py b/coloraide/spaces/prophoto_rgb_linear.py
index 95fcc688e..e8143daee 100644
--- a/coloraide/spaces/prophoto_rgb_linear.py
+++ b/coloraide/spaces/prophoto_rgb_linear.py
@@ -42,14 +42,12 @@ class ProPhotoRGBLinear(SRGB):
     SERIALIZE = ('--prophoto-rgb-linear',)
     WHITE = WHITES['2deg']['D50']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Linear Pro Photo RGB."""
 
         return lin_prophoto_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Linear Pro Photo RGB."""
 
         return xyz_to_lin_prophoto(coords)
diff --git a/coloraide/spaces/rec2020.py b/coloraide/spaces/rec2020.py
index 84a0b31ec..a411d90c7 100644
--- a/coloraide/spaces/rec2020.py
+++ b/coloraide/spaces/rec2020.py
@@ -53,14 +53,12 @@ class Rec2020(SRGB):
     NAME = "rec2020"
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Rec 2020."""
 
         return lin_2020(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Rec 2020."""
 
         return gam_2020(coords)
diff --git a/coloraide/spaces/rec2020_linear.py b/coloraide/spaces/rec2020_linear.py
index 64280eab4..adc29c754 100644
--- a/coloraide/spaces/rec2020_linear.py
+++ b/coloraide/spaces/rec2020_linear.py
@@ -46,14 +46,12 @@ class Rec2020Linear(SRGB):
     SERIALIZE = ('--rec2020-linear',)
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Linear Rec 2020."""
 
         return lin_2020_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Linear Rec 2020."""
 
         return xyz_to_lin_2020(coords)
diff --git a/coloraide/spaces/rec2100pq.py b/coloraide/spaces/rec2100pq.py
index 955f00566..36675beb7 100644
--- a/coloraide/spaces/rec2100pq.py
+++ b/coloraide/spaces/rec2100pq.py
@@ -18,14 +18,12 @@ class Rec2100PQ(SRGB):
     SERIALIZE = ('--rec2100pq',)
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Rec 2100 PQ."""
 
         return alg.divide(util.pq_st2084_eotf(coords), util.YW, dims=alg.D1_SC)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Rec 2100 PQ."""
 
         return util.pq_st2084_inverse_eotf(alg.multiply(coords, util.YW, dims=alg.D1_SC))
diff --git a/coloraide/spaces/rlab.py b/coloraide/spaces/rlab.py
index cad7e5d2d..e90b4afa6 100644
--- a/coloraide/spaces/rlab.py
+++ b/coloraide/spaces/rlab.py
@@ -59,14 +59,12 @@ class RLAB(Lab):
         Channel("b", -125.0, 125.0, flags=FLG_MIRROR_PERCENT)
     )
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from Hunter Lab."""
 
         return rlab_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to Hunter Lab."""
 
         return xyz_to_rlab(coords)
diff --git a/coloraide/spaces/srgb/__init__.py b/coloraide/spaces/srgb/__init__.py
index 1df3e2aa7..96a2b7817 100644
--- a/coloraide/spaces/srgb/__init__.py
+++ b/coloraide/spaces/srgb/__init__.py
@@ -62,14 +62,12 @@ class SRGB(Space):
 
     EXTENDED_RANGE = True
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From sRGB Linear to sRGB."""
 
         return gam_srgb(coords)
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To sRGB Linear from sRGB."""
 
         return lin_srgb(coords)
diff --git a/coloraide/spaces/srgb/css.py b/coloraide/spaces/srgb/css.py
index 4019619fd..e1de2ccc5 100644
--- a/coloraide/spaces/srgb/css.py
+++ b/coloraide/spaces/srgb/css.py
@@ -12,15 +12,21 @@
 class SRGB(base.SRGB):
     """SRGB class."""
 
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[bool, str] = True,
         none: bool = False,
+        color: bool = False,
+        hex: bool = False,  # noqa: A002
+        names: bool = False,
+        comma: bool = False,
+        upper: bool = False,
+        percent: bool = False,
+        compress: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -32,23 +38,22 @@ def to_string(
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            hexa=kwargs.get('hex', False),
-            name=kwargs.get('names', False),
-            legacy=kwargs.get('comma', False),
-            upper=kwargs.get('upper', False),
-            percent=kwargs.get('percent', False),
-            compress=kwargs.get('compress', False),
+            color=color,
+            hexa=hex,
+            name=names,
+            legacy=comma,
+            upper=upper,
+            percent=percent,
+            compress=compress,
             scale=255
         )
 
-    @classmethod
     def match(
-        cls,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
diff --git a/coloraide/spaces/srgb_linear.py b/coloraide/spaces/srgb_linear.py
index b8b668f5f..a87e8bf82 100644
--- a/coloraide/spaces/srgb_linear.py
+++ b/coloraide/spaces/srgb_linear.py
@@ -42,14 +42,12 @@ class SRGBLinear(SRGB):
     SERIALIZE = ("srgb-linear",)
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ from SRGB Linear."""
 
         return lin_srgb_to_xyz(coords)
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ to SRGB Linear."""
 
         return xyz_to_lin_srgb(coords)
diff --git a/coloraide/spaces/xyy.py b/coloraide/spaces/xyy.py
index da8ab95d9..0f798dff2 100644
--- a/coloraide/spaces/xyy.py
+++ b/coloraide/spaces/xyy.py
@@ -24,14 +24,12 @@ class XyY(Space):
     )
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """To XYZ."""
 
         return util.xy_to_xyz(coords[0:2], coords[2])
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """From XYZ."""
 
-        return util.xyz_to_xyY(coords, cls.white())
+        return util.xyz_to_xyY(coords, self.white())
diff --git a/coloraide/spaces/xyz_d65.py b/coloraide/spaces/xyz_d65.py
index 83cb23958..1aab72ffb 100644
--- a/coloraide/spaces/xyz_d65.py
+++ b/coloraide/spaces/xyz_d65.py
@@ -19,8 +19,7 @@ class XYZD65(Space):
     )
     WHITE = WHITES['2deg']['D65']
 
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """
         To XYZ (no change).
 
@@ -29,8 +28,7 @@ def to_base(cls, coords: Vector) -> Vector:
 
         return coords
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """
         From XYZ (no change).
 
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index e2a3b9339..8ab6df0df 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,5 +1,30 @@
 # Changelog
 
+## 1.0b4
+
+!!! warning "Plugin Refactor"
+    For more flexibility there was one final rework of plugins. Registering requires all plugins to be instantiated
+    before being passed into `Color.register`, but this allows a user redefine some defaults of certain plugins.
+
+    `coloraide.ColorAll` was moved to `coloraide.everythng.ColorAll` to avoid allocating plugins when they are not
+    desired.
+
+- **NEW**: Updated migration guide with recent plugin changes.
+- **NEW**: `coloraide.ColorAll` renamed and moved to `coloraide.everything.ColorAll`. This prevents unnecessary
+  inclusion and allocation of objects that are not desired.
+- **NEW**: Default `Color` object now only registers `bradford` CAT by default, all others must be registered
+  separately, or `coloraide.everything.Color` could be used.
+- **NEW**: All plugin classes must be instantiated when being registered. This allows some plugins to be instantiated
+  with different defaults. This allows some plugins to be configured with different defaults.
+
+    ```py
+    # Before change:
+    Color.register([Plugin1, Plugin2])
+
+    # After change:
+    Color.register([Plugin1(), Plugin2(optional_parm=True)])
+    ```
+
 ## 1.0b3
 
 - **FIX**: Fixed the bad `CAT16` matrix for chromatic adaptation.
diff --git a/docs/src/markdown/about/releases/1.0.md b/docs/src/markdown/about/releases/1.0.md
index a1b974335..951ddd700 100644
--- a/docs/src/markdown/about/releases/1.0.md
+++ b/docs/src/markdown/about/releases/1.0.md
@@ -4,23 +4,42 @@ ColorAide has been a constantly evolving project. As we've pushed for a stable r
 exception. For any of the early adopters, there are a number of things to be aware of when migrating to 1.0. In this
 guide, we'll cover the changes most likely to impact users.
 
-## Color Spaces
+## Plugins
+
+Plugins have gone through a number of reworks. In 1.0, we now require all plugins to be registered as instances. Prior
+to 1.0 plugins were just passed in un-instantiated. Some plugins were used as static classes almost, and some (color
+spaces) were instantiated on the creation of every color.
+
+In 1.0, all plugins are required to be instantiated prior to registration. This gives the user the opportunity to
+specify any alternative defaults if desired.
+
+```playground
+Color('red').delta_e('blue', method='cmc')
+from coloraide.distance.delta_e_cmc import DECMC
+class Custom(Color):
+    DELTA_E = "cmc"
+Custom.register(DECMC(l=1, c=1), overwrite=True)
+Custom('red').delta_e('blue')
+```
+
+## Default Plugins
 
 Over the course of development, we've added a good number of color spaces. With the 1.0 release, it was decided to have
-the default `Color` object not register all available color spaces out of the box.
+the default `Color` object not register all available color spaces out of the box as the amount of color spaces has
+grown quite substantially.
 
-With the 1.0 release, `Color` registers all of the CSS Level 4 colors. It also registers linear variants of all the CSS
-RGB spaces and D65 variants of the Lab and Lch color space.
+As not all color spaces are registered by default, ∆E plugins tied to color spaces no longer registered by default will
+also not be registered by default.
 
-!!! warning "Color Space Specific ∆E Methods"
-    It should be noted that any ∆E methods that were dependent on a color space being registered that is no longer
-    registered will also not be registered.
+Lastly, since the `bradford` CAT is the default, it was deemed unnecessary to register all the other CAT plugins by
+default.
 
-If prior to 1.0 releases you relied on a color space that is no longer registered by default, there are a couple of
-options.
+With all of that said, all of the exiting plugins are still available and can be included if/when needed. If any of the
+plugins that are no longer registered by default are needed, there are a couple of options:
 
-1. The recommended way is to just subclass the `Color` object and cherry pick the color space(s) and ∆E method(s) you
-   might need.
+1. The recommended way is to just subclass the `Color` object and cherry pick the plugins that are needed. When
+   classing, all the plugins registered in the base will be copied over to the derived class. Then we can just pass
+   in the instantiated plugins.
 
     ```playground
     from coloraide import Color as Base
@@ -29,18 +48,18 @@ options.
 
     class Color(Base): ...
 
-    Color.register([Jzazbz, DEZ])
+    Color.register([Jzazbz(), DEZ()])
 
     Color('red').convert('jzazbz')
     Color('red').delta_e('blue', method='jz')
     ```
 
 2. We also provide a new color object derived from `Color` that includes **all** color spaces called `ColorAll`. This
-  object won't be as light, but will provide quick and easy access to everything ColorAide offers. By default, it
-  registers every plugin.
+   object won't be as light, but will provide quick and easy access to everything ColorAide offers. By default, it
+   registers every plugin. It can be found under `coloraide.everything`.
 
     ```playground
-    from coloraide import ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 
     Color('purple').convert('hunter-lab')
     ```
diff --git a/docs/src/markdown/api/index.md b/docs/src/markdown/api/index.md
index 059bf6cd5..f3f3a9dcd 100644
--- a/docs/src/markdown/api/index.md
+++ b/docs/src/markdown/api/index.md
@@ -106,7 +106,7 @@ 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}
+## `coloraide.everything.ColorAll` {#colorall}
 
 ```py3
 class ColorAll(Color):
@@ -130,7 +130,7 @@ Import path
     `ColorAll` is imported from the `coloraide` library:
 
     ```py3
-    from coloraide import ColorAll
+    from coloraide.everything import ColorAll
     ```
 
 Parameters
diff --git a/docs/src/markdown/color.md b/docs/src/markdown/color.md
index 67725e78e..1d066dfdb 100644
--- a/docs/src/markdown/color.md
+++ b/docs/src/markdown/color.md
@@ -12,14 +12,14 @@ The `Color` object contains all the logic to create and manipulate colors. It ca
 from coloraide import Color
 ```
 
-By default, the `Color` object contains only a subset of the available color spaces 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`.
+By default, the `Color` object contains only a subset of the available color spaces, related distancing algorithms, etc.
+This is done in order to keep the `Color` object lighter. If desired, the `ColorAll` object can be used which includes
+_everything_. This is generally not recommended as most users do not need _everything_. In general, it is recommended to
+subclass the `Color` object and cherry pick any additional plugins that are required, but if you'd like to have access
+to all the color spaces that are available, along with any other optional plugins, you can import and use `ColorAll`.
 
 ```py3
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 ```
 
 !!! tip "Custom Color Objects"
@@ -378,11 +378,23 @@ except:
 
 class Custom(Color): ...
 
-Custom.register(XyY)
+Custom.register(XyY())
 
 Custom('red').convert('xyy')
 ```
 
+Used in conjunction with [default settings override](#override-default-settings), we can not only change a default ∆E,
+but we can alter a ∆E method's configuration by registering it with different defaults:
+
+```playground
+Color('red').delta_e('blue', method='cmc')
+from coloraide.distance.delta_e_cmc import DECMC
+class Custom(Color):
+    DELTA_E = "cmc"
+Custom.register(DECMC(l=1, c=1), overwrite=True)
+Custom('red').delta_e('blue')
+```
+
 If a deregistration was desired, the `deregister` method can be used. It takes a string that describes the plugin to
 deregister: `category:name`.
 
diff --git a/docs/src/markdown/colors/index.md b/docs/src/markdown/colors/index.md
index f5661a4a0..bf759e45a 100644
--- a/docs/src/markdown/colors/index.md
+++ b/docs/src/markdown/colors/index.md
@@ -19,9 +19,9 @@ The following color spaces are registered by default:
 
 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.
+desired). With that said, ColorAide does also provide `coloraide.everything.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
diff --git a/docs/src/markdown/distance.md b/docs/src/markdown/distance.md
index 34d9a6c72..ccf3de04b 100644
--- a/docs/src/markdown/distance.md
+++ b/docs/src/markdown/distance.md
@@ -57,7 +57,7 @@ Delta\ E                                 | Symmetrical           | Name
 ∆E~ok~                                   | :octicons-check-16:   | `ok`            | `scalar=1`
 
 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
+[custom class](./color.md#custom-color-classes), or can be accessed by use `coloraide.everything.ColorAll` instead of
 `coloraide.Color`. The associated color space must be registered as well.
 
 Delta\ E                                 | Symmetrical           | Name            | Parameters
diff --git a/docs/src/markdown/plugins/index.md b/docs/src/markdown/plugins/index.md
index d0432ccec..b3e6a1e2f 100644
--- a/docs/src/markdown/plugins/index.md
+++ b/docs/src/markdown/plugins/index.md
@@ -26,9 +26,8 @@ class DeltaE(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def distance(cls, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
+    def distance(self, color: 'Color', sample: 'Color', **kwargs: Any) -> float:
         """Get distance between color and sample."""
 ```
 
@@ -54,9 +53,8 @@ class Fit(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def fit(cls, color: 'Color', **kwargs) -> None:
+    def fit(self, color: 'Color', **kwargs) -> None:
         """Get coordinates of the new gamut mapped color."""
 ```
 
@@ -83,9 +81,8 @@ class CAT(Plugin, metaclass=ABCMeta):
 
     NAME = ""
 
-    @classmethod
     @abstractmethod
-    def adapt(cls, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
+    def adapt(self, w1: Tuple[float, float], w2: Tuple[float, float], xyz: VectorLike) -> Vector:
         """Adapt a given XYZ color using the provided white points."""
 ```
 
@@ -128,9 +125,8 @@ class Filter(Plugin, metaclass=ABCMeta):
     DEFAULT_SPACE = 'srgb-linear'
     ALLOWED_SPACES = ('srgb-linear', 'srgb')
 
-    @classmethod
     @abstractmethod
-    def filter(color: 'Color', amount: Optional[float], **kwargs: Any) -> None:
+    def filter(self, color: 'Color', amount: Optional[float], **kwargs: Any) -> None:
         """Filter the given color."""
 ```
 
@@ -158,9 +154,8 @@ class ColorContrast(Plugin, metaclass=ABCMeta):
 
     NAME = ''
 
-    @classmethod
     @abstractmethod
-    def contrast(cls, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
+    def contrast(self, color1: 'Color', color2: 'Color', **kwargs: Any) -> float:
         """Get the contrast of the two provided colors."""
 
 ```
@@ -186,13 +181,6 @@ plugins.
 In general, a color space plugin is created by subclassing from `#!py3 coloraide.spaces.Space`. When defining a color
 space, there are a couple things that must be defined. Using XYZ as an example, we will go over them.
 
-!!! tip "Chromatic Adaptation"
-    Color spaces do **not** perform chromatic adaptation. That is handled by the `Color` object. Color spaces should
-    never change the white point, but simply provide the appropriate `BASE` linkage so that the color can resolve
-    eventually to XYZ D65. Other XYZ color spaces should all have `xyz-d65` as their base. Chromatic adaptation should
-    automatically occur on transitions between two XYZ spaces with different white points white points, e.g., `xyz-d65`
-    to `xyz-d50`.
-
 ````py
 from coloraide import cat
 from coloraide.channels import Channel
@@ -298,8 +286,7 @@ class XYZD65(Space):
     ############################
     # To and from conversion functions that transform the color to and from the `BASE` color.
     ############################
-    @classmethod
-    def to_base(cls, coords: Vector) -> Vector:
+    def to_base(self, coords: Vector) -> Vector:
         """
         To XYZ (no change).
 
@@ -308,8 +295,7 @@ class XYZD65(Space):
 
         return coords
 
-    @classmethod
-    def from_base(cls, coords: Vector) -> Vector:
+    def from_base(self, coords: Vector) -> Vector:
         """
         From XYZ (no change).
 
@@ -319,9 +305,40 @@ class XYZD65(Space):
         return coords
 ````
 
+!!! warning "Important"
+    When subclassing existing color spaces, if changes in the derived class are superficial, such as changing accepted
+    string input or output, they can usually be used as drop in replacements for the original, but if there are any
+    changes that affect how the coordinates are calculated, it is advised that the plugin specifies a new `Space.NAME`
+    value.
+
+    For example, a color space which requires certain viewing conditions to be specified may calculate very different
+    coordinates if viewing conditions are altered. If registered as a direct replacement of the original, this could
+    not only break conversion to or from that specific color space, but it could break conversion of color spaces
+    that pass through that color space in the conversion chain.
+
+    It is expected that color space plugins with the same name will have compatible coordinates. That is why we have
+    color spaces like: `lab` and `lab-d65`. As they have completely different white spaces, even if they are based on
+    the exact same color model, they will not render colors identically, so they should not be treated as equivalent.
+
+!!! tip "Chromatic Adaptation"
+    Chromatic adaptation between different XYZ color spaces is not generally handled by the color space directly, but
+    is handled by ColorAide in general. By simply providing the appropriate `BASE` linkage, ColorAide will know how to
+    get to and from that color space and will utilize the `to_base` and `from_base` functions in order to apply the
+    conversion.
+
+    All conversion chains are required to pass through XYZ D65. Chromatic adaptation will occur any time the white
+    point changes and XYZ D65 is one of the colors in the conversion.
+
+    If there is no registered XYZ with your required white point, you do not **need** to register a new XYZ color space
+    unless you plan to work in that XYZ variant directly. All a color space needs to do is set `Space.BASE` to `xyz-d65`
+    and specify the white point via `Space.WHITE`. The XYZ D65 coordinates will be adapted to the appropriate white
+    point during conversion to and from the targeted color space.
+
+### Color Normalization
+
 In addition to the above methods, some color spaces, such as cylindrical spaces, have some additional logic that
 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
+returned with undefined hues if the color is achromatic.
 
 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
@@ -332,13 +349,12 @@ close to zero, maybe even when lightness is equal to `#!py3 0` or `#!py3 1`. Thi
 space.
 
 ```py
-    @classmethod
-    def normalize(cls, coords: Vector) -> Vector:
-        """On color update."""
+    def normalize(self, coords: Vector) -> Vector:
+        """Normalize color."""
 
-        coords = util.no_nans(coords)
+        coords = alg.no_nans(coords)
         if coords[1] == 0 or coords[2] in (0, 1):
-            coords[0] = util.NaN
+            coords[0] = alg.NaN
 
         return coords
 ```
@@ -354,17 +370,15 @@ mix-in class: `Cylindrical`, `Labish`, or `Lchish`. It should be noted that `Lch
     class Cylindrical:
         """Cylindrical space."""
 
-        @classmethod
-        def hue_name(cls) -> str:
+        def hue_name(self) -> str:
             """Hue channel name."""
 
             return "h"
 
-        @classmethod
-        def hue_index(cls) -> int:
+        def hue_index(self) -> int:  # pragma: no cover
             """Get hue index."""
 
-            return cls.get_channel_index(cls.hue_name())
+            return cast('Space', self).get_channel_index(self.hue_name())
     ```
 
 === "Labish"
@@ -373,18 +387,15 @@ mix-in class: `Cylindrical`, `Labish`, or `Lchish`. It should be noted that `Lch
     class Labish:
         """Lab-ish color spaces."""
 
-        @classmethod
-        def labish_names(cls) -> Tuple[str, ...]:
+        def labish_names(self) -> Tuple[str, ...]:
             """Return Lab-ish names in the order L a b."""
 
-            return cls.CHANNELS
+            return cast('Space', self).channels[:-1]
 
-        @classmethod
-        def labish_indexes(cls) -> List[int]:
+        def labish_indexes(self) -> List[int]:  # pragma: no cover
             """Return the index of the Lab-ish channels."""
 
-            names = cls.labish_names()
-            return [cls.get_channel_index(name) for name in names]
+            return [cast('Space', self).get_channel_index(name) for name in self.labish_names()]
     ```
 
 === "Lchish"
@@ -393,18 +404,15 @@ mix-in class: `Cylindrical`, `Labish`, or `Lchish`. It should be noted that `Lch
     class Lchish(Cylindrical):
         """Lch-ish color spaces."""
 
-        @classmethod
-        def lchish_names(cls) -> Tuple[str, ...]:
+        def lchish_names(self) -> Tuple[str, ...]:  # pragma: no cover
             """Return Lch-ish names in the order L c h."""
 
-            return cls.CHANNELS
+            return cast('Space', self).channels[:-1]
 
-        @classmethod
-        def lchish_indexes(cls) -> List[int]:
+        def lchish_indexes(self) -> List[int]:  # pragma: no cover
             """Return the index of the Lab-ish channels."""
 
-            names = cls.lchish_names()
-            return [cls.get_channel_index(name) for name in names]
+            return [cast('Space', self).get_channel_index(name) for name in self.lchish_names()]
     ```
 
 Mix-in classes are mainly available so that a color space can be inspected to see if it falls into a specific generic
@@ -426,7 +434,7 @@ jzazbz = srgb.convert('jzazbz')
 ictcp = srgb.convert('ictcp')
 
 for c in (srgb, jzazbz, ictcp):
-    if issubclass(c._space, Labish):
+    if isinstance(c._space, Labish):
         print('color: ', c)
         l = c._space.labish_names()[0]
         print('channel: ', l)
@@ -481,16 +489,15 @@ class SRGB(base.SRGB):
     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,
+        self,
         string: str,
         start: int = 0,
         fullmatch: bool = True
     ) -> Optional[Tuple[Tuple[Vector, float], int]]:
         """Match a CSS color string."""
 
-        return parse.parse_css(cls, string, start, fullmatch)
+        return parse.parse_css(self, string, start, fullmatch)
 ```
 
 Additionally, we control the output formats by overriding the `#!py3 to_string()` function. We ensure that it accepts
@@ -499,15 +506,21 @@ all the parameters we need, in our case we accept the common parameters and late
 
 
 ```py
-    @classmethod
     def to_string(
-        cls,
+        self,
         parent: 'Color',
         *,
         alpha: Optional[bool] = None,
         precision: Optional[int] = None,
         fit: Union[bool, str] = True,
         none: bool = False,
+        color: bool = False,
+        hex: bool = False,
+        names: bool = False,
+        comma: bool = False,
+        upper: bool = False,
+        percent: bool = False,
+        compress: bool = False,
         **kwargs: Any
     ) -> str:
         """Convert to CSS."""
@@ -519,13 +532,13 @@ all the parameters we need, in our case we accept the common parameters and late
             precision=precision,
             fit=fit,
             none=none,
-            color=kwargs.get('color', False),
-            hexa=kwargs.get('hex', False),
-            name=kwargs.get('names', False),
-            legacy=kwargs.get('comma', False),
-            upper=kwargs.get('upper', False),
-            percent=kwargs.get('percent', False),
-            compress=kwargs.get('compress', False),
+            color=color,
+            hexa=hex,
+            name=names,
+            legacy=comma,
+            upper=upper,
+            percent=percent,
+            compress=compress,
             scale=255
         )
 ```
diff --git a/docs/src/mkdocs.yml b/docs/src/mkdocs.yml
index b0d4af9a6..7efe32750 100644
--- a/docs/src/mkdocs.yml
+++ b/docs/src/mkdocs.yml
@@ -131,7 +131,7 @@ extra_css:
   - assets/coloraide-extras/extra.css
 extra_javascript:
   - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
-  - playground-config-af252839.js
+  - playground-config-05233d65.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 eb9ffc730..789648580 100644
--- a/docs/src/py/notebook.py
+++ b/docs/src/py/notebook.py
@@ -86,7 +86,8 @@ def std_output(stdout=None):
 def get_colors(result):
     """Get color from results."""
 
-    from coloraide import Color, ColorAll
+    from coloraide import Color
+    from coloraide.everything import ColorAll
     from coloraide.interpolate import Interpolator
     try:
         from coloraide_extras import Color as Color2
@@ -131,7 +132,7 @@ def find_colors(text):
     try:
         from coloraide_extras import Color
     except ImportError:
-        from coloraide import ColorAll as Color
+        from coloraide.everything import ColorAll as Color
 
     colors = []
     for m in RE_COLOR_START.finditer(text):
@@ -146,13 +147,14 @@ def execute(cmd, no_except=True, inline=False):
     """Execute color commands."""
 
     import coloraide
+    from coloraide.everything import ColorAll
     try:
         import coloraide_extras
     except ImportError:
         coloraide_extras = None
 
     g = {
-        'Color': coloraide.ColorAll,
+        'Color': ColorAll,
         'coloraide': coloraide,
         'NaN': coloraide.NaN,
         'stop': coloraide.stop,
@@ -400,7 +402,7 @@ def color_formatter(src="", language="", class_name=None, md="", exceptions=True
     try:
         from coloraide_extras import Color
     except ImportError:
-        from coloraide import ColorAll as Color
+        from coloraide.everything 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
deleted file mode 100644
index 5f45e2674..000000000
--- a/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.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, 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-a46d1aff.js b/docs/theme/assets/coloraide-extras/extra-notebook-a46d1aff.js
new file mode 100644
index 000000000..230624826
--- /dev/null
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-a46d1aff.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:{},i=r.iterator||"@@iterator",a=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,i=Object.create(r.prototype),a=new T(o||[]);return i._invoke=function(e,t,n){var o=d;return function(r,i){if(o===f)throw new Error("Generator is already running");if(o===m){if("throw"===r)throw i;return P()}for(n.method=r,n.arg=i;;){var a=n.delegate;if(a){var s=L(a,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,a),i}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,i,(function(){return this}));var _=Object.getPrototypeOf,x=_&&_(_(I([])));x&&x!==n&&o.call(x,i)&&(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,i,a,s){var c=u(e[r],e,i);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,a,s)}),(function(e){n("throw",e,a,s)})):t.resolve(d).then((function(e){l.value=e,a(l)}),(function(e){return n("throw",e,a,s)}))}s(c.arg)}var r;this._invoke=function(e,o){function i(){return new t((function(t,r){n(e,o,t,r)}))}return r=r?r.then(i,i):i()}}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 i=r.arg;return i?i.done?(n[e.resultName]=i.value,n.next=e.nextLoc,"return"!==n.method&&(n.method="next",n.arg=t),n.delegate=null,h):i:(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[i];if(n)return n.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var r=-1,a=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 a.next=a}}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,a,(function(){return this})),e.AsyncIterator=k,e.async=function(t,n,o,r,i){void 0===i&&(i=Promise);var a=new k(l(t,n,o,r),i);return e.isGeneratorFunction(n)?a:a.next().then((function(e){return e.done?e.value:a.next()}))},E(b),c(b,s,"Generator"),c(b,i,(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 i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],s=a.completion;if("root"===a.tryLoc)return r("end");if(a.tryLoc<=this.prev){var c=o.call(a,"catchLoc"),l=o.call(a,"finallyLoc");if(c&&l){if(this.prev<a.catchLoc)return r(a.catchLoc,!0);if(this.prev<a.finallyLoc)return r(a.finallyLoc)}else if(c){if(this.prev<a.catchLoc)return r(a.catchLoc,!0)}else{if(!l)throw new Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return r(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 i=r;break}}i&&("break"===e||"continue"===e)&&i.tryLoc<=t&&t<=i.finallyLoc&&(i=null);var a=i?i.completion:{};return a.type=e,a.arg=t,i?(this.method="next",this.next=i.finallyLoc,h):this.complete(a)},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:{},i=r.iterator||"@@iterator",a=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,i=Object.create(r.prototype),a=new k(o||[]);return i._invoke=function(e,t,n){var o="suspendedStart";return function(r,i){if("executing"===o)throw new Error("Generator is already running");if("completed"===o){if("throw"===r)throw i;return S()}for(n.method=r,n.arg=i;;){var a=n.delegate;if(a){var s=x(a,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,a),i}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,i,(function(){return this}));var y=Object.getPrototypeOf,g=y&&y(y(L([])));g&&g!==n&&o.call(g,i)&&(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,i,a,s){var c=u(e[r],e,i);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,a,s)}),(function(e){n("throw",e,a,s)})):t.resolve(d).then((function(e){l.value=e,a(l)}),(function(e){return n("throw",e,a,s)}))}s(c.arg)}var r;this._invoke=function(e,o){function i(){return new t((function(t,r){n(e,o,t,r)}))}return r=r?r.then(i,i):i()}}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[i];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n<e.length;)if(o.call(e,n))return t.value=e[n],t.done=!1,t;return t.value=void 0,t.done=!0,t};return r.next=r}}return{next: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,a,(function(){return this})),t.AsyncIterator=_,t.async=function(e,n,o,r,i){void 0===i&&(i=Promise);var a=new _(l(e,n,o,r),i);return t.isGeneratorFunction(n)?a:a.next().then((function(e){return e.done?e.value:a.next()}))},w(v),c(v,s,"Generator"),c(v,i,(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 a.type="throw",a.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 i=this.tryEntries[r],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=o.call(i,"catchLoc"),c=o.call(i,"finallyLoc");if(s&&c){if(this.prev<i.catchLoc)return n(i.catchLoc,!0);if(this.prev<i.finallyLoc)return n(i.finallyLoc)}else if(s){if(this.prev<i.catchLoc)return n(i.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return n(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 i=r;break}}i&&("break"===e||"continue"===e)&&i.tryLoc<=t&&t<=i.finallyLoc&&(i=null);var a=i?i.completion:{};return a.type=e,a.arg=t,i?(this.method="next",this.next=i.finallyLoc,d):this.complete(a)},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,i,a){try{var s=e[i](a),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,i){var a=e.apply(n,o);function s(e){t(a,r,i,s,c,"next",e)}function c(e){t(a,r,i,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,i=[],a=!0,s=!1;try{for(n=n.call(e);!(a=(o=n.next()).done)&&(i.push(o.value),!t||i.length!==t);a=!0);}catch(e){s=!0,r=e}finally{try{a||null==n.return||n.return()}finally{if(s)throw r}}return i}(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 i(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)?i(e,t):void 0}}function i(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 a,s,c,l,u,d,p,f,m,h,y,g,v,w,_,x,b,E,k,L,S,C,T,I;a=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.everything import 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.everything 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    from coloraide.everything import ColorAll\n    try:\n        import coloraide_extras\n    except ImportError:\n        coloraide_extras = None\n\n    g = {\n        'Color': 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.everything 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",""),a.globals.set("id_num",n),a.globals.set("action","playground"),a.globals.set("wheels",window.color_notebook.playground_wheels),e.next=7,a.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 a.globals.set("content",n),a.globals.set("action","notebook"),a.globals.set("wheels",window.color_notebook.notebook_wheels),e.next=5,a.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 a=e.sent,e.next=7,a.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",i=document.createElement("template");i.innerHTML='<div class="'.concat(r,'"><div class="loader"></div><div>').concat(o,"</div></div>"),e.appendChild(i.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 i=r.substr(n,o-n).split("\n"),a=0;a<i.length;a++)a===i.length-1&&0===i[a].length||(e.shiftKey?i[a]=i[a].replace(h,""):i[a]="    ".concat(i[a]));i=i.join("\n"),t.value=r.substr(0,n)+i+r.substr(o),t.selectionStart=n,t.selectionEnd=n+i.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 i=t.id.replace(d,"$1"),a=document.getElementById("__playground-inputs_".concat(i)),p=document.getElementById("__playground-results_".concat(i)),f=document.getElementById("__playground-code_".concat(i)),m=document.querySelector("button#__playground-edit_".concat(i)),h=document.querySelector("button#__playground-share_".concat(i)),y=document.querySelector("button#__playground-run_".concat(i)),g=document.querySelector("button#__playground-cancel_".concat(i));if(a.addEventListener("input",(function(){_(a)})),a.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)}))))}a.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[i]=a.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"),_(a),a.focus();case 9:case"end":return e.stop()}}),t)})))),h.addEventListener("click",n(e().mark((function t(){var o,r,i,s,c;return e().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:o=window.location.pathname.split("/")[1],r=x(a.value),i=window.location,s="/playground/",i.pathname.startsWith("/".concat(o,"/"))&&(s="/".concat(o,"/playground/")),c="".concat(i.protocol,"//").concat(i.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(i);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[i],s=!1;case 22:case"end":return e.stop()}}),t)})))),g.addEventListener("click",(function(){a.value=u[i],delete u[i],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,i,a,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),i="Loading Pyodide...",a="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,i),t.next=11,k();case 11:S(c),L(c,a);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,i),t.next=22,k();case 22:return S(c),L(c,a),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 i,a={},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,i=function(){};return{s:i,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:i}}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 a,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,a=e},f:function(){try{s||null==n.return||n.return()}finally{if(c)throw a}}}}(new URLSearchParams(n.search));try{for(s.s();!(i=s.n()).done;){var c=o(i.value,2),l=c[0],u=c[1];a[l]=u}}catch(e){s.e(e)}finally{s.f()}history.pushState(a,"",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-a46d1aff.js.map
diff --git a/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map b/docs/theme/assets/coloraide-extras/extra-notebook-a46d1aff.js.map
similarity index 99%
rename from docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map
rename to docs/theme/assets/coloraide-extras/extra-notebook-a46d1aff.js.map
index 73a31f163..d418ad96d 100644
--- a/docs/theme/assets/coloraide-extras/extra-notebook-1d173e7b.js.map
+++ b/docs/theme/assets/coloraide-extras/extra-notebook-a46d1aff.js.map
@@ -1,1 +1,1 @@
-{"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
+{"version":3,"file":"extra-notebook-a46d1aff.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/playground-config-af252839.js b/docs/theme/playground-config-05233d65.js
similarity index 78%
rename from docs/theme/playground-config-af252839.js
rename to docs/theme/playground-config-05233d65.js
index 0210a3ef2..a424c82a3 100644
--- a/docs/theme/playground-config-af252839.js
+++ b/docs/theme/playground-config-05233d65.js
@@ -1,5 +1,5 @@
 var color_notebook = {
-    "playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.0b3-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.0b3-py3-none-any.whl'],
+    "playground_wheels": ['Pygments-2.12.0-py3-none-any.whl', 'coloraide-1.0b4-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.0b4-py3-none-any.whl'],
     "default_playground": "import coloraide\ncoloraide.__version__\nColor('red')"
 }
diff --git a/mkdocs.yml b/mkdocs.yml
index c6e41fa72..4e64a710d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -131,9 +131,9 @@ extra_css:
   - assets/coloraide-extras/extra-753860a807.css
 extra_javascript:
   - https://unpkg.com/mermaid@8.13.3/dist/mermaid.min.js
-  - playground-config-af252839.js
+  - playground-config-05233d65.js
   - https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js
-  - assets/coloraide-extras/extra-notebook-1d173e7b.js
+  - assets/coloraide-extras/extra-notebook-a46d1aff.js
 
 extra:
   social:
diff --git a/tools/calc_lch_threshold.py b/tools/calc_lch_threshold.py
index 2652779cb..7e3834f0b 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 ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 
 RE_LEAD_ZERO = re.compile(r'^0\.0+')
 
diff --git a/tools/calc_range_rgb.py b/tools/calc_range_rgb.py
index 17c9e72a4..60cf079db 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 ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 from coloraide import algebra as alg  # noqa: E402
 
 
diff --git a/tools/cie_diagrams.py b/tools/cie_diagrams.py
index e95671e38..ee359fb24 100644
--- a/tools/cie_diagrams.py
+++ b/tools/cie_diagrams.py
@@ -15,7 +15,7 @@
 try:
     from coloraide_extras import Color
 except ImportError:
-    from coloraide import ColorAll as Color
+    from coloraide.everything 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 ce162ca06..c345544b6 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 ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 
 
 def printt(t):
diff --git a/tools/filter_img.py b/tools/filter_img.py
index 6a1bc1df4..74ce686b8 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 ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 
 
 def printt(t):
diff --git a/tools/gamut_3d_diagrams.py b/tools/gamut_3d_diagrams.py
index 789a09443..ccebeba9b 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 ColorAll as Color
+    from coloraide.everything import ColorAll as Color
 from coloraide.spaces import Cylindrical, Lchish, Labish  # noqa: E402
 from coloraide.algebra import is_nan  # noqa: E402
 
@@ -162,9 +162,9 @@ def render_space(space, gamut, resolution, factor, data, c):
     res2 = int(resolution * 1.5)
 
     color = ColorCyl('srgb', [])
-    is_cyl = issubclass(ColorCyl.CS_MAP[space], Cylindrical)
-    is_labish = issubclass(ColorCyl.CS_MAP[space], Labish)
-    is_lchish = issubclass(ColorCyl.CS_MAP[space], Lchish)
+    is_cyl = isinstance(ColorCyl.CS_MAP[space], Cylindrical)
+    is_labish = isinstance(ColorCyl.CS_MAP[space], Labish)
+    is_lchish = isinstance(ColorCyl.CS_MAP[space], Lchish)
     add = add_rect_color if not is_cyl or is_labish else add_cyl_color
     force_max_radius = not is_lchish and is_cyl and color_options.get(space, {}).get('force_max_radius', False)
     force_bottom = color_options.get(space, {}).get('force_bottom', False)
@@ -228,9 +228,9 @@ def plot_gamut_in_space(space, gamut, title="", dark=False, resolution=70, rotat
     # Get names for
     target = Color.CS_MAP[space]
     names = target.CHANNELS
-    is_cyl = issubclass(target, Cylindrical)
-    is_labish = issubclass(target, Labish)
-    is_lchish = issubclass(target, Lchish)
+    is_cyl = isinstance(target, Cylindrical)
+    is_labish = isinstance(target, Labish)
+    is_lchish = isinstance(target, Lchish)
 
     if not is_lchish and is_cyl:
         g = Color.CS_MAP[space].GAMUT_CHECK
diff --git a/tools/gamut_diagram.py b/tools/gamut_diagram.py
index 50d183009..084b7071d 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 ColorAll as Color
+    from coloraide.everything 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 e71be7e81..c7d108fd2 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 ColorAll as Color
+    from coloraide.everything 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 796218c08..c5e61a99f 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 ColorAll as Color
+    from coloraide.everything 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 01d8330cf..5bfdb7c9f 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 ColorAll as Color
+    from coloraide.everything 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_aces2065_1.py b/tests/test_aces2065_1.py
index 5aa93f506..38d62d934 100644
--- a/tests/test_aces2065_1.py
+++ b/tests/test_aces2065_1.py
@@ -1,7 +1,7 @@
 """Test ACES 2065-1."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_acescc.py b/tests/test_acescc.py
index 7f867d75f..1bed6c556 100644
--- a/tests/test_acescc.py
+++ b/tests/test_acescc.py
@@ -1,7 +1,7 @@
 """Test ACEScc."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_acescct.py b/tests/test_acescct.py
index 61e69f9b1..04002eb62 100644
--- a/tests/test_acescct.py
+++ b/tests/test_acescct.py
@@ -1,7 +1,7 @@
 """Test ACEScct."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_acescg.py b/tests/test_acescg.py
index 1e0620187..c0e1b69d4 100644
--- a/tests/test_acescg.py
+++ b/tests/test_acescg.py
@@ -1,7 +1,7 @@
 """Test ACEScg."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_api.py b/tests/test_api.py
index ca6546eec..4ad44dcac 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,6 +1,7 @@
 """Test miscellaneous API features."""
 import unittest
-from coloraide import Color, ColorAll, NaN
+from coloraide import Color, NaN
+from coloraide.everything import ColorAll
 from . import util
 import math
 
diff --git a/tests/test_cat.py b/tests/test_cat.py
index 929dc7337..217f2c199 100644
--- a/tests/test_cat.py
+++ b/tests/test_cat.py
@@ -1,5 +1,5 @@
 """Test chromatic adaptation."""
-from coloraide import Color
+from coloraide.everything import ColorAll as Color
 from coloraide import cat
 from coloraide import util as cutil
 from . import util
diff --git a/tests/test_cmy.py b/tests/test_cmy.py
index 5fae1934f..f7d933402 100644
--- a/tests/test_cmy.py
+++ b/tests/test_cmy.py
@@ -1,7 +1,7 @@
 """Test CMY."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_cmyk.py b/tests/test_cmyk.py
index 5881d41d8..448e49ac6 100644
--- a/tests/test_cmyk.py
+++ b/tests/test_cmyk.py
@@ -1,7 +1,7 @@
 """Test CMYK."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_din99o.py b/tests/test_din99o.py
index 69aacda1a..89606d388 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 
 
 class TestDIN99oInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_distance.py b/tests/test_distance.py
index 0886d99ca..054850fa3 100644
--- a/tests/test_distance.py
+++ b/tests/test_distance.py
@@ -1,5 +1,5 @@
 """Test distance methods."""
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from . import util
 import pytest
 import unittest
diff --git a/tests/test_hsi.py b/tests/test_hsi.py
index f150b88a7..f1f2ab825 100644
--- a/tests/test_hsi.py
+++ b/tests/test_hsi.py
@@ -1,7 +1,7 @@
 """Test HSI."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from coloraide import NaN
 import pytest
 
diff --git a/tests/test_hsluv/test_hsluv.py b/tests/test_hsluv/test_hsluv.py
index 97140452f..354ac2924 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import json
 import os
 
diff --git a/tests/test_hunter_lab.py b/tests/test_hunter_lab.py
index 31bd9479f..ff50211c9 100644
--- a/tests/test_hunter_lab.py
+++ b/tests/test_hunter_lab.py
@@ -1,7 +1,7 @@
 """Test Hunter Lab."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_ictcp.py b/tests/test_ictcp.py
index 73f21e4e6..039860862 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 
 
 class TestICtCpInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_igpgtg.py b/tests/test_igpgtg.py
index 8e70efc3d..0c5ad3688 100644
--- a/tests/test_igpgtg.py
+++ b/tests/test_igpgtg.py
@@ -1,7 +1,7 @@
 """Test IgPgTg."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_ipt.py b/tests/test_ipt.py
index 6fd0fa113..da6a59b78 100644
--- a/tests/test_ipt.py
+++ b/tests/test_ipt.py
@@ -1,7 +1,7 @@
 """Test IPT."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_jzCzhz.py b/tests/test_jzCzhz.py
index 572edbc55..0614a3295 100644
--- a/tests/test_jzCzhz.py
+++ b/tests/test_jzCzhz.py
@@ -1,7 +1,7 @@
 """Test JzCzhz library."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from coloraide import NaN
 
 
diff --git a/tests/test_jzazbz.py b/tests/test_jzazbz.py
index c6fcaa7a9..9c70a8f68 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 
 
 class TestJzazbzInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_lch99o.py b/tests/test_lch99o.py
index f06905bbb..d9af4ac9c 100644
--- a/tests/test_lch99o.py
+++ b/tests/test_lch99o.py
@@ -1,7 +1,7 @@
 """Test DIN99o LCH library."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from coloraide import NaN
 
 
diff --git a/tests/test_lchuv.py b/tests/test_lchuv.py
index 773a2cc41..d15943752 100644
--- a/tests/test_lchuv.py
+++ b/tests/test_lchuv.py
@@ -1,7 +1,7 @@
 """Test LCHuv library."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from coloraide import NaN
 
 
diff --git a/tests/test_luv.py b/tests/test_luv.py
index b852d842e..16867b982 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 
 
 class TestLuvInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_okhsl.py b/tests/test_okhsl.py
index 49c96fd02..f952db47e 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 
 
 class TestOkhslInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_okhsv.py b/tests/test_okhsv.py
index ba9251ac4..1425242e2 100644
--- a/tests/test_okhsv.py
+++ b/tests/test_okhsv.py
@@ -1,7 +1,7 @@
 """Test Okhsv library."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 from coloraide import NaN
 
 
diff --git a/tests/test_oklch.py b/tests/test_oklch.py
index 822524629..a097594fa 100644
--- a/tests/test_oklch.py
+++ b/tests/test_oklch.py
@@ -2,15 +2,7 @@
 import unittest
 from . import util
 from coloraide import NaN
-from coloraide import Color as ColorOrig
-from coloraide.spaces.oklch.css import Oklch
-
-
-class Color(ColorOrig):
-    """Utilize Oklab CSS (currently) experimental."""
-
-
-Color.register(Oklch, overwrite=True)
+from coloraide import Color
 
 
 class TestOklchInputOutput(util.ColorAsserts, unittest.TestCase):
diff --git a/tests/test_orgb.py b/tests/test_orgb.py
index d5d7836f2..9fa8702c6 100644
--- a/tests/test_orgb.py
+++ b/tests/test_orgb.py
@@ -1,7 +1,7 @@
 """Test oRGB."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 01e7d9128..ac5cabcb3 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -115,7 +115,7 @@ class Custom(Color):
         self.assertEqual(Color('red').convert('lab-d65').to_string(), expected)
 
         # Now it is registered again
-        Custom.register(lab_d65.LabD65)
+        Custom.register(lab_d65.LabD65())
         self.assertEqual(Custom('red').convert('lab-d65').to_string(), expected)
 
     def test_plugin_registration_delta_e(self):
@@ -137,7 +137,7 @@ class Custom(Color):
         self.assertEqual(Color('red').delta_e('green', method='2000'), expected)
 
         # Now it is registered again
-        Custom.register(delta_e_2000.DE2000)
+        Custom.register(delta_e_2000.DE2000())
         self.assertEqual(Custom('red').delta_e('green', method='2000'), expected)
 
     def test_plugin_registration_fit(self):
@@ -159,7 +159,7 @@ class Custom(Color):
         self.assertEqual(Color('color(srgb 110% 140% 20%)').fit(method='lch-chroma').to_string(), expected)
 
         # Now it is registered again
-        Custom.register(fit_lch_chroma.LchChroma)
+        Custom.register(fit_lch_chroma.LchChroma())
         self.assertEqual(Custom('color(srgb 110% 140% 20%)').fit(method='lch-chroma').to_string(), expected)
 
     def test_plugin_registration_contrast(self):
@@ -181,7 +181,7 @@ class Custom(Color):
         self.assertEqual(Color('red').contrast('blue', method='wcag21'), expected)
 
         # Now it is registered again
-        Custom.register(wcag21.WCAG21Contrast)
+        Custom.register(wcag21.WCAG21Contrast())
         self.assertEqual(Custom('red').contrast('blue', method='wcag21'), expected)
 
     def test_plugin_registration_cat(self):
@@ -195,38 +195,37 @@ def test_plugin_registration_cat(self):
         class Custom(Color):
             pass
 
-        Custom.deregister('cat:von-kries')
         c1 = Custom('red').convert('xyz-d65')
-        with self.assertRaises(ValueError):
+
+        # Register the plugin
+        Custom.register(VonKries())
+        self.assertEqual(
             Custom.chromatic_adaptation(
                 WHITES['2deg']['D65'],
                 WHITES['2deg']['D50'],
                 c1[:-1],
                 method='von-kries'
-            )
+            ),
+            expected
+        )
 
         # But it should not affect the base class
-        self.assertEqual(
+        with self.assertRaises(ValueError):
             Color.chromatic_adaptation(
                 WHITES['2deg']['D65'],
                 WHITES['2deg']['D50'],
                 c1[:-1],
                 method='von-kries'
-            ),
-            expected
-        )
+            )
 
-        # Now it is registered again
-        Custom.register(VonKries)
-        self.assertEqual(
+        Custom.deregister('cat:von-kries')
+        with self.assertRaises(ValueError):
             Custom.chromatic_adaptation(
                 WHITES['2deg']['D65'],
                 WHITES['2deg']['D50'],
                 c1[:-1],
                 method='von-kries'
-            ),
-            expected
-        )
+            )
 
     def test_plugin_registration_filter(self):
         """Test plugin registration of `filter`."""
@@ -244,7 +243,7 @@ class Custom(Color):
             Custom('red').filter('sepia')
 
         # Now it is registered again
-        Custom.register(Sepia)
+        Custom.register(Sepia())
         self.assertColorEqual(
             Custom('red').filter('sepia'),
             color
@@ -287,7 +286,7 @@ def fit(color):
                 return [0, 0, 0]
 
         with self.assertRaises(ValueError):
-            Custom.register(CustomFit, overwrite=True)
+            Custom.register(CustomFit(), overwrite=True)
 
     def test_bad_registration_type(self):
         """Test bad registration type."""
@@ -299,7 +298,7 @@ class BadClass:
             pass
 
         with self.assertRaises(TypeError):
-            Custom.register(BadClass)
+            Custom.register(BadClass())
 
     def test_bad_registration_star(self):
         """Test bad registration type."""
@@ -317,7 +316,7 @@ def distance(color, sample, **kwargs):
                 return 0
 
         with self.assertRaises(ValueError):
-            Custom.register(CustomDE)
+            Custom.register(CustomDE())
 
     def test_bad_registration_exists(self):
         """Test bad registration of plugin that exists."""
@@ -328,9 +327,9 @@ class Custom(Color):
             pass
 
         with self.assertRaises(ValueError):
-            Custom.register(LabD65)
+            Custom.register(LabD65())
 
-        Custom.register(LabD65, overwrite=True)
+        Custom.register(LabD65(), overwrite=True)
 
     def test_silent_registration_exists(self):
         """Test silent handling of already registered plugin."""
@@ -340,7 +339,7 @@ def test_silent_registration_exists(self):
         class Custom(Color):
             pass
 
-        Custom.register(Jzazbz, silent=True)
+        Custom.register(Jzazbz(), silent=True)
 
     def test_bad_deregister_category(self):
         """Test bad deregistration category."""
diff --git a/tests/test_prismatic.py b/tests/test_prismatic.py
index 0ac2a5924..e2ff3dc8e 100644
--- a/tests/test_prismatic.py
+++ b/tests/test_prismatic.py
@@ -1,7 +1,7 @@
 """Test Prismatic."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_rec2100pq.py b/tests/test_rec2100pq.py
index 51e4644f6..760614ee2 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 ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_rlab.py b/tests/test_rlab.py
index 1174c45ce..3e2516127 100644
--- a/tests/test_rlab.py
+++ b/tests/test_rlab.py
@@ -1,7 +1,7 @@
 """Test RLAB."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest
 
 
diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py
index df88f7858..78192c9ab 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 ColorAll
+from coloraide.everything import ColorAll
 from coloraide.spaces import Cylindrical
 from coloraide import algebra as alg
 import pytest
@@ -59,7 +59,7 @@ def assert_round_trip(self, color, space):
             c2.convert(c1.space(), in_place=True)
             # Catch cases where we are really close to 360 which should wrap to 0
             for c in (c1, c2):
-                if issubclass(c._space, Cylindrical):
+                if isinstance(c._space, Cylindrical):
                     if alg.round_half_up(alg.no_nan(c['hue']), c.PRECISION) == 360:
                         c.set('hue', 0)
             # Run rounded string back through parsing in case we hit something like a hue that needs normalization.
diff --git a/tests/test_xyy.py b/tests/test_xyy.py
index 6cc32ca4d..f90117488 100644
--- a/tests/test_xyy.py
+++ b/tests/test_xyy.py
@@ -1,7 +1,7 @@
 """Test xyY."""
 import unittest
 from . import util
-from coloraide import ColorAll as Color
+from coloraide.everything import ColorAll as Color
 import pytest