diff options
Diffstat (limited to 'subprojects/frontend')
-rw-r--r-- | subprojects/frontend/index.html | 10 | ||||
-rw-r--r-- | subprojects/frontend/src/Loading.tsx | 7 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorPane.tsx | 23 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/EditorTheme.ts | 13 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/SearchToolbar.tsx | 32 | ||||
-rw-r--r-- | subprojects/frontend/src/theme/ThemeProvider.tsx | 65 | ||||
-rw-r--r-- | subprojects/frontend/vite.config.ts | 2 |
7 files changed, 105 insertions, 47 deletions
diff --git a/subprojects/frontend/index.html b/subprojects/frontend/index.html index a3e52ef9..f9e87485 100644 --- a/subprojects/frontend/index.html +++ b/subprojects/frontend/index.html | |||
@@ -10,7 +10,7 @@ | |||
10 | <link rel="icon" href="/favicon-96x96.png" type="image/png" sizes="96x96"> | 10 | <link rel="icon" href="/favicon-96x96.png" type="image/png" sizes="96x96"> |
11 | <link rel="apple-touch-icon" href="/apple-touch-icon.png" type="image/png" sizes="180x180"> | 11 | <link rel="apple-touch-icon" href="/apple-touch-icon.png" type="image/png" sizes="180x180"> |
12 | <link rel="mask-icon" href="/mask-icon.svg" type="image/svg+xml" color="#038a99"> | 12 | <link rel="mask-icon" href="/mask-icon.svg" type="image/svg+xml" color="#038a99"> |
13 | <meta name="theme-color" media="(prefers-color-scheme:light)" content="#fafafa"> | 13 | <meta name="theme-color" media="(prefers-color-scheme:light)" content="#f5f5f5"> |
14 | <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b"> | 14 | <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b"> |
15 | <style> | 15 | <style> |
16 | @import '@fontsource/jetbrains-mono/variable.css'; | 16 | @import '@fontsource/jetbrains-mono/variable.css'; |
@@ -270,15 +270,15 @@ | |||
270 | } | 270 | } |
271 | 271 | ||
272 | body { | 272 | body { |
273 | color: #35373e; | 273 | color: #19202b; |
274 | background: #ffffff; | 274 | background: #f5f5f5; |
275 | font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; | 275 | font-family: 'InterVariable', 'Inter', 'Roboto', 'Helvetica', 'Arial', sans-serif; |
276 | } | 276 | } |
277 | 277 | ||
278 | @media (prefers-color-scheme: dark) { | 278 | @media (prefers-color-scheme: dark) { |
279 | body { | 279 | body { |
280 | color: #ebebff; | 280 | color: #ebebff; |
281 | background: #282c34; | 281 | background: #21252b; |
282 | } | 282 | } |
283 | } | 283 | } |
284 | </style> | 284 | </style> |
diff --git a/subprojects/frontend/src/Loading.tsx b/subprojects/frontend/src/Loading.tsx index 72020a43..8c293239 100644 --- a/subprojects/frontend/src/Loading.tsx +++ b/subprojects/frontend/src/Loading.tsx | |||
@@ -2,18 +2,19 @@ import CircularProgress from '@mui/material/CircularProgress'; | |||
2 | import { styled } from '@mui/material/styles'; | 2 | import { styled } from '@mui/material/styles'; |
3 | import React from 'react'; | 3 | import React from 'react'; |
4 | 4 | ||
5 | const LoadingRoot = styled('div')({ | 5 | const LoadingRoot = styled('div')(({ theme }) => ({ |
6 | width: '100%', | 6 | width: '100%', |
7 | height: '100%', | 7 | height: '100%', |
8 | display: 'flex', | 8 | display: 'flex', |
9 | alignItems: 'center', | 9 | alignItems: 'center', |
10 | justifyContent: 'center', | 10 | justifyContent: 'center', |
11 | }); | 11 | background: theme.palette.outer.background, |
12 | })); | ||
12 | 13 | ||
13 | export default function Loading() { | 14 | export default function Loading() { |
14 | return ( | 15 | return ( |
15 | <LoadingRoot> | 16 | <LoadingRoot> |
16 | <CircularProgress /> | 17 | <CircularProgress size={60} color="inherit" /> |
17 | </LoadingRoot> | 18 | </LoadingRoot> |
18 | ); | 19 | ); |
19 | } | 20 | } |
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx index 935a84e8..df43d2c9 100644 --- a/subprojects/frontend/src/editor/EditorPane.tsx +++ b/subprojects/frontend/src/editor/EditorPane.tsx | |||
@@ -1,10 +1,10 @@ | |||
1 | import Box from '@mui/material/Box'; | 1 | import Box from '@mui/material/Box'; |
2 | import Skeleton from '@mui/material/Skeleton'; | ||
2 | import Stack from '@mui/material/Stack'; | 3 | import Stack from '@mui/material/Stack'; |
3 | import Toolbar from '@mui/material/Toolbar'; | 4 | import Toolbar from '@mui/material/Toolbar'; |
4 | import { observer } from 'mobx-react-lite'; | 5 | import { observer } from 'mobx-react-lite'; |
5 | import React from 'react'; | 6 | import React, { useState } from 'react'; |
6 | 7 | ||
7 | import Loading from '../Loading'; | ||
8 | import { useRootStore } from '../RootStore'; | 8 | import { useRootStore } from '../RootStore'; |
9 | 9 | ||
10 | import EditorArea from './EditorArea'; | 10 | import EditorArea from './EditorArea'; |
@@ -12,6 +12,23 @@ import EditorButtons from './EditorButtons'; | |||
12 | import GenerateButton from './GenerateButton'; | 12 | import GenerateButton from './GenerateButton'; |
13 | import SearchPanelPortal from './SearchPanelPortal'; | 13 | import SearchPanelPortal from './SearchPanelPortal'; |
14 | 14 | ||
15 | function EditorLoading(): JSX.Element { | ||
16 | const [skeletonSizes] = useState(() => | ||
17 | new Array(10).fill(0).map(() => Math.random() * 60 + 10), | ||
18 | ); | ||
19 | |||
20 | return ( | ||
21 | <Box mx={2}> | ||
22 | {skeletonSizes.map((length, i) => ( | ||
23 | /* eslint-disable-next-line react/no-array-index-key -- | ||
24 | Random placeholders have no identity. | ||
25 | */ | ||
26 | <Skeleton key={i} width={`${length}%`} /> | ||
27 | ))} | ||
28 | </Box> | ||
29 | ); | ||
30 | } | ||
31 | |||
15 | function EditorPane(): JSX.Element { | 32 | function EditorPane(): JSX.Element { |
16 | const { editorStore } = useRootStore(); | 33 | const { editorStore } = useRootStore(); |
17 | 34 | ||
@@ -23,7 +40,7 @@ function EditorPane(): JSX.Element { | |||
23 | </Toolbar> | 40 | </Toolbar> |
24 | <Box flexGrow={1} flexShrink={1} overflow="auto"> | 41 | <Box flexGrow={1} flexShrink={1} overflow="auto"> |
25 | {editorStore === undefined ? ( | 42 | {editorStore === undefined ? ( |
26 | <Loading /> | 43 | <EditorLoading /> |
27 | ) : ( | 44 | ) : ( |
28 | <> | 45 | <> |
29 | <SearchPanelPortal editorStore={editorStore} /> | 46 | <SearchPanelPortal editorStore={editorStore} /> |
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index ca07d602..dfeeb547 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts | |||
@@ -14,8 +14,10 @@ export default styled('div', { | |||
14 | })<{ showLineNumbers: boolean }>(({ theme, showLineNumbers }) => { | 14 | })<{ showLineNumbers: boolean }>(({ theme, showLineNumbers }) => { |
15 | const editorFontStyle = { | 15 | const editorFontStyle = { |
16 | ...theme.typography.editor, | 16 | ...theme.typography.editor, |
17 | fontWeight: theme.typography.fontWeightEditorNormal, | ||
17 | [theme.breakpoints.down('sm')]: { | 18 | [theme.breakpoints.down('sm')]: { |
18 | fontSize: '0.875rem', | 19 | // `rem` for JetBrains MonoVariable make the text too large in Safari. |
20 | fontSize: '14px', | ||
19 | lineHeight: 1.43, | 21 | lineHeight: 1.43, |
20 | }, | 22 | }, |
21 | }; | 23 | }; |
@@ -29,7 +31,12 @@ export default styled('div', { | |||
29 | color: theme.palette.text.secondary, | 31 | color: theme.palette.text.secondary, |
30 | }, | 32 | }, |
31 | '.cm-gutters': { | 33 | '.cm-gutters': { |
32 | background: theme.palette.background.default, | 34 | background: `linear-gradient( |
35 | to right, | ||
36 | ${theme.palette.background.default} 0%, | ||
37 | ${theme.palette.background.default} calc(100% - 12px), | ||
38 | transparent 100% | ||
39 | )`, | ||
33 | border: 'none', | 40 | border: 'none', |
34 | }, | 41 | }, |
35 | '.cm-content': { | 42 | '.cm-content': { |
@@ -93,7 +100,7 @@ export default styled('div', { | |||
93 | fontStyle: 'italic', | 100 | fontStyle: 'italic', |
94 | }, | 101 | }, |
95 | '.tok-problem-containment': { | 102 | '.tok-problem-containment': { |
96 | fontWeight: theme.typography.fontWeightBold, | 103 | fontWeight: theme.typography.fontWeightEditorBold, |
97 | textDecorationSkipInk: 'none', | 104 | textDecorationSkipInk: 'none', |
98 | }, | 105 | }, |
99 | '.tok-problem-error': { | 106 | '.tok-problem-error': { |
diff --git a/subprojects/frontend/src/editor/SearchToolbar.tsx b/subprojects/frontend/src/editor/SearchToolbar.tsx index 7e6ff4f7..895f1ca1 100644 --- a/subprojects/frontend/src/editor/SearchToolbar.tsx +++ b/subprojects/frontend/src/editor/SearchToolbar.tsx | |||
@@ -11,6 +11,7 @@ import Stack from '@mui/material/Stack'; | |||
11 | import TextField from '@mui/material/TextField'; | 11 | import TextField from '@mui/material/TextField'; |
12 | import ToggleButton from '@mui/material/ToggleButton'; | 12 | import ToggleButton from '@mui/material/ToggleButton'; |
13 | import Toolbar from '@mui/material/Toolbar'; | 13 | import Toolbar from '@mui/material/Toolbar'; |
14 | import { styled } from '@mui/material/styles'; | ||
14 | import useMediaQuery from '@mui/material/useMediaQuery'; | 15 | import useMediaQuery from '@mui/material/useMediaQuery'; |
15 | import { observer } from 'mobx-react-lite'; | 16 | import { observer } from 'mobx-react-lite'; |
16 | import React, { useCallback, useState } from 'react'; | 17 | import React, { useCallback, useState } from 'react'; |
@@ -18,7 +19,13 @@ import React, { useCallback, useState } from 'react'; | |||
18 | import type SearchPanelStore from './SearchPanelStore'; | 19 | import type SearchPanelStore from './SearchPanelStore'; |
19 | 20 | ||
20 | const SPLIT_MEDIA_QUERY = '@media (max-width: 1200px)'; | 21 | const SPLIT_MEDIA_QUERY = '@media (max-width: 1200px)'; |
21 | const ABBREVIATE_MEDIA_QUERY = '@media (max-width: 720px)'; | 22 | |
23 | const DimLabel = styled(FormControlLabel)(({ theme }) => ({ | ||
24 | '.MuiFormControlLabel-label': { | ||
25 | ...theme.typography.body2, | ||
26 | color: theme.palette.text.secondary, | ||
27 | }, | ||
28 | })); | ||
22 | 29 | ||
23 | function SearchToolbar({ | 30 | function SearchToolbar({ |
24 | searchPanelStore, | 31 | searchPanelStore, |
@@ -31,7 +38,6 @@ function SearchToolbar({ | |||
31 | invalidRegexp, | 38 | invalidRegexp, |
32 | } = searchPanelStore; | 39 | } = searchPanelStore; |
33 | const split = useMediaQuery(SPLIT_MEDIA_QUERY); | 40 | const split = useMediaQuery(SPLIT_MEDIA_QUERY); |
34 | const abbreviate = useMediaQuery(ABBREVIATE_MEDIA_QUERY); | ||
35 | const [showRepalceState, setShowReplaceState] = useState(false); | 41 | const [showRepalceState, setShowReplaceState] = useState(false); |
36 | 42 | ||
37 | const showReplace = !split || showRepalceState || replace !== ''; | 43 | const showReplace = !split || showRepalceState || replace !== ''; |
@@ -132,7 +138,7 @@ function SearchToolbar({ | |||
132 | alignItems="center" | 138 | alignItems="center" |
133 | rowGap={0.5} | 139 | rowGap={0.5} |
134 | > | 140 | > |
135 | <FormControlLabel | 141 | <DimLabel |
136 | control={ | 142 | control={ |
137 | <Checkbox | 143 | <Checkbox |
138 | checked={caseSensitive} | 144 | checked={caseSensitive} |
@@ -144,10 +150,9 @@ function SearchToolbar({ | |||
144 | size="small" | 150 | size="small" |
145 | /> | 151 | /> |
146 | } | 152 | } |
147 | aria-label="Match case" | 153 | label="Match case" |
148 | label={abbreviate ? 'Case' : 'Match case'} | ||
149 | /> | 154 | /> |
150 | <FormControlLabel | 155 | <DimLabel |
151 | control={ | 156 | control={ |
152 | <Checkbox | 157 | <Checkbox |
153 | checked={literal} | 158 | checked={literal} |
@@ -159,10 +164,9 @@ function SearchToolbar({ | |||
159 | size="small" | 164 | size="small" |
160 | /> | 165 | /> |
161 | } | 166 | } |
162 | aria-label="Literal" | 167 | label="Literal" |
163 | label={abbreviate ? 'Lit' : 'Literal'} | ||
164 | /> | 168 | /> |
165 | <FormControlLabel | 169 | <DimLabel |
166 | control={ | 170 | control={ |
167 | <Checkbox | 171 | <Checkbox |
168 | checked={regexp} | 172 | checked={regexp} |
@@ -248,7 +252,15 @@ function SearchToolbar({ | |||
248 | </Stack> | 252 | </Stack> |
249 | </Stack> | 253 | </Stack> |
250 | </Stack> | 254 | </Stack> |
251 | <Stack direction="row" alignSelf="stretch" alignItems="start" mt="1px"> | 255 | <Stack |
256 | direction="row" | ||
257 | alignSelf="stretch" | ||
258 | alignItems="start" | ||
259 | mt="1px" | ||
260 | sx={{ | ||
261 | [SPLIT_MEDIA_QUERY]: { display: 'none' }, | ||
262 | }} | ||
263 | > | ||
252 | <IconButton | 264 | <IconButton |
253 | aria-label="Close find/replace" | 265 | aria-label="Close find/replace" |
254 | onClick={() => searchPanelStore.close()} | 266 | onClick={() => searchPanelStore.close()} |
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 3a0703fe..0c780e85 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -43,11 +43,15 @@ interface HighlightPalette { | |||
43 | 43 | ||
44 | declare module '@mui/material/styles' { | 44 | declare module '@mui/material/styles' { |
45 | interface TypographyVariants { | 45 | interface TypographyVariants { |
46 | fontWeightEditorNormal: number; | ||
47 | fontWeightEditorBold: number; | ||
46 | editor: TypographyStyle; | 48 | editor: TypographyStyle; |
47 | } | 49 | } |
48 | 50 | ||
49 | // eslint-disable-next-line @typescript-eslint/no-shadow -- Augment imported interface. | 51 | // eslint-disable-next-line @typescript-eslint/no-shadow -- Augment imported interface. |
50 | interface TypographyVariantsOptions { | 52 | interface TypographyVariantsOptions { |
53 | fontWeightEditorNormal: number; | ||
54 | fontWeightEditorBold: number; | ||
51 | editor: TypographyStyle; | 55 | editor: TypographyStyle; |
52 | } | 56 | } |
53 | 57 | ||
@@ -63,11 +67,14 @@ declare module '@mui/material/styles' { | |||
63 | } | 67 | } |
64 | 68 | ||
65 | const typography: TypographyVariantsOptions = { | 69 | const typography: TypographyVariantsOptions = { |
70 | fontWeightEditorNormal: 400, | ||
71 | fontWeightEditorBold: 600, | ||
66 | editor: { | 72 | editor: { |
67 | fontFamily: | 73 | fontFamily: |
68 | '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', | 74 | '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', |
69 | fontFeatureSettings: '"liga", "calt"', | 75 | fontFeatureSettings: '"liga", "calt"', |
70 | fontSize: '1rem', | 76 | // `rem` for JetBrains MonoVariable make the text too large in Safari. |
77 | fontSize: '16px', | ||
71 | fontWeight: 400, | 78 | fontWeight: 400, |
72 | lineHeight: 1.5, | 79 | lineHeight: 1.5, |
73 | letterSpacing: 0, | 80 | letterSpacing: 0, |
@@ -78,10 +85,20 @@ const typography: TypographyVariantsOptions = { | |||
78 | const components: Components = { | 85 | const components: Components = { |
79 | MuiButton: { | 86 | MuiButton: { |
80 | styleOverrides: { | 87 | styleOverrides: { |
81 | root: { '&.rounded': { borderRadius: '50em' } }, | 88 | root: { |
82 | text: { '&.rounded': { padding: '6px 16px' } }, | 89 | '&.rounded': { borderRadius: '50em' }, |
83 | textSizeSmall: { '&.rounded': { padding: '4px 10px' } }, | 90 | '.MuiButton-startIcon': { marginRight: 6 }, |
84 | textSizeLarge: { '&.rounded': { padding: '8px 22px' } }, | 91 | '.MuiButton-endIcon': { marginLeft: 6 }, |
92 | }, | ||
93 | text: { '&.rounded': { padding: '6px 14px' } }, | ||
94 | textSizeSmall: { | ||
95 | fontSize: '0.875rem', | ||
96 | '&.rounded': { padding: '4px 8px' }, | ||
97 | }, | ||
98 | textSizeLarge: { | ||
99 | fontSize: '1rem', | ||
100 | '&.rounded': { padding: '8px 20px' }, | ||
101 | }, | ||
85 | }, | 102 | }, |
86 | }, | 103 | }, |
87 | MuiToggleButton: { | 104 | MuiToggleButton: { |
@@ -142,31 +159,31 @@ const lightTheme = createResponsiveTheme({ | |||
142 | success: { main: '#50a14f' }, | 159 | success: { main: '#50a14f' }, |
143 | info: { main: '#4078f2' }, | 160 | info: { main: '#4078f2' }, |
144 | background: { | 161 | background: { |
145 | default: '#ffffff', | 162 | default: '#fff', |
146 | paper: '#ffffff', | 163 | paper: '#fff', |
147 | }, | 164 | }, |
148 | text: { | 165 | text: { |
149 | primary: '#35373e', | 166 | primary: '#19202b', |
150 | secondary: '#696c77', | 167 | secondary: '#696c77', |
151 | disabled: '#8e8f97', | 168 | disabled: '#a0a1a7', |
152 | }, | 169 | }, |
153 | divider: alpha('#35373e', 0.21), | 170 | divider: alpha('#19202b', 0.16), |
154 | outer: { | 171 | outer: { |
155 | background: '#fafafa', | 172 | background: '#f5f5f5', |
156 | border: '#d4d4d6', | 173 | border: '#d6d7d9', |
157 | }, | 174 | }, |
158 | highlight: { | 175 | highlight: { |
159 | number: '#0084bc', | 176 | number: '#0084bc', |
160 | parameter: '#6a3e3e', | 177 | parameter: '#6a3e3e', |
161 | comment: '#8e8f97', | 178 | comment: '#a0a1a7', |
162 | activeLine: '#fafafa', | 179 | activeLine: '#f5f5f5', |
163 | selection: '#c8e4fb', | 180 | selection: '#c8e4fb', |
164 | lineNumber: '#8e8f97', | 181 | lineNumber: '#a0a1a7', |
165 | foldPlaceholder: alpha('#35373e', 0.08), | 182 | foldPlaceholder: alpha('#19202b', 0.08), |
166 | activeLintRange: alpha('#f2a60d', 0.28), | 183 | activeLintRange: alpha('#f2a60d', 0.28), |
167 | occurences: { | 184 | occurences: { |
168 | read: alpha('#35373e', 0.16), | 185 | read: alpha('#19202b', 0.16), |
169 | write: alpha('#35373e', 0.16), | 186 | write: alpha('#19202b', 0.16), |
170 | }, | 187 | }, |
171 | search: { | 188 | search: { |
172 | match: '#00bcd4', | 189 | match: '#00bcd4', |
@@ -178,7 +195,11 @@ const lightTheme = createResponsiveTheme({ | |||
178 | }); | 195 | }); |
179 | 196 | ||
180 | const darkTheme = createResponsiveTheme({ | 197 | const darkTheme = createResponsiveTheme({ |
181 | typography, | 198 | typography: { |
199 | ...typography, | ||
200 | fontWeightEditorNormal: 350, | ||
201 | fontWeightEditorBold: 550, | ||
202 | }, | ||
182 | components: { | 203 | components: { |
183 | ...components, | 204 | ...components, |
184 | MuiSnackbarContent: { | 205 | MuiSnackbarContent: { |
@@ -218,7 +239,7 @@ const darkTheme = createResponsiveTheme({ | |||
218 | text: { | 239 | text: { |
219 | primary: '#ebebff', | 240 | primary: '#ebebff', |
220 | secondary: '#abb2bf', | 241 | secondary: '#abb2bf', |
221 | disabled: '#828997', | 242 | disabled: '#5c6370', |
222 | }, | 243 | }, |
223 | divider: alpha('#abb2bf', 0.24), | 244 | divider: alpha('#abb2bf', 0.24), |
224 | outer: { | 245 | outer: { |
@@ -228,10 +249,10 @@ const darkTheme = createResponsiveTheme({ | |||
228 | highlight: { | 249 | highlight: { |
229 | number: '#6188a6', | 250 | number: '#6188a6', |
230 | parameter: '#c8ae9d', | 251 | parameter: '#c8ae9d', |
231 | comment: '#828997', | 252 | comment: '#5c6370', |
232 | activeLine: '#21252b', | 253 | activeLine: '#21252b', |
233 | selection: '#3e4453', | 254 | selection: '#3e4453', |
234 | lineNumber: '#828997', | 255 | lineNumber: '#5c6370', |
235 | foldPlaceholder: alpha('#ebebff', 0.12), | 256 | foldPlaceholder: alpha('#ebebff', 0.12), |
236 | activeLintRange: alpha('#fbc346', 0.28), | 257 | activeLintRange: alpha('#fbc346', 0.28), |
237 | occurences: { | 258 | occurences: { |
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index f2e0f15c..0c13f133 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -110,7 +110,7 @@ export default defineConfig({ | |||
110 | short_name: 'Refinery', | 110 | short_name: 'Refinery', |
111 | description: | 111 | description: |
112 | 'An efficient graph sovler for generating well-formed models', | 112 | 'An efficient graph sovler for generating well-formed models', |
113 | theme_color: '#21252b', | 113 | theme_color: '#f5f5f5', |
114 | display_override: ['window-controls-overlay'], | 114 | display_override: ['window-controls-overlay'], |
115 | display: 'standalone', | 115 | display: 'standalone', |
116 | background_color: '#21252b', | 116 | background_color: '#21252b', |