diff --git a/postcss.config.cjs b/postcss.config.cjs index 33ad091..8615996 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,6 +1,7 @@ module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} + plugins: { + 'tailwindcss/nesting': {}, + 'tailwindcss': {}, + 'autoprefixer': {}, + }, +}; diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 1f699e2..edb9458 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -9,7 +9,7 @@ interface ButtonProps extends ButtonHTMLAttributes { export const Button: React.FC = ({ loading, className, children, ...props }) => ( @@ -210,7 +210,12 @@ export const Instrument: React.FC = ({ uuid, name, element, x, y, width -
+
diff --git a/src/components/contextmenu/CanvasMenu.tsx b/src/components/contextmenu/CanvasMenu.tsx index db5ff9c..95010d7 100644 --- a/src/components/contextmenu/CanvasMenu.tsx +++ b/src/components/contextmenu/CanvasMenu.tsx @@ -34,7 +34,7 @@ export const CanvasMenu: React.FC> = (props) return ( -
+
Instruments
{instruments.map((instrument) => ( diff --git a/src/components/contextmenu/ElementMenu.tsx b/src/components/contextmenu/ElementMenu.tsx index d261656..acb0754 100644 --- a/src/components/contextmenu/ElementMenu.tsx +++ b/src/components/contextmenu/ElementMenu.tsx @@ -18,7 +18,7 @@ export const ElementMenu: React.FC = ({ element, ...props }) = return ( -
+
{element.name}
diff --git a/src/components/menu/ElementsMenu.tsx b/src/components/menu/ElementsMenu.tsx index 45a166a..d302593 100644 --- a/src/components/menu/ElementsMenu.tsx +++ b/src/components/menu/ElementsMenu.tsx @@ -29,21 +29,21 @@ export const ElementsMenu: React.FC = ({ ...props }) => {

Instruments

{instruments.map((instrument) => ( -
+
{instrument.name} - {instrument.dimensions.width}x{instrument.dimensions.height} + {instrument.dimensions.width}x{instrument.dimensions.height}
))}

Utilities

-
Button
-
WebView
+
Button
+
WebView
diff --git a/src/components/menu/SettingsMenu.tsx b/src/components/menu/SettingsMenu.tsx new file mode 100644 index 0000000..248974b --- /dev/null +++ b/src/components/menu/SettingsMenu.tsx @@ -0,0 +1,26 @@ +import { MdSettings } from 'react-icons/md'; +import { Menu } from './index'; +import { ThemeButton, themeButtons } from './ThemeButton'; + +interface SettingsMenuCanvasProps { + show?: boolean; + onClick?: () => void; + onExit?: () => void; +} + +export const SettingsMenuCanvas: React.FC = ({ ...props }) => ( + } {...props}> +
+
+

Theme

+
    + {themeButtons.map((button) => ( +
  • + +
  • + ))} +
+
+
+
+); diff --git a/src/components/menu/SimVarsMenu.tsx b/src/components/menu/SimVarsMenu.tsx index ebb61c1..eda9b3e 100644 --- a/src/components/menu/SimVarsMenu.tsx +++ b/src/components/menu/SimVarsMenu.tsx @@ -47,15 +47,15 @@ const SimVarSlider: React.FC = memo(({ varKey }) => { />
{simVar.name} - {simVar.index > 0 && :{simVar.index}} + {simVar.index > 0 && :{simVar.index}}
@@ -201,14 +201,14 @@ const CollapsibleSimVarSection: React.FC = ({ tit return ( <>
v.pinned ?? false} /> A Vars} + title={<>A Vars} filter={(v) => v.name.toLowerCase().includes(filter.toLowerCase()) && v.type === 'A'} /> L Vars} + title={<>L Vars} filter={(v) => v.name.toLowerCase().includes(filter.toLowerCase()) && v.type === 'L'} /> diff --git a/src/components/menu/ThemeButton.tsx b/src/components/menu/ThemeButton.tsx new file mode 100644 index 0000000..5a72121 --- /dev/null +++ b/src/components/menu/ThemeButton.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react'; +import { MdCheckCircle } from 'react-icons/md'; +import { useGlobalSelector, GlobalState, GlobalDispatch, useGlobalDispatch } from '../../redux/global'; +import { themeConfigs, fallbackThemeConfig, themes } from '../../redux/global/ui/uitheme'; +import { setTheme } from '../../redux/global/configSlice'; +import { Button } from '../Button'; + +export interface ThemeButtonProps { + themeName: string; + title: string | undefined; + className?: string; +} + +export const ThemeButton: React.FC = ({ themeName, title, className }: ThemeButtonProps) => { + const globalDispatch = useGlobalDispatch(); + + const { theme } = useGlobalSelector((state: GlobalState) => state.config); + + const themeConfig = theme !== undefined ? theme : fallbackThemeConfig; + + const { colors: { primary, accent, text, padding, workspacePadding, background } } = themeConfig; + + const [selected, setSelected] = useState(themeConfig.name === themeName); + + const changeTheme = (name: string) => { + const newThemeConfig = themeConfigs.get(name); + + if (newThemeConfig === undefined) { + console.error(`Theme ${name} not found.`); + return; + } + + globalDispatch(setTheme(newThemeConfig)); + }; + + useEffect(() => { + setSelected(themeConfig.name === themeName); + }, [themeConfig, themeName]); + + useEffect(() => { + document.documentElement.style.setProperty('--primary-color', primary); + document.documentElement.style.setProperty('--accent-color', accent); + document.documentElement.style.setProperty('--text-color', text); + document.documentElement.style.setProperty('--padding-color', padding); + document.documentElement.style.setProperty('--workspace-padding-color', workspacePadding); + document.documentElement.style.setProperty('--background-color', background); + }, [background, padding, primary, accent, workspacePadding, text]); + + return ( + + ); +}; + +export const themeButtons = themes.map((theme) => { + + return { + themeName: theme.name, + title: theme.title, + className: 'font-mono hover:font-semibold', + }; +}); diff --git a/src/components/menu/index.tsx b/src/components/menu/index.tsx index 9c0fae3..d16df71 100644 --- a/src/components/menu/index.tsx +++ b/src/components/menu/index.tsx @@ -13,7 +13,7 @@ export const Menu: React.FC = ({ title, icon, show, onClick, onExit, <> + + {children} )} diff --git a/src/components/modal/NewProjectModal.tsx b/src/components/modal/NewProjectModal.tsx index dbb1260..3479801 100644 --- a/src/components/modal/NewProjectModal.tsx +++ b/src/components/modal/NewProjectModal.tsx @@ -102,7 +102,7 @@ export const NewProjectModal: React.FC = ({ show, onExit } }} /> {status &&

{status}

} - + )} diff --git a/src/components/modal/index.tsx b/src/components/modal/index.tsx index 9c937a0..9a46416 100644 --- a/src/components/modal/index.tsx +++ b/src/components/modal/index.tsx @@ -57,7 +57,7 @@ export const Modal: React.FC = ({ title, show, onExit, children }) = onClick={onExit} > = ({ title, show, onExit, children }) = exit="hidden" onClick={(e) => e.stopPropagation()} > -
+

{title}

diff --git a/src/index.css b/src/index.css index 8912124..74e211d 100644 --- a/src/index.css +++ b/src/index.css @@ -8,40 +8,62 @@ } :root { - @apply bg-silver-950 text-silver-200; + --primary-color: #9F59F8; + --accent-color: #B47EFA; + --text-color: #FFFFFF; + --padding-color: #202028; + --workspace-padding-color: #3B3B44; + --background-color: #111217; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; + background-color: var(--background-color); + color: var(--text-color); } .rc-slider-rail { - @apply bg-silver-700; + background-color: var(--background-color); } .rc-slider-track { - @apply bg-amethyst-400/50; + background-color: var(--workspace-padding-color); } - .rc-slider-handle, - .rc-slider-handle:hover { - @apply opacity-100 border-2 border-amethyst-400 bg-silver-800 ring-0 !ring-opacity-50 ring-amethyst-400 transition-shadow duration-300; + .rc-slider-handle, .rc-slider-handle:hover { + @apply opacity-100 border-2 ring-0 !ring-opacity-50 + transition-shadow duration-300; + + background-color: var(--padding-color); + border-color: var(--primary-color); + --tw-ring-opacity: 0.5 !important; + --tw-ring-color: var(--primary-color); + } - .rc-slider-handle-dragging, - .rc-slider-handle:focus-visible, - .rc-slider-handle:active { - @apply !border-amethyst-400 !shadow-none ring-4 ring-amethyst-400; + .rc-slider-handle-dragging, .rc-slider-handle:focus-visible, .rc-slider-handle:active { + @apply !shadow-none ring-4; + + + border-color: var(--primary-color) !important; + --tw-ring-opacity: 1 !important; + --tw-ring-color: var(--primary-color); + --tw-border-opacity: 1 !important; + } .titlebar { - @apply flex items-center fixed inset-x-0 top-0 h-8 bg-silver-900 pl-5 z-50; + @apply flex items-center fixed inset-x-0 top-0 h-8 pl-5 z-50; + + background: var(--background-color); } .titlebar-title { - @apply text-white text-sm font-medium; + @apply text-sm font-medium; + + color: var(--text-color) } .titlebar-controls { @@ -49,25 +71,110 @@ } .titlebar-controls button { - @apply flex justify-center items-center w-8 h-8 fill-none stroke-white stroke-2 disabled:stroke-silver-600 hover:bg-silver-800 disabled:hover:bg-transparent transition-colors duration-150; + @apply flex justify-center items-center w-8 h-8 + fill-none stroke-2 + transition-colors duration-150; stroke-linecap: round; stroke-linejoin: round; + stroke: #FFFFFF; + + &:hover { + background-color: var(--background-color); + } + + &:disabled { + stroke: var(--workspace-padding-color); + + &:hover { + background-color: transparent; + } + } + } + + .text-theme-primary { + color: var(--primary-color); } - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; + .bg-theme-primary { + background-color: var(--primary-color); } - select { - @apply bg-transparent border-2 border-silver-600 rounded-md + .border-theme-primary { + border-color: var(--primary-color); } - .bg-grid { - background-size: 20px 20px; - background-image: radial-gradient(#202028 1.25px, transparent 0); - background-position: -9px -9px; + .text-theme-accent { + color: var(--accent-color); } -} \ No newline at end of file + + .bg-theme-accent { + background-color: var(--accent-color); + } + + .border-theme-accent { + border-color: var(--accent-color); + } + + .text-theme-text { + color: var(--text-color); + } + + .border-theme-text { + border-color: var(--text-color); + } + + .text-theme-pd { + color: var(--padding-color); + } + + .text-theme-workspace-pd { + color: var(--workspace-padding-color); + } + + .bg-theme-pd { + background-color: var(--padding-color); + } + + .border-theme-pd { + border-color: var(--padding-color); + } + + .bg-theme-bg { + background-color: var(--background-color); + } + + .border-theme-bg { + border-color: var(--background-color); + } + + .bg-theme-workspace-pd { + background-color: var(--workspace-padding-color); + } + + .ring-theme-primary { + --tw-ring-color: var(--primary-color); + } + + .ring-theme-accent { + --tw-ring-color: var(--accent-color); + } + + .ring-theme-workspace-pd { + --tw-ring-color: var(--workspace-padding-color); + } + + .border-theme-workspace-pd { + --tw-border-opacity: 1; + border: 1px solid var(--workspace-padding-color); + } + + .border-b-theme-workspace-pd { + border-bottom-color: var(--workspace-padding-color); + } + +.bg-grid { + background-size: 20px 20px; + background-image: radial-gradient(var(--padding-color) 1.25px, transparent 0); + background-position: -9px -9px; +} diff --git a/src/index.tsx b/src/index.tsx index 0534e74..c7bc55c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,6 +12,7 @@ import '@fontsource-variable/jetbrains-mono'; import '@fontsource-variable/space-grotesk'; import 'tippy.js/dist/tippy.css'; import './index.css'; +import { AppearanceSettings, SettingsMenu } from './pages/Settings'; document .getElementById('titlebar-minimize')! @@ -33,6 +34,7 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( } /> } /> + } /> diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 5576cef..a8d07f4 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -4,6 +4,7 @@ import { open } from '@tauri-apps/api/dialog'; import { invoke } from '@tauri-apps/api/tauri'; import { LuFolderOpen, LuPlus } from 'react-icons/lu'; import { appWindow } from '@tauri-apps/api/window'; +import { MdSettings } from 'react-icons/md'; import { useWorkspaceDispatch } from '../redux/workspace'; import { setActive } from '../redux/workspace/projectSlice'; import { initConfig, pushRecentProject } from '../redux/global/configSlice'; @@ -12,17 +13,34 @@ import { AceProject } from '../types'; import { Card } from '../components/Card'; import { NewProjectModal } from '../components/modal/NewProjectModal'; import { timeFromTimestamp } from '../utils/date'; +import { fallbackThemeConfig } from '../redux/global/ui/uitheme'; export const Home: React.FC = () => { const navigate = useNavigate(); const workspaceDispatch = useWorkspaceDispatch(); const globalDispatch = useGlobalDispatch(); - const { version, recentProjects } = useGlobalSelector((state: GlobalState) => ({ + const { version, recentProjects, theme } = useGlobalSelector((state: GlobalState) => ({ version: state.config.version, recentProjects: state.config.recentProjects, + theme: state.config.theme, })); + const themeConfig = theme !== undefined ? theme : fallbackThemeConfig; + + const { colors: { primary, accent, text, padding, workspacePadding, background } } = themeConfig; + + useEffect(() => { + document.documentElement.style.setProperty('--primary-color', primary); + document.documentElement.style.setProperty('--accent-color', accent); + document.documentElement.style.setProperty('--text-color', text); + document.documentElement.style.setProperty('--padding-color', padding); + document.documentElement.style.setProperty('--workspace-padding-color', workspacePadding); + document.documentElement.style.setProperty('--background-color', background); + }, [background, padding, primary, accent, workspacePadding, text]); + + console.log(padding); + const [showNewProjectModal, setShowNewProjectModal] = useState(false); const openProject = useCallback(() => { @@ -45,6 +63,10 @@ export const Home: React.FC = () => { .catch((error) => console.error(error)); }, [workspaceDispatch, globalDispatch, navigate]); + const navigateToSettings = () => { + navigate('/settings'); + }; + useEffect(() => { appWindow.setResizable(false); appWindow.unmaximize(); @@ -60,13 +82,18 @@ export const Home: React.FC = () => { return ( <> -
-

- Welcome to ACE - - v{version} +
+
+ Welcome to ACE + v{version} -

+ {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} + +
{
-

+

Recent Projects

{recentProjects.length === 0 && ( @@ -96,10 +123,10 @@ export const Home: React.FC = () => { .map(({ name, path, timestamp }) => ( loadProject(path)} key={path}> -

{name}

+

{name}

Last opened {timeFromTimestamp(timestamp)}
- {path} + {path}
))}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..65d6f48 --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,36 @@ +import { useNavigate } from 'react-router'; +import { MdClose } from 'react-icons/md'; +import { ThemeButton, themeButtons } from '../components/menu/ThemeButton'; + +export const SettingsMenu: React.FC = () => ( + +); + +export const AppearanceSettings: React.FC = () => { + const navigate = useNavigate(); + + const returnToHome = () => { + navigate('/'); + }; + + return ( +
+ +
+ +

Settings

+
+
+

Themes

+
    + {themeButtons.map((button) => ( +
  • + +
  • + ))} +
+
+ ); +}; diff --git a/src/pages/Workspace.tsx b/src/pages/Workspace.tsx index 23d3421..d68a61d 100644 --- a/src/pages/Workspace.tsx +++ b/src/pages/Workspace.tsx @@ -12,6 +12,7 @@ import { Instrument } from '../components/Instrument'; import { SimVarsMenu } from '../components/menu/SimVarsMenu'; import { CanvasMenu } from '../components/contextmenu/CanvasMenu'; import { ElementsMenu } from '../components/menu/ElementsMenu'; +import { SettingsMenuCanvas } from '../components/menu/SettingsMenu'; import { InstrumentConfig, SimVar } from '../types'; import { EventsMenu } from '../components/menu/EventsMenu'; @@ -21,7 +22,7 @@ export const CANVAS_HEIGHT = 5000; enum MenuTabs { SimVars, Elements, - Events, + Settings } export const Workspace: React.FC = () => { @@ -63,7 +64,7 @@ export const Workspace: React.FC = () => { return ( -
+
setMenuTab(MenuTabs.SimVars)} @@ -74,9 +75,9 @@ export const Workspace: React.FC = () => { onClick={() => setMenuTab(MenuTabs.Elements)} onExit={() => setMenuTab(undefined)} /> - setMenuTab(MenuTabs.Events)} + setMenuTab(MenuTabs.Settings)} onExit={() => setMenuTab(undefined)} />
diff --git a/src/redux/global/configSlice.ts b/src/redux/global/configSlice.ts index 6966c58..4d395db 100644 --- a/src/redux/global/configSlice.ts +++ b/src/redux/global/configSlice.ts @@ -2,6 +2,9 @@ import { createAsyncThunk, createSlice, Middleware, PayloadAction } from '@redux import { Platform, platform as getPlatform } from '@tauri-apps/api/os'; import { getVersion } from '@tauri-apps/api/app'; import { AceProject } from '../../types'; +import { ThemeConfig, fallbackThemeConfig } from './ui/uitheme'; + +const THEME_LOCAL_STORAGE_KEY = 'ace_ui_theme'; export interface RecentProject { name: string; @@ -13,11 +16,14 @@ interface ConfigState { platform?: Platform; version?: string; recentProjects?: RecentProject[]; + theme?: ThemeConfig; } const configSlice = createSlice({ name: 'config', - initialState: {} as ConfigState, + initialState: { + theme: JSON.parse(localStorage.getItem(THEME_LOCAL_STORAGE_KEY) ?? JSON.stringify(fallbackThemeConfig)) as ThemeConfig, + } as ConfigState, reducers: { pushRecentProject(state, action: PayloadAction) { if (state.recentProjects === undefined) return; @@ -30,27 +36,37 @@ const configSlice = createSlice({ timestamp: new Date().toISOString(), }); }, + setTheme: (state, action: PayloadAction) => { + state.theme = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(initConfig.fulfilled, (state, action) => action.payload); }, }); -export const { pushRecentProject } = configSlice.actions; -export const configReducer = configSlice.reducer; - const LOCAL_STORAGE_KEY = 'ace_recent_projects'; +export const { pushRecentProject, setTheme } = configSlice.actions; +export const configReducer = configSlice.reducer; + export const localStorageMiddleware: Middleware = (store) => (next) => (action) => { next(action); - const { recentProjects } = store.getState().config; + const { recentProjects, theme } = store.getState().config; if (recentProjects !== undefined) { localStorage.setItem( LOCAL_STORAGE_KEY, JSON.stringify(recentProjects), ); } + + if (theme !== undefined) { + localStorage.setItem( + THEME_LOCAL_STORAGE_KEY, + JSON.stringify(theme), + ); + } }; export const initConfig = createAsyncThunk( @@ -64,6 +80,9 @@ export const initConfig = createAsyncThunk( ? [] : JSON.parse(local) as RecentProject[]; - return { platform, version, recentProjects }; + const storedTheme = localStorage.getItem(THEME_LOCAL_STORAGE_KEY); + const theme = storedTheme !== null ? JSON.parse(storedTheme) as ThemeConfig : fallbackThemeConfig; + + return { platform, version, recentProjects, theme }; }, ); diff --git a/src/redux/global/ui/colors.ts b/src/redux/global/ui/colors.ts new file mode 100644 index 0000000..9eef749 --- /dev/null +++ b/src/redux/global/ui/colors.ts @@ -0,0 +1,45 @@ +export interface ColorPalette { + primary: string; + accent: string; + text: string; + padding: string; + workspacePadding: string; + background: string; +} + +// using default export because tailwind.config.cjs doesn't support ESM modules. +// eslint-disable-next-line import/no-default-export +export default { + 'amethyst-dark': { + primary: '#B47EFA', + accent: '#9F59F8', + text: '#FFFFFF', + padding: '#202028', + workspacePadding: '#3B3B44', + background: '#111217', + }, + 'flybywire-dark': { + primary: '#00E0FE', + secondary: '#00CCFF', + text: '#FAFAFA', + padding: '#171E2C', + workspacePadding: '#1F2A3C', + background: '#0E131B', + }, + 'atom-one-dark': { + primary: '#E06C75', + secondary: '#98C379', + text: '#FAFAFA', + padding: '#1D2025', + workspacePadding: '#252931', + background: '#282C34', + }, + 'monokai': { + primary: '#A6E22E', + secondary: '#60D8EF', + text: '#F8F8F2', + padding: '#1E1F1C', + workspacePadding: '#323331', + background: '#272822', + }, +}; diff --git a/src/redux/global/ui/uitheme.ts b/src/redux/global/ui/uitheme.ts new file mode 100644 index 0000000..67421be --- /dev/null +++ b/src/redux/global/ui/uitheme.ts @@ -0,0 +1,30 @@ +import colors, { ColorPalette } from './colors'; + +export interface ThemeConfig { + name: string; + title: string | undefined; + colors: ColorPalette; +} + +const themeTitles = new Map([ + ['amethyst-dark', 'Amethyst Dark'], + ['flybywire-dark', 'FlyByWire Dark'], + ['atom-one-dark', 'Atom One Dark'], + ['monokai', 'Monokai'], +]); + +// Then modify the theme creation code to include the title: +export const themes = Object.entries(colors).map(([name, color]) => ({ + name, + title: themeTitles.get(name) ?? name, // This will match the title based on the name + colors: color, +})); + +export const themeConfigs = new Map(); +themes.forEach((theme) => themeConfigs.set(theme.name, theme)); + +export const fallbackThemeConfig: ThemeConfig = { + name: 'amthyst-dark', + title: 'Amethyst Dark', + colors: colors['amethyst-dark'], +}; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 1dd9882..f720cbb 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,4 +1,7 @@ /** @type {import('tailwindcss').Config} */ + +const colors = require('./src/redux/global/ui/colors'); + module.exports = { content: [ './src/**/*.{js,jsx,ts,tsx}', @@ -7,39 +10,38 @@ module.exports = { theme: { fontFamily: { sans: ['"Space Grotesk Variable"', 'sans-serif'], - mono: ['"JetBrains Mono Variable"', 'monospace'] + mono: ['"JetBrains Mono Variable"', 'monospace'], }, container: { center: true, }, extend: { - colors: { - silver: { - 950: '#111217', - 900: '#191920', - 800: '#202028', - 700: '#3B3B44', - 600: '#565660', - 500: '#71717C', - 400: '#8D8D99', - 300: '#A8A8B5', - 200: '#C3C3D1', - 100: '#DEDEED', - 50: '#EFEFF9', - }, - amethyst: { - 950: '#35234F', - 900: '#4A2E71', - 800: '#5F3993', - 700: '#7543B4', - 600: '#8A4ED6', - 500: '#9F59F8', - 400: '#B47EFA', - 300: '#CAA3FB', - 200: '#D4B5FC', - 100: '#DFC8FD', - 50: '#EADAFD', - }, + colors, + silver: { + 950: '#111217', + 900: '#191920', + 800: '#202028', + 700: '#3B3B44', + 600: '#565660', + 500: '#71717C', + 400: '#8D8D99', + 300: '#A8A8B5', + 200: '#C3C3D1', + 100: '#DEDEED', + 50: '#EFEFF9', + }, + amethyst: { + 950: '#35234F', + 900: '#4A2E71', + 800: '#5F3993', + 700: '#7543B4', + 600: '#8A4ED6', + 500: '#9F59F8', + 400: '#B47EFA', + 300: '#CAA3FB', + 200: '#D4B5FC', + 100: '#DFC8FD', + 50: '#EADAFD', }, }, },