@timmo001/groundwork-tokens 0.9.39 → 0.10.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.
@@ -2706,6 +2706,53 @@
2706
2706
  "row-padding": "var(--gw-space-3)"
2707
2707
  },
2708
2708
  "modes": {
2709
+ "light": {
2710
+ "surface-lower": "#d0d5e3",
2711
+ "surface-low": "#dadbe0",
2712
+ "surface-default": "#e1e2e7",
2713
+ "surface-raised": "#e9e9ed",
2714
+ "surface-overlay": "#e1e1e5",
2715
+ "border-quiet": "#ced2e4",
2716
+ "border-normal": "#c4c8da",
2717
+ "border-loud": "#abafc0",
2718
+ "text-strong": "#3760bf",
2719
+ "text-primary": "#3760bf",
2720
+ "text-secondary": "#6172b0",
2721
+ "text-disabled": "#6172b099",
2722
+ "fill-neutral-resting": "#dfdfe3",
2723
+ "fill-neutral-hovered": "#d5d5d9",
2724
+ "fill-neutral-pressed": "#ccccd0",
2725
+ "fill-neutral-selected": "#cfcfd3",
2726
+ "fill-neutral-disabled": "#e4e4e8",
2727
+ "fill-primary-resting": "#3760bf",
2728
+ "fill-primary-hovered": "#4772d3",
2729
+ "fill-primary-pressed": "#537fe1",
2730
+ "on-primary": "#e1e2e7",
2731
+ "accent": "#2e7de9",
2732
+ "fill-accent-resting": "#2e7de9",
2733
+ "fill-accent-hovered": "#0042a5",
2734
+ "fill-accent-pressed": "#0059c0",
2735
+ "fill-accent-quiet": "#0051b729",
2736
+ "border-accent": "#0051b780",
2737
+ "on-accent": "#ffffff",
2738
+ "focus": "#2e7de9",
2739
+ "link": "#0051b7",
2740
+ "info": "#2e7de9",
2741
+ "fill-info-quiet": "#2e7de924",
2742
+ "border-info": "#2e7de973",
2743
+ "success": "#587539",
2744
+ "fill-success-quiet": "#58753924",
2745
+ "border-success": "#58753973",
2746
+ "important": "#9854f1",
2747
+ "fill-important-quiet": "#9854f124",
2748
+ "border-important": "#9854f173",
2749
+ "warning": "#8c6c3e",
2750
+ "fill-warning-quiet": "#8c6c3e24",
2751
+ "border-warning": "#8c6c3e73",
2752
+ "danger": "#f52a65",
2753
+ "fill-danger-quiet": "#f52a6524",
2754
+ "border-danger": "#f52a6573"
2755
+ },
2709
2756
  "dark": {
2710
2757
  "surface-lower": "#16161e",
2711
2758
  "surface-low": "#171823",
@@ -2803,9 +2850,9 @@
2803
2850
  }
2804
2851
  },
