@sanity/themer 0.0.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/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/_chunks/mix.cjs +52 -0
- package/dist/_chunks/mix.cjs.map +1 -0
- package/dist/_chunks/mix.js +42 -0
- package/dist/_chunks/mix.js.map +1 -0
- package/dist/_chunks/presets.cjs +215 -0
- package/dist/_chunks/presets.cjs.map +1 -0
- package/dist/_chunks/presets.js +201 -0
- package/dist/_chunks/presets.js.map +1 -0
- package/dist/_chunks/types.d.cts +26 -0
- package/dist/_chunks/types.d.cts.map +1 -0
- package/dist/_chunks/types.d.ts +26 -0
- package/dist/_chunks/types.d.ts.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/legacy.cjs +1199 -0
- package/dist/legacy.cjs.map +1 -0
- package/dist/legacy.d.cts +166 -0
- package/dist/legacy.d.cts.map +1 -0
- package/dist/legacy.d.ts +166 -0
- package/dist/legacy.d.ts.map +1 -0
- package/dist/legacy.js +1199 -0
- package/dist/legacy.js.map +1 -0
- package/dist/tool.cjs +407 -0
- package/dist/tool.cjs.map +1 -0
- package/dist/tool.d.cts +45 -0
- package/dist/tool.d.cts.map +1 -0
- package/dist/tool.d.ts +45 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +415 -0
- package/dist/tool.js.map +1 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanity.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @sanity/themer
|
|
2
|
+
|
|
3
|
+
Generate [Sanity Studio](https://www.sanity.io/studio) themes, and preview them with a Studio tool.
|
|
4
|
+
|
|
5
|
+
This package is the npm migration path off the hosted Themer service ([themer.sanity.build](https://themer.sanity.build)) — the generators run locally, so Studio configs no longer need to import modules from a hosted URL.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @sanity/themer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Generate a theme from a handful of colors:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// sanity.config.ts
|
|
17
|
+
import {createTheme} from '@sanity/themer'
|
|
18
|
+
import {defineConfig} from 'sanity'
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
theme: createTheme({primary: '#2276fc'}),
|
|
22
|
+
// ...rest of the config
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Every color is optional — omitted colors keep their default Sanity ramps:
|
|
27
|
+
|
|
28
|
+
| Color | Drives |
|
|
29
|
+
| ---------- | --------------------------------------- |
|
|
30
|
+
| `primary` | Buttons, focus rings, links, selections |
|
|
31
|
+
| `gray` | Neutral surfaces, borders and text |
|
|
32
|
+
| `positive` | Success accents |
|
|
33
|
+
| `caution` | Warning accents |
|
|
34
|
+
| `critical` | Errors and destructive actions |
|
|
35
|
+
| `lightest` | Light mode background |
|
|
36
|
+
| `darkest` | Dark mode background |
|
|
37
|
+
|
|
38
|
+
Preset themes are available from the `presets` export:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import {createTheme, presets} from '@sanity/themer'
|
|
42
|
+
|
|
43
|
+
const verdant = presets.find((preset) => preset.slug === 'verdant')
|
|
44
|
+
const theme = createTheme(verdant.colors)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For customization beyond colors, `themeConfigFromColors` maps colors to a [`@sanity/ui`](https://github.com/sanity-io/ui) `ThemeConfig` to pass to `buildTheme`:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {themeConfigFromColors} from '@sanity/themer'
|
|
51
|
+
import {buildTheme} from '@sanity/ui/theme'
|
|
52
|
+
|
|
53
|
+
const theme = buildTheme({
|
|
54
|
+
...themeConfigFromColors({primary: '#2276fc'}),
|
|
55
|
+
// ...other ThemeConfig properties
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## The Studio tool
|
|
60
|
+
|
|
61
|
+
The `themerTool` plugin previews generated themes live on your own Studio:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// sanity.config.ts
|
|
65
|
+
import {themerTool} from '@sanity/themer/tool'
|
|
66
|
+
import {defineConfig} from 'sanity'
|
|
67
|
+
|
|
68
|
+
export default defineConfig({
|
|
69
|
+
plugins: [themerTool()],
|
|
70
|
+
// ...rest of the config
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A color wheel toggle in the navbar opens the themer sidebar next to the active tool, so you can browse around the Studio while tweaking presets and colors. Toggle between light and dark mode with the regular appearance menu — the preview follows it. When the theme looks right, copy the generated `createTheme` snippet into the Studio config.
|
|
75
|
+
|
|
76
|
+
If the Studio is already themed, pass the same colors to the plugin so the sidebar starts editing from them:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import {createTheme} from '@sanity/themer'
|
|
80
|
+
import {themerTool} from '@sanity/themer/tool'
|
|
81
|
+
import {defineConfig} from 'sanity'
|
|
82
|
+
|
|
83
|
+
const colors = {primary: '#2276fc'}
|
|
84
|
+
|
|
85
|
+
export default defineConfig({
|
|
86
|
+
theme: createTheme(colors),
|
|
87
|
+
plugins: [themerTool({colors})],
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Migrating from themer.sanity.build
|
|
92
|
+
|
|
93
|
+
`@sanity/themer/legacy` generates the exact same colors as the hosted service, with the same `createTheme`, `hues` and `theme` exports that `https://themer.sanity.build/api/hues` served. Replace the URL import with `createThemeFromUrl` and the URL as a string:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// Before:
|
|
97
|
+
import {theme} from 'https://themer.sanity.build/api/hues?preset=verdant&primary=22fca8'
|
|
98
|
+
|
|
99
|
+
// After:
|
|
100
|
+
import {createThemeFromUrl} from '@sanity/themer/legacy'
|
|
101
|
+
|
|
102
|
+
const theme = createThemeFromUrl(
|
|
103
|
+
'https://themer.sanity.build/api/hues?preset=verdant&primary=22fca8',
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Configs that used `createTheme` and `hues` from the URL import work the same way with `parseHuesFromUrl`:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// Before:
|
|
111
|
+
// import {createTheme, hues} from 'https://themer.sanity.build/api/hues?preset=verdant'
|
|
112
|
+
|
|
113
|
+
// After:
|
|
114
|
+
import {createTheme, parseHuesFromUrl} from '@sanity/themer/legacy'
|
|
115
|
+
|
|
116
|
+
const hues = parseHuesFromUrl('https://themer.sanity.build/api/hues?preset=verdant')
|
|
117
|
+
|
|
118
|
+
export default defineConfig({
|
|
119
|
+
theme: createTheme({...hues, primary: {...hues.primary, mid: '#22fca8'}}),
|
|
120
|
+
// ...rest of the config
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The hosted presets are also available directly:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import {createTheme, getPreset} from '@sanity/themer/legacy'
|
|
128
|
+
|
|
129
|
+
const theme = createTheme(getPreset('verdant').hues)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Once migrated, remove any `themer.d.ts` module declarations and `urlImports` config that the URL imports needed.
|
|
133
|
+
|
|
134
|
+
The legacy API is frozen — it exists to make leaving the hosted service painless. New themes should use `createTheme` from the `@sanity/themer` root export instead.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT © Sanity.io — see [LICENSE](https://github.com/sanity-io/ui/blob/main/LICENSE)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color mixing that is byte-for-byte compatible with the `mix` function from
|
|
3
|
+
* `polished`, which the hosted Themer service (themer.sanity.build) used to
|
|
4
|
+
* interpolate hue tints. Only opaque hex colors are supported, which is all
|
|
5
|
+
* the theme generators ever pass.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
function mix(weight, color, otherColor) {
|
|
10
|
+
if (weight === 0) return otherColor;
|
|
11
|
+
let [r1, g1, b1] = hexToRgb(color), [r2, g2, b2] = hexToRgb(otherColor), w2 = 1 - weight;
|
|
12
|
+
return reduceHex(`#${channelToHex(Math.floor(r1 * weight + r2 * w2))}${channelToHex(Math.floor(g1 * weight + g2 * w2))}${channelToHex(Math.floor(b1 * weight + b2 * w2))}`);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Matches `#abc` and `#aabbcc` hex colors (the only formats the theme
|
|
16
|
+
* generators accept), same as the hosted Themer service did.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function isColor(input) {
|
|
21
|
+
return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input);
|
|
22
|
+
}
|
|
23
|
+
function hexToRgb(color) {
|
|
24
|
+
if (!isColor(color)) throw TypeError(`Invalid color: ${JSON.stringify(color)} — expected a hex color`);
|
|
25
|
+
let hex = color.length === 4 ? `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color.slice(1);
|
|
26
|
+
return [
|
|
27
|
+
parseInt(hex.slice(0, 2), 16),
|
|
28
|
+
parseInt(hex.slice(2, 4), 16),
|
|
29
|
+
parseInt(hex.slice(4, 6), 16)
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function channelToHex(value) {
|
|
33
|
+
let hex = value.toString(16);
|
|
34
|
+
return hex.length === 1 ? `0${hex}` : hex;
|
|
35
|
+
}
|
|
36
|
+
/** Shortens `#aabbcc` to `#abc` when possible, same as `polished` does */
|
|
37
|
+
function reduceHex(hex) {
|
|
38
|
+
return hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6] ? `#${hex[1]}${hex[3]}${hex[5]}` : hex;
|
|
39
|
+
}
|
|
40
|
+
Object.defineProperty(exports, "n", {
|
|
41
|
+
enumerable: !0,
|
|
42
|
+
get: function() {
|
|
43
|
+
return mix;
|
|
44
|
+
}
|
|
45
|
+
}), Object.defineProperty(exports, "t", {
|
|
46
|
+
enumerable: !0,
|
|
47
|
+
get: function() {
|
|
48
|
+
return isColor;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=mix.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mix.cjs","names":[],"sources":["../../src/lib/mix.ts"],"sourcesContent":["/**\n * Color mixing that is byte-for-byte compatible with the `mix` function from\n * `polished`, which the hosted Themer service (themer.sanity.build) used to\n * interpolate hue tints. Only opaque hex colors are supported, which is all\n * the theme generators ever pass.\n *\n * @internal\n */\nexport function mix(weight: number, color: string, otherColor: string): string {\n // `polished` returns the other color as-is when the weight is 0\n if (weight === 0) return otherColor\n\n const [r1, g1, b1] = hexToRgb(color)\n const [r2, g2, b2] = hexToRgb(otherColor)\n\n const w2 = 1 - weight\n\n return reduceHex(\n `#${channelToHex(Math.floor(r1 * weight + r2 * w2))}${channelToHex(\n Math.floor(g1 * weight + g2 * w2),\n )}${channelToHex(Math.floor(b1 * weight + b2 * w2))}`,\n )\n}\n\n/**\n * Matches `#abc` and `#aabbcc` hex colors (the only formats the theme\n * generators accept), same as the hosted Themer service did.\n *\n * @internal\n */\nexport function isColor(input: string): boolean {\n return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input)\n}\n\nfunction hexToRgb(color: string): [number, number, number] {\n if (!isColor(color)) {\n throw new TypeError(`Invalid color: ${JSON.stringify(color)} — expected a hex color`)\n }\n\n const hex =\n color.length === 4\n ? `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`\n : color.slice(1)\n\n return [\n parseInt(hex.slice(0, 2), 16),\n parseInt(hex.slice(2, 4), 16),\n parseInt(hex.slice(4, 6), 16),\n ]\n}\n\nfunction channelToHex(value: number): string {\n const hex = value.toString(16)\n\n return hex.length === 1 ? `0${hex}` : hex\n}\n\n/** Shortens `#aabbcc` to `#abc` when possible, same as `polished` does */\nfunction reduceHex(hex: string): string {\n if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {\n return `#${hex[1]}${hex[3]}${hex[5]}`\n }\n\n return hex\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,IAAI,QAAgB,OAAe,YAA4B;CAE7E,IAAI,WAAW,GAAG,OAAO;CAEzB,IAAM,CAAC,IAAI,IAAI,MAAM,SAAS,KAAK,GAC7B,CAAC,IAAI,IAAI,MAAM,SAAS,UAAU,GAElC,KAAK,IAAI;CAEf,OAAO,UACL,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,IAAI,aACpD,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAClC,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,GACpD;AACF;;;;;;;AAQA,SAAgB,QAAQ,OAAwB;CAC9C,OAAO,2BAA2B,KAAK,KAAK;AAC9C;AAEA,SAAS,SAAS,OAAyC;CACzD,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAU,UAAU,kBAAkB,KAAK,UAAU,KAAK,EAAE,wBAAwB;CAGtF,IAAM,MACJ,MAAM,WAAW,IACb,GAAG,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,OAChE,MAAM,MAAM,CAAC;CAEnB,OAAO;EACL,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAC9B;AACF;AAEA,SAAS,aAAa,OAAuB;CAC3C,IAAM,MAAM,MAAM,SAAS,EAAE;CAE7B,OAAO,IAAI,WAAW,IAAI,IAAI,QAAQ;AACxC;;AAGA,SAAS,UAAU,KAAqB;CAKtC,OAJI,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,KACpD,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,OAG5B;AACT"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color mixing that is byte-for-byte compatible with the `mix` function from
|
|
3
|
+
* `polished`, which the hosted Themer service (themer.sanity.build) used to
|
|
4
|
+
* interpolate hue tints. Only opaque hex colors are supported, which is all
|
|
5
|
+
* the theme generators ever pass.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
function mix(weight, color, otherColor) {
|
|
10
|
+
if (weight === 0) return otherColor;
|
|
11
|
+
let [r1, g1, b1] = hexToRgb(color), [r2, g2, b2] = hexToRgb(otherColor), w2 = 1 - weight;
|
|
12
|
+
return reduceHex(`#${channelToHex(Math.floor(r1 * weight + r2 * w2))}${channelToHex(Math.floor(g1 * weight + g2 * w2))}${channelToHex(Math.floor(b1 * weight + b2 * w2))}`);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Matches `#abc` and `#aabbcc` hex colors (the only formats the theme
|
|
16
|
+
* generators accept), same as the hosted Themer service did.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function isColor(input) {
|
|
21
|
+
return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input);
|
|
22
|
+
}
|
|
23
|
+
function hexToRgb(color) {
|
|
24
|
+
if (!isColor(color)) throw TypeError(`Invalid color: ${JSON.stringify(color)} — expected a hex color`);
|
|
25
|
+
let hex = color.length === 4 ? `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color.slice(1);
|
|
26
|
+
return [
|
|
27
|
+
parseInt(hex.slice(0, 2), 16),
|
|
28
|
+
parseInt(hex.slice(2, 4), 16),
|
|
29
|
+
parseInt(hex.slice(4, 6), 16)
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function channelToHex(value) {
|
|
33
|
+
let hex = value.toString(16);
|
|
34
|
+
return hex.length === 1 ? `0${hex}` : hex;
|
|
35
|
+
}
|
|
36
|
+
/** Shortens `#aabbcc` to `#abc` when possible, same as `polished` does */
|
|
37
|
+
function reduceHex(hex) {
|
|
38
|
+
return hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6] ? `#${hex[1]}${hex[3]}${hex[5]}` : hex;
|
|
39
|
+
}
|
|
40
|
+
export { mix as n, isColor as t };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=mix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mix.js","names":[],"sources":["../../src/lib/mix.ts"],"sourcesContent":["/**\n * Color mixing that is byte-for-byte compatible with the `mix` function from\n * `polished`, which the hosted Themer service (themer.sanity.build) used to\n * interpolate hue tints. Only opaque hex colors are supported, which is all\n * the theme generators ever pass.\n *\n * @internal\n */\nexport function mix(weight: number, color: string, otherColor: string): string {\n // `polished` returns the other color as-is when the weight is 0\n if (weight === 0) return otherColor\n\n const [r1, g1, b1] = hexToRgb(color)\n const [r2, g2, b2] = hexToRgb(otherColor)\n\n const w2 = 1 - weight\n\n return reduceHex(\n `#${channelToHex(Math.floor(r1 * weight + r2 * w2))}${channelToHex(\n Math.floor(g1 * weight + g2 * w2),\n )}${channelToHex(Math.floor(b1 * weight + b2 * w2))}`,\n )\n}\n\n/**\n * Matches `#abc` and `#aabbcc` hex colors (the only formats the theme\n * generators accept), same as the hosted Themer service did.\n *\n * @internal\n */\nexport function isColor(input: string): boolean {\n return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input)\n}\n\nfunction hexToRgb(color: string): [number, number, number] {\n if (!isColor(color)) {\n throw new TypeError(`Invalid color: ${JSON.stringify(color)} — expected a hex color`)\n }\n\n const hex =\n color.length === 4\n ? `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`\n : color.slice(1)\n\n return [\n parseInt(hex.slice(0, 2), 16),\n parseInt(hex.slice(2, 4), 16),\n parseInt(hex.slice(4, 6), 16),\n ]\n}\n\nfunction channelToHex(value: number): string {\n const hex = value.toString(16)\n\n return hex.length === 1 ? `0${hex}` : hex\n}\n\n/** Shortens `#aabbcc` to `#abc` when possible, same as `polished` does */\nfunction reduceHex(hex: string): string {\n if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {\n return `#${hex[1]}${hex[3]}${hex[5]}`\n }\n\n return hex\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,IAAI,QAAgB,OAAe,YAA4B;CAE7E,IAAI,WAAW,GAAG,OAAO;CAEzB,IAAM,CAAC,IAAI,IAAI,MAAM,SAAS,KAAK,GAC7B,CAAC,IAAI,IAAI,MAAM,SAAS,UAAU,GAElC,KAAK,IAAI;CAEf,OAAO,UACL,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,IAAI,aACpD,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAClC,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS,KAAK,EAAE,CAAC,GACpD;AACF;;;;;;;AAQA,SAAgB,QAAQ,OAAwB;CAC9C,OAAO,2BAA2B,KAAK,KAAK;AAC9C;AAEA,SAAS,SAAS,OAAyC;CACzD,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAU,UAAU,kBAAkB,KAAK,UAAU,KAAK,EAAE,wBAAwB;CAGtF,IAAM,MACJ,MAAM,WAAW,IACb,GAAG,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,OAChE,MAAM,MAAM,CAAC;CAEnB,OAAO;EACL,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAC9B;AACF;AAEA,SAAS,aAAa,OAAuB;CAC3C,IAAM,MAAM,MAAM,SAAS,EAAE;CAE7B,OAAO,IAAI,WAAW,IAAI,IAAI,QAAQ;AACxC;;AAGA,SAAS,UAAU,KAAqB;CAKtC,OAJI,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,KACpD,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,OAG5B;AACT"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
const require_mix = require("./mix.cjs");
|
|
2
|
+
let _sanity_color = require("@sanity/color"), _sanity_ui_theme = require("@sanity/ui/theme");
|
|
3
|
+
/** The palette hue that each themeable color drives */
|
|
4
|
+
const HUE_MAPPING = [
|
|
5
|
+
["gray", "gray"],
|
|
6
|
+
["primary", "blue"],
|
|
7
|
+
["positive", "green"],
|
|
8
|
+
["caution", "yellow"],
|
|
9
|
+
["critical", "red"]
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Maps themeable colors to a `@sanity/ui` `ThemeConfig`, for when a theme
|
|
13
|
+
* needs further customization than `createTheme` exposes:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import {themeConfigFromColors} from '@sanity/themer'
|
|
17
|
+
* import {buildTheme} from '@sanity/ui/theme'
|
|
18
|
+
*
|
|
19
|
+
* const theme = buildTheme({
|
|
20
|
+
* ...themeConfigFromColors({primary: '#2276fc'}),
|
|
21
|
+
* // ...other ThemeConfig properties
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
function themeConfigFromColors(colors) {
|
|
28
|
+
for (let [key, value] of Object.entries(colors)) if (typeof value == "string" && !require_mix.t(value)) throw TypeError(`Invalid color for ${JSON.stringify(key)}: ${JSON.stringify(value)} — expected a hex color`);
|
|
29
|
+
let anchored = !!(colors.lightest || colors.darkest);
|
|
30
|
+
if (!anchored && !HUE_MAPPING.some(([option]) => colors[option])) return {};
|
|
31
|
+
let lightest = (colors.lightest ?? _sanity_color.color.white.hex).toLowerCase(), darkest = (colors.darkest ?? _sanity_color.color.black.hex).toLowerCase(), palette = {
|
|
32
|
+
..._sanity_color.color,
|
|
33
|
+
black: darkest,
|
|
34
|
+
white: lightest
|
|
35
|
+
};
|
|
36
|
+
for (let [option, hueKey] of HUE_MAPPING) {
|
|
37
|
+
let mid = colors[option]?.toLowerCase();
|
|
38
|
+
(mid || anchored) && (palette[hueKey] = buildHueTints({
|
|
39
|
+
mid: mid ?? _sanity_color.color[hueKey][500].hex,
|
|
40
|
+
lightest,
|
|
41
|
+
darkest
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
return { palette };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generates a Sanity Studio theme from a handful of colors:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* import {createTheme} from '@sanity/themer'
|
|
51
|
+
* import {defineConfig} from 'sanity'
|
|
52
|
+
*
|
|
53
|
+
* export default defineConfig({
|
|
54
|
+
* theme: createTheme({primary: '#2276fc'}),
|
|
55
|
+
* // ...rest of the config
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Calling it without options returns the default Sanity Studio theme.
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
function createTheme(colors = {}) {
|
|
64
|
+
return (0, _sanity_ui_theme.buildTheme)(themeConfigFromColors(colors));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Interpolates a full tint ramp (50–950) from a mid color placed at the 500
|
|
68
|
+
* tint, sliding towards `lightest` and `darkest` at the extremes.
|
|
69
|
+
*/
|
|
70
|
+
function buildHueTints(options) {
|
|
71
|
+
let { mid, lightest, darkest } = options;
|
|
72
|
+
return _sanity_color.COLOR_TINTS.reduce((tints, tintKey) => {
|
|
73
|
+
let tint = Number(tintKey);
|
|
74
|
+
return tint === 500 ? tints[tintKey] = mid : tint < 500 ? tints[tintKey] = require_mix.n(tint / 500, mid, lightest) : tints[tintKey] = require_mix.n((tint - 500) / 500, darkest, mid), tints;
|
|
75
|
+
}, {});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Preset themes, carried over from the hosted Themer service
|
|
79
|
+
* (themer.sanity.build) and re-expressed as `createTheme` colors.
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* import {createTheme, presets} from '@sanity/themer'
|
|
83
|
+
*
|
|
84
|
+
* const verdant = presets.find((preset) => preset.slug === 'verdant')
|
|
85
|
+
* const theme = createTheme(verdant.colors)
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @public
|
|
89
|
+
*/
|
|
90
|
+
const presets = [
|
|
91
|
+
{
|
|
92
|
+
slug: "default",
|
|
93
|
+
title: "Studio",
|
|
94
|
+
colors: {}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
slug: "dew",
|
|
98
|
+
title: "Dew",
|
|
99
|
+
colors: {
|
|
100
|
+
gray: "#5e63b4",
|
|
101
|
+
primary: "#d1a308",
|
|
102
|
+
positive: "#43d675",
|
|
103
|
+
caution: "#fb9f24",
|
|
104
|
+
critical: "#f03e2f",
|
|
105
|
+
lightest: "#fcfcfd",
|
|
106
|
+
darkest: "#0d0d15"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
slug: "pink-synth",
|
|
111
|
+
title: "Pink Synth",
|
|
112
|
+
colors: {
|
|
113
|
+
gray: "#8b6584",
|
|
114
|
+
primary: "#ec4899",
|
|
115
|
+
positive: "#10b981",
|
|
116
|
+
caution: "#fde047",
|
|
117
|
+
critical: "#fe3459",
|
|
118
|
+
lightest: "#f7f2f5",
|
|
119
|
+
darkest: "#171721"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
slug: "pixel-art",
|
|
124
|
+
title: "Pixel Art",
|
|
125
|
+
colors: {
|
|
126
|
+
gray: "#57619c",
|
|
127
|
+
primary: "#f10784",
|
|
128
|
+
positive: "#43d675",
|
|
129
|
+
caution: "#fbd024",
|
|
130
|
+
lightest: "#fcfcfd",
|
|
131
|
+
darkest: "#0d0e15"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
slug: "retro-colonial",
|
|
136
|
+
title: "Retro Colonial",
|
|
137
|
+
colors: {
|
|
138
|
+
gray: "#8bb9b5",
|
|
139
|
+
primary: "#fa7a78",
|
|
140
|
+
positive: "#43d675",
|
|
141
|
+
caution: "#fbd024",
|
|
142
|
+
critical: "#f02f53",
|
|
143
|
+
lightest: "#fcfdfd",
|
|
144
|
+
darkest: "#0d1515"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
slug: "rosabel",
|
|
149
|
+
title: "Rosabel",
|
|
150
|
+
colors: {
|
|
151
|
+
gray: "#9d8966",
|
|
152
|
+
primary: "#ed2555",
|
|
153
|
+
positive: "#43d675",
|
|
154
|
+
caution: "#fbd024",
|
|
155
|
+
lightest: "#fdfdfc",
|
|
156
|
+
darkest: "#15120d"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
slug: "stereofidelic",
|
|
161
|
+
title: "Stereofidelic",
|
|
162
|
+
colors: {
|
|
163
|
+
gray: "#678e9a",
|
|
164
|
+
primary: "#f13009",
|
|
165
|
+
positive: "#43d675",
|
|
166
|
+
caution: "#fbd024",
|
|
167
|
+
critical: "#f02f35",
|
|
168
|
+
lightest: "#fcfdfd",
|
|
169
|
+
darkest: "#0e1315"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
slug: "tw-cyan",
|
|
174
|
+
title: "Tailwind Cyan",
|
|
175
|
+
colors: {
|
|
176
|
+
gray: "#677389",
|
|
177
|
+
primary: "#51b4d0",
|
|
178
|
+
positive: "#55b785",
|
|
179
|
+
caution: "#e2b53e",
|
|
180
|
+
critical: "#e14f62",
|
|
181
|
+
lightest: "#f9fafb",
|
|
182
|
+
darkest: "#101728"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
slug: "verdant",
|
|
187
|
+
title: "Verdant",
|
|
188
|
+
colors: {
|
|
189
|
+
gray: "#5c9199",
|
|
190
|
+
primary: "#1cb485",
|
|
191
|
+
positive: "#43d675",
|
|
192
|
+
caution: "#fbd024",
|
|
193
|
+
lightest: "#fcfdfd",
|
|
194
|
+
darkest: "#0d1415"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
Object.defineProperty(exports, "n", {
|
|
199
|
+
enumerable: !0,
|
|
200
|
+
get: function() {
|
|
201
|
+
return createTheme;
|
|
202
|
+
}
|
|
203
|
+
}), Object.defineProperty(exports, "r", {
|
|
204
|
+
enumerable: !0,
|
|
205
|
+
get: function() {
|
|
206
|
+
return themeConfigFromColors;
|
|
207
|
+
}
|
|
208
|
+
}), Object.defineProperty(exports, "t", {
|
|
209
|
+
enumerable: !0,
|
|
210
|
+
get: function() {
|
|
211
|
+
return presets;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
//# sourceMappingURL=presets.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.cjs","names":["isColor","color","COLOR_TINTS","mix"],"sources":["../../src/createTheme.ts","../../src/presets.ts"],"sourcesContent":["import {color, COLOR_TINTS, ColorHueKey, ColorTintKey} from '@sanity/color'\nimport {buildTheme, RootTheme, ThemeColorPalette, ThemeConfig} from '@sanity/ui/theme'\n\nimport {isColor, mix} from './lib/mix'\nimport {CreateThemeOptions} from './types'\n\n/** The palette hue that each themeable color drives */\nconst HUE_MAPPING: ReadonlyArray<[option: keyof CreateThemeOptions, hueKey: ColorHueKey]> = [\n ['gray', 'gray'],\n ['primary', 'blue'],\n ['positive', 'green'],\n ['caution', 'yellow'],\n ['critical', 'red'],\n]\n\n/**\n * Maps themeable colors to a `@sanity/ui` `ThemeConfig`, for when a theme\n * needs further customization than `createTheme` exposes:\n *\n * ```ts\n * import {themeConfigFromColors} from '@sanity/themer'\n * import {buildTheme} from '@sanity/ui/theme'\n *\n * const theme = buildTheme({\n * ...themeConfigFromColors({primary: '#2276fc'}),\n * // ...other ThemeConfig properties\n * })\n * ```\n *\n * @public\n */\nexport function themeConfigFromColors(colors: CreateThemeOptions): ThemeConfig {\n for (const [key, value] of Object.entries(colors)) {\n if (typeof value === 'string' && !isColor(value)) {\n throw new TypeError(\n `Invalid color for ${JSON.stringify(key)}: ${JSON.stringify(value)} — expected a hex color`,\n )\n }\n }\n\n const anchored = Boolean(colors.lightest || colors.darkest)\n\n if (!anchored && !HUE_MAPPING.some(([option]) => colors[option])) {\n return {}\n }\n\n const lightest = (colors.lightest ?? color.white.hex).toLowerCase()\n const darkest = (colors.darkest ?? color.black.hex).toLowerCase()\n\n const palette: ThemeColorPalette = {...color, black: darkest, white: lightest}\n\n for (const [option, hueKey] of HUE_MAPPING) {\n const mid = colors[option]?.toLowerCase()\n\n // Untouched ramps only need regenerating when the surface endpoints moved\n if (mid || anchored) {\n palette[hueKey] = buildHueTints({\n mid: mid ?? color[hueKey][500].hex,\n lightest,\n darkest,\n })\n }\n }\n\n return {palette}\n}\n\n/**\n * Generates a Sanity Studio theme from a handful of colors:\n *\n * ```ts\n * import {createTheme} from '@sanity/themer'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * theme: createTheme({primary: '#2276fc'}),\n * // ...rest of the config\n * })\n * ```\n *\n * Calling it without options returns the default Sanity Studio theme.\n *\n * @public\n */\nexport function createTheme(colors: CreateThemeOptions = {}): RootTheme {\n return buildTheme(themeConfigFromColors(colors))\n}\n\n/**\n * Interpolates a full tint ramp (50–950) from a mid color placed at the 500\n * tint, sliding towards `lightest` and `darkest` at the extremes.\n */\nfunction buildHueTints(options: {\n mid: string\n lightest: string\n darkest: string\n}): Record<ColorTintKey, string> {\n const {mid, lightest, darkest} = options\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the reduce assigns every COLOR_TINTS key, so the Partial is complete\n return COLOR_TINTS.reduce<Partial<Record<ColorTintKey, string>>>((tints, tintKey) => {\n const tint = Number(tintKey)\n\n if (tint === 500) {\n tints[tintKey] = mid\n } else if (tint < 500) {\n tints[tintKey] = mix(tint / 500, mid, lightest)\n } else {\n tints[tintKey] = mix((tint - 500) / 500, darkest, mid)\n }\n\n return tints\n }, {}) as Record<ColorTintKey, string>\n}\n","import {CreateThemeOptions} from './types'\n\n/**\n * A named set of colors for `createTheme`.\n *\n * @public\n */\nexport interface ThemePreset {\n slug: string\n title: string\n colors: CreateThemeOptions\n}\n\n/**\n * Preset themes, carried over from the hosted Themer service\n * (themer.sanity.build) and re-expressed as `createTheme` colors.\n *\n * ```ts\n * import {createTheme, presets} from '@sanity/themer'\n *\n * const verdant = presets.find((preset) => preset.slug === 'verdant')\n * const theme = createTheme(verdant.colors)\n * ```\n *\n * @public\n */\nexport const presets: ThemePreset[] = [\n {\n slug: 'default',\n title: 'Studio',\n colors: {},\n },\n {\n slug: 'dew',\n title: 'Dew',\n colors: {\n gray: '#5e63b4',\n primary: '#d1a308',\n positive: '#43d675',\n caution: '#fb9f24',\n critical: '#f03e2f',\n lightest: '#fcfcfd',\n darkest: '#0d0d15',\n },\n },\n {\n slug: 'pink-synth',\n title: 'Pink Synth',\n colors: {\n gray: '#8b6584',\n primary: '#ec4899',\n positive: '#10b981',\n caution: '#fde047',\n critical: '#fe3459',\n lightest: '#f7f2f5',\n darkest: '#171721',\n },\n },\n {\n slug: 'pixel-art',\n title: 'Pixel Art',\n colors: {\n gray: '#57619c',\n primary: '#f10784',\n positive: '#43d675',\n caution: '#fbd024',\n lightest: '#fcfcfd',\n darkest: '#0d0e15',\n },\n },\n {\n slug: 'retro-colonial',\n title: 'Retro Colonial',\n colors: {\n gray: '#8bb9b5',\n primary: '#fa7a78',\n positive: '#43d675',\n caution: '#fbd024',\n critical: '#f02f53',\n lightest: '#fcfdfd',\n darkest: '#0d1515',\n },\n },\n {\n slug: 'rosabel',\n title: 'Rosabel',\n colors: {\n gray: '#9d8966',\n primary: '#ed2555',\n positive: '#43d675',\n caution: '#fbd024',\n lightest: '#fdfdfc',\n darkest: '#15120d',\n },\n },\n {\n slug: 'stereofidelic',\n title: 'Stereofidelic',\n colors: {\n gray: '#678e9a',\n primary: '#f13009',\n positive: '#43d675',\n caution: '#fbd024',\n critical: '#f02f35',\n lightest: '#fcfdfd',\n darkest: '#0e1315',\n },\n },\n {\n slug: 'tw-cyan',\n title: 'Tailwind Cyan',\n colors: {\n gray: '#677389',\n primary: '#51b4d0',\n positive: '#55b785',\n caution: '#e2b53e',\n critical: '#e14f62',\n lightest: '#f9fafb',\n darkest: '#101728',\n },\n },\n {\n slug: 'verdant',\n title: 'Verdant',\n colors: {\n gray: '#5c9199',\n primary: '#1cb485',\n positive: '#43d675',\n caution: '#fbd024',\n lightest: '#fcfdfd',\n darkest: '#0d1415',\n },\n },\n]\n"],"mappings":";;;AAOA,MAAM,cAAsF;CAC1F,CAAC,QAAQ,MAAM;CACf,CAAC,WAAW,MAAM;CAClB,CAAC,YAAY,OAAO;CACpB,CAAC,WAAW,QAAQ;CACpB,CAAC,YAAY,KAAK;AACpB;;;;;;;;;;;;;;;;;AAkBA,SAAgB,sBAAsB,QAAyC;CAC7E,KAAK,IAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,OAAO,SAAU,YAAY,CAACA,YAAAA,EAAQ,KAAK,GAC7C,MAAU,UACR,qBAAqB,KAAK,UAAU,GAAG,EAAE,IAAI,KAAK,UAAU,KAAK,EAAE,wBACrE;CAIJ,IAAM,WAAW,GAAQ,OAAO,YAAY,OAAO;CAEnD,IAAI,CAAC,YAAY,CAAC,YAAY,MAAM,CAAC,YAAY,OAAO,OAAO,GAC7D,OAAO,CAAC;CAGV,IAAM,YAAY,OAAO,YAAYC,cAAAA,MAAM,MAAM,IAAA,CAAK,YAAY,GAC5D,WAAW,OAAO,WAAWA,cAAAA,MAAM,MAAM,IAAA,CAAK,YAAY,GAE1D,UAA6B;EAAC,GAAGA,cAAAA;EAAO,OAAO;EAAS,OAAO;CAAQ;CAE7E,KAAK,IAAM,CAAC,QAAQ,WAAW,aAAa;EAC1C,IAAM,MAAM,OAAO,OAAO,EAAE,YAAY;EAGxC,CAAI,OAAO,cACT,QAAQ,UAAU,cAAc;GAC9B,KAAK,OAAOA,cAAAA,MAAM,OAAO,CAAC,IAAI,CAAC;GAC/B;GACA;EACF,CAAC;CAEL;CAEA,OAAO,EAAC,QAAO;AACjB;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,YAAY,SAA6B,CAAC,GAAc;CACtE,QAAA,GAAA,iBAAA,WAAA,CAAkB,sBAAsB,MAAM,CAAC;AACjD;;;;;AAMA,SAAS,cAAc,SAIU;CAC/B,IAAM,EAAC,KAAK,UAAU,YAAW;CAGjC,OAAOC,cAAAA,YAAY,QAA+C,OAAO,YAAY;EACnF,IAAM,OAAO,OAAO,OAAO;EAU3B,OARI,SAAS,MACX,MAAM,WAAW,MACR,OAAO,MAChB,MAAM,WAAWC,YAAAA,EAAI,OAAO,KAAK,KAAK,QAAQ,IAE9C,MAAM,WAAWA,YAAAA,GAAK,OAAO,OAAO,KAAK,SAAS,GAAG,GAGhD;CACT,GAAG,CAAC,CAAC;AACP;;;;;;;;;;;;;;ACvFA,MAAa,UAAyB;CACpC;EACE,MAAM;EACN,OAAO;EACP,QAAQ,CAAC;CACX;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,UAAU;GACV,SAAS;EACX;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS;GACT,UAAU;GACV,SAAS;EACX;CACF;AACF"}
|