diff --git a/src/App.tsx b/src/App.tsx
index 98c5a64..7f91fc7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import { ColorFunctionProvider } from './ColorFunctionContext';
import { Notifications } from './components/Notifications';
import { ColorCards } from './pages/Cards';
import { CardsDetail } from './pages/CardsDetail';
+import { ColorCompare } from './pages/Compare';
import { Harmonies } from './pages/Harmonies';
import { Home } from './pages/Home';
import { LightenDarken } from './pages/LightenDarken';
@@ -39,6 +40,7 @@ const routes = createBrowserRouter([
{ path: 'lighten-darken', element: },
{ path: 'mixer', element: },
{ path: 'wacg', element: },
+ { path: 'compare', element: },
{
path: 'cards',
element: ,
diff --git a/src/page-components/color-compare/CompareLayout.module.css b/src/page-components/color-compare/CompareLayout.module.css
new file mode 100644
index 0000000..2bc395e
--- /dev/null
+++ b/src/page-components/color-compare/CompareLayout.module.css
@@ -0,0 +1,25 @@
+@layer pages {
+ .elements {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ gap: var(--spacing-m);
+ .element {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: var(--spacing-xs);
+ .element_name {
+ font-size: var(--font-size-xxl);
+ font-weight: bold;
+ }
+ .element_values {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xs);
+ font-size: var(--font-size-xs);
+ line-height: var(--font-size-xs);
+ }
+ }
+ }
+}
diff --git a/src/page-components/color-compare/HCTCompare.tsx b/src/page-components/color-compare/HCTCompare.tsx
new file mode 100644
index 0000000..59e0dc0
--- /dev/null
+++ b/src/page-components/color-compare/HCTCompare.tsx
@@ -0,0 +1,74 @@
+import { useMemo } from 'react';
+import { HctDiffference } from '../../color_functions/color_module';
+import { useColorFunction } from '../../ColorFunctionContext';
+import styles from './CompareLayout.module.css';
+import { CompareMethodProps } from './share-props';
+
+const defaultCompareResult: HctDiffference = {
+ hue: {
+ delta: 0,
+ percent: 0,
+ },
+ chroma: {
+ delta: 0,
+ percent: 0,
+ },
+ lightness: {
+ delta: 0,
+ percent: 0,
+ },
+};
+
+export function HCTCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
+ const { colorFn } = useColorFunction();
+ const differ = useMemo(() => {
+ if (!colorFn) {
+ return defaultCompareResult;
+ }
+ try {
+ const diff = colorFn.differ_in_hct(basic, compare);
+ return diff;
+ } catch (e) {
+ console.error('[Compare in HCT]', e);
+ }
+ return defaultCompareResult;
+ }, [basic, compare]);
+
+ return (
+
+
Compare in HCT Space
+
+
+
H
+
+ {differ.hue.delta.toFixed(2)}
+
+ {isNaN(differ.hue.percent) ? '0.00' : (differ.hue.percent * 100).toFixed(2)}%
+
+
+
+
+
C
+
+ {differ.chroma.delta.toFixed(2)}
+
+ {isNaN(differ.chroma.percent) ? '0.00' : (differ.chroma.percent * 100).toFixed(2)}%
+
+
+
+
+
T
+
+ {differ.lightness.delta.toFixed(2)}
+
+ {isNaN(differ.lightness.percent)
+ ? '0.00'
+ : (differ.lightness.percent * 100).toFixed(2)}
+ %
+
+
+
+
+
+ );
+}
diff --git a/src/page-components/color-compare/HSLCompare.tsx b/src/page-components/color-compare/HSLCompare.tsx
new file mode 100644
index 0000000..4e4d114
--- /dev/null
+++ b/src/page-components/color-compare/HSLCompare.tsx
@@ -0,0 +1,77 @@
+import { useMemo } from 'react';
+import { HSLDifference } from '../../color_functions/color_module';
+import { useColorFunction } from '../../ColorFunctionContext';
+import styles from './CompareLayout.module.css';
+import { CompareMethodProps } from './share-props';
+
+const defaultCompareResult: HSLDifference = {
+ hue: {
+ delta: 0,
+ percent: 0,
+ },
+ saturation: {
+ delta: 0,
+ percent: 0,
+ },
+ lightness: {
+ delta: 0,
+ percent: 0,
+ },
+};
+
+export function HSLCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
+ const { colorFn } = useColorFunction();
+ const differ = useMemo(() => {
+ if (!colorFn) {
+ return defaultCompareResult;
+ }
+ try {
+ const diff = colorFn.differ_in_hsl(basic, compare);
+ return diff;
+ } catch (e) {
+ console.error('[Compare in HSL]', e);
+ }
+ return defaultCompareResult;
+ }, [basic, compare]);
+
+ return (
+
+
Compare in HSL Space
+
+
+
H
+
+ {differ.hue.delta.toFixed(2)}
+
+ {isNaN(differ.hue.percent) ? '0.00' : (differ.hue.percent * 100).toFixed(2)}%
+
+
+
+
+
S
+
+ {(differ.saturation.delta * 100).toFixed(2)}%
+
+ {isNaN(differ.saturation.percent)
+ ? '0.00'
+ : (differ.saturation.percent * 100).toFixed(2)}
+ %
+
+
+
+
+
L
+
+ {(differ.lightness.delta * 100).toFixed(2)}%
+
+ {isNaN(differ.lightness.percent)
+ ? '0.00'
+ : (differ.lightness.percent * 100).toFixed(2)}
+ %
+
+
+
+
+
+ );
+}
diff --git a/src/page-components/color-compare/OKLCHCompare.tsx b/src/page-components/color-compare/OKLCHCompare.tsx
new file mode 100644
index 0000000..b7263a7
--- /dev/null
+++ b/src/page-components/color-compare/OKLCHCompare.tsx
@@ -0,0 +1,74 @@
+import { useMemo } from 'react';
+import { OklchDifference } from '../../color_functions/color_module';
+import { useColorFunction } from '../../ColorFunctionContext';
+import styles from './CompareLayout.module.css';
+import { CompareMethodProps } from './share-props';
+
+const defaultCompareResult: OklchDifference = {
+ hue: {
+ delta: 0,
+ percent: 0,
+ },
+ chroma: {
+ delta: 0,
+ percent: 0,
+ },
+ lightness: {
+ delta: 0,
+ percent: 0,
+ },
+};
+
+export function OklchCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
+ const { colorFn } = useColorFunction();
+ const differ = useMemo(() => {
+ if (!colorFn) {
+ return defaultCompareResult;
+ }
+ try {
+ const diff = colorFn.differ_in_oklch(basic, compare);
+ return diff;
+ } catch (e) {
+ console.error('[Compare in Oklch]', e);
+ }
+ return defaultCompareResult;
+ }, [basic, compare]);
+
+ return (
+
+
Compare in Oklch Space
+
+
+
L
+
+ {(differ.lightness.delta * 100).toFixed(2)}%
+
+ {isNaN(differ.lightness.percent)
+ ? '0.0000'
+ : (differ.lightness.percent * 100).toFixed(2)}
+ %
+
+
+
+
+
C
+
+ {differ.chroma.delta.toFixed(4)}
+
+ {isNaN(differ.chroma.percent) ? '0.0000' : (differ.chroma.percent * 100).toFixed(2)}%
+
+
+
+
+
H
+
+ {differ.hue.delta.toFixed(2)}
+
+ {isNaN(differ.hue.percent) ? '0.00' : (differ.hue.percent * 100).toFixed(2)}%
+
+
+
+
+
+ );
+}
diff --git a/src/page-components/color-compare/RGBCompare.tsx b/src/page-components/color-compare/RGBCompare.tsx
new file mode 100644
index 0000000..fb7262c
--- /dev/null
+++ b/src/page-components/color-compare/RGBCompare.tsx
@@ -0,0 +1,78 @@
+import { useMemo } from 'react';
+import { RGBDifference } from '../../color_functions/color_module';
+import { useColorFunction } from '../../ColorFunctionContext';
+import styles from './CompareLayout.module.css';
+import { CompareMethodProps } from './share-props';
+
+const defaultCompareResult: RGBDifference = {
+ r: {
+ delta: 0,
+ percent: 0,
+ },
+ g: {
+ delta: 0,
+ percent: 0,
+ },
+ b: {
+ delta: 0,
+ percent: 0,
+ },
+};
+
+export function RGBCompare({ basic = '000000', compare = '000000' }: CompareMethodProps) {
+ const { colorFn } = useColorFunction();
+ const differ = useMemo(() => {
+ if (!colorFn) {
+ return defaultCompareResult;
+ }
+ try {
+ const diff = colorFn?.differ_in_rgb(basic, compare);
+ return {
+ r: {
+ delta: Math.round(diff.r.delta * 255),
+ percent: diff.r.percent,
+ },
+ g: {
+ delta: Math.round(diff.g.delta * 255),
+ percent: diff.g.percent,
+ },
+ b: {
+ delta: Math.round(diff.b.delta * 255),
+ percent: diff.b.percent,
+ },
+ } as RGBDifference;
+ } catch (e) {
+ console.error('[Compare in RGB]', e);
+ }
+ return defaultCompareResult;
+ }, [basic, compare]);
+
+ return (
+
+
Compare in RGB Space
+
+
+
R
+
+ {differ.r.delta}
+ {isNaN(differ.r.percent) ? '0.00' : (differ.r.percent * 100).toFixed(2)}%
+
+
+
+
G
+
+ {differ.g.delta}
+ {isNaN(differ.g.percent) ? '0.00' : (differ.g.percent * 100).toFixed(2)}%
+
+
+
+
B
+
+ {differ.b.delta}
+ {isNaN(differ.b.percent) ? '0.00' : (differ.b.percent * 100).toFixed(2)}%
+
+
+
+
+ );
+}
diff --git a/src/page-components/color-compare/share-props.ts b/src/page-components/color-compare/share-props.ts
new file mode 100644
index 0000000..31da566
--- /dev/null
+++ b/src/page-components/color-compare/share-props.ts
@@ -0,0 +1,4 @@
+export type CompareMethodProps = {
+ basic?: string;
+ compare?: string;
+};
diff --git a/src/pages/Compare.module.css b/src/pages/Compare.module.css
new file mode 100644
index 0000000..f88020d
--- /dev/null
+++ b/src/pages/Compare.module.css
@@ -0,0 +1,18 @@
+@layer pages {
+ .compare_workspace {
+ flex-direction: column;
+ }
+ .compare_content {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ gap: var(--spacing-l);
+ .compare_column {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-s);
+ font-size: var(--font-size-s);
+ }
+ }
+}
diff --git a/src/pages/Compare.tsx b/src/pages/Compare.tsx
new file mode 100644
index 0000000..e03a999
--- /dev/null
+++ b/src/pages/Compare.tsx
@@ -0,0 +1,44 @@
+import cx from 'clsx';
+import { useState } from 'react';
+import { ColorPicker } from '../components/ColorPicker';
+import { ScrollArea } from '../components/ScrollArea';
+import { HCTCompare } from '../page-components/color-compare/HCTCompare';
+import { HSLCompare } from '../page-components/color-compare/HSLCompare';
+import { OklchCompare } from '../page-components/color-compare/OKLCHCompare';
+import { RGBCompare } from '../page-components/color-compare/RGBCompare';
+import styles from './Compare.module.css';
+
+export function ColorCompare() {
+ const [basicColor, setBasicColor] = useState('000000');
+ const [compareColor, setCompareColor] = useState('000000');
+
+ return (
+
+
+
+
+
+
Basic Color
+
+
+
+
Compare Color
+
+
+
+
Compare Result
+
+
+
+
+
+
+
+
+ );
+}