2805
2852
  {
2806
- "id": "tokyo-night-day",
2807
- "name": "Tokyo Night Day",
2808
- "description": "Tokyo Night Day, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2853
+ "id": "tokyo-night-moon",
2854
+ "name": "Tokyo Night Moon",
2855
+ "description": "Tokyo Night Moon, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2809
2856
  "optional": true,
2810
2857
  "family": "Tokyo Night",
2811
2858
  "examples": [],
@@ -2925,86 +2972,7 @@
2925
2972
  "danger": "#f52a65",
2926
2973
  "fill-danger-quiet": "#f52a6524",
2927
2974
  "border-danger": "#f52a6573"
2928
- }
2929
- }
2930
- },
2931
- {
2932
- "id": "tokyo-night-moon",
2933
- "name": "Tokyo Night Moon",
2934
- "description": "Tokyo Night Moon, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2935
- "optional": true,
2936
- "family": "Tokyo Night",
2937
- "examples": [],
2938
- "style": {
2939
- "font": "Mona Sans",
2940
- "mono": "Geist Mono",
2941
- "radius": {
2942
- "panel": 10,
2943
- "control": 8,
2944
- "chip": 6
2945
- },
2946
- "border": {
2947
- "width": 1,
2948
- "strength": "hairline"
2949
- },
2950
- "weights": {
2951
- "regular": 400,
2952
- "strong": 600
2953
- },
2954
- "density": "normal",
2955
- "capitals": false,
2956
- "link": {
2957
- "color": "accent",
2958
- "underline": "thin"
2959
2975
  },
2960
- "surfaceAccent": "none",
2961
- "rows": "cards"
2962
- },
2963
- "properties": {
2964
- "font-sans": "\"Mona Sans Variable\", \"Mona Sans\", system-ui, sans-serif",
2965
- "font-figures": "\"Mona Sans Variable\", \"Mona Sans\", system-ui, sans-serif",
2966
- "font-mono": "\"Geist Mono Variable\", \"Geist Mono\", ui-monospace, monospace",
2967
- "font-weight-regular": "400",
2968
- "font-weight-strong": "600",
2969
- "density": "1",
2970
- "control-scale": "1",
2971
- "space-0-5": "calc(0.125rem * var(--gw-density))",
2972
- "space-1": "calc(0.25rem * var(--gw-density))",
2973
- "space-2": "calc(0.5rem * var(--gw-density))",
2974
- "space-3": "calc(0.75rem * var(--gw-density))",
2975
- "space-4": "calc(1rem * var(--gw-density))",
2976
- "space-5": "calc(1.25rem * var(--gw-density))",
2977
- "space-6": "calc(1.5rem * var(--gw-density))",
2978
- "space-7": "calc(1.75rem * var(--gw-density))",
2979
- "space-8": "calc(2rem * var(--gw-density))",
2980
- "space-9": "calc(2.25rem * var(--gw-density))",
2981
- "space-10": "calc(2.5rem * var(--gw-density))",
2982
- "space-11": "calc(2.75rem * var(--gw-density))",
2983
- "space-12": "calc(3rem * var(--gw-density))",
2984
- "space-13": "calc(3.25rem * var(--gw-density))",
2985
- "space-14": "calc(3.5rem * var(--gw-density))",
2986
- "space-15": "calc(3.75rem * var(--gw-density))",
2987
- "space-16": "calc(4rem * var(--gw-density))",
2988
- "space-17": "calc(4.25rem * var(--gw-density))",
2989
- "space-18": "calc(4.5rem * var(--gw-density))",
2990
- "space-19": "calc(4.75rem * var(--gw-density))",
2991
- "space-20": "calc(5rem * var(--gw-density))",
2992
- "radius-panel": "10px",
2993
- "radius-control": "8px",
2994
- "radius-chip": "6px",
2995
- "border-width": "1px",
2996
- "border-color": "var(--gw-color-border-quiet)",
2997
- "label-transform": "none",
2998
- "label-tracking": "normal",
2999
- "link-color": "var(--gw-color-link)",
3000
- "link-underline": "1px",
3001
- "panel-sheen": "none",
3002
- "panel-marker": "0px",
3003
- "panel-flange": "0px",
3004
- "panel-flange-color": "var(--gw-border-color)",
3005
- "row-padding": "var(--gw-space-3)"
3006
- },
3007
- "modes": {
3008
2976
  "dark": {
3009
2977
  "surface-lower": "#1e2030",
3010
2978
  "surface-low": "#202234",
@@ -3178,6 +3146,53 @@
3178
3146
  "row-padding": "var(--gw-space-3)"
3179
3147
  },
3180
3148
  "modes": {
3149
+ "light": {
3150
+ "surface-lower": "#d0d5e3",
3151
+ "surface-low": "#dadbe0",
3152
+ "surface-default": "#e1e2e7",
3153
+ "surface-raised": "#e9e9ed",
3154
+ "surface-overlay": "#e1e1e5",
3155
+ "border-quiet": "#ced2e4",
3156
+ "border-normal": "#c4c8da",
3157
+ "border-loud": "#abafc0",
3158
+ "text-strong": "#3760bf",
3159
+ "text-primary": "#3760bf",
3160
+ "text-secondary": "#6172b0",
3161
+ "text-disabled": "#6172b099",
3162
+ "fill-neutral-resting": "#dfdfe3",
3163
+ "fill-neutral-hovered": "#d5d5d9",
3164
+ "fill-neutral-pressed": "#ccccd0",
3165
+ "fill-neutral-selected": "#cfcfd3",
3166
+ "fill-neutral-disabled": "#e4e4e8",
3167
+ "fill-primary-resting": "#3760bf",
3168
+ "fill-primary-hovered": "#4772d3",
3169
+ "fill-primary-pressed": "#537fe1",
3170
+ "on-primary": "#e1e2e7",
3171
+ "accent": "#2e7de9",
3172
+ "fill-accent-resting": "#2e7de9",
3173
+ "fill-accent-hovered": "#0042a5",
3174
+ "fill-accent-pressed": "#0059c0",
3175
+ "fill-accent-quiet": "#0051b729",
3176
+ "border-accent": "#0051b780",
3177
+ "on-accent": "#ffffff",
3178
+ "focus": "#2e7de9",
3179
+ "link": "#0051b7",
3180
+ "info": "#2e7de9",
3181
+ "fill-info-quiet": "#2e7de924",
3182
+ "border-info": "#2e7de973",
3183
+ "success": "#587539",
3184
+ "fill-success-quiet": "#58753924",
3185
+ "border-success": "#58753973",
3186
+ "important": "#9854f1",
3187
+ "fill-important-quiet": "#9854f124",
3188
+ "border-important": "#9854f173",
3189
+ "warning": "#8c6c3e",
3190
+ "fill-warning-quiet": "#8c6c3e24",
3191
+ "border-warning": "#8c6c3e73",
3192
+ "danger": "#f52a65",
3193
+ "fill-danger-quiet": "#f52a6524",
3194
+ "border-danger": "#f52a6573"
3195
+ },
3181
3196
  "dark": {
3182
3197
  "surface-lower": "#1f2335",
3183
3198
  "surface-low": "#222538",
@@ -2707,6 +2707,53 @@ export const themes = [
2707
2707
  "row-padding": "var(--gw-space-3)"
2708
2708
  },
2709
2709
  "modes": {
2710
+ "light": {
2711
+ "surface-lower": "#d0d5e3",
2712
+ "surface-low": "#dadbe0",
2713
+ "surface-default": "#e1e2e7",
2714
+ "surface-raised": "#e9e9ed",
2715
+ "surface-overlay": "#e1e1e5",
2716
+ "border-quiet": "#ced2e4",
2717
+ "border-normal": "#c4c8da",
2718
+ "border-loud": "#abafc0",
2719
+ "text-strong": "#3760bf",
2720
+ "text-primary": "#3760bf",
2721
+ "text-secondary": "#6172b0",
2722
+ "text-disabled": "#6172b099",
2723
+ "fill-neutral-resting": "#dfdfe3",
2724
+ "fill-neutral-hovered": "#d5d5d9",
2725
+ "fill-neutral-pressed": "#ccccd0",
2726
+ "fill-neutral-selected": "#cfcfd3",
2727
+ "fill-neutral-disabled": "#e4e4e8",
2728
+ "fill-primary-resting": "#3760bf",
2729
+ "fill-primary-hovered": "#4772d3",
2730
+ "fill-primary-pressed": "#537fe1",
2731
+ "on-primary": "#e1e2e7",
2732
+ "accent": "#2e7de9",
2733
+ "fill-accent-resting": "#2e7de9",
2734
+ "fill-accent-hovered": "#0042a5",
2735
+ "fill-accent-pressed": "#0059c0",
2736
+ "fill-accent-quiet": "#0051b729",
2737
+ "border-accent": "#0051b780",
2738
+ "on-accent": "#ffffff",
2739
+ "focus": "#2e7de9",
2740
+ "link": "#0051b7",
2741
+ "info": "#2e7de9",
2742
+ "fill-info-quiet": "#2e7de924",
2743
+ "border-info": "#2e7de973",
2744
+ "success": "#587539",
2745
+ "fill-success-quiet": "#58753924",
2746
+ "border-success": "#58753973",
2747
+ "important": "#9854f1",
2748
+ "fill-important-quiet": "#9854f124",
2749
+ "border-important": "#9854f173",
2750
+ "warning": "#8c6c3e",
2751
+ "fill-warning-quiet": "#8c6c3e24",
2752
+ "border-warning": "#8c6c3e73",
2753
+ "danger": "#f52a65",
2754
+ "fill-danger-quiet": "#f52a6524",
2755
+ "border-danger": "#f52a6573"
2756
+ },
2710
2757
  "dark": {
2711
2758
  "surface-lower": "#16161e",
2712
2759
  "surface-low": "#171823",
@@ -2804,9 +2851,9 @@ export const themes = [
2804
2851
  }
2805
2852
  },
2806
2853
  {
2807
- "id": "tokyo-night-day",
2808
- "name": "Tokyo Night Day",
2809
- "description": "Tokyo Night Day, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2854
+ "id": "tokyo-night-moon",
2855
+ "name": "Tokyo Night Moon",
2856
+ "description": "Tokyo Night Moon, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2810
2857
  "optional": true,
2811
2858
  "family": "Tokyo Night",
2812
2859
  "examples": [],
@@ -2926,86 +2973,7 @@ export const themes = [
2926
2973
  "danger": "#f52a65",
2927
2974
  "fill-danger-quiet": "#f52a6524",
2928
2975
  "border-danger": "#f52a6573"
2929
- }
2930
- }
2931
- },
2932
- {
2933
- "id": "tokyo-night-moon",
2934
- "name": "Tokyo Night Moon",
2935
- "description": "Tokyo Night Moon, mapped onto the roles so it stays faithful to the Tokyo Night palette.",
2936
- "optional": true,
2937
- "family": "Tokyo Night",
2938
- "examples": [],
2939
- "style": {
2940
- "font": "Mona Sans",
2941
- "mono": "Geist Mono",
2942
- "radius": {
2943
- "panel": 10,
2944
- "control": 8,
2945
- "chip": 6
2946
- },
2947
- "border": {
2948
- "width": 1,
2949
- "strength": "hairline"
2950
- },
2951
- "weights": {
2952
- "regular": 400,
2953
- "strong": 600
2954
- },
2955
- "density": "normal",
2956
- "capitals": false,
2957
- "link": {
2958
- "color": "accent",
2959
- "underline": "thin"
2960
2976
  },
2961
- "surfaceAccent": "none",
2962
- "rows": "cards"
2963
- },
2964
- "properties": {
2965
- "font-sans": "\"Mona Sans Variable\", \"Mona Sans\", system-ui, sans-serif",
2966
- "font-figures": "\"Mona Sans Variable\", \"Mona Sans\", system-ui, sans-serif",
2967
- "font-mono": "\"Geist Mono Variable\", \"Geist Mono\", ui-monospace, monospace",
2968
- "font-weight-regular": "400",
2969
- "font-weight-strong": "600",
2970
- "density": "1",
2971
- "control-scale": "1",
2972
- "space-0-5": "calc(0.125rem * var(--gw-density))",
2973
- "space-1": "calc(0.25rem * var(--gw-density))",
2974
- "space-2": "calc(0.5rem * var(--gw-density))",
2975
- "space-3": "calc(0.75rem * var(--gw-density))",
2976
- "space-4": "calc(1rem * var(--gw-density))",
2977
- "space-5": "calc(1.25rem * var(--gw-density))",
2978
- "space-6": "calc(1.5rem * var(--gw-density))",
2979
- "space-7": "calc(1.75rem * var(--gw-density))",
2980
- "space-8": "calc(2rem * var(--gw-density))",
2981
- "space-9": "calc(2.25rem * var(--gw-density))",
2982
- "space-10": "calc(2.5rem * var(--gw-density))",
2983
- "space-11": "calc(2.75rem * var(--gw-density))",
2984
- "space-12": "calc(3rem * var(--gw-density))",
2985
- "space-13": "calc(3.25rem * var(--gw-density))",
2986
- "space-14": "calc(3.5rem * var(--gw-density))",
2987
- "space-15": "calc(3.75rem * var(--gw-density))",
2988
- "space-16": "calc(4rem * var(--gw-density))",
2989
- "space-17": "calc(4.25rem * var(--gw-density))",
2990
- "space-18": "calc(4.5rem * var(--gw-density))",
2991
- "space-19": "calc(4.75rem * var(--gw-density))",
2992
- "space-20": "calc(5rem * var(--gw-density))",
2993
- "radius-panel": "10px",
2994
- "radius-control": "8px",
2995
- "radius-chip": "6px",
2996
- "border-width": "1px",
2997
- "border-color": "var(--gw-color-border-quiet)",
2998
- "label-transform": "none",
2999
- "label-tracking": "normal",
3000
- "link-color": "var(--gw-color-link)",
3001
- "link-underline": "1px",
3002
- "panel-sheen": "none",
3003
- "panel-marker": "0px",
3004
- "panel-flange": "0px",
3005
- "panel-flange-color": "var(--gw-border-color)",
3006
- "row-padding": "var(--gw-space-3)"
3007
- },
3008
- "modes": {
3009
2977
  "dark": {
3010
2978
  "surface-lower": "#1e2030",
3011
2979
  "surface-low": "#202234",
@@ -3179,6 +3147,53 @@ export const themes = [
3179
3147
  "row-padding": "var(--gw-space-3)"
3180
3148
  },
3181
3149
  "modes": {
3150
+ "light": {
3151
+ "surface-lower": "#d0d5e3",
3152
+ "surface-low": "#dadbe0",
3153
+ "surface-default": "#e1e2e7",
3154
+ "surface-raised": "#e9e9ed",
3155
+ "surface-overlay": "#e1e1e5",
3156
+ "border-quiet": "#ced2e4",
3157
+ "border-normal": "#c4c8da",
3158
+ "border-loud": "#abafc0",
3159
+ "text-strong": "#3760bf",
3160
+ "text-primary": "#3760bf",
3161
+ "text-secondary": "#6172b0",
3162
+ "text-disabled": "#6172b099",
3163
+ "fill-neutral-resting": "#dfdfe3",
3164
+ "fill-neutral-hovered": "#d5d5d9",
3165
+ "fill-neutral-pressed": "#ccccd0",
3166
+ "fill-neutral-selected": "#cfcfd3",
3167
+ "fill-neutral-disabled": "#e4e4e8",
3168
+ "fill-primary-resting": "#3760bf",
3169
+ "fill-primary-hovered": "#4772d3",
3170
+ "fill-primary-pressed": "#537fe1",
3171
+ "on-primary": "#e1e2e7",
3172
+ "accent": "#2e7de9",
3173
+ "fill-accent-resting": "#2e7de9",
3174
+ "fill-accent-hovered": "#0042a5",
3175
+ "fill-accent-pressed": "#0059c0",
3176
+ "fill-accent-quiet": "#0051b729",
3177
+ "border-accent": "#0051b780",
3178
+ "on-accent": "#ffffff",
3179
+ "focus": "#2e7de9",
3180
+ "link": "#0051b7",
3181
+ "info": "#2e7de9",
3182
+ "fill-info-quiet": "#2e7de924",
3183
+ "border-info": "#2e7de973",
3184
+ "success": "#587539",
3185
+ "fill-success-quiet": "#58753924",
3186
+ "border-success": "#58753973",
3187
+ "important": "#9854f1",
3188
+ "fill-important-quiet": "#9854f124",
3189
+ "border-important": "#9854f173",
3190
+ "warning": "#8c6c3e",
3191
+ "fill-warning-quiet": "#8c6c3e24",
3192
+ "border-warning": "#8c6c3e73",
3193
+ "danger": "#f52a65",
3194
+ "fill-danger-quiet": "#f52a6524",
3195
+ "border-danger": "#f52a6573"
3196
+ },
3182
3197
  "dark": {
3183
3198
  "surface-lower": "#1f2335",
3184
3199
  "surface-low": "#222538",
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-tokens",
3
- "version": "0.9.39",
3
+ "version": "0.10.0",
4
4
  "description": "Groundwork's design tokens: the theme schema, the OKLCH solver and every theme as CSS and JSON",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
9
  "./runtime": "./src/runtime.ts",
10
+ "./choices": "./src/choices.ts",
10
11
  "./page": "./src/page.ts",
11
12
  "./webmcp": "./src/webmcp.ts",
12
13
  "./omarchy-current": "./src/omarchy-current.ts",
package/src/choices.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * The built-in themes as choices, the optional themes' documents and the
3
+ * inline script that applies a stored pick before the first paint. It uses
4
+ * no DOM types, so a server or CLI building a page can import it without
5
+ * the DOM lib; `page` re-exports all of it for the browser.
6
+ *
7
+ * @module
8
+ */
9
+ import base from "../themes/base.json" with { type: "json" };
10
+ import bridge from "../themes/bridge.json" with { type: "json" };
11
+ import catppuccinFrappe from "../themes/catppuccin-frappe.json" with { type: "json" };
12
+ import catppuccinLatte from "../themes/catppuccin-latte.json" with { type: "json" };
13
+ import catppuccinMacchiato from "../themes/catppuccin-macchiato.json" with { type: "json" };
14
+ import catppuccinMocha from "../themes/catppuccin-mocha.json" with { type: "json" };
15
+ import githubDimmed from "../themes/github-dimmed.json" with { type: "json" };
16
+ import githubHighContrast from "../themes/github-high-contrast.json" with { type: "json" };
17
+ import github from "../themes/github.json" with { type: "json" };
18
+ import glazing from "../themes/glazing.json" with { type: "json" };
19
+ import slate from "../themes/slate.json" with { type: "json" };
20
+ import snag from "../themes/snag.json" with { type: "json" };
21
+ import survey from "../themes/survey.json" with { type: "json" };
22
+ import tokyoNightMoon from "../themes/tokyo-night-moon.json" with { type: "json" };
23
+ import tokyoNightStorm from "../themes/tokyo-night-storm.json" with { type: "json" };
24
+ import tokyoNight from "../themes/tokyo-night.json" with { type: "json" };
25
+
26
+ /** How light the page is: `auto` follows the system. */
27
+ export type ThemeMode = "auto" | "light" | "dark" | "darker";
28
+
29
+ /** Light or dark: the side of a mode a theme has to cover. Darker is dark. */
30
+ export type ThemeScheme = "light" | "dark";
31
+
32
+ /** The theme id a visitor's own theme is solved under. */
33
+ export const customThemeId = "custom";
34
+
35
+ /** The id of the `<style>` holding a custom theme's CSS. */
36
+ export const customThemeStyleId = "gw-custom-theme";
37
+
38
+ /** The theme id the desktop's Omarchy theme is solved under. */
39
+ export const omarchyThemeId = "omarchy";
40
+
41
+ /** The id of the `<style>` holding the Omarchy theme's CSS. */
42
+ export const omarchyThemeStyleId = "gw-omarchy-theme";
43
+
44
+ /**
45
+ * A theme to offer, by id and name. A `Theme` document is one. `mode` marks
46
+ * a theme with only one scheme, such as Catppuccin Latte: while it's picked,
47
+ * the page stays in that scheme.
48
+ */
49
+ export interface ThemeChoice {
50
+ readonly id: string;
51
+ readonly name: string;
52
+ readonly mode?: ThemeScheme | undefined;
53
+ /** The palette an optional theme comes from, such as `Catppuccin`. */
54
+ readonly family?: string | undefined;
55
+ }
56
+
57
+ /** Whether `choice` can show in `scheme`. */
58
+ export const themeCovers = (choice: ThemeChoice, scheme: ThemeScheme) =>
59
+ choice.mode === undefined || choice.mode === scheme;
60
+
61
+ /**
62
+ * Whether `mode` can be used with `choice`: any for a theme with both
63
+ * schemes, and only its own for one with one. Auto could land on the other
64
+ * scheme, so it's left out too.
65
+ */
66
+ export const themeAllows = (choice: ThemeChoice, mode: ThemeMode) =>
67
+ choice.mode === undefined ||
68
+ (mode !== "auto" && themeCovers(choice, mode === "light" ? "light" : "dark"));
69
+
70
+ /**
71
+ * Choices as a picker's options, grouped: a choice's `family`, or `group`
72
+ * for those without one. Order the choices as the picker should list them,
73
+ * usually the app's own theme, then the other core themes, then the optional
74
+ * ones.
75
+ */
76
+ export const themeOptions = (
77
+ choices: readonly ThemeChoice[],
78
+ group = "Groundwork",
79
+ ) =>
80
+ choices.map(({ id, name, family }) => ({
81
+ value: id,
82
+ label: name,
83
+ group: family ?? group,
84
+ }));
85
+
86
+ /** The core themes by id and name, in the order the docs list them. */
87
+ export const coreThemeChoices: readonly ThemeChoice[] = [
88
+ base,
89
+ glazing,
90
+ survey,
91
+ slate,
92
+ bridge,
93
+ snag,
94
+ ].map(({ id, name }) => ({ id, name }));
95
+
96
+ /**
97
+ * The optional themes' documents, by family and then from light to dark.
98
+ * They're bundled with the module, so an app compiled to one binary has them
99
+ * without reading `node_modules`. Decode one with the `MappedTheme` Schema.
100
+ */
101
+ export const optionalThemeDocuments = [
102
+ catppuccinLatte,
103
+ catppuccinFrappe,
104
+ catppuccinMacchiato,
105
+ catppuccinMocha,
106
+ tokyoNight,
107
+ tokyoNightStorm,
108
+ tokyoNightMoon,
109
+ github,
110
+ githubDimmed,
111
+ githubHighContrast,
112
+ ] as const;
113
+
114
+ /**
115
+ * The optional themes, mapped from other palettes, by family and then from
116
+ * light to dark. One with a single mode is offered for that scheme alone;
117
+ * one with both, such as GitHub's, for either.
118
+ */
119
+ export const optionalThemeChoices: readonly ThemeChoice[] =
120
+ optionalThemeDocuments.map((theme) => ({
121
+ id: theme.id,
122
+ name: theme.name,
123
+ family: theme.family,
124
+ mode:
125
+ "otherPalette" in theme
126
+ ? undefined
127
+ : theme.mode === "light"
128
+ ? "light"
129
+ : "dark",
130
+ }));
131
+
132
+ /** What the page's pick starts on, and where it's kept. */
133
+ export interface ThemeBootOptions {
134
+ /** The local storage key prefix, such as `my-site:theme`. */
135
+ readonly key: string;
136
+ /** The theme id to start with. */
137
+ readonly defaultTheme: string;
138
+ /** The mode to start with. Defaults to `auto`. */
139
+ readonly defaultMode?: ThemeMode;
140
+ }
141
+
142
+ /** The local storage keys a pick is kept under. */
143
+ export const themeStorageKeys = (key: string) => ({
144
+ mode: `${key}:mode`,
145
+ theme: `${key}:id`,
146
+ document: `${key}:custom`,
147
+ css: `${key}:custom-css`,
148
+ name: `${key}:custom-name`,
149
+ scheme: `${key}:custom-mode`,
150
+ });
151
+
152
+ /** The Omarchy theme's cache, kept apart so a reset doesn't drop it. */
153
+ export const omarchyStorageKeys = (key: string) => ({
154
+ css: `${key}:omarchy-css`,
155
+ name: `${key}:omarchy-name`,
156
+ mode: `${key}:omarchy-mode`,
157
+ });
158
+
159
+ /**
160
+ * An inline script that applies the stored mode, theme and any custom or
161
+ * Omarchy theme's cached CSS before the first paint. Takes the same key and
162
+ * defaults as `pageTheme`. Picking a theme stores the mode it needs, so the
163
+ * two stay in step.
164
+ */
165
+ export const themeBootScript = (options: ThemeBootOptions) => {
166
+ const config = JSON.stringify({
167
+ keys: themeStorageKeys(options.key),
168
+ omarchy: omarchyStorageKeys(options.key),
169
+ theme: options.defaultTheme,
170
+ mode: options.defaultMode ?? "auto",
171
+ custom: customThemeId,
172
+ style: customThemeStyleId,
173
+ omarchyId: omarchyThemeId,
174
+ omarchyStyle: omarchyThemeStyleId,
175
+ });
176
+
177
+ // An imported or Omarchy theme only applies once it's been picked, and
178
+ // falls back to the default when its CSS has gone
179
+ return `(()=>{const c=${config};let s;try{s=localStorage}catch{}const g=(k)=>{try{return s?.getItem(k)??null}catch{return null}};const r=document.documentElement;const a=(id,css)=>{const e=document.createElement("style");e.id=id;e.textContent=css;document.head.append(e)};let m=g(c.keys.mode)??c.mode;let t=g(c.keys.theme)??c.theme;const o=t===c.custom?g(c.keys.css):t===c.omarchyId?g(c.omarchy.css):null;if(t===c.custom||t===c.omarchyId){if(o)a(t===c.custom?c.style:c.omarchyStyle,o);else{t=c.theme;m=c.mode}}r.setAttribute("data-gw-theme",t);if(m==="auto")r.removeAttribute("data-gw-mode");else r.setAttribute("data-gw-mode",m)})();`;
180
+ };
package/src/generate.ts CHANGED
@@ -536,17 +536,17 @@ export const blumeTheme = (theme: ResolvedTheme) =>
536
536
  ].join("\n\n") + "\n";
537
537
 
538
538
  /**
539
- * Every core theme as Blume's tokens, each under `data-gw-docs-theme`, for a
540
- * site that lets readers pick one with the theme picker.
539
+ * Every theme, core and optional, as Blume's tokens, each under
540
+ * `data-gw-docs-theme`, for a site that lets readers pick one with the theme
541
+ * picker. An optional theme with one scheme has rules for that scheme only;
542
+ * the picker keeps the page in it.
541
543
  */
542
544
  export const blumeThemes = (themes: readonly ResolvedTheme[]) =>
543
545
  [
544
546
  `/* ${header} */`,
545
- ...themes
546
- .filter((theme) => !theme.optional)
547
- .flatMap((theme) =>
548
- blumeRules(theme, `:root[data-gw-docs-theme="${theme.id}"]`),
549
- ),
547
+ ...themes.flatMap((theme) =>
548
+ blumeRules(theme, `:root[data-gw-docs-theme="${theme.id}"]`),
549
+ ),
550
550
  ].join("\n\n") + "\n";
551
551
 
552
552
  const hexRoles = (roles: Roles) =>
@@ -761,7 +761,7 @@ export const optionalPage = (themes: readonly ResolvedTheme[]) => {
761
761
  "",
762
762
  `{/* ${header} */}`,
763
763
  "",
764
- "Most flavors are one mode of their palette, and GitHub's cover both light and dark. Each is mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
764
+ "Catppuccin's flavors are one mode each, so an app in one stays in its scheme. GitHub's cover both light and dark, and so do Tokyo Night's, each taking Day as its light side. Each is mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
765
765
  "",
766
766
  ...families.flatMap((family) => [
767
767
  `## ${family}`,