@reopt-ai/opt-ui 1.4.1 → 1.6.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.
- package/COMPONENT_CATALOG.md +374 -84
- package/README.md +28 -7
- package/dist/app.css +146 -0
- package/dist/core/index.cjs +164 -324
- package/dist/core/index.d.cts +3 -5
- package/dist/core/index.d.ts +3 -5
- package/dist/core/index.js +3 -323
- package/dist/docs/01-getting-started.md +25 -5
- package/dist/docs/02-components/01-core.md +192 -46
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +177 -42
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +1 -1
- package/dist/docs/03-recipes/03-layouts.md +20 -12
- package/dist/docs/04-theming.md +3 -1
- package/dist/docs/05-migration/01-breaking-changes.md +28 -0
- package/dist/id-registry.cjs +1899 -1872
- package/dist/id-registry.d.cts +13 -10
- package/dist/id-registry.d.cts.map +1 -0
- package/dist/id-registry.d.ts +13 -10
- package/dist/id-registry.d.ts.map +1 -0
- package/dist/id-registry.js +1897 -1871
- package/dist/id-registry.js.map +1 -0
- package/dist/id-registry.json +44 -44
- package/dist/index.cjs +8294 -9613
- package/dist/index.d.cts +1486 -1438
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1486 -1438
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7933 -9600
- package/dist/index.js.map +1 -0
- package/dist/meta.cjs +7461 -6765
- package/dist/meta.d.cts.map +1 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +7460 -6736
- package/dist/meta.js.map +1 -0
- package/dist/pagination-By7Ozv3U.js +3788 -0
- package/dist/pagination-By7Ozv3U.js.map +1 -0
- package/dist/pagination-Uv3CnGJz.cjs +4677 -0
- package/dist/shader-surface-BK1GMt5_.d.cts +1788 -0
- package/dist/shader-surface-BK1GMt5_.d.cts.map +1 -0
- package/dist/shader-surface-BWhO2xkk.js +3715 -0
- package/dist/shader-surface-BWhO2xkk.js.map +1 -0
- package/dist/shader-surface-DIVWtpFl.d.ts +1788 -0
- package/dist/shader-surface-DIVWtpFl.d.ts.map +1 -0
- package/dist/shader-surface-DxLLfrE6.cjs +4108 -0
- package/dist/shells/index.cjs +51 -65
- package/dist/shells/index.d.cts +4 -5
- package/dist/shells/index.d.ts +4 -5
- package/dist/shells/index.js +3 -64
- package/dist/tailwind.css +54 -5
- package/dist/theme/presets/default.css +12 -12
- package/dist/theme/server.cjs +310 -0
- package/dist/theme/server.d.cts +117 -0
- package/dist/theme/server.d.cts.map +1 -0
- package/dist/theme/server.d.ts +117 -0
- package/dist/theme/server.d.ts.map +1 -0
- package/dist/theme/server.js +287 -0
- package/dist/theme/server.js.map +1 -0
- package/dist/types-D0FlcYnM.d.cts +301 -0
- package/dist/types-D0FlcYnM.d.cts.map +1 -0
- package/dist/types-D0FlcYnM.d.ts +301 -0
- package/dist/types-D0FlcYnM.d.ts.map +1 -0
- package/dist/visuals/index.cjs +10 -4
- package/dist/visuals/index.d.cts +2 -1
- package/dist/visuals/index.d.ts +2 -1
- package/dist/visuals/index.js +1 -2
- package/dist/workflow-canvas-3Jeevkgy.cjs +2599 -0
- package/dist/workflow-canvas-BGBAcjf-.js +2403 -0
- package/dist/workflow-canvas-BGBAcjf-.js.map +1 -0
- package/dist/workflow-canvas-Bar_O2SQ.d.ts +494 -0
- package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +1 -0
- package/dist/workflow-canvas-DghDRtHn.d.cts +494 -0
- package/dist/workflow-canvas-DghDRtHn.d.cts.map +1 -0
- package/package.json +24 -8
- package/dist/chunk-3GWWZKX7.js +0 -38
- package/dist/chunk-AFF2HPE5.cjs +0 -5008
- package/dist/chunk-ELWICXYY.js +0 -4745
- package/dist/chunk-N4NDU5ET.cjs +0 -4745
- package/dist/chunk-ONE3C5RV.cjs +0 -38
- package/dist/chunk-QWBHD54V.js +0 -3218
- package/dist/chunk-RBM2RNC2.js +0 -5008
- package/dist/chunk-X5WCXJAF.cjs +0 -3218
- package/dist/index-BZ_lBlO1.d.ts +0 -474
- package/dist/index-BuvxoWHf.d.cts +0 -474
- package/dist/index-DlAcuvQp.d.cts +0 -1686
- package/dist/index-Slu5hOj1.d.ts +0 -1686
- package/dist/types-D4-0lwaE.d.cts +0 -298
- package/dist/types-D4-0lwaE.d.ts +0 -298
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { PaletteConfig } from "@reopt-ai/opt-palette";
|
|
2
|
+
//#region src/lib/theme/presets/index.d.ts
|
|
3
|
+
interface ThemeDescriptor {
|
|
4
|
+
/** Stable id used in `<html data-theme="...">` and localStorage. */
|
|
5
|
+
readonly id: string;
|
|
6
|
+
/** Human-readable label shown in ThemeSwitcher. */
|
|
7
|
+
readonly label: string;
|
|
8
|
+
/** Short description shown under the label. */
|
|
9
|
+
readonly description: string;
|
|
10
|
+
/**
|
|
11
|
+
* Optional engine input that approximates this preset. Used by
|
|
12
|
+
* ThemeSwitcher to derive swatches when `switcherSwatches` is omitted, and
|
|
13
|
+
* as the starting point when a user clones a preset into a custom palette.
|
|
14
|
+
* Codegen does NOT consume this in the current phase — the static `*.css`
|
|
15
|
+
* file remains the source of token values.
|
|
16
|
+
*/
|
|
17
|
+
readonly palette?: PaletteConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Three CSS color strings for the ThemeSwitcher swatch row. Falls back to
|
|
20
|
+
* the live `--opt-accent` / `--opt-surface` / `--opt-text` tokens when
|
|
21
|
+
* omitted.
|
|
22
|
+
*/
|
|
23
|
+
readonly switcherSwatches?: readonly [string, string, string];
|
|
24
|
+
/** Disable dark variant generation entirely. */
|
|
25
|
+
readonly forceLightOnly?: boolean;
|
|
26
|
+
}
|
|
27
|
+
declare const THEME_REGISTRY: {
|
|
28
|
+
readonly default: ThemeDescriptor;
|
|
29
|
+
readonly minimal: ThemeDescriptor;
|
|
30
|
+
readonly natural: ThemeDescriptor;
|
|
31
|
+
readonly pro: ThemeDescriptor;
|
|
32
|
+
readonly "mono-dark": ThemeDescriptor;
|
|
33
|
+
};
|
|
34
|
+
/** Compile-time-derived union of registered preset ids. */
|
|
35
|
+
type ThemePresetId = keyof typeof THEME_REGISTRY;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/lib/theme/theme-resolution.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* Registered preset id list. Preferred for runtime iteration.
|
|
40
|
+
* @deprecated Prefer `THEME_PRESET_IDS` from `./presets`.
|
|
41
|
+
*/
|
|
42
|
+
declare const THEME_PRESET_VALUES: readonly ThemePresetId[];
|
|
43
|
+
/** Theme preset id — alias of `ThemePresetId` from the registry. */
|
|
44
|
+
type ThemePreset = ThemePresetId;
|
|
45
|
+
declare const THEME_MODE_VALUES: readonly ["light", "dark", "system"];
|
|
46
|
+
/** Type definition for theme mode. */
|
|
47
|
+
type ThemeMode = (typeof THEME_MODE_VALUES)[number];
|
|
48
|
+
/** Runtime constraints for apps that expose only a subset of opt-ui theming. */
|
|
49
|
+
interface ThemeRuntimeOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Force the root theme to one preset regardless of stored preset/generated
|
|
52
|
+
* theme state. Mode is still resolved normally unless constrained.
|
|
53
|
+
*/
|
|
54
|
+
lockedPreset?: ThemePreset;
|
|
55
|
+
/**
|
|
56
|
+
* Restrict mode reads/writes to a smaller set, e.g. `["light", "dark"]`.
|
|
57
|
+
* The first value becomes the fallback when the stored/default mode is not
|
|
58
|
+
* allowed.
|
|
59
|
+
*/
|
|
60
|
+
allowedModes?: readonly ThemeMode[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compound theme value applied to `<html data-theme>`. Derived from the
|
|
64
|
+
* registered preset ids: `<id>` (light) or `<id>-dark` (dark). Force-light
|
|
65
|
+
* presets only emit the bare id form.
|
|
66
|
+
*/
|
|
67
|
+
type CompoundTheme = `${ThemePreset}` | `${ThemePreset}-dark`;
|
|
68
|
+
declare const LEGACY_THEME_STORAGE_KEY = "opt-theme";
|
|
69
|
+
declare const THEME_PRESET_STORAGE_KEY = "opt-preset";
|
|
70
|
+
declare const THEME_MODE_STORAGE_KEY = "opt-mode";
|
|
71
|
+
declare const DARK_MQ = "(prefers-color-scheme: dark)";
|
|
72
|
+
/**
|
|
73
|
+
* Presets that force light mode regardless of system dark/light preference.
|
|
74
|
+
* Mirrors `FORCE_LIGHT_PRESET_IDS` from `./presets` — kept as alias for
|
|
75
|
+
* existing imports.
|
|
76
|
+
*/
|
|
77
|
+
declare const FORCE_LIGHT_PRESETS: ReadonlySet<ThemePreset>;
|
|
78
|
+
/** All valid compound theme names. Useful for static enumeration. */
|
|
79
|
+
declare const ALL_COMPOUND_THEMES: CompoundTheme[];
|
|
80
|
+
interface ThemeStorageReader {
|
|
81
|
+
getItem(key: string): string | null;
|
|
82
|
+
}
|
|
83
|
+
/** Handles is theme preset. */
|
|
84
|
+
declare function isThemePreset(value: string | null): value is ThemePreset;
|
|
85
|
+
/** Handles is theme mode. */
|
|
86
|
+
declare function isThemeMode(value: string | null): value is ThemeMode;
|
|
87
|
+
/** Compute compound theme name from preset + resolved mode. */
|
|
88
|
+
declare function getCompoundTheme(preset: ThemePreset, resolvedMode: "light" | "dark"): CompoundTheme;
|
|
89
|
+
/** Handles resolve theme mode. */
|
|
90
|
+
declare function resolveThemeMode(preset: ThemePreset, mode: ThemeMode, systemDark: boolean): "light" | "dark";
|
|
91
|
+
/** Handles read stored theme. */
|
|
92
|
+
declare function readStoredTheme(storage: ThemeStorageReader, defaultPreset: ThemePreset, defaultMode: ThemeMode, options?: {
|
|
93
|
+
migrateLegacy?: boolean;
|
|
94
|
+
}): {
|
|
95
|
+
preset: ThemePreset;
|
|
96
|
+
mode: ThemeMode;
|
|
97
|
+
};
|
|
98
|
+
/** Normalize a mode against an optional allowed-mode list. */
|
|
99
|
+
declare function coerceThemeMode(mode: ThemeMode, allowedModes: readonly ThemeMode[] | undefined, fallback?: ThemeMode): ThemeMode;
|
|
100
|
+
/**
|
|
101
|
+
* Build the inline `<head>` boot script that paints the correct theme before
|
|
102
|
+
* React mounts. Two paths:
|
|
103
|
+
*
|
|
104
|
+
* 1. **Generated** — when both `opt-theme-spec` (kind: "generated") and
|
|
105
|
+
* `opt-theme-tokens` are present and well-formed, a `<style id="…">` tag
|
|
106
|
+
* is inserted with the cached tokens and `<html data-theme>` is set to the
|
|
107
|
+
* generated id (with optional `-dark` suffix). This bypasses opt-palette
|
|
108
|
+
* at startup so first paint is instant.
|
|
109
|
+
* 2. **Preset (legacy)** — falls through to the existing preset selector,
|
|
110
|
+
* including legacy `opt-theme` → `opt-preset` migration.
|
|
111
|
+
*
|
|
112
|
+
* Returned as a single minified line; intended for `dangerouslySetInnerHTML`.
|
|
113
|
+
*/
|
|
114
|
+
declare function createThemeBootScript(defaultPreset?: ThemePreset, defaultMode?: ThemeMode, options?: ThemeRuntimeOptions): string;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { ALL_COMPOUND_THEMES, type CompoundTheme, DARK_MQ, FORCE_LIGHT_PRESETS, LEGACY_THEME_STORAGE_KEY, THEME_MODE_STORAGE_KEY, THEME_MODE_VALUES, THEME_PRESET_STORAGE_KEY, THEME_PRESET_VALUES, type ThemeMode, type ThemePreset, type ThemeRuntimeOptions, coerceThemeMode, createThemeBootScript, getCompoundTheme, isThemeMode, isThemePreset, readStoredTheme, resolveThemeMode };
|
|
117
|
+
//# sourceMappingURL=server.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.cts","names":[],"sources":["../../src/lib/theme/presets/index.ts","../../src/lib/theme/theme-resolution.ts"],"mappings":";;UAgBiB;;WAEN;;WAEA;;WAEA;;;;;;;;WAQA,UAAU;;;;;;WAMV;;WAEA;;cA8EE;;;;;;;;KASD,6BAA6B;;;;;;;cC5G5B,8BAA8B;;KAG/B,cAAc;cAEb;;KAED,oBAAoB;;UAGf;;;;;EAKf,eAAe;;;;;;EAMf,wBAAwB;;;;;;;KAQd,mBAAmB,mBAAmB;cAErC;cACA;cACA;cACA;;;;;;cAOA,qBAAqB,YAAY;;cAIjC,qBAAqB;UAOxB;EACR,QAAQ;;;iBAkBM,cAAc,uBAAuB,SAAS;;iBAQ9C,YAAY,uBAAuB,SAAS;;iBAO5C,iBACd,QAAQ,aACR,iCACC;;iBAMa,iBACd,QAAQ,aACR,MAAM,WACN;;iBAQc,gBACd,SAAS,oBACT,eAAe,aACf,aAAa,WACb;EAAW;;EACR,QAAQ;EAAa,MAAM;;;iBAqChB,gBACd,MAAM,WACN,uBAAuB,yBACvB,WAAU,YACT;;;;;;;;;;;;;;;iBAqBa,sBACd,gBAAe,aACf,cAAa,WACb,UAAS"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { PaletteConfig } from "@reopt-ai/opt-palette";
|
|
2
|
+
//#region src/lib/theme/presets/index.d.ts
|
|
3
|
+
interface ThemeDescriptor {
|
|
4
|
+
/** Stable id used in `<html data-theme="...">` and localStorage. */
|
|
5
|
+
readonly id: string;
|
|
6
|
+
/** Human-readable label shown in ThemeSwitcher. */
|
|
7
|
+
readonly label: string;
|
|
8
|
+
/** Short description shown under the label. */
|
|
9
|
+
readonly description: string;
|
|
10
|
+
/**
|
|
11
|
+
* Optional engine input that approximates this preset. Used by
|
|
12
|
+
* ThemeSwitcher to derive swatches when `switcherSwatches` is omitted, and
|
|
13
|
+
* as the starting point when a user clones a preset into a custom palette.
|
|
14
|
+
* Codegen does NOT consume this in the current phase — the static `*.css`
|
|
15
|
+
* file remains the source of token values.
|
|
16
|
+
*/
|
|
17
|
+
readonly palette?: PaletteConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Three CSS color strings for the ThemeSwitcher swatch row. Falls back to
|
|
20
|
+
* the live `--opt-accent` / `--opt-surface` / `--opt-text` tokens when
|
|
21
|
+
* omitted.
|
|
22
|
+
*/
|
|
23
|
+
readonly switcherSwatches?: readonly [string, string, string];
|
|
24
|
+
/** Disable dark variant generation entirely. */
|
|
25
|
+
readonly forceLightOnly?: boolean;
|
|
26
|
+
}
|
|
27
|
+
declare const THEME_REGISTRY: {
|
|
28
|
+
readonly default: ThemeDescriptor;
|
|
29
|
+
readonly minimal: ThemeDescriptor;
|
|
30
|
+
readonly natural: ThemeDescriptor;
|
|
31
|
+
readonly pro: ThemeDescriptor;
|
|
32
|
+
readonly "mono-dark": ThemeDescriptor;
|
|
33
|
+
};
|
|
34
|
+
/** Compile-time-derived union of registered preset ids. */
|
|
35
|
+
type ThemePresetId = keyof typeof THEME_REGISTRY;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/lib/theme/theme-resolution.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* Registered preset id list. Preferred for runtime iteration.
|
|
40
|
+
* @deprecated Prefer `THEME_PRESET_IDS` from `./presets`.
|
|
41
|
+
*/
|
|
42
|
+
declare const THEME_PRESET_VALUES: readonly ThemePresetId[];
|
|
43
|
+
/** Theme preset id — alias of `ThemePresetId` from the registry. */
|
|
44
|
+
type ThemePreset = ThemePresetId;
|
|
45
|
+
declare const THEME_MODE_VALUES: readonly ["light", "dark", "system"];
|
|
46
|
+
/** Type definition for theme mode. */
|
|
47
|
+
type ThemeMode = (typeof THEME_MODE_VALUES)[number];
|
|
48
|
+
/** Runtime constraints for apps that expose only a subset of opt-ui theming. */
|
|
49
|
+
interface ThemeRuntimeOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Force the root theme to one preset regardless of stored preset/generated
|
|
52
|
+
* theme state. Mode is still resolved normally unless constrained.
|
|
53
|
+
*/
|
|
54
|
+
lockedPreset?: ThemePreset;
|
|
55
|
+
/**
|
|
56
|
+
* Restrict mode reads/writes to a smaller set, e.g. `["light", "dark"]`.
|
|
57
|
+
* The first value becomes the fallback when the stored/default mode is not
|
|
58
|
+
* allowed.
|
|
59
|
+
*/
|
|
60
|
+
allowedModes?: readonly ThemeMode[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compound theme value applied to `<html data-theme>`. Derived from the
|
|
64
|
+
* registered preset ids: `<id>` (light) or `<id>-dark` (dark). Force-light
|
|
65
|
+
* presets only emit the bare id form.
|
|
66
|
+
*/
|
|
67
|
+
type CompoundTheme = `${ThemePreset}` | `${ThemePreset}-dark`;
|
|
68
|
+
declare const LEGACY_THEME_STORAGE_KEY = "opt-theme";
|
|
69
|
+
declare const THEME_PRESET_STORAGE_KEY = "opt-preset";
|
|
70
|
+
declare const THEME_MODE_STORAGE_KEY = "opt-mode";
|
|
71
|
+
declare const DARK_MQ = "(prefers-color-scheme: dark)";
|
|
72
|
+
/**
|
|
73
|
+
* Presets that force light mode regardless of system dark/light preference.
|
|
74
|
+
* Mirrors `FORCE_LIGHT_PRESET_IDS` from `./presets` — kept as alias for
|
|
75
|
+
* existing imports.
|
|
76
|
+
*/
|
|
77
|
+
declare const FORCE_LIGHT_PRESETS: ReadonlySet<ThemePreset>;
|
|
78
|
+
/** All valid compound theme names. Useful for static enumeration. */
|
|
79
|
+
declare const ALL_COMPOUND_THEMES: CompoundTheme[];
|
|
80
|
+
interface ThemeStorageReader {
|
|
81
|
+
getItem(key: string): string | null;
|
|
82
|
+
}
|
|
83
|
+
/** Handles is theme preset. */
|
|
84
|
+
declare function isThemePreset(value: string | null): value is ThemePreset;
|
|
85
|
+
/** Handles is theme mode. */
|
|
86
|
+
declare function isThemeMode(value: string | null): value is ThemeMode;
|
|
87
|
+
/** Compute compound theme name from preset + resolved mode. */
|
|
88
|
+
declare function getCompoundTheme(preset: ThemePreset, resolvedMode: "light" | "dark"): CompoundTheme;
|
|
89
|
+
/** Handles resolve theme mode. */
|
|
90
|
+
declare function resolveThemeMode(preset: ThemePreset, mode: ThemeMode, systemDark: boolean): "light" | "dark";
|
|
91
|
+
/** Handles read stored theme. */
|
|
92
|
+
declare function readStoredTheme(storage: ThemeStorageReader, defaultPreset: ThemePreset, defaultMode: ThemeMode, options?: {
|
|
93
|
+
migrateLegacy?: boolean;
|
|
94
|
+
}): {
|
|
95
|
+
preset: ThemePreset;
|
|
96
|
+
mode: ThemeMode;
|
|
97
|
+
};
|
|
98
|
+
/** Normalize a mode against an optional allowed-mode list. */
|
|
99
|
+
declare function coerceThemeMode(mode: ThemeMode, allowedModes: readonly ThemeMode[] | undefined, fallback?: ThemeMode): ThemeMode;
|
|
100
|
+
/**
|
|
101
|
+
* Build the inline `<head>` boot script that paints the correct theme before
|
|
102
|
+
* React mounts. Two paths:
|
|
103
|
+
*
|
|
104
|
+
* 1. **Generated** — when both `opt-theme-spec` (kind: "generated") and
|
|
105
|
+
* `opt-theme-tokens` are present and well-formed, a `<style id="…">` tag
|
|
106
|
+
* is inserted with the cached tokens and `<html data-theme>` is set to the
|
|
107
|
+
* generated id (with optional `-dark` suffix). This bypasses opt-palette
|
|
108
|
+
* at startup so first paint is instant.
|
|
109
|
+
* 2. **Preset (legacy)** — falls through to the existing preset selector,
|
|
110
|
+
* including legacy `opt-theme` → `opt-preset` migration.
|
|
111
|
+
*
|
|
112
|
+
* Returned as a single minified line; intended for `dangerouslySetInnerHTML`.
|
|
113
|
+
*/
|
|
114
|
+
declare function createThemeBootScript(defaultPreset?: ThemePreset, defaultMode?: ThemeMode, options?: ThemeRuntimeOptions): string;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { ALL_COMPOUND_THEMES, type CompoundTheme, DARK_MQ, FORCE_LIGHT_PRESETS, LEGACY_THEME_STORAGE_KEY, THEME_MODE_STORAGE_KEY, THEME_MODE_VALUES, THEME_PRESET_STORAGE_KEY, THEME_PRESET_VALUES, type ThemeMode, type ThemePreset, type ThemeRuntimeOptions, coerceThemeMode, createThemeBootScript, getCompoundTheme, isThemeMode, isThemePreset, readStoredTheme, resolveThemeMode };
|
|
117
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/lib/theme/presets/index.ts","../../src/lib/theme/theme-resolution.ts"],"mappings":";;UAgBiB;;WAEN;;WAEA;;WAEA;;;;;;;;WAQA,UAAU;;;;;;WAMV;;WAEA;;cA8EE;;;;;;;;KASD,6BAA6B;;;;;;;cC5G5B,8BAA8B;;KAG/B,cAAc;cAEb;;KAED,oBAAoB;;UAGf;;;;;EAKf,eAAe;;;;;;EAMf,wBAAwB;;;;;;;KAQd,mBAAmB,mBAAmB;cAErC;cACA;cACA;cACA;;;;;;cAOA,qBAAqB,YAAY;;cAIjC,qBAAqB;UAOxB;EACR,QAAQ;;;iBAkBM,cAAc,uBAAuB,SAAS;;iBAQ9C,YAAY,uBAAuB,SAAS;;iBAO5C,iBACd,QAAQ,aACR,iCACC;;iBAMa,iBACd,QAAQ,aACR,MAAM,WACN;;iBAQc,gBACd,SAAS,oBACT,eAAe,aACf,aAAa,WACb;EAAW;;EACR,QAAQ;EAAa,MAAM;;;iBAqChB,gBACd,MAAM,WACN,uBAAuB,yBACvB,WAAU,YACT;;;;;;;;;;;;;;;iBAqBa,sBACd,gBAAe,aACf,cAAa,WACb,UAAS"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import "@reopt-ai/opt-palette";
|
|
2
|
+
const THEME_REGISTRY = {
|
|
3
|
+
default: {
|
|
4
|
+
id: "default",
|
|
5
|
+
label: "Default",
|
|
6
|
+
description: "Blue accent, balanced radius & shadow",
|
|
7
|
+
palette: {
|
|
8
|
+
seed: "#2563eb",
|
|
9
|
+
harmony: "split-complementary",
|
|
10
|
+
radiusScale: "medium",
|
|
11
|
+
shadowIntensity: "subtle",
|
|
12
|
+
neutralTemperature: "cool"
|
|
13
|
+
},
|
|
14
|
+
switcherSwatches: [
|
|
15
|
+
"hsl(221 83% 53%)",
|
|
16
|
+
"hsl(142 71% 45%)",
|
|
17
|
+
"hsl(38 92% 50%)"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
minimal: {
|
|
21
|
+
id: "minimal",
|
|
22
|
+
label: "Minimal",
|
|
23
|
+
description: "Monochrome, no shadow, sharp edges",
|
|
24
|
+
palette: {
|
|
25
|
+
seed: "#333333",
|
|
26
|
+
harmony: "analogous",
|
|
27
|
+
radiusScale: "sharp",
|
|
28
|
+
shadowIntensity: "none",
|
|
29
|
+
neutralTemperature: "cool"
|
|
30
|
+
},
|
|
31
|
+
switcherSwatches: [
|
|
32
|
+
"hsl(0 0% 20%)",
|
|
33
|
+
"hsl(0 0% 45%)",
|
|
34
|
+
"hsl(0 0% 70%)"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
natural: {
|
|
38
|
+
id: "natural",
|
|
39
|
+
label: "Natural",
|
|
40
|
+
description: "Always light — sage accent, warm cream",
|
|
41
|
+
forceLightOnly: true,
|
|
42
|
+
palette: {
|
|
43
|
+
seed: "#5a9376",
|
|
44
|
+
harmony: "complementary",
|
|
45
|
+
radiusScale: "medium",
|
|
46
|
+
shadowIntensity: "subtle",
|
|
47
|
+
neutralTemperature: "warm"
|
|
48
|
+
},
|
|
49
|
+
switcherSwatches: [
|
|
50
|
+
"hsl(155 38% 45%)",
|
|
51
|
+
"hsl(25 65% 55%)",
|
|
52
|
+
"hsl(45 75% 50%)"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
pro: {
|
|
56
|
+
id: "pro",
|
|
57
|
+
label: "Pro",
|
|
58
|
+
description: "Neon blue accent, mono headings, ultra-dense",
|
|
59
|
+
palette: {
|
|
60
|
+
seed: "#0080ff",
|
|
61
|
+
harmony: "tetradic",
|
|
62
|
+
radiusScale: "sharp",
|
|
63
|
+
shadowIntensity: "subtle",
|
|
64
|
+
neutralTemperature: "cool"
|
|
65
|
+
},
|
|
66
|
+
switcherSwatches: [
|
|
67
|
+
"hsl(210 100% 55%)",
|
|
68
|
+
"hsl(170 90% 48%)",
|
|
69
|
+
"hsl(290 85% 60%)"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"mono-dark": {
|
|
73
|
+
id: "mono-dark",
|
|
74
|
+
label: "Mono Dark",
|
|
75
|
+
description: "OLED-friendly black, neon cyan accent, mono headings",
|
|
76
|
+
palette: {
|
|
77
|
+
seed: "#00d4ff",
|
|
78
|
+
harmony: "tetradic",
|
|
79
|
+
radiusScale: "sharp",
|
|
80
|
+
shadowIntensity: "subtle",
|
|
81
|
+
neutralTemperature: "cool"
|
|
82
|
+
},
|
|
83
|
+
switcherSwatches: [
|
|
84
|
+
"hsl(190 100% 50%)",
|
|
85
|
+
"hsl(0 0% 8%)",
|
|
86
|
+
"hsl(280 90% 65%)"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/** All registered preset ids in declaration order. */
|
|
91
|
+
const THEME_PRESET_IDS = Object.keys(THEME_REGISTRY);
|
|
92
|
+
/** Type guard for a known preset id. */
|
|
93
|
+
function isThemePresetId(id) {
|
|
94
|
+
return id in THEME_REGISTRY;
|
|
95
|
+
}
|
|
96
|
+
/** Preset ids that suppress dark mode generation. */
|
|
97
|
+
const FORCE_LIGHT_PRESET_IDS = new Set(THEME_PRESET_IDS.filter((id) => THEME_REGISTRY[id].forceLightOnly));
|
|
98
|
+
/**
|
|
99
|
+
* Map from removed preset ids to the closest surviving preset. Boot script and
|
|
100
|
+
* `readStoredTheme` consult this so users with persisted `corporate`/`playful`
|
|
101
|
+
* values silently land on a working preset on next page load instead of
|
|
102
|
+
* showing the global default fallback for an unknown id.
|
|
103
|
+
*
|
|
104
|
+
* Keep this around indefinitely — localStorage values are forever and the
|
|
105
|
+
* cost of two extra entries is negligible.
|
|
106
|
+
*/
|
|
107
|
+
const DEPRECATED_PRESET_MIGRATIONS = Object.freeze({
|
|
108
|
+
corporate: "default",
|
|
109
|
+
playful: "default"
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Resolve a possibly-deprecated preset id to a registered one. Returns the
|
|
113
|
+
* input unchanged when it is already valid; returns the migration target when
|
|
114
|
+
* it is a known deprecated id; returns `null` when the id is unknown
|
|
115
|
+
* altogether (caller should fall back to its default).
|
|
116
|
+
*/
|
|
117
|
+
function migrateDeprecatedPresetId(id) {
|
|
118
|
+
if (!id) return null;
|
|
119
|
+
if (isThemePresetId(id)) return id;
|
|
120
|
+
if (id in DEPRECATED_PRESET_MIGRATIONS) return DEPRECATED_PRESET_MIGRATIONS[id];
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/lib/theme/theme-serialize.ts
|
|
125
|
+
/** Element id used for the global generated theme style tag. */
|
|
126
|
+
const GLOBAL_THEME_STYLE_ID = "opt-global-theme";
|
|
127
|
+
/** localStorage key for the persisted ThemeSpec (generated mode). */
|
|
128
|
+
const THEME_SPEC_STORAGE_KEY = "opt-theme-spec";
|
|
129
|
+
/**
|
|
130
|
+
* localStorage key for the cached, pre-serialized token maps that match the
|
|
131
|
+
* spec under `THEME_SPEC_STORAGE_KEY`. The boot script reads this directly so
|
|
132
|
+
* it can inline tokens without invoking opt-palette at startup.
|
|
133
|
+
*/
|
|
134
|
+
const THEME_TOKENS_STORAGE_KEY = "opt-theme-tokens";
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/lib/theme/theme-resolution.ts
|
|
137
|
+
/**
|
|
138
|
+
* Registered preset id list. Preferred for runtime iteration.
|
|
139
|
+
* @deprecated Prefer `THEME_PRESET_IDS` from `./presets`.
|
|
140
|
+
*/
|
|
141
|
+
const THEME_PRESET_VALUES = THEME_PRESET_IDS;
|
|
142
|
+
const THEME_MODE_VALUES = [
|
|
143
|
+
"light",
|
|
144
|
+
"dark",
|
|
145
|
+
"system"
|
|
146
|
+
];
|
|
147
|
+
const LEGACY_THEME_STORAGE_KEY = "opt-theme";
|
|
148
|
+
const THEME_PRESET_STORAGE_KEY = "opt-preset";
|
|
149
|
+
const THEME_MODE_STORAGE_KEY = "opt-mode";
|
|
150
|
+
const DARK_MQ = "(prefers-color-scheme: dark)";
|
|
151
|
+
/**
|
|
152
|
+
* Presets that force light mode regardless of system dark/light preference.
|
|
153
|
+
* Mirrors `FORCE_LIGHT_PRESET_IDS` from `./presets` — kept as alias for
|
|
154
|
+
* existing imports.
|
|
155
|
+
*/
|
|
156
|
+
const FORCE_LIGHT_PRESETS = FORCE_LIGHT_PRESET_IDS;
|
|
157
|
+
/** All valid compound theme names. Useful for static enumeration. */
|
|
158
|
+
const ALL_COMPOUND_THEMES = THEME_PRESET_IDS.flatMap((id) => FORCE_LIGHT_PRESET_IDS.has(id) ? [id] : [id, `${id}-dark`]);
|
|
159
|
+
function hasStorageMutation(storage) {
|
|
160
|
+
return typeof storage.setItem === "function" && typeof storage.removeItem === "function";
|
|
161
|
+
}
|
|
162
|
+
/** Handles is theme preset. */
|
|
163
|
+
function isThemePreset(value) {
|
|
164
|
+
return typeof value === "string" && THEME_PRESET_VALUES.includes(value);
|
|
165
|
+
}
|
|
166
|
+
/** Handles is theme mode. */
|
|
167
|
+
function isThemeMode(value) {
|
|
168
|
+
return typeof value === "string" && THEME_MODE_VALUES.includes(value);
|
|
169
|
+
}
|
|
170
|
+
/** Compute compound theme name from preset + resolved mode. */
|
|
171
|
+
function getCompoundTheme(preset, resolvedMode) {
|
|
172
|
+
if (FORCE_LIGHT_PRESETS.has(preset)) return preset;
|
|
173
|
+
return resolvedMode === "dark" ? `${preset}-dark` : preset;
|
|
174
|
+
}
|
|
175
|
+
/** Handles resolve theme mode. */
|
|
176
|
+
function resolveThemeMode(preset, mode, systemDark) {
|
|
177
|
+
if (FORCE_LIGHT_PRESETS.has(preset)) return "light";
|
|
178
|
+
if (mode === "system") return systemDark ? "dark" : "light";
|
|
179
|
+
return mode;
|
|
180
|
+
}
|
|
181
|
+
/** Handles read stored theme. */
|
|
182
|
+
function readStoredTheme(storage, defaultPreset, defaultMode, options = {}) {
|
|
183
|
+
const legacy = storage.getItem(LEGACY_THEME_STORAGE_KEY);
|
|
184
|
+
const storedPreset = storage.getItem(THEME_PRESET_STORAGE_KEY);
|
|
185
|
+
const storedMode = storage.getItem(THEME_MODE_STORAGE_KEY);
|
|
186
|
+
let preset = defaultPreset;
|
|
187
|
+
if (typeof legacy === "string") {
|
|
188
|
+
const migratedLegacy = migrateDeprecatedPresetId(legacy);
|
|
189
|
+
if (migratedLegacy) {
|
|
190
|
+
if (options.migrateLegacy && hasStorageMutation(storage)) {
|
|
191
|
+
storage.setItem(THEME_PRESET_STORAGE_KEY, migratedLegacy);
|
|
192
|
+
storage.removeItem(LEGACY_THEME_STORAGE_KEY);
|
|
193
|
+
}
|
|
194
|
+
preset = migratedLegacy;
|
|
195
|
+
}
|
|
196
|
+
} else if (typeof storedPreset === "string") {
|
|
197
|
+
const migrated = migrateDeprecatedPresetId(storedPreset);
|
|
198
|
+
if (migrated) {
|
|
199
|
+
preset = migrated;
|
|
200
|
+
if (hasStorageMutation(storage) && migrated !== storedPreset && storedPreset in DEPRECATED_PRESET_MIGRATIONS) storage.setItem(THEME_PRESET_STORAGE_KEY, migrated);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const mode = isThemeMode(storedMode) ? storedMode : defaultMode;
|
|
204
|
+
return {
|
|
205
|
+
preset,
|
|
206
|
+
mode
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** Normalize a mode against an optional allowed-mode list. */
|
|
210
|
+
function coerceThemeMode(mode, allowedModes, fallback = "system") {
|
|
211
|
+
var _allowedModes$;
|
|
212
|
+
if (!allowedModes || allowedModes.length === 0) return mode;
|
|
213
|
+
if (allowedModes.includes(mode)) return mode;
|
|
214
|
+
if (allowedModes.includes(fallback)) return fallback;
|
|
215
|
+
return (_allowedModes$ = allowedModes[0]) !== null && _allowedModes$ !== void 0 ? _allowedModes$ : fallback;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Build the inline `<head>` boot script that paints the correct theme before
|
|
219
|
+
* React mounts. Two paths:
|
|
220
|
+
*
|
|
221
|
+
* 1. **Generated** — when both `opt-theme-spec` (kind: "generated") and
|
|
222
|
+
* `opt-theme-tokens` are present and well-formed, a `<style id="…">` tag
|
|
223
|
+
* is inserted with the cached tokens and `<html data-theme>` is set to the
|
|
224
|
+
* generated id (with optional `-dark` suffix). This bypasses opt-palette
|
|
225
|
+
* at startup so first paint is instant.
|
|
226
|
+
* 2. **Preset (legacy)** — falls through to the existing preset selector,
|
|
227
|
+
* including legacy `opt-theme` → `opt-preset` migration.
|
|
228
|
+
*
|
|
229
|
+
* Returned as a single minified line; intended for `dangerouslySetInnerHTML`.
|
|
230
|
+
*/
|
|
231
|
+
function createThemeBootScript(defaultPreset = "default", defaultMode = "system", options = {}) {
|
|
232
|
+
var _options$allowedModes, _allowedModes$2;
|
|
233
|
+
const presetValues = JSON.stringify(THEME_PRESET_VALUES);
|
|
234
|
+
const modeValues = JSON.stringify(THEME_MODE_VALUES);
|
|
235
|
+
const allowedModes = ((_options$allowedModes = options.allowedModes) === null || _options$allowedModes === void 0 ? void 0 : _options$allowedModes.length) ? options.allowedModes : THEME_MODE_VALUES;
|
|
236
|
+
const allowedModeValues = JSON.stringify(allowedModes);
|
|
237
|
+
const forceLightPresets = JSON.stringify(Array.from(FORCE_LIGHT_PRESETS));
|
|
238
|
+
const deprecatedMap = JSON.stringify(DEPRECATED_PRESET_MIGRATIONS);
|
|
239
|
+
const lockedPreset = options.lockedPreset;
|
|
240
|
+
const defaultPresetLiteral = JSON.stringify(lockedPreset !== null && lockedPreset !== void 0 ? lockedPreset : defaultPreset);
|
|
241
|
+
const defaultModeLiteral = JSON.stringify(coerceThemeMode(defaultMode, allowedModes, (_allowedModes$2 = allowedModes[0]) !== null && _allowedModes$2 !== void 0 ? _allowedModes$2 : "system"));
|
|
242
|
+
const lockedPresetLiteral = JSON.stringify(lockedPreset !== null && lockedPreset !== void 0 ? lockedPreset : null);
|
|
243
|
+
const styleId = JSON.stringify(GLOBAL_THEME_STYLE_ID);
|
|
244
|
+
const specKey = JSON.stringify(THEME_SPEC_STORAGE_KEY);
|
|
245
|
+
const tokensKey = JSON.stringify(THEME_TOKENS_STORAGE_KEY);
|
|
246
|
+
const presetKey = JSON.stringify(THEME_PRESET_STORAGE_KEY);
|
|
247
|
+
const modeKey = JSON.stringify(THEME_MODE_STORAGE_KEY);
|
|
248
|
+
const legacyKey = JSON.stringify(LEGACY_THEME_STORAGE_KEY);
|
|
249
|
+
const darkMq = JSON.stringify(DARK_MQ);
|
|
250
|
+
return `
|
|
251
|
+
try {
|
|
252
|
+
var pv=${presetValues},mv=${modeValues},am=${allowedModeValues},fp=${forceLightPresets},dm=${deprecatedMap},lk=${lockedPresetLiteral};
|
|
253
|
+
function mp(v){if(typeof v!=="string")return null;if(pv.indexOf(v)>=0)return v;return Object.prototype.hasOwnProperty.call(dm,v)?dm[v]:null;}
|
|
254
|
+
var p=${defaultPresetLiteral};
|
|
255
|
+
if(!lk){
|
|
256
|
+
var lp=localStorage.getItem(${legacyKey}),sp=localStorage.getItem(${presetKey});
|
|
257
|
+
var lpm=mp(lp),spm=mp(sp);
|
|
258
|
+
p=lpm||spm||${defaultPresetLiteral};
|
|
259
|
+
if(lpm){localStorage.setItem(${presetKey},lpm);localStorage.removeItem(${legacyKey});}
|
|
260
|
+
else if(sp&&spm&&sp!==spm){localStorage.setItem(${presetKey},spm);}
|
|
261
|
+
}
|
|
262
|
+
var sm=localStorage.getItem(${modeKey});
|
|
263
|
+
var m=(typeof sm==="string"&&mv.indexOf(sm)>=0&&am.indexOf(sm)>=0)?sm:${defaultModeLiteral};
|
|
264
|
+
var spec=null,tk=null;
|
|
265
|
+
if(!lk){try{var sj=localStorage.getItem(${specKey});if(sj)spec=JSON.parse(sj);}catch(e){}}
|
|
266
|
+
if(!lk){try{var tj=localStorage.getItem(${tokensKey});if(tj)tk=JSON.parse(tj);}catch(e){}}
|
|
267
|
+
if(!lk&&spec&&spec.kind==="generated"&&tk&&typeof tk.id==="string"&&tk.light&&typeof tk.light==="object"){
|
|
268
|
+
var fld=spec.forceLightOnly===true;
|
|
269
|
+
var dk=!fld&&(m==="dark"||(m==="system"&&matchMedia(${darkMq}).matches));
|
|
270
|
+
var tt=(dk&&tk.dark)?tk.dark:tk.light;
|
|
271
|
+
var cid=(dk&&tk.dark)?(tk.id+"-dark"):tk.id;
|
|
272
|
+
var css="";
|
|
273
|
+
for(var k in tt){if(Object.prototype.hasOwnProperty.call(tt,k))css+=k+":"+tt[k]+";"}
|
|
274
|
+
var st=document.getElementById(${styleId});
|
|
275
|
+
if(!st){st=document.createElement("style");st.id=${styleId};document.head.appendChild(st);}
|
|
276
|
+
st.textContent='[data-theme="'+cid+'"]{'+css+'}';
|
|
277
|
+
document.documentElement.setAttribute("data-theme",cid);
|
|
278
|
+
} else {
|
|
279
|
+
var d=fp.indexOf(p)<0&&(m==="dark"||(m==="system"&&matchMedia(${darkMq}).matches));
|
|
280
|
+
document.documentElement.setAttribute("data-theme",d?p+"-dark":p);
|
|
281
|
+
}
|
|
282
|
+
} catch(e){}`.replace(/\n\s*/g, "");
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { ALL_COMPOUND_THEMES, DARK_MQ, FORCE_LIGHT_PRESETS, LEGACY_THEME_STORAGE_KEY, THEME_MODE_STORAGE_KEY, THEME_MODE_VALUES, THEME_PRESET_STORAGE_KEY, THEME_PRESET_VALUES, coerceThemeMode, createThemeBootScript, getCompoundTheme, isThemeMode, isThemePreset, readStoredTheme, resolveThemeMode };
|
|
286
|
+
|
|
287
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/lib/theme/presets/index.ts","../../src/lib/theme/theme-serialize.ts","../../src/lib/theme/theme-resolution.ts"],"sourcesContent":["/**\n * presets/index.ts\n *\n * Single registry of theme preset metadata. Each entry pairs a stable id\n * (used in `<html data-theme>` and localStorage) with display metadata and an\n * optional `PaletteConfig` that approximates the preset's seed for the\n * ThemeSwitcher swatch row and \"clone to custom\" workflow.\n *\n * Token values themselves currently live in the sibling `*.css` files. A\n * follow-up sub-phase will migrate the full token tables into this file and\n * have a codegen step emit the CSS, but the metadata layer is enough to\n * unblock dynamic ThemeSwitcher rendering and type-safe preset id unions.\n */\n\nimport type { PaletteConfig } from \"@reopt-ai/opt-palette\";\n\nexport interface ThemeDescriptor {\n /** Stable id used in `<html data-theme=\"...\">` and localStorage. */\n readonly id: string;\n /** Human-readable label shown in ThemeSwitcher. */\n readonly label: string;\n /** Short description shown under the label. */\n readonly description: string;\n /**\n * Optional engine input that approximates this preset. Used by\n * ThemeSwitcher to derive swatches when `switcherSwatches` is omitted, and\n * as the starting point when a user clones a preset into a custom palette.\n * Codegen does NOT consume this in the current phase — the static `*.css`\n * file remains the source of token values.\n */\n readonly palette?: PaletteConfig;\n /**\n * Three CSS color strings for the ThemeSwitcher swatch row. Falls back to\n * the live `--opt-accent` / `--opt-surface` / `--opt-text` tokens when\n * omitted.\n */\n readonly switcherSwatches?: readonly [string, string, string];\n /** Disable dark variant generation entirely. */\n readonly forceLightOnly?: boolean;\n}\n\nconst DEFAULT_DESCRIPTOR: ThemeDescriptor = {\n id: \"default\",\n label: \"Default\",\n description: \"Blue accent, balanced radius & shadow\",\n palette: {\n seed: \"#2563eb\",\n harmony: \"split-complementary\",\n radiusScale: \"medium\",\n shadowIntensity: \"subtle\",\n neutralTemperature: \"cool\",\n },\n switcherSwatches: [\"hsl(221 83% 53%)\", \"hsl(142 71% 45%)\", \"hsl(38 92% 50%)\"],\n};\n\nconst MINIMAL_DESCRIPTOR: ThemeDescriptor = {\n id: \"minimal\",\n label: \"Minimal\",\n description: \"Monochrome, no shadow, sharp edges\",\n palette: {\n seed: \"#333333\",\n harmony: \"analogous\",\n radiusScale: \"sharp\",\n shadowIntensity: \"none\",\n neutralTemperature: \"cool\",\n },\n switcherSwatches: [\"hsl(0 0% 20%)\", \"hsl(0 0% 45%)\", \"hsl(0 0% 70%)\"],\n};\n\nconst NATURAL_DESCRIPTOR: ThemeDescriptor = {\n id: \"natural\",\n label: \"Natural\",\n description: \"Always light — sage accent, warm cream\",\n forceLightOnly: true,\n palette: {\n seed: \"#5a9376\",\n harmony: \"complementary\",\n radiusScale: \"medium\",\n shadowIntensity: \"subtle\",\n neutralTemperature: \"warm\",\n },\n switcherSwatches: [\"hsl(155 38% 45%)\", \"hsl(25 65% 55%)\", \"hsl(45 75% 50%)\"],\n};\n\nconst PRO_DESCRIPTOR: ThemeDescriptor = {\n id: \"pro\",\n label: \"Pro\",\n description: \"Neon blue accent, mono headings, ultra-dense\",\n palette: {\n seed: \"#0080ff\",\n harmony: \"tetradic\",\n radiusScale: \"sharp\",\n shadowIntensity: \"subtle\",\n neutralTemperature: \"cool\",\n },\n switcherSwatches: [\n \"hsl(210 100% 55%)\",\n \"hsl(170 90% 48%)\",\n \"hsl(290 85% 60%)\",\n ],\n};\n\nconst MONO_DARK_DESCRIPTOR: ThemeDescriptor = {\n id: \"mono-dark\",\n label: \"Mono Dark\",\n description: \"OLED-friendly black, neon cyan accent, mono headings\",\n palette: {\n seed: \"#00d4ff\",\n harmony: \"tetradic\",\n radiusScale: \"sharp\",\n shadowIntensity: \"subtle\",\n neutralTemperature: \"cool\",\n },\n switcherSwatches: [\"hsl(190 100% 50%)\", \"hsl(0 0% 8%)\", \"hsl(280 90% 65%)\"],\n};\n\nexport const THEME_REGISTRY = {\n default: DEFAULT_DESCRIPTOR,\n minimal: MINIMAL_DESCRIPTOR,\n natural: NATURAL_DESCRIPTOR,\n pro: PRO_DESCRIPTOR,\n \"mono-dark\": MONO_DARK_DESCRIPTOR,\n} as const satisfies Record<string, ThemeDescriptor>;\n\n/** Compile-time-derived union of registered preset ids. */\nexport type ThemePresetId = keyof typeof THEME_REGISTRY;\n\n/** All registered preset ids in declaration order. */\nexport const THEME_PRESET_IDS = Object.keys(THEME_REGISTRY) as ThemePresetId[];\n\n/** Look up a descriptor by id. Returns `undefined` for unknown ids. */\nexport function getThemeDescriptor(id: string): ThemeDescriptor | undefined {\n return (THEME_REGISTRY as Record<string, ThemeDescriptor>)[id];\n}\n\n/** Type guard for a known preset id. */\nexport function isThemePresetId(id: string): id is ThemePresetId {\n return id in THEME_REGISTRY;\n}\n\n/** Preset ids that suppress dark mode generation. */\nexport const FORCE_LIGHT_PRESET_IDS: ReadonlySet<ThemePresetId> = new Set(\n THEME_PRESET_IDS.filter((id) => THEME_REGISTRY[id].forceLightOnly),\n);\n\n/**\n * Map from removed preset ids to the closest surviving preset. Boot script and\n * `readStoredTheme` consult this so users with persisted `corporate`/`playful`\n * values silently land on a working preset on next page load instead of\n * showing the global default fallback for an unknown id.\n *\n * Keep this around indefinitely — localStorage values are forever and the\n * cost of two extra entries is negligible.\n */\nexport const DEPRECATED_PRESET_MIGRATIONS: Readonly<\n Record<string, ThemePresetId>\n> = Object.freeze({\n corporate: \"default\",\n playful: \"default\",\n});\n\n/**\n * Resolve a possibly-deprecated preset id to a registered one. Returns the\n * input unchanged when it is already valid; returns the migration target when\n * it is a known deprecated id; returns `null` when the id is unknown\n * altogether (caller should fall back to its default).\n */\nexport function migrateDeprecatedPresetId(\n id: string | null | undefined,\n): ThemePresetId | null {\n if (!id) return null;\n if (isThemePresetId(id)) return id;\n if (id in DEPRECATED_PRESET_MIGRATIONS) {\n return DEPRECATED_PRESET_MIGRATIONS[id]!;\n }\n return null;\n}\n","/**\n * theme-serialize.ts\n *\n * Pure helpers that turn a `ThemeSpec` into the artifacts the runtime needs:\n * resolved descriptor + mode, computed token maps for generated palettes, and\n * `<style>` CSS strings ready to be injected into `<head>`.\n *\n * Kept side-effect free so the boot script and the provider can share the\n * same logic without dragging React into the client bundle's critical path.\n */\n\nimport {\n generatePalette,\n type PaletteConfig,\n type GeneratedPalette,\n} from \"@reopt-ai/opt-palette\";\nimport {\n THEME_REGISTRY,\n isThemePresetId,\n type ThemeDescriptor,\n type ThemePresetId,\n} from \"./presets\";\nimport {\n isDescriptorSpec,\n isGeneratedSpec,\n isPresetSpec,\n type GeneratedThemeSpec,\n type ThemeOverrides,\n type ThemeSpec,\n} from \"./theme-types\";\n\n/** Element id used for the global generated theme style tag. */\nexport const GLOBAL_THEME_STYLE_ID = \"opt-global-theme\";\n\n/** localStorage key for the persisted ThemeSpec (generated mode). */\nexport const THEME_SPEC_STORAGE_KEY = \"opt-theme-spec\";\n\n/**\n * localStorage key for the cached, pre-serialized token maps that match the\n * spec under `THEME_SPEC_STORAGE_KEY`. The boot script reads this directly so\n * it can inline tokens without invoking opt-palette at startup.\n */\nexport const THEME_TOKENS_STORAGE_KEY = \"opt-theme-tokens\";\n\n/** Stable id used for unnamed generated themes. */\nexport const GENERATED_THEME_ID_DEFAULT = \"generated\";\n\n/** Cached token maps keyed by spec hash. Lazy-built per process. */\nconst generatedCache = new Map<string, GeneratedPalette>();\n\n/** Build a stable cache key for a `PaletteConfig`. */\nfunction paletteCacheKey(config: PaletteConfig): string {\n return JSON.stringify(config);\n}\n\n/** Run opt-palette with caching. */\nexport function generatePaletteCached(config: PaletteConfig): GeneratedPalette {\n const key = paletteCacheKey(config);\n let cached = generatedCache.get(key);\n if (!cached) {\n cached = generatePalette(config);\n generatedCache.set(key, cached);\n }\n return cached;\n}\n\n/** Apply sparse override maps onto a base token record. */\nfunction applyOverrides(\n base: Record<string, string>,\n overrides: ThemeOverrides | undefined,\n mode: \"light\" | \"dark\",\n): Record<string, string> {\n if (!overrides) return base;\n const merged: Record<string, string> = { ...base };\n if (overrides.shared) Object.assign(merged, overrides.shared);\n const modeOverrides = mode === \"light\" ? overrides.light : overrides.dark;\n if (modeOverrides) Object.assign(merged, modeOverrides);\n return merged;\n}\n\n/**\n * Stable id used in `<html data-theme>` and the `<style>` selector.\n * - preset → the preset id (force-light) or `${id}` / `${id}-dark`\n * - generated → `spec.id ?? \"generated\"` with `-dark` suffix when applicable\n * - descriptor → `descriptor.id` with same suffix logic\n */\nexport function getCompoundIdForSpec(\n spec: ThemeSpec,\n resolvedMode: \"light\" | \"dark\",\n forceLightOnly: boolean,\n): string {\n const baseId = baseIdFor(spec);\n if (forceLightOnly) return baseId;\n return resolvedMode === \"dark\" ? `${baseId}-dark` : baseId;\n}\n\nfunction baseIdFor(spec: ThemeSpec): string {\n if (isPresetSpec(spec)) return spec.preset;\n if (isDescriptorSpec(spec)) return spec.descriptor.id;\n return spec.id ?? GENERATED_THEME_ID_DEFAULT;\n}\n\n/** Whether the given spec emits dark variants at all. */\nexport function isForceLightSpec(spec: ThemeSpec): boolean {\n if (isPresetSpec(spec)) {\n const desc = THEME_REGISTRY[spec.preset];\n return desc?.forceLightOnly === true;\n }\n if (isDescriptorSpec(spec)) return spec.descriptor.forceLightOnly === true;\n return spec.forceLightOnly === true;\n}\n\n/** Computed token bundle for a generated theme. `dark` is `null` when force-light. */\nexport interface GeneratedTokens {\n id: string;\n light: Record<string, string>;\n dark: Record<string, string> | null;\n}\n\n/**\n * Compute the inline tokens for a `GeneratedThemeSpec` or\n * `DescriptorThemeSpec` whose descriptor carries a `palette`. Returns `null`\n * for preset specs (their tokens live in the static CSS files).\n */\nexport function computeGeneratedTokens(\n spec: ThemeSpec,\n): GeneratedTokens | null {\n if (isPresetSpec(spec)) return null;\n\n let palette: PaletteConfig | undefined;\n let overrides: ThemeOverrides | undefined;\n let id: string;\n let forceLight: boolean;\n\n if (isGeneratedSpec(spec)) {\n palette = spec.palette;\n overrides = spec.overrides;\n id = spec.id ?? GENERATED_THEME_ID_DEFAULT;\n forceLight = spec.forceLightOnly === true;\n } else {\n // descriptor spec\n palette = spec.descriptor.palette;\n overrides = spec.overrides;\n id = spec.descriptor.id;\n forceLight = spec.descriptor.forceLightOnly === true;\n }\n\n if (!palette) return null;\n\n const generated = generatePaletteCached(palette);\n return {\n id,\n light: applyOverrides(generated.light, overrides, \"light\"),\n dark: forceLight ? null : applyOverrides(generated.dark, overrides, \"dark\"),\n };\n}\n\n/**\n * Serialize a token record into a CSS rule body — `--name:value;...` — without\n * surrounding selector or braces. Values are not escaped; callers are\n * responsible for trusted input. Boot script and provider both rely on this\n * shape.\n */\nexport function serializeTokenBody(tokens: Record<string, string>): string {\n return Object.entries(tokens)\n .map(([k, v]) => `${k}:${v}`)\n .join(\";\");\n}\n\n/**\n * Build a CSS rule scoped to `[data-theme=\"<id>\"]`.\n */\nexport function serializeThemeRule(\n themeId: string,\n tokens: Record<string, string>,\n): string {\n return `[data-theme=\"${themeId}\"]{${serializeTokenBody(tokens)}}`;\n}\n\n/**\n * Build the full CSS for a generated theme style tag — covers light + dark\n * selectors based on the computed tokens. Caller chooses the id used in\n * the selector (typically the spec base id).\n */\nexport function serializeGeneratedTheme(tokens: GeneratedTokens): string {\n const { id, light, dark } = tokens;\n let css = serializeThemeRule(id, light);\n if (dark) {\n css += serializeThemeRule(`${id}-dark`, dark);\n }\n return css;\n}\n\n/**\n * Resolve a spec into its descriptor counterpart. Preset specs map to\n * `THEME_REGISTRY[preset]`. Generated specs are wrapped into a synthetic\n * descriptor so callers can treat them uniformly.\n */\nexport function resolveDescriptor(spec: ThemeSpec): ThemeDescriptor {\n if (isPresetSpec(spec)) {\n const desc = THEME_REGISTRY[spec.preset];\n if (!desc) {\n throw new Error(\n `[opt-ui] Unknown theme preset id: \"${spec.preset}\". ` +\n `Registered ids: ${Object.keys(THEME_REGISTRY).join(\", \")}`,\n );\n }\n return desc;\n }\n if (isDescriptorSpec(spec)) return spec.descriptor;\n return generatedSpecToDescriptor(spec);\n}\n\nfunction generatedSpecToDescriptor(spec: GeneratedThemeSpec): ThemeDescriptor {\n return {\n id: spec.id ?? GENERATED_THEME_ID_DEFAULT,\n label: spec.label ?? \"Custom\",\n description: \"Generated theme\",\n palette: spec.palette,\n forceLightOnly: spec.forceLightOnly,\n };\n}\n\n/**\n * Validate a `ThemeSpec` parsed from localStorage. Returns the spec on\n * success or `null` if any required field is missing or the preset id is\n * unknown. Defensive parsing — never throws.\n */\nexport function safeParseStoredSpec(json: string | null): ThemeSpec | null {\n if (!json) return null;\n try {\n const parsed = JSON.parse(json) as Partial<ThemeSpec> & {\n kind?: unknown;\n };\n if (!parsed || typeof parsed !== \"object\" || !(\"kind\" in parsed)) {\n return null;\n }\n if (parsed.kind === \"preset\") {\n const preset = (parsed as { preset?: unknown }).preset;\n if (typeof preset !== \"string\" || !isThemePresetId(preset)) return null;\n return { kind: \"preset\", preset } as ThemeSpec;\n }\n if (parsed.kind === \"generated\") {\n const palette = (parsed as { palette?: unknown }).palette;\n if (!isPaletteConfig(palette)) return null;\n const out: GeneratedThemeSpec = {\n kind: \"generated\",\n palette,\n id: typeof parsed.id === \"string\" ? parsed.id : undefined,\n label: typeof parsed.label === \"string\" ? parsed.label : undefined,\n forceLightOnly: parsed.forceLightOnly === true,\n overrides: isObjectMap(parsed.overrides)\n ? (parsed.overrides as ThemeOverrides)\n : undefined,\n };\n return out;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction isObjectMap(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isPaletteConfig(value: unknown): value is PaletteConfig {\n if (!isObjectMap(value)) return false;\n const v = value as Partial<PaletteConfig>;\n return (\n typeof v.seed === \"string\" &&\n typeof v.harmony === \"string\" &&\n typeof v.radiusScale === \"string\" &&\n typeof v.shadowIntensity === \"string\" &&\n typeof v.neutralTemperature === \"string\"\n );\n}\n\n/** Cached token payload as written to `opt-theme-tokens`. */\nexport interface StoredTokenCache {\n id: string;\n light: Record<string, string>;\n dark?: Record<string, string>;\n}\n\n/**\n * Parse the cached token payload from localStorage. Returns `null` on any\n * shape mismatch — caller will recompute from the spec.\n */\nexport function safeParseStoredTokens(\n json: string | null,\n): StoredTokenCache | null {\n if (!json) return null;\n try {\n const parsed = JSON.parse(json) as Partial<StoredTokenCache>;\n if (\n !parsed ||\n typeof parsed.id !== \"string\" ||\n !isObjectMap(parsed.light)\n ) {\n return null;\n }\n return {\n id: parsed.id,\n light: parsed.light as Record<string, string>,\n dark: isObjectMap(parsed.dark)\n ? (parsed.dark as Record<string, string>)\n : undefined,\n };\n } catch {\n return null;\n }\n}\n\n/** Build the storage payload that pairs with `opt-theme-spec`. */\nexport function buildTokenCache(tokens: GeneratedTokens): StoredTokenCache {\n return {\n id: tokens.id,\n light: tokens.light,\n dark: tokens.dark ?? undefined,\n };\n}\n\n/** Strip caches — used by tests and when switching back to a preset spec. */\nexport function clearGeneratedCache(): void {\n generatedCache.clear();\n}\n\n/** Re-export descriptor lookups for consumers of the serializer. */\nexport { THEME_REGISTRY, isThemePresetId };\nexport type { ThemePresetId };\n","import {\n DEPRECATED_PRESET_MIGRATIONS,\n FORCE_LIGHT_PRESET_IDS,\n THEME_PRESET_IDS,\n migrateDeprecatedPresetId,\n type ThemePresetId,\n} from \"./presets\";\nimport {\n GLOBAL_THEME_STYLE_ID,\n THEME_SPEC_STORAGE_KEY,\n THEME_TOKENS_STORAGE_KEY,\n} from \"./theme-serialize\";\n\n/**\n * Registered preset id list. Preferred for runtime iteration.\n * @deprecated Prefer `THEME_PRESET_IDS` from `./presets`.\n */\nexport const THEME_PRESET_VALUES: readonly ThemePresetId[] = THEME_PRESET_IDS;\n\n/** Theme preset id — alias of `ThemePresetId` from the registry. */\nexport type ThemePreset = ThemePresetId;\n\nexport const THEME_MODE_VALUES = [\"light\", \"dark\", \"system\"] as const;\n/** Type definition for theme mode. */\nexport type ThemeMode = (typeof THEME_MODE_VALUES)[number];\n\n/** Runtime constraints for apps that expose only a subset of opt-ui theming. */\nexport interface ThemeRuntimeOptions {\n /**\n * Force the root theme to one preset regardless of stored preset/generated\n * theme state. Mode is still resolved normally unless constrained.\n */\n lockedPreset?: ThemePreset;\n /**\n * Restrict mode reads/writes to a smaller set, e.g. `[\"light\", \"dark\"]`.\n * The first value becomes the fallback when the stored/default mode is not\n * allowed.\n */\n allowedModes?: readonly ThemeMode[];\n}\n\n/**\n * Compound theme value applied to `<html data-theme>`. Derived from the\n * registered preset ids: `<id>` (light) or `<id>-dark` (dark). Force-light\n * presets only emit the bare id form.\n */\nexport type CompoundTheme = `${ThemePreset}` | `${ThemePreset}-dark`;\n\nexport const LEGACY_THEME_STORAGE_KEY = \"opt-theme\";\nexport const THEME_PRESET_STORAGE_KEY = \"opt-preset\";\nexport const THEME_MODE_STORAGE_KEY = \"opt-mode\";\nexport const DARK_MQ = \"(prefers-color-scheme: dark)\";\n\n/**\n * Presets that force light mode regardless of system dark/light preference.\n * Mirrors `FORCE_LIGHT_PRESET_IDS` from `./presets` — kept as alias for\n * existing imports.\n */\nexport const FORCE_LIGHT_PRESETS: ReadonlySet<ThemePreset> =\n FORCE_LIGHT_PRESET_IDS;\n\n/** All valid compound theme names. Useful for static enumeration. */\nexport const ALL_COMPOUND_THEMES: CompoundTheme[] = THEME_PRESET_IDS.flatMap(\n (id) =>\n FORCE_LIGHT_PRESET_IDS.has(id)\n ? [id satisfies CompoundTheme]\n : [id satisfies CompoundTheme, `${id}-dark` satisfies CompoundTheme],\n);\n\ninterface ThemeStorageReader {\n getItem(key: string): string | null;\n}\n\ninterface ThemeStorageMutator extends ThemeStorageReader {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nfunction hasStorageMutation(\n storage: ThemeStorageReader,\n): storage is ThemeStorageMutator {\n return (\n typeof (storage as ThemeStorageMutator).setItem === \"function\" &&\n typeof (storage as ThemeStorageMutator).removeItem === \"function\"\n );\n}\n\n/** Handles is theme preset. */\nexport function isThemePreset(value: string | null): value is ThemePreset {\n return (\n typeof value === \"string\" &&\n THEME_PRESET_VALUES.includes(value as ThemePreset)\n );\n}\n\n/** Handles is theme mode. */\nexport function isThemeMode(value: string | null): value is ThemeMode {\n return (\n typeof value === \"string\" && THEME_MODE_VALUES.includes(value as ThemeMode)\n );\n}\n\n/** Compute compound theme name from preset + resolved mode. */\nexport function getCompoundTheme(\n preset: ThemePreset,\n resolvedMode: \"light\" | \"dark\",\n): CompoundTheme {\n if (FORCE_LIGHT_PRESETS.has(preset)) return preset as CompoundTheme;\n return (resolvedMode === \"dark\" ? `${preset}-dark` : preset) as CompoundTheme;\n}\n\n/** Handles resolve theme mode. */\nexport function resolveThemeMode(\n preset: ThemePreset,\n mode: ThemeMode,\n systemDark: boolean,\n): \"light\" | \"dark\" {\n if (FORCE_LIGHT_PRESETS.has(preset)) return \"light\";\n if (mode === \"system\") return systemDark ? \"dark\" : \"light\";\n return mode;\n}\n\n/** Handles read stored theme. */\nexport function readStoredTheme(\n storage: ThemeStorageReader,\n defaultPreset: ThemePreset,\n defaultMode: ThemeMode,\n options: { migrateLegacy?: boolean } = {},\n): { preset: ThemePreset; mode: ThemeMode } {\n const legacy = storage.getItem(LEGACY_THEME_STORAGE_KEY);\n const storedPreset = storage.getItem(THEME_PRESET_STORAGE_KEY);\n const storedMode = storage.getItem(THEME_MODE_STORAGE_KEY);\n\n let preset = defaultPreset;\n // Legacy `opt-theme` key — migrate to `opt-preset` when valid (or deprecated).\n if (typeof legacy === \"string\") {\n const migratedLegacy = migrateDeprecatedPresetId(legacy);\n if (migratedLegacy) {\n if (options.migrateLegacy && hasStorageMutation(storage)) {\n storage.setItem(THEME_PRESET_STORAGE_KEY, migratedLegacy);\n storage.removeItem(LEGACY_THEME_STORAGE_KEY);\n }\n preset = migratedLegacy;\n }\n } else if (typeof storedPreset === \"string\") {\n const migrated = migrateDeprecatedPresetId(storedPreset);\n if (migrated) {\n preset = migrated;\n // If the stored value was a deprecated id, silently rewrite it to the\n // migration target so the next read is a hit on the registered id.\n if (\n hasStorageMutation(storage) &&\n migrated !== storedPreset &&\n storedPreset in DEPRECATED_PRESET_MIGRATIONS\n ) {\n storage.setItem(THEME_PRESET_STORAGE_KEY, migrated);\n }\n }\n }\n\n const mode = isThemeMode(storedMode) ? storedMode : defaultMode;\n return { preset, mode };\n}\n\n/** Normalize a mode against an optional allowed-mode list. */\nexport function coerceThemeMode(\n mode: ThemeMode,\n allowedModes: readonly ThemeMode[] | undefined,\n fallback: ThemeMode = \"system\",\n): ThemeMode {\n if (!allowedModes || allowedModes.length === 0) return mode;\n if (allowedModes.includes(mode)) return mode;\n if (allowedModes.includes(fallback)) return fallback;\n return allowedModes[0] ?? fallback;\n}\n\n/**\n * Build the inline `<head>` boot script that paints the correct theme before\n * React mounts. Two paths:\n *\n * 1. **Generated** — when both `opt-theme-spec` (kind: \"generated\") and\n * `opt-theme-tokens` are present and well-formed, a `<style id=\"…\">` tag\n * is inserted with the cached tokens and `<html data-theme>` is set to the\n * generated id (with optional `-dark` suffix). This bypasses opt-palette\n * at startup so first paint is instant.\n * 2. **Preset (legacy)** — falls through to the existing preset selector,\n * including legacy `opt-theme` → `opt-preset` migration.\n *\n * Returned as a single minified line; intended for `dangerouslySetInnerHTML`.\n */\nexport function createThemeBootScript(\n defaultPreset: ThemePreset = \"default\",\n defaultMode: ThemeMode = \"system\",\n options: ThemeRuntimeOptions = {},\n): string {\n const presetValues = JSON.stringify(THEME_PRESET_VALUES);\n const modeValues = JSON.stringify(THEME_MODE_VALUES);\n const allowedModes = options.allowedModes?.length\n ? options.allowedModes\n : THEME_MODE_VALUES;\n const allowedModeValues = JSON.stringify(allowedModes);\n const forceLightPresets = JSON.stringify(Array.from(FORCE_LIGHT_PRESETS));\n const deprecatedMap = JSON.stringify(DEPRECATED_PRESET_MIGRATIONS);\n const lockedPreset = options.lockedPreset;\n const resolvedDefaultPreset = lockedPreset ?? defaultPreset;\n const defaultPresetLiteral = JSON.stringify(resolvedDefaultPreset);\n const defaultModeLiteral = JSON.stringify(\n coerceThemeMode(defaultMode, allowedModes, allowedModes[0] ?? \"system\"),\n );\n const lockedPresetLiteral = JSON.stringify(lockedPreset ?? null);\n const styleId = JSON.stringify(GLOBAL_THEME_STYLE_ID);\n const specKey = JSON.stringify(THEME_SPEC_STORAGE_KEY);\n const tokensKey = JSON.stringify(THEME_TOKENS_STORAGE_KEY);\n const presetKey = JSON.stringify(THEME_PRESET_STORAGE_KEY);\n const modeKey = JSON.stringify(THEME_MODE_STORAGE_KEY);\n const legacyKey = JSON.stringify(LEGACY_THEME_STORAGE_KEY);\n const darkMq = JSON.stringify(DARK_MQ);\n\n // Multi-line for readability here; collapsed on emit.\n // `mp(v)` resolves a stored preset id: returns the value itself when\n // registered, the migration target when deprecated, or null otherwise.\n const source = `\ntry {\n var pv=${presetValues},mv=${modeValues},am=${allowedModeValues},fp=${forceLightPresets},dm=${deprecatedMap},lk=${lockedPresetLiteral};\n function mp(v){if(typeof v!==\"string\")return null;if(pv.indexOf(v)>=0)return v;return Object.prototype.hasOwnProperty.call(dm,v)?dm[v]:null;}\n var p=${defaultPresetLiteral};\n if(!lk){\n var lp=localStorage.getItem(${legacyKey}),sp=localStorage.getItem(${presetKey});\n var lpm=mp(lp),spm=mp(sp);\n p=lpm||spm||${defaultPresetLiteral};\n if(lpm){localStorage.setItem(${presetKey},lpm);localStorage.removeItem(${legacyKey});}\n else if(sp&&spm&&sp!==spm){localStorage.setItem(${presetKey},spm);}\n }\n var sm=localStorage.getItem(${modeKey});\n var m=(typeof sm===\"string\"&&mv.indexOf(sm)>=0&&am.indexOf(sm)>=0)?sm:${defaultModeLiteral};\n var spec=null,tk=null;\n if(!lk){try{var sj=localStorage.getItem(${specKey});if(sj)spec=JSON.parse(sj);}catch(e){}}\n if(!lk){try{var tj=localStorage.getItem(${tokensKey});if(tj)tk=JSON.parse(tj);}catch(e){}}\n if(!lk&&spec&&spec.kind===\"generated\"&&tk&&typeof tk.id===\"string\"&&tk.light&&typeof tk.light===\"object\"){\n var fld=spec.forceLightOnly===true;\n var dk=!fld&&(m===\"dark\"||(m===\"system\"&&matchMedia(${darkMq}).matches));\n var tt=(dk&&tk.dark)?tk.dark:tk.light;\n var cid=(dk&&tk.dark)?(tk.id+\"-dark\"):tk.id;\n var css=\"\";\n for(var k in tt){if(Object.prototype.hasOwnProperty.call(tt,k))css+=k+\":\"+tt[k]+\";\"}\n var st=document.getElementById(${styleId});\n if(!st){st=document.createElement(\"style\");st.id=${styleId};document.head.appendChild(st);}\n st.textContent='[data-theme=\"'+cid+'\"]{'+css+'}';\n document.documentElement.setAttribute(\"data-theme\",cid);\n } else {\n var d=fp.indexOf(p)<0&&(m===\"dark\"||(m===\"system\"&&matchMedia(${darkMq}).matches));\n document.documentElement.setAttribute(\"data-theme\",d?p+\"-dark\":p);\n }\n} catch(e){}`;\n\n // Collapse leading newlines + indentation. Safe because all string\n // literals in the source are double-quoted with no embedded newlines.\n return source.replace(/\\n\\s*/g, \"\");\n}\n"],"mappings":"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"}
|