@timmo001/groundwork-tokens 0.9.36 → 0.9.38

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-tokens",
3
- "version": "0.9.36",
3
+ "version": "0.9.38",
4
4
  "description": "Groundwork's design tokens: the theme schema, the OKLCH solver and every theme as CSS and JSON",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -10,6 +10,8 @@
10
10
  "./page": "./src/page.ts",
11
11
  "./webmcp": "./src/webmcp.ts",
12
12
  "./omarchy-current": "./src/omarchy-current.ts",
13
+ "./desktop-themes": "./src/desktop-themes.ts",
14
+ "./translate": "./src/translate.ts",
13
15
  "./opentui": "./generated/opentui.ts",
14
16
  "./themes.css": "./generated/themes.css",
15
17
  "./themes.json": "./generated/themes.json",
@@ -53,7 +55,7 @@
53
55
  "devDependencies": {
54
56
  "@effect/platform-node": "4.1.2",
55
57
  "@oxlint/plugins": "1.91.0",
56
- "@timmo001/oxlint-rules": "0.6.3",
58
+ "@timmo001/oxlint-rules": "0.6.4",
57
59
  "@types/bun": "1.4.4",
58
60
  "oxlint": "1.91.0",
59
61
  "oxlint-tsgolint": "7.0.2005",
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The color themes a machine has, translated into mapped themes so an app can
3
+ * offer them beside its own: every installed Omarchy theme and the theme
4
+ * each terminal (Ghostty, kitty, Alacritty and foot) uses. It needs a file system, so it's an entry
5
+ * of its own; drop the ones an app already offers with `distinctThemes`.
6
+ *
7
+ * @module
8
+ */
9
+ import type { Dirent } from "node:fs";
10
+ import { readdir, readFile } from "node:fs/promises";
11
+ import { homedir } from "node:os";
12
+ import { dirname, isAbsolute, join, resolve } from "node:path";
13
+
14
+ import { omarchyTheme } from "./omarchy.ts";
15
+ import type { MappedTheme } from "./schema.ts";
16
+ import {
17
+ alacrittyPalette,
18
+ colorsTomlPalette,
19
+ distinctThemes,
20
+ footPalette,
21
+ ghosttyPalette,
22
+ kittyPalette,
23
+ translatePalette,
24
+ type TerminalPalette,
25
+ } from "./translate.ts";
26
+
27
+ export { distinctThemes, sameTheme, translatePalette } from "./translate.ts";
28
+
29
+ /** Where a desktop theme came from, such as `Omarchy` or `Ghostty`. */
30
+ export type DesktopThemeSource =
31
+ "Omarchy" | "Ghostty" | "kitty" | "Alacritty" | "foot";
32
+
33
+ const home = homedir();
34
+
35
+ const config = process.env.XDG_CONFIG_HOME || join(home, ".config");
36
+ const data = process.env.XDG_DATA_HOME || join(home, ".local", "share");
37
+
38
+ const expand = (path: string, from: string) => {
39
+ const unquoted = path
40
+ .trim()
41
+ .replace(/^\?/, "")
42
+ .replace(/^["']|["']$/g, "");
43
+
44
+ const tilde = unquoted.replace(/^~(?=\/|$)/, home);
45
+
46
+ return isAbsolute(tilde) ? tilde : resolve(from, tilde);
47
+ };
48
+
49
+ const text = (path: string) => readFile(path, "utf8").catch(() => undefined);
50
+
51
+ const folders = (path: string) =>
52
+ readdir(path, { withFileTypes: true }).catch((): Dirent[] => []);
53
+
54
+ const slug = (value: string) =>
55
+ value
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9]+/g, "-")
58
+ .replace(/^-|-$/g, "");
59
+
60
+ // `tokyo-night` reads as `Tokyo Night`
61
+ const titled = (value: string) =>
62
+ value
63
+ .split(/[-_\s]+/)
64
+ .filter((word) => word !== "")
65
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
66
+ .join(" ");
67
+
68
+ const named = (source: DesktopThemeSource, name: string) => ({
69
+ id: `desktop-${slug(source)}-${slug(name)}`,
70
+ name: `${name} (${source})`,
71
+ family: source,
72
+ });
73
+
74
+ /** Every Omarchy theme installed, the shipped ones and the user's own. */
75
+ const omarchyThemes = async () => {
76
+ const found: MappedTheme[] = [];
77
+
78
+ for (const root of [
79
+ join(data, "omarchy", "themes"),
80
+ join(config, "omarchy", "themes"),
81
+ ]) {
82
+ for (const entry of await folders(root)) {
83
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
84
+
85
+ const folder = join(root, entry.name);
86
+ const theme = named("Omarchy", titled(entry.name));
87
+ const colors = await text(join(folder, "colors.toml"));
88
+
89
+ const parsed =
90
+ colors === undefined
91
+ ? undefined
92
+ : omarchyTheme(colors, theme.name, theme.id);
93
+
94
+ if (parsed?._tag === "Success") {
95
+ found.push(parsed.success);
96
+ continue;
97
+ }
98
+
99
+ // Older themes number their colors, or only have each app's file
100
+ for (const [file, palette] of [
101
+ ["colors.toml", colorsTomlPalette],
102
+ ["alacritty.toml", alacrittyPalette],
103
+ ["ghostty.conf", ghosttyPalette],
104
+ ["kitty.conf", kittyPalette],
105
+ ] as const) {
106
+ const read = await text(join(folder, file));
107
+
108
+ const translated =
109
+ read === undefined
110
+ ? undefined
111
+ : translatePalette(palette(read), theme);
112
+
113
+ if (translated !== undefined) {
114
+ found.push(translated);
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ return found;
122
+ };
123
+
124
+ /**
125
+ * A terminal config with the files it includes, one level deep, joined in
126
+ * order so later colors win, as the terminal reads them.
127
+ */
128
+ const withIncludes = async (
129
+ path: string,
130
+ include: RegExp,
131
+ extra: (text: string, from: string) => readonly string[] = () => [],
132
+ ) => {
133
+ const main = await text(path);
134
+
135
+ if (main === undefined) return undefined;
136
+
137
+ const from = dirname(path);
138
+ const parts: string[] = [];
139
+
140
+ for (const line of main.split(/\r?\n/)) {
141
+ const match = include.exec(line);
142
+
143
+ if (match?.[1] !== undefined) {
144
+ for (const file of match[1].split(/,\s*/)) {
145
+ const included = await text(expand(file.replace(/[[\]]/g, ""), from));
146
+
147
+ if (included !== undefined) parts.push(included);
148
+ }
149
+ }
150
+ }
151
+
152
+ for (const file of extra(main, from)) {
153
+ const included = await text(file);
154
+
155
+ if (included !== undefined) parts.push(included);
156
+ }
157
+
158
+ return [...parts, main].join("\n");
159
+ };
160
+
161
+ /** Ghostty's theme: its config, the files it pulls in, and a named `theme`. */
162
+ const ghosttyConfig = () =>
163
+ withIncludes(
164
+ join(config, "ghostty", "config"),
165
+ /^\s*config-file\s*=\s*(.+)$/,
166
+ (main) => {
167
+ const theme = /^\s*theme\s*=\s*(.+)$/m.exec(main)?.[1]?.trim();
168
+
169
+ if (theme === undefined) return [];
170
+
171
+ // `light:A,dark:B` takes the dark one
172
+ const name =
173
+ /dark:([^,]+)/.exec(theme)?.[1]?.trim() ??
174
+ theme
175
+ .replace(/^light:/, "")
176
+ .split(",")[0]
177
+ ?.trim() ??
178
+ theme;
179
+
180
+ return [
181
+ join(config, "ghostty", "themes", name),
182
+ join("/usr/share/ghostty/themes", name),
183
+ ];
184
+ },
185
+ );
186
+
187
+ const terminals: readonly {
188
+ readonly source: DesktopThemeSource;
189
+ readonly read: () => Promise<string | undefined>;
190
+ readonly palette: (text: string) => TerminalPalette;
191
+ }[] = [
192
+ { source: "Ghostty", read: ghosttyConfig, palette: ghosttyPalette },
193
+ {
194
+ source: "kitty",
195
+ read: () =>
196
+ withIncludes(
197
+ join(config, "kitty", "kitty.conf"),
198
+ /^\s*(?:include|globinclude)\s+(.+)$/,
199
+ ),
200
+ palette: kittyPalette,
201
+ },
202
+ {
203
+ source: "Alacritty",
204
+ read: () =>
205
+ withIncludes(
206
+ join(config, "alacritty", "alacritty.toml"),
207
+ /^\s*(?:general\.)?import\s*=\s*\[(.+)\]/,
208
+ ),
209
+ palette: alacrittyPalette,
210
+ },
211
+ {
212
+ source: "foot",
213
+ read: () =>
214
+ withIncludes(join(config, "foot", "foot.ini"), /^\s*include\s*=\s*(.+)$/),
215
+ palette: footPalette,
216
+ },
217
+ ];
218
+
219
+ /** The theme each terminal uses. */
220
+ const terminalThemes = async () => {
221
+ const found: MappedTheme[] = [];
222
+
223
+ for (const terminal of terminals) {
224
+ const read = await terminal.read();
225
+
226
+ if (read === undefined) continue;
227
+
228
+ const translated = translatePalette(
229
+ terminal.palette(read),
230
+ named(terminal.source, terminal.source),
231
+ );
232
+
233
+ if (translated !== undefined) found.push(translated);
234
+ }
235
+
236
+ return found;
237
+ };
238
+
239
+ /**
240
+ * Every theme on the machine, translated: the terminals' first, as the ones
241
+ * in use, then Omarchy's. Themes that look the same are kept once; pass the
242
+ * app's own as `existing` to drop those too.
243
+ */
244
+ export const readDesktopThemes = async (
245
+ existing: readonly Pick<MappedTheme, "name" | "palette">[] = [],
246
+ ): Promise<readonly MappedTheme[]> => {
247
+ const [terminal, omarchy] = await Promise.all([
248
+ terminalThemes(),
249
+ omarchyThemes(),
250
+ ]);
251
+
252
+ return distinctThemes([...terminal, ...omarchy], existing);
253
+ };
package/src/generate.ts CHANGED
@@ -756,12 +756,12 @@ export const optionalPage = (themes: readonly ResolvedTheme[]) => {
756
756
  return [
757
757
  "---",
758
758
  "title: Optional themes",
759
- "description: Catppuccin and Tokyo Night, mapped onto the roles so they stay faithful to their palettes. Apps opt in.",
759
+ "description: Catppuccin, Tokyo Night and GitHub, mapped onto the roles so they stay faithful to their palettes. Apps opt in.",
760
760
  "---",
761
761
  "",
762
762
  `{/* ${header} */}`,
763
763
  "",
764
- "Each flavor is one mode of its palette, mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
764
+ "Most flavors are one mode of their palette, and GitHub's cover both light and dark. Each is mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
765
765
  "",
766
766
  ...families.flatMap((family) => [
767
767
  `## ${family}`,
package/src/page.ts CHANGED
@@ -11,10 +11,21 @@
11
11
  */
12
12
  import base from "../themes/base.json" with { type: "json" };
13
13
  import bridge from "../themes/bridge.json" with { type: "json" };
14
+ import catppuccinFrappe from "../themes/catppuccin-frappe.json" with { type: "json" };
15
+ import catppuccinLatte from "../themes/catppuccin-latte.json" with { type: "json" };
16
+ import catppuccinMacchiato from "../themes/catppuccin-macchiato.json" with { type: "json" };
17
+ import catppuccinMocha from "../themes/catppuccin-mocha.json" with { type: "json" };
18
+ import githubDimmed from "../themes/github-dimmed.json" with { type: "json" };
19
+ import githubHighContrast from "../themes/github-high-contrast.json" with { type: "json" };
20
+ import github from "../themes/github.json" with { type: "json" };
14
21
  import glazing from "../themes/glazing.json" with { type: "json" };
15
22
  import slate from "../themes/slate.json" with { type: "json" };
16
23
  import snag from "../themes/snag.json" with { type: "json" };
17
24
  import survey from "../themes/survey.json" with { type: "json" };
25
+ import tokyoNightDay from "../themes/tokyo-night-day.json" with { type: "json" };
26
+ import tokyoNightMoon from "../themes/tokyo-night-moon.json" with { type: "json" };
27
+ import tokyoNightStorm from "../themes/tokyo-night-storm.json" with { type: "json" };
28
+ import tokyoNight from "../themes/tokyo-night.json" with { type: "json" };
18
29
  import type { OmarchyCurrentTheme } from "./omarchy.ts";
19
30
  import type { ThemeStore } from "./runtime.ts";
20
31
  import type { Theme } from "./schema.ts";
@@ -71,6 +82,8 @@ export interface ThemeChoice {
71
82
  readonly id: string;
72
83
  readonly name: string;
73
84
  readonly mode?: ThemeScheme | undefined;
85
+ /** The palette an optional theme comes from, such as `Catppuccin`. */
86
+ readonly family?: string | undefined;
74
87
  }
75
88
 
76
89
  /** Whether `choice` can show in `scheme`. */
@@ -87,6 +100,35 @@ export const coreThemeChoices: readonly ThemeChoice[] = [
87
100
  snag,
88
101
  ].map(({ id, name }) => ({ id, name }));
89
102
 
103
+ /**
104
+ * The optional themes, mapped from other palettes, by family and then from
105
+ * light to dark. One with a single mode is offered for that scheme alone;
106
+ * one with both, such as GitHub's, for either.
107
+ */
108
+ export const optionalThemeChoices: readonly ThemeChoice[] = [
109
+ catppuccinLatte,
110
+ catppuccinFrappe,
111
+ catppuccinMacchiato,
112
+ catppuccinMocha,
113
+ tokyoNightDay,
114
+ tokyoNight,
115
+ tokyoNightStorm,
116
+ tokyoNightMoon,
117
+ github,
118
+ githubDimmed,
119
+ githubHighContrast,
120
+ ].map((theme) => ({
121
+ id: theme.id,
122
+ name: theme.name,
123
+ family: theme.family,
124
+ mode:
125
+ "otherPalette" in theme
126
+ ? undefined
127
+ : theme.mode === "light"
128
+ ? "light"
129
+ : "dark",
130
+ }));
131
+
90
132
  export interface PageThemeOptions {
91
133
  /** The local storage key prefix, such as `my-site:theme`. */
92
134
  readonly key: string;
@@ -101,6 +143,12 @@ export interface PageThemeOptions {
101
143
  readonly defaultTheme: string;
102
144
  /** The mode to start with. Defaults to `auto`. */
103
145
  readonly defaultMode?: ThemeMode;
146
+ /**
147
+ * Whether the Omarchy theme takes over while the visitor hasn't picked
148
+ * one. With `false` it's only offered, and `defaultTheme` stays in use.
149
+ * Defaults to `true`.
150
+ */
151
+ readonly followOmarchy?: boolean;
104
152
  /** The element the attributes go on. Defaults to `<html>`. */
105
153
  readonly root?: HTMLElement;
106
154
  /**
@@ -178,21 +226,26 @@ const write = (key: string, value: string | null) => {
178
226
  * defaults as `pageTheme`.
179
227
  */
180
228
  export const themeBootScript = (
181
- options: Pick<PageThemeOptions, "key" | "defaultTheme" | "defaultMode">,
229
+ options: Pick<
230
+ PageThemeOptions,
231
+ "key" | "defaultTheme" | "defaultMode" | "followOmarchy"
232
+ >,
182
233
  ) => {
183
234
  const config = JSON.stringify({
184
235
  keys: keys(options.key),
185
236
  omarchy: omarchyKeys(options.key),
186
237
  theme: options.defaultTheme,
187
238
  mode: options.defaultMode ?? "auto",
239
+ follow: options.followOmarchy !== false,
188
240
  custom: customThemeId,
189
241
  style: customThemeStyleId,
190
242
  omarchyId: omarchyThemeId,
191
243
  omarchyStyle: omarchyThemeStyleId,
192
244
  });
193
245
 
194
- // With nothing picked, a cached Omarchy theme for the scheme is the default
195
- return `(()=>{const c=${config};let s;try{s=localStorage}catch{}const g=(k)=>{try{return s?.getItem(k)??null}catch{return null}};const r=document.documentElement;const a=(id,css)=>{const e=document.createElement("style");e.id=id;e.textContent=css;document.head.append(e)};const m=g(c.keys.mode)??c.mode;const d=m==="dark"||m==="darker"||(m==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches);const o=g(c.omarchy.css);let t=(d?g(c.keys.dark):null)??g(c.keys.theme)??(o&&g(c.omarchy.mode)===(d?"dark":"light")?c.omarchyId:c.theme);if(t===c.custom){const css=g(c.keys.css);if(css)a(c.style,css);else t=c.theme}else if(t===c.omarchyId){if(o)a(c.omarchyStyle,o);else t=c.theme}r.setAttribute("data-gw-theme",t);if(m==="auto")r.removeAttribute("data-gw-mode");else r.setAttribute("data-gw-mode",m)})();`;
246
+ // With nothing picked, a cached Omarchy theme for the scheme is the
247
+ // default, unless it's only offered
248
+ return `(()=>{const c=${config};let s;try{s=localStorage}catch{}const g=(k)=>{try{return s?.getItem(k)??null}catch{return null}};const r=document.documentElement;const a=(id,css)=>{const e=document.createElement("style");e.id=id;e.textContent=css;document.head.append(e)};const m=g(c.keys.mode)??c.mode;const d=m==="dark"||m==="darker"||(m==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches);const o=g(c.omarchy.css);let t=(d?g(c.keys.dark):null)??g(c.keys.theme)??(c.follow&&o&&g(c.omarchy.mode)===(d?"dark":"light")?c.omarchyId:c.theme);if(t===c.custom){const css=g(c.keys.css);if(css)a(c.style,css);else t=c.theme}else if(t===c.omarchyId){if(o)a(c.omarchyStyle,o);else t=c.theme}r.setAttribute("data-gw-theme",t);if(m==="auto")r.removeAttribute("data-gw-mode");else r.setAttribute("data-gw-mode",m)})();`;
196
249
  };
197
250
 
198
251
  /** Reads a theme from JSON text or an Omarchy `colors.toml`. */
@@ -284,9 +337,12 @@ export const pageTheme = (options: PageThemeOptions): PageTheme => {
284
337
  choices().some((theme) => theme.id === id && themeCovers(theme, scheme)));
285
338
 
286
339
  // The Omarchy theme comes first where it covers the scheme, so the app
287
- // matches the desktop until the visitor picks another
340
+ // matches the desktop until the visitor picks another, unless it's only
341
+ // offered
288
342
  const fallback = (scheme: ThemeScheme) =>
289
- omarchy !== null && themeCovers(omarchy, scheme)
343
+ options.followOmarchy !== false &&
344
+ omarchy !== null &&
345
+ themeCovers(omarchy, scheme)
290
346
  ? omarchy.id
291
347
  : options.themes.some(
292
348
  (theme) =>
@@ -437,7 +493,9 @@ export const pageTheme = (options: PageThemeOptions): PageTheme => {
437
493
  read(stored.dark),
438
494
  ].includes(omarchyThemeId);
439
495
 
440
- const unpicked = read(stored.theme) === null;
496
+ const unpicked =
497
+ options.followOmarchy !== false && read(stored.theme) === null;
498
+
441
499
  let mode: ThemeScheme | undefined;
442
500
 
443
501
  if (theme !== null) {
package/src/schema.ts CHANGED
@@ -108,15 +108,19 @@ export interface Palette extends Schema.Schema.Type<typeof Palette> {}
108
108
 
109
109
  /**
110
110
  * An optional theme mapped from an existing palette, such as Catppuccin, so it
111
- * stays faithful. Each flavor is one mode, and it takes Base's style.
111
+ * stays faithful. It takes Base's style. Most flavors are one mode; one that
112
+ * comes in both, such as GitHub's, also gives the other mode's palette.
112
113
  */
113
114
  export const MappedTheme = Schema.Struct({
114
115
  kind: Schema.tag("mapped"),
115
116
  id: Schema.String,
116
117
  name: Schema.String,
117
118
  family: Schema.String,
119
+ /** The mode `palette` is for. */
118
120
  mode: Schema.Literals(["light", "dark"]),
119
121
  palette: Palette,
122
+ /** The other mode's palette, for a theme that covers both. */
123
+ otherPalette: Schema.optionalKey(Palette),
120
124
  });
121
125
 
122
126
  export interface MappedTheme extends Schema.Schema.Type<typeof MappedTheme> {}
package/src/solve.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  withLightness,
15
15
  type Oklch,
16
16
  } from "./color.ts";
17
- import type { MappedTheme, SolvedTheme, Style, Theme } from "./schema.ts";
17
+ import type { Palette, SolvedTheme, Style, Theme } from "./schema.ts";
18
18
 
19
19
  export const statuses = [
20
20
  "info",
@@ -261,18 +261,17 @@ const darker = (dark: Roles, by = 0.045) => {
261
261
  };
262
262
  };
263
263
 
264
- const mappedRoles = (theme: MappedTheme) => {
264
+ const mappedRoles = (source: Palette, dark: boolean) => {
265
265
  const palette = {
266
- lower: fromHex(theme.palette.lower),
267
- background: fromHex(theme.palette.background),
268
- raised: fromHex(theme.palette.raised),
269
- border: fromHex(theme.palette.border),
270
- text: fromHex(theme.palette.text),
271
- muted: fromHex(theme.palette.muted),
272
- accent: fromHex(theme.palette.accent),
266
+ lower: fromHex(source.lower),
267
+ background: fromHex(source.background),
268
+ raised: fromHex(source.raised),
269
+ border: fromHex(source.border),
270
+ text: fromHex(source.text),
271
+ muted: fromHex(source.muted),
272
+ accent: fromHex(source.accent),
273
273
  };
274
274
 
275
- const dark = theme.mode === "dark";
276
275
  const away = dark ? 1 : -1;
277
276
 
278
277
  const surfaces = {
@@ -281,11 +280,11 @@ const mappedRoles = (theme: MappedTheme) => {
281
280
  };
282
281
 
283
282
  const seeds: Record<Status, Oklch> = {
284
- info: fromHex(theme.palette.info),
285
- success: fromHex(theme.palette.success),
286
- important: fromHex(theme.palette.important),
287
- warning: fromHex(theme.palette.warning),
288
- danger: fromHex(theme.palette.danger),
283
+ info: fromHex(source.info),
284
+ success: fromHex(source.success),
285
+ important: fromHex(source.important),
286
+ warning: fromHex(source.warning),
287
+ danger: fromHex(source.danger),
289
288
  };
290
289
 
291
290
  return {
@@ -338,7 +337,20 @@ const mappedStyle = (base: Style): Style => ({
338
337
  /** Resolve a theme into roles. Mapped themes take `baseStyle`, Base's style. */
339
338
  export const resolve = (theme: Theme, baseStyle: Style): ResolvedTheme => {
340
339
  if (theme.kind === "mapped") {
341
- const roles = mappedRoles(theme);
340
+ // `palette` is for its own mode, `otherPalette` for the opposite one
341
+ const light = theme.mode === "light" ? theme.palette : theme.otherPalette;
342
+
343
+ const dark = theme.mode === "dark" ? theme.palette : theme.otherPalette;
344
+ const modes: Partial<Record<Mode, Roles>> = {};
345
+
346
+ if (light !== undefined) modes.light = mappedRoles(light, false);
347
+
348
+ if (dark !== undefined) {
349
+ const roles = mappedRoles(dark, true);
350
+
351
+ modes.dark = roles;
352
+ modes.darker = darker(roles);
353
+ }
342
354
 
343
355
  return {
344
356
  id: theme.id,
@@ -348,10 +360,7 @@ export const resolve = (theme: Theme, baseStyle: Style): ResolvedTheme => {
348
360
  family: theme.family,
349
361
  examples: [],
350
362
  style: mappedStyle(baseStyle),
351
- modes:
352
- theme.mode === "dark"
353
- ? { dark: roles, darker: darker(roles) }
354
- : { light: roles },
363
+ modes,
355
364
  };
356
365
  }
357
366