theme-vir 28.13.0 → 28.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,79 +1,99 @@
1
+ import { check } from '@augment-vir/assert';
2
+ import { groupArrayBy } from '@augment-vir/common';
1
3
  import { BookPageControlType, defineBookPage, definePageControl, } from 'element-book';
2
4
  import { css, html, listen, nothing } from 'element-vir';
5
+ import { generateThemeCode } from './color-theme.js';
3
6
  import { ThemeVirColorExample } from './elements/theme-vir-color-example.element.js';
4
7
  /**
5
8
  * Create multiple element-book pages to showcase a theme its overrides (if any).
6
9
  *
7
10
  * @category Color Theme
8
11
  */
9
- export function createColorThemeBookPages({ parent, title, theme, hideInverseColors, overrides, }) {
12
+ export function createColorThemeBookPages({ parent, title, theme, hideInverseColors, overrides, useVerticalLayout, prefixGroupByCount = 0, }) {
13
+ const themeControls = {
14
+ 'Show Var Names': definePageControl({
15
+ controlType: BookPageControlType.Checkbox,
16
+ initValue: false,
17
+ }),
18
+ 'Show Contrast Tips': definePageControl({
19
+ controlType: BookPageControlType.Checkbox,
20
+ initValue: true,
21
+ }),
22
+ };
10
23
  const themeParentPage = defineBookPage({
11
24
  parent,
12
25
  title,
13
- controls: {
14
- 'Show Var Names': definePageControl({
15
- controlType: BookPageControlType.Checkbox,
16
- initValue: false,
17
- }),
18
- 'Show Contrast Tips': definePageControl({
19
- controlType: BookPageControlType.Checkbox,
20
- initValue: true,
21
- }),
22
- },
26
+ controls: themeControls,
23
27
  });
24
- function createThemePage(defineExample, theme) {
25
- Object.values(theme.colors).forEach((themeColor) => {
28
+ function buildThemeColorTemplate({ controls, theme, themeColorName, }) {
29
+ const themeColor = check.isKeyOf(themeColorName, theme.colors)
30
+ ? theme.colors[themeColorName]
31
+ : undefined;
32
+ const inverseThemeColor = check.isKeyOf(themeColorName, theme.inverse)
33
+ ? theme.inverse[themeColorName]
34
+ : undefined;
35
+ if (!themeColor || !inverseThemeColor) {
36
+ throw new Error(`No theme color found by name '${themeColorName}'`);
37
+ }
38
+ const normalTemplate = html `
39
+ <${ThemeVirColorExample.assign({
40
+ color: themeColor,
41
+ showVarValues: true,
42
+ showVarNames: controls['Show Var Names'],
43
+ showContrast: controls['Show Contrast Tips'],
44
+ fontWeight: 400,
45
+ })}></${ThemeVirColorExample}>
46
+ `;
47
+ const inverseColor = hideInverseColors ? undefined : inverseThemeColor;
48
+ const inverseTemplate = inverseColor
49
+ ? html `
50
+ <${ThemeVirColorExample.assign({
51
+ color: inverseColor,
52
+ showVarValues: false,
53
+ showVarNames: controls['Show Var Names'],
54
+ showContrast: controls['Show Contrast Tips'],
55
+ fontWeight: 400,
56
+ })}></${ThemeVirColorExample}>
57
+ `
58
+ : nothing;
59
+ return html `
60
+ <div class="with-inverse">${normalTemplate}${inverseTemplate}</div>
61
+ `;
62
+ }
63
+ function createThemePageExamples(defineExample, theme) {
64
+ const groups = groupArrayBy(Object.keys(theme.colors), (value) => {
65
+ if (prefixGroupByCount) {
66
+ return value.split('-').slice(0, prefixGroupByCount).join('-');
67
+ }
68
+ else {
69
+ return value;
70
+ }
71
+ });
72
+ Object.entries(groups).forEach(([groupName, group,]) => {
73
+ if (!group) {
74
+ return;
75
+ }
26
76
  defineExample({
27
- title: themeColor.name,
77
+ title: groupName,
28
78
  styles: css `
29
- :host {
30
- display: flex;
31
- flex-direction: column;
32
- gap: 4px;
33
- }
34
- `,
35
- state() {
36
- return {
37
- forceShowEverything: false,
38
- };
39
- },
40
- render({ controls, state, updateState }) {
41
- const normalTemplate = html `
42
- <${ThemeVirColorExample.assign({
43
- color: themeColor,
44
- showVarValues: true,
45
- showVarNames: controls['Show Var Names'] || state.forceShowEverything,
46
- showContrast: controls['Show Contrast Tips'] || state.forceShowEverything,
47
- })}
48
- ${listen(ThemeVirColorExample.events.toggleShowVars, () => {
49
- updateState({
50
- forceShowEverything: !state.forceShowEverything,
51
- });
52
- })}
53
- ></${ThemeVirColorExample}>
54
- `;
55
- const inverseColor = hideInverseColors
56
- ? undefined
57
- : theme.inverse[themeColor.name];
58
- const inverseTemplate = inverseColor
59
- ? html `
60
- <${ThemeVirColorExample.assign({
61
- color: inverseColor,
62
- showVarValues: false,
63
- showVarNames: controls['Show Var Names'] || state.forceShowEverything,
64
- showContrast: controls['Show Contrast Tips'] || state.forceShowEverything,
65
- })}
66
- ${listen(ThemeVirColorExample.events.toggleShowVars, () => {
67
- updateState({
68
- forceShowEverything: !state.forceShowEverything,
69
- });
70
- })}
71
- ></${ThemeVirColorExample}>
72
- `
73
- : nothing;
74
- return html `
75
- ${normalTemplate}${inverseTemplate}
76
- `;
79
+ :host {
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 4px;
83
+ }
84
+
85
+ .with-inverse {
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: 4px;
89
+ }
90
+ `,
91
+ render({ controls }) {
92
+ return group.map((entry) => buildThemeColorTemplate({
93
+ controls,
94
+ theme,
95
+ themeColorName: entry,
96
+ }));
77
97
  },
78
98
  });
79
99
  });
@@ -85,17 +105,34 @@ export function createColorThemeBookPages({ parent, title, theme, hideInverseCol
85
105
  parent: themeParentPage,
86
106
  title: 'Default Theme',
87
107
  descriptionParagraphs,
108
+ useVerticalExamples: useVerticalLayout,
109
+ controls: {
110
+ copy: definePageControl({
111
+ controlType: BookPageControlType.Custom,
112
+ content: html `
113
+ <button
114
+ ${listen('click', async () => {
115
+ const code = generateThemeCode(theme, 'viraColorPalette');
116
+ await navigator.clipboard.writeText(code);
117
+ })}
118
+ >
119
+ Copy Code
120
+ </button>
121
+ `,
122
+ }),
123
+ },
88
124
  defineExamples({ defineExample }) {
89
- createThemePage(defineExample, theme);
125
+ createThemePageExamples(defineExample, theme);
90
126
  },
91
127
  });
92
128
  const overridePages = (overrides || []).map((override) => {
93
129
  return defineBookPage({
94
130
  parent: themeParentPage,
95
131
  title: override.name,
132
+ useVerticalExamples: useVerticalLayout,
96
133
  descriptionParagraphs,
97
134
  defineExamples({ defineExample }) {
98
- createThemePage(defineExample, override.asTheme);
135
+ createThemePageExamples(defineExample, override.asTheme);
99
136
  },
100
137
  });
101
138
  });
@@ -1,4 +1,4 @@
1
- import { type RequiredAndNotNull } from '@augment-vir/common';
1
+ import { type RequiredAndNotNull, type Values } from '@augment-vir/common';
2
2
  import { type CSSResult } from 'element-vir';
3
3
  import { type CssVarName, type SingleCssVarDefinition } from 'lit-css-vars';
4
4
  import { type RequireAtLeastOne } from 'type-fest';
@@ -10,13 +10,15 @@ import { type RequireAtLeastOne } from 'type-fest';
10
10
  export type ColorInitReference = RequireAtLeastOne<{
11
11
  refForeground: CssVarName;
12
12
  refBackground: CssVarName;
13
+ refDefaultBackground: true;
14
+ refDefaultForeground: true;
13
15
  }>;
14
16
  /**
15
17
  * All possible types for {@link ColorInit}.
16
18
  *
17
19
  * @category Internal
18
20
  */
19
- export type ColorInitValue = string | number | CSSResult | ColorInitReference;
21
+ export type ColorInitValue = string | number | CSSResult | ColorInitReference | SingleCssVarDefinition;
20
22
  /**
21
23
  * An individual theme color init.
22
24
  *
@@ -80,12 +82,14 @@ export type AllColorThemeColors<Init extends ColorThemeInit = ColorThemeInit> =
80
82
  } & {
81
83
  [themeDefaultKey]: ColorThemeColor<RequiredAndNotNull<NoRefColorInit>, typeof themeDefaultKey>;
82
84
  };
85
+ /** @category Internal */
86
+ export declare function noRefColorInitToString(init: Values<NoRefColorInit>): string;
83
87
  /**
84
88
  * Handles a color init value.
85
89
  *
86
90
  * @category Internal
87
91
  */
88
- export declare function createColorCssVarDefault(fromName: string, init: ColorInitValue, defaultInit: RequiredAndNotNull<NoRefColorInit>, colorsInit: ColorThemeInit): Exclude<ColorInitValue, ColorInitReference>;
92
+ export declare function createColorCssVarDefault(fromName: string, init: ColorInitValue, defaultInit: RequiredAndNotNull<NoRefColorInit>, colorsInit: ColorThemeInit): string | number | CSSResult;
89
93
  /**
90
94
  * Default foreground/background color theme used in {@link ColorTheme}. Do not define a theme color
91
95
  * with this name!
@@ -99,3 +103,9 @@ export declare const themeDefaultKey = "theme-default";
99
103
  * @category Color Theme
100
104
  */
101
105
  export declare function defineColorTheme<const Init extends ColorThemeInit>(defaultInit: RequiredAndNotNull<NoRefColorInit>, allColorsInit: Init): ColorTheme<Init>;
106
+ /**
107
+ * Convert a color theme into code to define that color theme.
108
+ *
109
+ * @category Color Theme
110
+ */
111
+ export declare function generateThemeCode(theme: ColorTheme, paletteVarName?: string | undefined): string;
@@ -1,21 +1,39 @@
1
1
  import { assert, check } from '@augment-vir/assert';
2
- import { getObjectTypedEntries, log } from '@augment-vir/common';
2
+ import { getObjectTypedEntries, log, } from '@augment-vir/common';
3
3
  import { defineCssVars, } from 'lit-css-vars';
4
+ /** @category Internal */
5
+ export function noRefColorInitToString(init) {
6
+ if (check.isPrimitive(init) || '_$cssResult$' in init) {
7
+ return String(init);
8
+ }
9
+ else {
10
+ return init.default;
11
+ }
12
+ }
4
13
  /**
5
14
  * Handles a color init value.
6
15
  *
7
16
  * @category Internal
8
17
  */
9
18
  export function createColorCssVarDefault(fromName, init, defaultInit, colorsInit) {
10
- const referenceKey = check.hasKey(init, 'refBackground')
11
- ? 'refBackground'
12
- : check.hasKey(init, 'refForeground')
13
- ? 'refForeground'
14
- : undefined;
15
- const reference = referenceKey && check.hasKey(init, referenceKey) ? init[referenceKey] : undefined;
16
- if (reference) {
19
+ if (check.isPrimitive(init) || '_$cssResult$' in init) {
20
+ return init;
21
+ }
22
+ else if ('refDefaultBackground' in init) {
23
+ return '--var(default-bg)';
24
+ }
25
+ else if ('refDefaultForeground' in init) {
26
+ return '--var(default-fg)';
27
+ }
28
+ else if ('refBackground' in init || 'refForeground' in init) {
29
+ const referenceKey = check.hasKey(init, 'refBackground')
30
+ ? 'refBackground'
31
+ : check.hasKey(init, 'refForeground')
32
+ ? 'refForeground'
33
+ : undefined;
34
+ const reference = referenceKey && check.hasKey(init, referenceKey) ? init[referenceKey] : undefined;
17
35
  const layerKey = referenceKey === 'refBackground' ? 'background' : 'foreground';
18
- const referenced = colorsInit[reference];
36
+ const referenced = reference && colorsInit[reference];
19
37
  if (!referenced) {
20
38
  throw new Error(`Color theme ${referenceKey} reference '${reference}' does not exist. (Referenced from '${fromName}'.)`);
21
39
  }
@@ -26,7 +44,7 @@ export function createColorCssVarDefault(fromName, init, defaultInit, colorsInit
26
44
  return `var(--${reference}-${layerKey === 'foreground' ? 'fg' : 'bg'}, ${createColorCssVarDefault(reference, colorValue, defaultInit, colorsInit)})`;
27
45
  }
28
46
  else {
29
- return init;
47
+ return init.value;
30
48
  }
31
49
  }
32
50
  /**
@@ -157,3 +175,104 @@ function createCssVarNames(colorName) {
157
175
  ].join('-'),
158
176
  };
159
177
  }
178
+ /**
179
+ * Convert a color theme into code to define that color theme.
180
+ *
181
+ * @category Color Theme
182
+ */
183
+ export function generateThemeCode(theme, paletteVarName) {
184
+ const defaultInitCode = colorInitToCode(theme.init.default, 1, undefined, paletteVarName);
185
+ const colorsInitCode = colorThemeInitToCode(theme.init.colors, 1, theme.init.default, paletteVarName);
186
+ return `defineColorTheme(\n${defaultInitCode},\n${colorsInitCode},\n)`;
187
+ }
188
+ function tab(level) {
189
+ return ' '.repeat(level);
190
+ }
191
+ function colorInitValuesEqual(a, b) {
192
+ if (typeof a !== typeof b) {
193
+ return false;
194
+ }
195
+ if (typeof a === 'string' || typeof a === 'number') {
196
+ return a === b;
197
+ }
198
+ if ('_$cssResult$' in a && '_$cssResult$' in b) {
199
+ return a.cssText === b.cssText;
200
+ }
201
+ // For references and SingleCssVarDefinition, compare as JSON
202
+ return JSON.stringify(a) === JSON.stringify(b);
203
+ }
204
+ function extractCssVarName(cssValue) {
205
+ const match = cssValue.match(/^var\(--([^,)]+)/);
206
+ return match ? match[1] : undefined;
207
+ }
208
+ function colorInitValueToCode(value, indentLevel, paletteVarName) {
209
+ if (typeof value === 'string') {
210
+ return `'${value}'`;
211
+ }
212
+ else if (typeof value === 'number') {
213
+ return String(value);
214
+ }
215
+ else if ('_$cssResult$' in value) {
216
+ const cssText = String(value);
217
+ if (paletteVarName) {
218
+ const varName = extractCssVarName(cssText);
219
+ if (varName) {
220
+ return `${paletteVarName}['${varName}']`;
221
+ }
222
+ }
223
+ return `css\`${cssText}\``;
224
+ }
225
+ else if ('refBackground' in value ||
226
+ 'refForeground' in value ||
227
+ 'refDefaultBackground' in value ||
228
+ 'refDefaultForeground' in value) {
229
+ const entries = [];
230
+ if ('refForeground' in value) {
231
+ entries.push(`${tab(indentLevel + 1)}refForeground: '${value.refForeground}',`);
232
+ }
233
+ if ('refBackground' in value) {
234
+ entries.push(`${tab(indentLevel + 1)}refBackground: '${value.refBackground}',`);
235
+ }
236
+ if ('refDefaultForeground' in value) {
237
+ entries.push(`${tab(indentLevel + 1)}refDefaultForeground: true,`);
238
+ }
239
+ if ('refDefaultBackground' in value) {
240
+ entries.push(`${tab(indentLevel + 1)}refDefaultBackground: true,`);
241
+ }
242
+ return `{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
243
+ }
244
+ else {
245
+ // SingleCssVarDefinition
246
+ return `'${value.default}'`;
247
+ }
248
+ }
249
+ function colorInitToCode(colorInit, indentLevel, defaultInit, paletteVarName) {
250
+ const entries = [];
251
+ if ('foreground' in colorInit &&
252
+ (!defaultInit || !colorInitValuesEqual(colorInit.foreground, defaultInit.foreground))) {
253
+ // Check if foreground matches default background (use refDefaultBackground)
254
+ if (defaultInit && colorInitValuesEqual(colorInit.foreground, defaultInit.background)) {
255
+ entries.push(`${tab(indentLevel + 1)}foreground: {\n${tab(indentLevel + 2)}refDefaultBackground: true,\n${tab(indentLevel + 1)}},`);
256
+ }
257
+ else {
258
+ entries.push(`${tab(indentLevel + 1)}foreground: ${colorInitValueToCode(colorInit.foreground, indentLevel + 1, paletteVarName)},`);
259
+ }
260
+ }
261
+ if ('background' in colorInit &&
262
+ (!defaultInit || !colorInitValuesEqual(colorInit.background, defaultInit.background))) {
263
+ // Check if background matches default foreground (use refDefaultForeground)
264
+ if (defaultInit && colorInitValuesEqual(colorInit.background, defaultInit.foreground)) {
265
+ entries.push(`${tab(indentLevel + 1)}background: {\n${tab(indentLevel + 2)}refDefaultForeground: true,\n${tab(indentLevel + 1)}},`);
266
+ }
267
+ else {
268
+ entries.push(`${tab(indentLevel + 1)}background: ${colorInitValueToCode(colorInit.background, indentLevel + 1, paletteVarName)},`);
269
+ }
270
+ }
271
+ return `${tab(indentLevel)}{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
272
+ }
273
+ function colorThemeInitToCode(colorsInit, indentLevel, defaultInit, paletteVarName) {
274
+ const entries = getObjectTypedEntries(colorsInit).map(([colorName, colorInit,]) => {
275
+ return `${tab(indentLevel + 1)}'${colorName}': ${colorInitToCode(colorInit, indentLevel + 1, defaultInit, paletteVarName).trimStart()},`;
276
+ });
277
+ return `${tab(indentLevel)}{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
278
+ }
@@ -1,17 +1,52 @@
1
- import { type ArrayElement } from '@augment-vir/common';
1
+ import { type ArrayElement, type ExtractKeysWithMatchingValues, type Values } from '@augment-vir/common';
2
2
  /**
3
3
  * All font weights that font sizes are calculated for. Used in {@link FontSizes} and
4
4
  * {@link calculateFontSizes}.
5
5
  *
6
6
  * @category Internal
7
7
  */
8
- export type FontSizeWeights = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
8
+ export type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
9
+ /**
10
+ * All considered font weights in {@link FontWeight} mapped by their weight name.
11
+ *
12
+ * @category Internal
13
+ */
14
+ export declare const fontWeightByName: {
15
+ readonly Thin: 100;
16
+ readonly ExtraLight: 200;
17
+ readonly Light: 300;
18
+ readonly Normal: 400;
19
+ readonly Medium: 500;
20
+ readonly SemiBold: 600;
21
+ readonly Bold: 700;
22
+ readonly ExtraBold: 800;
23
+ readonly Heavy: 900;
24
+ };
25
+ /**
26
+ * All font weight names from {@link fontWeightByName}.
27
+ *
28
+ * @category Internal
29
+ */
30
+ export type FontWeightName = keyof typeof fontWeightByName;
31
+ /**
32
+ * All font weight names from {@link fontWeightByName}.
33
+ *
34
+ * @category Internal
35
+ */
36
+ export declare const FontWeightName: { [Key in FontWeightName]: Key; };
37
+ /**
38
+ * All considered font weights in {@link FontWeight} mapped to their weight name from
39
+ * {@link fontWeightByName}.
40
+ *
41
+ * @category Internal
42
+ */
43
+ export declare const fontWeightToName: { [Weight in Values<typeof fontWeightByName>]: ExtractKeysWithMatchingValues<typeof fontWeightByName, Weight>; };
9
44
  /**
10
45
  * A mapping of font weights to font sizes. Used in {@link calculateFontSizes}.
11
46
  *
12
47
  * @category Internal
13
48
  */
14
- export type FontSizes = Record<FontSizeWeights, number>;
49
+ export type FontSizes = Record<FontWeight, number>;
15
50
  /**
16
51
  * Contrast calculations produced by {@link calculateContrast}.
17
52
  *
@@ -41,6 +76,21 @@ export type ColorPair = {
41
76
  * @category Internal
42
77
  */
43
78
  export declare function calculateContrast({ background, foreground, }: Readonly<ColorPair>): CalculatedContrast;
79
+ /** @category Internal */
80
+ export declare function findClosestColor(baseColor: string, possibleColors: ReadonlyArray<string>): string;
81
+ /**
82
+ * Find a color from an array that matches the desired contrast level.
83
+ *
84
+ * @category Internal
85
+ * @returns `undefined` if no color match is found.
86
+ */
87
+ export declare function findColorAtContrastLevel(colors: Readonly<{
88
+ foreground: string;
89
+ background: string[];
90
+ } | {
91
+ foreground: string[];
92
+ background: string;
93
+ }>, desiredContrastLevel: ContrastLevelName): string | undefined;
44
94
  /**
45
95
  * Calculated needed font sizes for each font weight for the given color contrast.
46
96
  *
@@ -59,11 +109,11 @@ export declare function determineContrastLevel(contrast: number): ContrastLevel;
59
109
  * @category Internal
60
110
  */
61
111
  export declare enum ContrastLevelName {
62
- SmallBodyText = "small-body-text",
63
- BodyText = "body-text",
64
- NonBodyText = "non-body-text",
65
- LargeText = "large-text",
66
- SpotText = "spot-text",
112
+ SmallBodyText = "small-body",
113
+ BodyText = "body",
114
+ NonBodyText = "non-body",
115
+ Header = "header",
116
+ Placeholder = "placeholder",
67
117
  Decoration = "decoration",
68
118
  Invisible = "invisible"
69
119
  }
@@ -78,7 +128,7 @@ export declare const contrastLevelLabel: Record<ContrastLevelName, string>;
78
128
  *
79
129
  * @category Internal
80
130
  */
81
- export declare const orderedContrastLevelNames: readonly [ContrastLevelName.SmallBodyText, ContrastLevelName.BodyText, ContrastLevelName.NonBodyText, ContrastLevelName.LargeText, ContrastLevelName.SpotText, ContrastLevelName.Decoration, ContrastLevelName.Invisible];
131
+ export declare const orderedContrastLevelNames: readonly [ContrastLevelName.SmallBodyText, ContrastLevelName.BodyText, ContrastLevelName.NonBodyText, ContrastLevelName.Header, ContrastLevelName.Placeholder, ContrastLevelName.Decoration, ContrastLevelName.Invisible];
82
132
  /**
83
133
  * Color contrast level details.
84
134
  *
@@ -121,13 +171,13 @@ export declare const contrastLevels: readonly [{
121
171
  readonly apcaDescription: "The minimum level recommended for content text that is not body, column, or block text. In other words, text you want people to read. The minimums: no smaller than 48px/200, 36px/300, 24px normal weight (400), 21px/500, 18px/600, 16px/700 (bold). These values based on the reference font Helvetica. To use these sizes as body text, add Lc 15 to the minimum contrast.";
122
172
  }, {
123
173
  readonly min: 45;
124
- readonly name: ContrastLevelName.LargeText;
174
+ readonly name: ContrastLevelName.Header;
125
175
  readonly description: "Okay for large or headline text.";
126
176
  readonly apcaName: "large & sub-fluent text";
127
177
  readonly apcaDescription: "The minimum for larger, heavier text (36px normal weight or 24px bold) such as headlines, and large text that should be fluently readable but is not body text. This is also the minimum for pictograms with fine details, or smaller outline icons, , no less than 4px in its smallest dimension.";
128
178
  }, {
129
179
  readonly min: 30;
130
- readonly name: ContrastLevelName.SpotText;
180
+ readonly name: ContrastLevelName.Placeholder;
131
181
  readonly description: "Okay for disabled or placeholder text, copyright lines, icons, or non-text elements.";
132
182
  readonly apcaName: "spot & non text only";
133
183
  readonly apcaDescription: "The absolute minimum for any text not listed above, which means non-content text considered as \"spot readable\". This includes placeholder text and disabled element text, and some non-content like a copyright bug. This is also the minimum for large/solid semantic & understandable non-text elements such as \"mostly solid\" icons or pictograms, no less than 10px in its smallest dimension.";