diff --git a/src/App.tsx b/src/App.tsx
index db7cfe8..83384b1 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import { ColorFunctionProvider } from './ColorFunctionContext';
import { Notifications } from './components/Notifications';
import { Harmonies } from './pages/Harmonies';
import { Home } from './pages/Home';
+import { LightenDarken } from './pages/LightenDarken';
import { MainLayout } from './pages/MainLayout';
import { NewScheme } from './pages/NewScheme';
import { SchemeDetail } from './pages/SchemeDetail';
@@ -31,6 +32,7 @@ const routes = createBrowserRouter([
{ path: 'wheels', element: },
{ path: 'tones', element: },
{ path: 'tints-shades', element: },
+ { path: 'lighten-darken', element: },
],
},
]);
diff --git a/src/page-components/lighten-darken/darkens.tsx b/src/page-components/lighten-darken/darkens.tsx
new file mode 100644
index 0000000..025f3c2
--- /dev/null
+++ b/src/page-components/lighten-darken/darkens.tsx
@@ -0,0 +1,59 @@
+import { last } from 'lodash-es';
+import { useMemo } from 'react';
+import { useColorFunction } from '../../ColorFunctionContext';
+import { FlexColorStand } from '../../components/FlexColorStand';
+
+type DarkensProps = {
+ color: string;
+ darkens: number;
+ mix: 'progressive' | 'linear' | 'average';
+ step?: number;
+ maximum?: number;
+ copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
+};
+
+export function Darkens({ color, darkens, mix, step, maximum, copyMode }: DarkensProps) {
+ const { colorFn } = useColorFunction();
+ const colors = useMemo(() => {
+ try {
+ if (!colorFn) {
+ return Array.from({ length: darkens + 1 }, () => color);
+ }
+ const darkenColors = [color];
+ switch (mix) {
+ case 'progressive':
+ for (let i = 1; i <= darkens; i++) {
+ const darkenColor = colorFn.darken(last(darkenColors), step);
+ darkenColors.push(darkenColor);
+ }
+ break;
+ case 'linear':
+ for (let i = 1; i <= darkens; i++) {
+ const darkenColor = colorFn.darken(color, step * i);
+ darkenColors.push(darkenColor);
+ }
+ break;
+ case 'average': {
+ const interval = maximum / darkens / 100;
+ for (let i = 1; i <= darkens; i++) {
+ const darkenColor = colorFn.darken(color, interval * i);
+ darkenColors.push(darkenColor);
+ }
+ break;
+ }
+ }
+ return darkenColors.reverse();
+ } catch (e) {
+ console.error('[Generate Lighten]', e);
+ }
+ return Array.from({ length: darkens + 1 }, () => color);
+ }, [color, darkens, mix, step, maximum]);
+
+ return (
+ <>
+ {colors.map((c, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/page-components/lighten-darken/lightens.tsx b/src/page-components/lighten-darken/lightens.tsx
new file mode 100644
index 0000000..5a0806f
--- /dev/null
+++ b/src/page-components/lighten-darken/lightens.tsx
@@ -0,0 +1,59 @@
+import { last } from 'lodash-es';
+import { useMemo } from 'react';
+import { useColorFunction } from '../../ColorFunctionContext';
+import { FlexColorStand } from '../../components/FlexColorStand';
+
+type LightensProps = {
+ color: string;
+ lightens: number;
+ mix: 'progressive' | 'linear' | 'average';
+ step?: number;
+ maximum?: number;
+ copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
+};
+
+export function Lightens({ color, lightens, mix, step, maximum, copyMode }: LightensProps) {
+ const { colorFn } = useColorFunction();
+ const colors = useMemo(() => {
+ try {
+ if (!colorFn) {
+ return Array.from({ length: lightens + 1 }, () => color);
+ }
+ const lightenColors = [color];
+ switch (mix) {
+ case 'progressive':
+ for (let i = 1; i <= lightens; i++) {
+ const lightenColor = colorFn.lighten(last(lightenColors), step);
+ lightenColors.push(lightenColor);
+ }
+ break;
+ case 'linear':
+ for (let i = 1; i <= lightens; i++) {
+ const lightenColor = colorFn.lighten(color, step * i);
+ lightenColors.push(lightenColor);
+ }
+ break;
+ case 'average': {
+ const interval = maximum / lightens / 100;
+ for (let i = 1; i <= lightens; i++) {
+ const lightenColor = colorFn.lighten(color, interval * i);
+ lightenColors.push(lightenColor);
+ }
+ break;
+ }
+ }
+ return lightenColors;
+ } catch (e) {
+ console.error('[Generate Lighten]', e);
+ }
+ return Array.from({ length: lightens + 1 }, () => color);
+ }, [color, lightens, mix, step, maximum]);
+
+ return (
+ <>
+ {colors.map((c, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/pages/LightenDarken.module.css b/src/pages/LightenDarken.module.css
new file mode 100644
index 0000000..cb73db7
--- /dev/null
+++ b/src/pages/LightenDarken.module.css
@@ -0,0 +1,55 @@
+@layer pages {
+ .lighten_workspace {
+ flex-direction: column;
+ }
+ .explore_section {
+ width: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ gap: var(--spacing-m);
+ }
+ .function_side {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-m);
+ font-size: var(--font-size-s);
+ .mode_navigation {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: var(--spacing-s);
+ }
+ h5 {
+ padding-block: var(--spacing-m);
+ font-size: var(--font-size-m);
+ }
+ }
+ .lights_content {
+ flex: 1;
+ padding: 0 var(--spacing-m);
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-s);
+ h5 {
+ padding-block: var(--spacing-m);
+ font-size: var(--font-size-m);
+ }
+ .color_value_mode {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: var(--spacing-s);
+ font-size: var(--font-size-s);
+ }
+ .colors_booth {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: stretch;
+ gap: var(--spacing-xs);
+ min-height: 12em;
+ }
+ }
+}
diff --git a/src/pages/LightenDarken.tsx b/src/pages/LightenDarken.tsx
new file mode 100644
index 0000000..93324b5
--- /dev/null
+++ b/src/pages/LightenDarken.tsx
@@ -0,0 +1,135 @@
+import cx from 'clsx';
+import { useAtom } from 'jotai';
+import { isEqual } from 'lodash-es';
+import { useState } from 'react';
+import { ColorPicker } from '../components/ColorPicker';
+import { HSegmentedControl } from '../components/HSegmentedControl';
+import { Labeled } from '../components/Labeled';
+import { LabeledPicker } from '../components/LabeledPicker';
+import { ScrollArea } from '../components/ScrollArea';
+import { VSegmentedControl } from '../components/VSegmentedControl';
+import { Darkens } from '../page-components/lighten-darken/darkens';
+import { Lightens } from '../page-components/lighten-darken/lightens';
+import { currentPickedColor } from '../stores/colors';
+import styles from './LightenDarken.module.css';
+
+export function LightenDarken() {
+ const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
+ const [lighten, setLighten] = useState(3);
+ const [darken, setDarken] = useState(3);
+ const [steps, setSteps] = useState(10);
+ const [maximum, setMaximum] = useState(90);
+ const [mixMode, setMixMode] = useState<'progressive' | 'average'>('progressive');
+ const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
+
+ return (
+
+ {' '}
+
+
+
+
+
+
Lighten Series
+
+
+
+
Darken Series
+
+
+
+
+
+
+
+
+
+
+
+ );
+}