@getvitops/generator 1.0.0 → 2.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/site.schema.json CHANGED
@@ -796,510 +796,448 @@
796
796
  },
797
797
  "designSystem": {
798
798
  "type": "object",
799
- "propertyNames": {
800
- "type": "string"
801
- },
802
- "additionalProperties": {
803
- "type": "object",
804
- "properties": {
805
- "$schema": {
806
- "description": "URL of the published JSON Schema (stamped by `vitops init`) so editors provide autocomplete + validation.",
799
+ "properties": {
800
+ "themes": {
801
+ "type": "object",
802
+ "propertyNames": {
807
803
  "type": "string"
808
804
  },
809
- "meta": {
810
- "description": "Brand identity for agent-facing output. Consumed only by the `design` format (`DESIGN.md`); it emits no CSS and no tokens.",
805
+ "additionalProperties": {
811
806
  "type": "object",
812
807
  "properties": {
813
- "name": {
814
- "description": "Brand/system name. Used as the `name` field and `<h1>` of the `design` format's `DESIGN.md`. Defaults to \"Design System\".",
808
+ "$schema": {
809
+ "description": "URL of the published JSON Schema (stamped by `vitops init`) so editors provide autocomplete + validation.",
815
810
  "type": "string"
816
811
  },
817
- "description": {
818
- "description": "One or two sentences on the brand personality and the feeling the UI should evoke — what an agent needs when no token answers the question. Becomes the DESIGN.md `description` field and opens its Overview section; if omitted, a generic description of the system's mechanics is used instead.",
819
- "type": "string"
820
- }
821
- },
822
- "additionalProperties": false
823
- },
824
- "colors": {
825
- "type": "object",
826
- "properties": {
827
- "palette": {
812
+ "meta": {
813
+ "description": "Brand identity for agent-facing output. Consumed only by the `design` format (`DESIGN.md`); it emits no CSS and no tokens.",
828
814
  "type": "object",
829
- "propertyNames": {
830
- "type": "string"
815
+ "properties": {
816
+ "name": {
817
+ "description": "Brand/system name. Used as the `name` field and `<h1>` of the `design` format's `DESIGN.md`. Defaults to \"Design System\".",
818
+ "type": "string"
819
+ },
820
+ "description": {
821
+ "description": "One or two sentences on the brand personality and the feeling the UI should evoke — what an agent needs when no token answers the question. Becomes the DESIGN.md `description` field and opens its Overview section; if omitted, a generic description of the system's mechanics is used instead.",
822
+ "type": "string"
823
+ }
831
824
  },
832
- "additionalProperties": {
833
- "anyOf": [
834
- {
835
- "type": "object",
836
- "properties": {
837
- "seed": {
838
- "type": "string",
839
- "description": "Seed colour (hex or oklch()). An 11-step numeric scale (50…950, tinted near-white → tinted near-black) is generated in OKLCH from it; the seed is preserved at its natural step."
840
- },
841
- "anchors": {
842
- "type": "object",
843
- "propertyNames": {
844
- "type": "string"
845
- },
846
- "additionalProperties": {
847
- "type": "string"
848
- },
849
- "description": "Step → colour overrides (hex or oklch()) pinned VERBATIM at those steps. Every other step takes its lightness from the shared ladder, with chroma and hue interpolated between the anchors — so an anchor is reproduced exactly and is the only step allowed off the ladder. An explicit anchor overrides the seed at that step; two anchors that resolve to the same step are an error."
850
- }
851
- },
852
- "required": [
853
- "seed"
854
- ],
855
- "additionalProperties": false,
856
- "description": "Seeded hue: the 11-step scale is GENERATED in OKLCH from `seed` (anchors pin specific steps)."
825
+ "additionalProperties": false
826
+ },
827
+ "colors": {
828
+ "type": "object",
829
+ "properties": {
830
+ "palette": {
831
+ "type": "object",
832
+ "propertyNames": {
833
+ "type": "string"
857
834
  },
858
- {
859
- "type": "object",
860
- "properties": {
861
- "tones": {
862
- "anyOf": [
863
- {
864
- "type": "array",
865
- "items": {
866
- "type": "string"
867
- }
835
+ "additionalProperties": {
836
+ "anyOf": [
837
+ {
838
+ "type": "object",
839
+ "properties": {
840
+ "seed": {
841
+ "type": "string",
842
+ "description": "Seed colour (hex or oklch()). An 11-step numeric scale (50…950, tinted near-white → tinted near-black) is generated in OKLCH from it; the seed is preserved at its natural step."
868
843
  },
869
- {
844
+ "anchors": {
870
845
  "type": "object",
871
846
  "propertyNames": {
872
847
  "type": "string"
873
848
  },
874
849
  "additionalProperties": {
875
850
  "type": "string"
876
- }
851
+ },
852
+ "description": "Step → colour overrides (hex or oklch()) pinned VERBATIM at those steps. Every other step takes its lightness from the shared ladder, with chroma and hue interpolated between the anchors — so an anchor is reproduced exactly and is the only step allowed off the ladder. An explicit anchor overrides the seed at that step; two anchors that resolve to the same step are an error."
853
+ }
854
+ },
855
+ "required": [
856
+ "seed"
857
+ ],
858
+ "additionalProperties": false,
859
+ "description": "Seeded hue: the 11-step scale is GENERATED in OKLCH from `seed` (anchors pin specific steps)."
860
+ },
861
+ {
862
+ "type": "object",
863
+ "properties": {
864
+ "tones": {
865
+ "anyOf": [
866
+ {
867
+ "type": "array",
868
+ "items": {
869
+ "type": "string"
870
+ }
871
+ },
872
+ {
873
+ "type": "object",
874
+ "propertyNames": {
875
+ "type": "string"
876
+ },
877
+ "additionalProperties": {
878
+ "type": "string"
879
+ }
880
+ }
881
+ ],
882
+ "description": "Fixed brand kit: authored tones placed verbatim at their nearest steps plus tinted off-white/off-black endpoints; no interpolation. Either an ordered light → dark array or a step → colour map."
877
883
  }
884
+ },
885
+ "required": [
886
+ "tones"
878
887
  ],
879
- "description": "Fixed brand kit: authored tones placed verbatim at their nearest steps plus tinted off-white/off-black endpoints; no interpolation. Either an ordered light → dark array or a step → colour map."
888
+ "additionalProperties": false,
889
+ "description": "Fixed hue: authored brand tones used verbatim; no generation."
880
890
  }
881
- },
882
- "required": [
883
- "tones"
884
891
  ],
885
- "additionalProperties": false,
886
- "description": "Fixed hue: authored brand tones used verbatim; no generation."
892
+ "description": "A palette hue, authored one of two ways: `{ seed, anchors? }` generates an 11-step numeric OKLCH scale (50…950) from the seed, or `{ tones }` supplies a fixed brand kit used verbatim."
887
893
  }
888
- ],
889
- "description": "A palette hue, authored one of two ways: `{ seed, anchors? }` generates an 11-step numeric OKLCH scale (50…950) from the seed, or `{ tones }` supplies a fixed brand kit used verbatim."
890
- }
891
- },
892
- "roles": {
893
- "type": "object",
894
- "propertyNames": {
895
- "type": "string"
896
- },
897
- "additionalProperties": {
898
- "anyOf": [
899
- {
900
- "type": "string",
901
- "description": "A palette hue name. Shorthand for `{ hue, kind: \"chromatic\" }`."
894
+ },
895
+ "roles": {
896
+ "type": "object",
897
+ "propertyNames": {
898
+ "type": "string"
902
899
  },
903
- {
904
- "type": "object",
905
- "properties": {
906
- "hue": {
900
+ "additionalProperties": {
901
+ "anyOf": [
902
+ {
907
903
  "type": "string",
908
- "description": "The palette hue this role resolves to."
904
+ "description": "A palette hue name. Shorthand for `{ hue, kind: \"chromatic\" }`."
909
905
  },
910
- "kind": {
911
- "type": "string",
912
- "enum": [
913
- "surface",
914
- "chromatic"
906
+ {
907
+ "type": "object",
908
+ "properties": {
909
+ "hue": {
910
+ "type": "string",
911
+ "description": "The palette hue this role resolves to."
912
+ },
913
+ "kind": {
914
+ "type": "string",
915
+ "enum": [
916
+ "surface",
917
+ "chromatic"
918
+ ],
919
+ "description": "`surface` — a page/panel colour: gets a bare `bg-<role>` plus the full emphasis range and text scale. `chromatic` (default) — a signal colour: tints and solids only, no bare `bg-<role>`."
920
+ }
921
+ },
922
+ "required": [
923
+ "hue"
915
924
  ],
916
- "description": "`surface` — a page/panel colour: gets a bare `bg-<role>` plus the full emphasis range and text scale. `chromatic` (default) — a signal colour: tints and solids only, no bare `bg-<role>`."
925
+ "additionalProperties": false
917
926
  }
918
- },
919
- "required": [
920
- "hue"
921
- ],
922
- "additionalProperties": false
927
+ ]
923
928
  }
924
- ]
925
- }
926
- },
927
- "utilities": {
928
- "type": "array",
929
- "items": {
930
- "type": "string",
931
- "enum": [
932
- "bg",
933
- "text",
934
- "icon",
935
- "border",
936
- "outline",
937
- "fill",
938
- "stroke"
939
- ]
940
- }
941
- }
942
- },
943
- "additionalProperties": false
944
- },
945
- "shadows": {
946
- "description": "Named shadows → `--shadow-<name>` tokens and `.drop-shadow-<name>` utilities. Values are shadow parameter lists (offset/blur/colour). Each token feeds two consumers with different grammars — `box-shadow` (pattern geometry, via the `--ds-*` group aliases) and `filter: drop-shadow(…)` (the utilities and the `shadow:` state shortcut) — so values must stay in the intersection: **one layer, no spread radius, no `inset`**. `drop-shadow()` rejects all three, and rejecting them invalidates the whole filter, so the shadow vanishes rather than degrading.",
947
- "type": "object",
948
- "propertyNames": {
949
- "type": "string"
950
- },
951
- "additionalProperties": {
952
- "type": "string"
953
- }
954
- },
955
- "fonts": {
956
- "description": "Raw font stacks by name, emitted as `--font-<name>` tokens (referenced by `typography.families`).",
957
- "type": "object",
958
- "propertyNames": {
959
- "type": "string"
960
- },
961
- "additionalProperties": {
962
- "type": "string"
963
- }
964
- },
965
- "typeScale": {
966
- "description": "Fluid modular TYPE scale → `--text-<name>` tokens, consumed by typography roles and text-size utilities.",
967
- "type": "object",
968
- "properties": {
969
- "base": {
970
- "type": "string",
971
- "description": "Anchor size (a CSS length, e.g. \"1rem\") — the value at `baseStep`."
972
- },
973
- "ratio": {
974
- "type": "number",
975
- "description": "Modular ratio between adjacent steps at large viewports."
976
- },
977
- "steps": {
978
- "description": "Token count when `names` is absent (steps are then named 1..steps).",
979
- "type": "number"
980
- },
981
- "names": {
982
- "description": "Step names, smallest → largest (e.g. [\"xs\",\"sm\",\"md\",…]); each becomes a token suffix.",
983
- "type": "array",
984
- "items": {
985
- "type": "string"
986
- }
987
- },
988
- "baseStep": {
989
- "description": "1-based index of the step whose value is `base`.",
990
- "type": "number"
991
- },
992
- "baseline": {
993
- "description": "Named step used as the fluid pivot / GUI scale centre (defaults to `baseStep`).",
994
- "type": "string"
995
- },
996
- "fluid": {
997
- "description": "Makes the scale fluid: each step compiles to a clamp() that interpolates from `minRatio` at `minVw` to `ratio` at `maxVw`.",
998
- "type": "object",
999
- "properties": {
1000
- "minVw": {
1001
- "type": "string",
1002
- "description": "Viewport width (CSS length) where fluid scaling bottoms out."
1003
929
  },
1004
- "maxVw": {
1005
- "type": "string",
1006
- "description": "Viewport width (CSS length) where fluid scaling tops out."
1007
- },
1008
- "minRatio": {
1009
- "type": "number",
1010
- "description": "Modular ratio at/below `minVw` (usually < `ratio`)."
930
+ "utilities": {
931
+ "type": "array",
932
+ "items": {
933
+ "type": "string",
934
+ "enum": [
935
+ "bg",
936
+ "text",
937
+ "icon",
938
+ "border",
939
+ "outline",
940
+ "fill",
941
+ "stroke"
942
+ ]
943
+ }
1011
944
  }
1012
945
  },
1013
- "required": [
1014
- "minVw",
1015
- "maxVw",
1016
- "minRatio"
1017
- ],
1018
946
  "additionalProperties": false
1019
- }
1020
- },
1021
- "required": [
1022
- "base",
1023
- "ratio"
1024
- ],
1025
- "additionalProperties": false
1026
- },
1027
- "spaceScale": {
1028
- "description": "Fluid modular SPACE scale → `--space-<name>` tokens, consumed by spacing/gap utilities and vertical rhythm.",
1029
- "type": "object",
1030
- "properties": {
1031
- "base": {
1032
- "type": "string",
1033
- "description": "Anchor size (a CSS length, e.g. \"1rem\") — the value at `baseStep`."
1034
947
  },
1035
- "ratio": {
1036
- "type": "number",
1037
- "description": "Modular ratio between adjacent steps at large viewports."
1038
- },
1039
- "steps": {
1040
- "description": "Token count when `names` is absent (steps are then named 1..steps).",
1041
- "type": "number"
1042
- },
1043
- "names": {
1044
- "description": "Step names, smallest → largest (e.g. [\"xs\",\"sm\",\"md\",…]); each becomes a token suffix.",
1045
- "type": "array",
1046
- "items": {
1047
- "type": "string"
1048
- }
1049
- },
1050
- "baseStep": {
1051
- "description": "1-based index of the step whose value is `base`.",
1052
- "type": "number"
1053
- },
1054
- "baseline": {
1055
- "description": "Named step used as the fluid pivot / GUI scale centre (defaults to `baseStep`).",
1056
- "type": "string"
1057
- },
1058
- "fluid": {
1059
- "description": "Makes the scale fluid: each step compiles to a clamp() that interpolates from `minRatio` at `minVw` to `ratio` at `maxVw`.",
1060
- "type": "object",
1061
- "properties": {
1062
- "minVw": {
1063
- "type": "string",
1064
- "description": "Viewport width (CSS length) where fluid scaling bottoms out."
1065
- },
1066
- "maxVw": {
1067
- "type": "string",
1068
- "description": "Viewport width (CSS length) where fluid scaling tops out."
1069
- },
1070
- "minRatio": {
1071
- "type": "number",
1072
- "description": "Modular ratio at/below `minVw` (usually < `ratio`)."
1073
- }
1074
- },
1075
- "required": [
1076
- "minVw",
1077
- "maxVw",
1078
- "minRatio"
1079
- ],
1080
- "additionalProperties": false
1081
- }
1082
- },
1083
- "required": [
1084
- "base",
1085
- "ratio"
1086
- ],
1087
- "additionalProperties": false
1088
- },
1089
- "patterns": {
1090
- "type": "object",
1091
- "properties": {
1092
- "defaults": {
948
+ "shadows": {
949
+ "description": "Named shadows → `--shadow-<name>` tokens and `.drop-shadow-<name>` utilities. Values are shadow parameter lists (offset/blur/colour). Each token feeds two consumers with different grammars — `box-shadow` (pattern geometry, via the `--ds-*` group aliases) and `filter: drop-shadow(…)` (the utilities and the `shadow:` state shortcut) — so values must stay in the intersection: **one layer, no spread radius, no `inset`**. `drop-shadow()` rejects all three, and rejecting them invalidates the whole filter, so the shadow vanishes rather than degrading.",
1093
950
  "type": "object",
1094
951
  "propertyNames": {
1095
952
  "type": "string"
1096
953
  },
1097
954
  "additionalProperties": {
1098
955
  "type": "string"
1099
- },
1100
- "description": "Cascade-wide fallback tokens, emitted as `--<prop>-default`."
956
+ }
1101
957
  },
1102
- "radii": {
958
+ "fonts": {
959
+ "description": "Raw font stacks by name, emitted as `--font-<name>` tokens (referenced by `typography.families`). **Stacks only — vitops does not load webfonts.** A value here is a `font-family` list and nothing more: it emits no `@font-face`, no preload, and no metrics-matched fallback. If a family needs loading, declare it in Astro's `fonts:` config (`astro.config`, or the site config's `fonts` array) and point the token at the family's `cssVariable` — `\"display\": \"var(--font-league-spartan), sans-serif\"`. Installing a `@fontsource*` package and importing its CSS also works but gives up subsetting, preload and `size-adjust`/`ascent-override` fallbacks, so it regresses CLS.",
1103
960
  "type": "object",
1104
961
  "propertyNames": {
1105
962
  "type": "string"
1106
963
  },
1107
964
  "additionalProperties": {
1108
965
  "type": "string"
1109
- },
1110
- "description": "Shape primitives, emitted as `--br-<name>` (referenced by pattern bases)."
966
+ }
1111
967
  },
1112
- "groups": {
1113
- "description": "Group-level tokens, emitted as `--<prop>-<group>`; patterns opt in via their `group` key.",
968
+ "typeScale": {
969
+ "description": "Fluid modular TYPE scale → `--text-<name>` tokens, consumed by typography roles and text-size utilities.",
1114
970
  "type": "object",
1115
- "propertyNames": {
1116
- "type": "string"
1117
- },
1118
- "additionalProperties": {
1119
- "type": "object",
1120
- "propertyNames": {
1121
- "type": "string"
971
+ "properties": {
972
+ "base": {
973
+ "type": "string",
974
+ "description": "Anchor size (a CSS length, e.g. \"1rem\") — the value at `baseStep`."
1122
975
  },
1123
- "additionalProperties": {
1124
- "type": "string"
976
+ "ratio": {
977
+ "type": "number",
978
+ "description": "Modular ratio between adjacent steps at large viewports."
1125
979
  },
1126
- "description": "A CSS declaration block: property → value. Values stay strings (they can be hex, var(), clamp(), keywords, …); the generator, not the schema, interprets them."
1127
- }
1128
- },
1129
- "z": {
1130
- "description": "Z-index tiers → `--z-tier-<name>`.",
1131
- "type": "object",
1132
- "propertyNames": {
1133
- "type": "string"
1134
- },
1135
- "additionalProperties": {
1136
- "type": "number"
1137
- }
1138
- },
1139
- "items": {
1140
- "description": "The component patterns to emit, keyed by name.",
1141
- "type": "object",
1142
- "propertyNames": {
1143
- "type": "string"
1144
- },
1145
- "additionalProperties": {
1146
- "type": "object",
1147
- "properties": {
1148
- "group": {
1149
- "description": "Token-cascade group this pattern belongs to (e.g. tag / control / panel); base declarations resolve through `--<prop>-<group>` before `--<prop>-default`.",
1150
- "type": "string"
1151
- },
1152
- "overrides": {
1153
- "type": "object",
1154
- "propertyNames": {
1155
- "type": "string"
1156
- },
1157
- "additionalProperties": {
1158
- "type": "string"
1159
- },
1160
- "description": "Per-pattern token overrides, emitted as `--<prop>-<name>-group` values."
1161
- },
1162
- "element": {
1163
- "description": "Style at element level via zero-specificity `:where(<element>)` (instead of, or alongside, a class).",
1164
- "type": "string"
1165
- },
1166
- "class": {
1167
- "description": "Class name to emit (defaults to the pattern's key when no `element` is set). Combined with `element`, the pattern emits one zero-specificity `:where(<element>, .<class>)` rule so the class works on any tag and any explicit class overrides it.",
1168
- "type": "string"
1169
- },
1170
- "fill": {
1171
- "description": "Whether this pattern is colour-filled (states/roles drive `background-color` + `on-solid` text) or text-coloured (they drive `color`). Defaults to true when `base` declares a background.",
1172
- "type": "boolean"
1173
- },
1174
- "default_role": {
1175
- "description": "Semantic colour role applied to the bare/default variant.",
980
+ "steps": {
981
+ "description": "Token count when `names` is absent (steps are then named 1..steps).",
982
+ "type": "number"
983
+ },
984
+ "names": {
985
+ "description": "Step names, smallest → largest (e.g. [\"xs\",\"sm\",\"md\",…]); each becomes a token suffix.",
986
+ "type": "array",
987
+ "items": {
1176
988
  "type": "string"
1177
- },
1178
- "base": {
1179
- "type": "object",
1180
- "propertyNames": {
1181
- "type": "string"
1182
- },
1183
- "additionalProperties": {
1184
- "type": "string"
989
+ }
990
+ },
991
+ "baseStep": {
992
+ "description": "1-based index of the step whose value is `base`.",
993
+ "type": "number"
994
+ },
995
+ "baseline": {
996
+ "description": "Named step used as the fluid pivot / GUI scale centre (defaults to `baseStep`).",
997
+ "type": "string"
998
+ },
999
+ "fluid": {
1000
+ "description": "Makes the scale fluid: each step compiles to a clamp() that interpolates from `minRatio` at `minVw` to `ratio` at `maxVw`.",
1001
+ "type": "object",
1002
+ "properties": {
1003
+ "minVw": {
1004
+ "type": "string",
1005
+ "description": "Viewport width (CSS length) where fluid scaling bottoms out."
1185
1006
  },
1186
- "description": "Base CSS declarations. Geometry properties (padding, border-radius, border, box-shadow, font-size) are wrapped in per-pattern override hooks (`--p-<name>`, `--br-<name>`, `--b-<name>`, `--ds-<name>`, `--fs-<name>`) so consumers can restyle one pattern by setting one variable."
1187
- },
1188
- "states": {
1189
- "description": "Interaction states (hover / active / focus-visible), each a map of shortcuts: `step` (intensify the fill or text by n rungs — `bg-<role>-solid` → `-solid-bold`, `text-<role>` → `-bold`), `scale` (transform scale), `lift` (translateY + shadow), `shadow` (a shadow name → drop-shadow(var(--shadow-<name>)), or true → lift shadow), `ring` (focus ring), or raw `css` declarations. Hover rules are wrapped in `@media (hover: hover)`.",
1190
- "type": "object",
1191
- "propertyNames": {
1192
- "type": "string"
1007
+ "maxVw": {
1008
+ "type": "string",
1009
+ "description": "Viewport width (CSS length) where fluid scaling tops out."
1193
1010
  },
1194
- "additionalProperties": {
1195
- "type": "object",
1196
- "propertyNames": {
1197
- "type": "string"
1198
- },
1199
- "additionalProperties": {}
1011
+ "minRatio": {
1012
+ "type": "number",
1013
+ "description": "Modular ratio at/below `minVw` (usually < `ratio`)."
1200
1014
  }
1201
1015
  },
1202
- "roles": {
1203
- "description": "Semantic colour role variants to emit as `<pattern>-<role>` classes (fills use the role solid / on-solid tokens).",
1204
- "type": "array",
1205
- "items": {
1206
- "type": "string"
1207
- }
1208
- }
1209
- },
1210
- "additionalProperties": false,
1211
- "description": "One component pattern (button, link, badge, card, …): base declarations + interaction states + semantic role variants, resolved through the pattern token cascade."
1212
- }
1213
- }
1214
- },
1215
- "additionalProperties": false,
1216
- "description": "Component patterns and their token cascade: `defaults` → `groups` → per-pattern `overrides`, plus shape (`radii`) and z-index primitives."
1217
- },
1218
- "typography": {
1219
- "type": "object",
1220
- "properties": {
1221
- "families": {
1222
- "description": "Role-facing family aliases → CSS font values, usually referencing the top-level `fonts` tokens (e.g. \"var(--font-display)\").",
1223
- "type": "object",
1224
- "propertyNames": {
1225
- "type": "string"
1016
+ "required": [
1017
+ "minVw",
1018
+ "maxVw",
1019
+ "minRatio"
1020
+ ],
1021
+ "additionalProperties": false
1022
+ }
1226
1023
  },
1227
- "additionalProperties": {
1228
- "type": "string"
1229
- }
1024
+ "required": [
1025
+ "base",
1026
+ "ratio"
1027
+ ],
1028
+ "additionalProperties": false
1230
1029
  },
1231
- "roles": {
1232
- "description": "Semantic type roles (display, title, heading, body, quote, caption, eyebrow, code, lead, footnote, tag, …), each emitted as a `font-<role>` class.",
1030
+ "spaceScale": {
1031
+ "description": "Fluid modular SPACE scale → `--space-<name>` tokens, consumed by spacing/gap utilities and vertical rhythm.",
1233
1032
  "type": "object",
1234
- "propertyNames": {
1235
- "type": "string"
1236
- },
1237
- "additionalProperties": {
1238
- "type": "object",
1239
- "propertyNames": {
1033
+ "properties": {
1034
+ "base": {
1035
+ "type": "string",
1036
+ "description": "Anchor size (a CSS length, e.g. \"1rem\") — the value at `baseStep`."
1037
+ },
1038
+ "ratio": {
1039
+ "type": "number",
1040
+ "description": "Modular ratio between adjacent steps at large viewports."
1041
+ },
1042
+ "steps": {
1043
+ "description": "Token count when `names` is absent (steps are then named 1..steps).",
1044
+ "type": "number"
1045
+ },
1046
+ "names": {
1047
+ "description": "Step names, smallest → largest (e.g. [\"xs\",\"sm\",\"md\",…]); each becomes a token suffix.",
1048
+ "type": "array",
1049
+ "items": {
1050
+ "type": "string"
1051
+ }
1052
+ },
1053
+ "baseStep": {
1054
+ "description": "1-based index of the step whose value is `base`.",
1055
+ "type": "number"
1056
+ },
1057
+ "baseline": {
1058
+ "description": "Named step used as the fluid pivot / GUI scale centre (defaults to `baseStep`).",
1240
1059
  "type": "string"
1241
1060
  },
1242
- "additionalProperties": {
1243
- "anyOf": [
1244
- {
1245
- "type": "string"
1061
+ "fluid": {
1062
+ "description": "Makes the scale fluid: each step compiles to a clamp() that interpolates from `minRatio` at `minVw` to `ratio` at `maxVw`.",
1063
+ "type": "object",
1064
+ "properties": {
1065
+ "minVw": {
1066
+ "type": "string",
1067
+ "description": "Viewport width (CSS length) where fluid scaling bottoms out."
1246
1068
  },
1247
- {
1248
- "type": "number"
1069
+ "maxVw": {
1070
+ "type": "string",
1071
+ "description": "Viewport width (CSS length) where fluid scaling tops out."
1072
+ },
1073
+ "minRatio": {
1074
+ "type": "number",
1075
+ "description": "Modular ratio at/below `minVw` (usually < `ratio`)."
1249
1076
  }
1250
- ]
1251
- },
1252
- "description": "An open bag of CSS-ish keys (family / size / weight / line-height / tracking / transform / decoration / text-wrap / …); the generator maps known keys and passes the rest through."
1253
- }
1254
- },
1255
- "headings": {
1256
- "description": "Maps bare elements to type roles so unclassed markup picks up role styling — `{ \"h1\": \"display\", \"h2\": \"heading\" }`. The key is used verbatim as a selector, so it is not limited to h1…h6: **map `\"body\"` to your prose role** to bind base page typography to the role rather than hand-writing it. That binding is what makes the role editable — a stylesheet that re-states `font-family`/`line-height` as literals on `body` shadows `--<role>-ff`/`--<role>-lh`, and the live theme editor then appears to do nothing.",
1257
- "type": "object",
1258
- "propertyNames": {
1259
- "type": "string"
1260
- },
1261
- "additionalProperties": {
1262
- "type": "string"
1263
- }
1264
- }
1265
- },
1266
- "additionalProperties": false,
1267
- "description": "Typography: family aliases, semantic type roles (→ `font-<role>` classes), and the bare-element → role mapping."
1268
- },
1269
- "animations": {
1270
- "type": "object",
1271
- "properties": {
1272
- "effects": {
1273
- "description": "Effect classes to emit (`.fade-in`, `.reveal-left`, …), keyed by class name.",
1274
- "type": "object",
1275
- "propertyNames": {
1276
- "type": "string"
1077
+ },
1078
+ "required": [
1079
+ "minVw",
1080
+ "maxVw",
1081
+ "minRatio"
1082
+ ],
1083
+ "additionalProperties": false
1084
+ }
1277
1085
  },
1278
- "additionalProperties": {
1279
- "type": "object",
1280
- "properties": {
1281
- "kf": {
1282
- "type": "string",
1283
- "description": "Keyframe family driving the effect: composite (transform/opacity), paint, or layout."
1086
+ "required": [
1087
+ "base",
1088
+ "ratio"
1089
+ ],
1090
+ "additionalProperties": false
1091
+ },
1092
+ "patterns": {
1093
+ "type": "object",
1094
+ "properties": {
1095
+ "defaults": {
1096
+ "type": "object",
1097
+ "propertyNames": {
1098
+ "type": "string"
1099
+ },
1100
+ "additionalProperties": {
1101
+ "type": "string"
1102
+ },
1103
+ "description": "Cascade-wide fallback tokens, emitted as `--<prop>-default`."
1104
+ },
1105
+ "radii": {
1106
+ "type": "object",
1107
+ "propertyNames": {
1108
+ "type": "string"
1109
+ },
1110
+ "additionalProperties": {
1111
+ "type": "string"
1112
+ },
1113
+ "description": "Shape primitives, emitted as `--br-<name>` (referenced by pattern bases)."
1114
+ },
1115
+ "groups": {
1116
+ "description": "Group-level tokens, emitted as `--<prop>-<group>`; patterns opt in via their `group` key.",
1117
+ "type": "object",
1118
+ "propertyNames": {
1119
+ "type": "string"
1284
1120
  },
1285
- "css": {
1121
+ "additionalProperties": {
1286
1122
  "type": "object",
1287
1123
  "propertyNames": {
1288
1124
  "type": "string"
1289
1125
  },
1290
1126
  "additionalProperties": {
1291
- "anyOf": [
1292
- {
1127
+ "type": "string"
1128
+ },
1129
+ "description": "A CSS declaration block: property → value. Values stay strings (they can be hex, var(), clamp(), keywords, …); the generator, not the schema, interprets them."
1130
+ }
1131
+ },
1132
+ "z": {
1133
+ "description": "Z-index tiers → `--z-tier-<name>`.",
1134
+ "type": "object",
1135
+ "propertyNames": {
1136
+ "type": "string"
1137
+ },
1138
+ "additionalProperties": {
1139
+ "type": "number"
1140
+ }
1141
+ },
1142
+ "items": {
1143
+ "description": "The component patterns to emit, keyed by name.",
1144
+ "type": "object",
1145
+ "propertyNames": {
1146
+ "type": "string"
1147
+ },
1148
+ "additionalProperties": {
1149
+ "type": "object",
1150
+ "properties": {
1151
+ "group": {
1152
+ "description": "Token-cascade group this pattern belongs to (e.g. tag / control / panel); base declarations resolve through `--<prop>-<group>` before `--<prop>-default`.",
1153
+ "type": "string"
1154
+ },
1155
+ "overrides": {
1156
+ "type": "object",
1157
+ "propertyNames": {
1293
1158
  "type": "string"
1294
1159
  },
1295
- {
1296
- "type": "number"
1160
+ "additionalProperties": {
1161
+ "type": "string"
1162
+ },
1163
+ "description": "Per-pattern token overrides, emitted as `--<prop>-<name>-group` values."
1164
+ },
1165
+ "element": {
1166
+ "description": "Style at element level via zero-specificity `:where(<element>)` (instead of, or alongside, a class).",
1167
+ "type": "string"
1168
+ },
1169
+ "class": {
1170
+ "description": "Class name to emit (defaults to the pattern's key when no `element` is set). Combined with `element`, the pattern emits one zero-specificity `:where(<element>, .<class>)` rule so the class works on any tag and any explicit class overrides it.",
1171
+ "type": "string"
1172
+ },
1173
+ "fill": {
1174
+ "description": "Whether this pattern is colour-filled (states/roles drive `background-color` + `on-solid` text) or text-coloured (they drive `color`). Defaults to true when `base` declares a background.",
1175
+ "type": "boolean"
1176
+ },
1177
+ "default_role": {
1178
+ "description": "Semantic colour role applied to the bare/default variant.",
1179
+ "type": "string"
1180
+ },
1181
+ "base": {
1182
+ "type": "object",
1183
+ "propertyNames": {
1184
+ "type": "string"
1185
+ },
1186
+ "additionalProperties": {
1187
+ "type": "string"
1188
+ },
1189
+ "description": "Base CSS declarations. Geometry properties (padding, border-radius, border, box-shadow, font-size) are wrapped in per-pattern override hooks (`--p-<name>`, `--br-<name>`, `--b-<name>`, `--ds-<name>`, `--fs-<name>`) so consumers can restyle one pattern by setting one variable."
1190
+ },
1191
+ "states": {
1192
+ "description": "Interaction states (hover / active / focus-visible), each a map of shortcuts: `step` (intensify the fill or text by n rungs — `bg-<role>-solid` → `-solid-bold`, `text-<role>` → `-bold`), `scale` (transform scale), `lift` (translateY + shadow), `shadow` (a shadow name → drop-shadow(var(--shadow-<name>)), or true → lift shadow), `ring` (focus ring), or raw `css` declarations. Hover rules are wrapped in `@media (hover: hover)`.",
1193
+ "type": "object",
1194
+ "propertyNames": {
1195
+ "type": "string"
1196
+ },
1197
+ "additionalProperties": {
1198
+ "type": "object",
1199
+ "propertyNames": {
1200
+ "type": "string"
1201
+ },
1202
+ "additionalProperties": {}
1297
1203
  }
1298
- ]
1204
+ },
1205
+ "roles": {
1206
+ "description": "Semantic colour role variants to emit as `<pattern>-<role>` classes (fills use the role solid / on-solid tokens).",
1207
+ "type": "array",
1208
+ "items": {
1209
+ "type": "string"
1210
+ }
1211
+ }
1299
1212
  },
1300
- "description": "Extra literal declarations merged into the effect class as-is."
1213
+ "additionalProperties": false,
1214
+ "description": "One component pattern (button, link, badge, card, …): base declarations + interaction states + semantic role variants, resolved through the pattern token cascade."
1215
+ }
1216
+ }
1217
+ },
1218
+ "additionalProperties": false,
1219
+ "description": "Component patterns and their token cascade: `defaults` → `groups` → per-pattern `overrides`, plus shape (`radii`) and z-index primitives."
1220
+ },
1221
+ "typography": {
1222
+ "type": "object",
1223
+ "properties": {
1224
+ "families": {
1225
+ "description": "Role-facing family aliases → CSS font values, usually referencing the top-level `fonts` tokens (e.g. \"var(--font-display)\").",
1226
+ "type": "object",
1227
+ "propertyNames": {
1228
+ "type": "string"
1229
+ },
1230
+ "additionalProperties": {
1231
+ "type": "string"
1232
+ }
1233
+ },
1234
+ "roles": {
1235
+ "description": "Semantic type roles (display, title, heading, body, quote, caption, eyebrow, code, lead, footnote, tag, …), each emitted as a `font-<role>` class.",
1236
+ "type": "object",
1237
+ "propertyNames": {
1238
+ "type": "string"
1301
1239
  },
1302
- "vars": {
1240
+ "additionalProperties": {
1303
1241
  "type": "object",
1304
1242
  "propertyNames": {
1305
1243
  "type": "string"
@@ -1314,104 +1252,173 @@
1314
1252
  }
1315
1253
  ]
1316
1254
  },
1317
- "description": "Effect endpoint variables (`--<key>: <value>`, e.g. opacity-from, translate-y-to) that override the keyframe defaults."
1255
+ "description": "A bag of CSS-ish keys, each mapped to a declaration plus a `--<role>-<sfx>` override hook. The recognised set is **closed**: `family`, `size`, `weight`, `style`, `line-height`, `tracking` (→ `letter-spacing`), `text-transform`, `text-decoration`, `text-wrap`, `color`. Note the last four are spelled with their full CSS property names — `transform` and `decoration` are NOT accepted. Anything unrecognised is **ignored**, not passed through, so the generator warns rather than emitting it: a silently-dropped `transform: uppercase` is how title-case navigation reaches production. Note also that `style`, `text-transform`, `text-decoration` and `text-wrap` are emitted on **every** role at their identity value (`normal`/`none`/`none`/`wrap`) whether declared or not, so applying one role class over another fully resets it — which means **omitting `text-wrap` is not \"inherit\"**: it emits `text-wrap: wrap` and cancels the `pretty` the role would otherwise inherit from a `pretty` ancestor such as a `body`-mapped role. Declare it on every role — `balance` for heading-like roles, `pretty` for copy, `wrap` for short single-line labels."
1318
1256
  }
1319
1257
  },
1320
- "required": [
1321
- "kf"
1322
- ],
1323
- "additionalProperties": false,
1324
- "description": "A named animation effect class — a pure value layer (`--_anim` + `--<prop>-from/-to`) over the static keyframe engine."
1325
- }
1258
+ "headings": {
1259
+ "description": "Maps bare elements to type roles so unclassed markup picks up role styling — `{ \"h1\": \"display\", \"h2\": \"heading\" }`. The key is used verbatim as a selector, so it is not limited to h1…h6: **map `\"body\"` to your prose role** to bind base page typography to the role rather than hand-writing it. That binding is what makes the role editable — a stylesheet that re-states `font-family`/`line-height` as literals on `body` shadows `--<role>-ff`/`--<role>-lh`, and the live theme editor then appears to do nothing.",
1260
+ "type": "object",
1261
+ "propertyNames": {
1262
+ "type": "string"
1263
+ },
1264
+ "additionalProperties": {
1265
+ "type": "string"
1266
+ }
1267
+ }
1268
+ },
1269
+ "additionalProperties": false,
1270
+ "description": "Typography: family aliases, semantic type roles (→ `font-<role>` classes), and the bare-element → role mapping."
1326
1271
  },
1327
- "journeys": {
1328
- "description": "Multi-part journey classes composed from `base` building blocks.",
1272
+ "animations": {
1329
1273
  "type": "object",
1330
1274
  "properties": {
1331
- "base": {
1332
- "description": "Named journey building blocks: part name → var map.",
1275
+ "effects": {
1276
+ "description": "Effect classes to emit (`.fade-in`, `.reveal-left`, …), keyed by class name.",
1333
1277
  "type": "object",
1334
1278
  "propertyNames": {
1335
1279
  "type": "string"
1336
1280
  },
1337
1281
  "additionalProperties": {
1338
1282
  "type": "object",
1339
- "propertyNames": {
1340
- "type": "string"
1283
+ "properties": {
1284
+ "kf": {
1285
+ "type": "string",
1286
+ "description": "Keyframe family driving the effect: composite (transform/opacity), paint, or layout."
1287
+ },
1288
+ "css": {
1289
+ "type": "object",
1290
+ "propertyNames": {
1291
+ "type": "string"
1292
+ },
1293
+ "additionalProperties": {
1294
+ "anyOf": [
1295
+ {
1296
+ "type": "string"
1297
+ },
1298
+ {
1299
+ "type": "number"
1300
+ }
1301
+ ]
1302
+ },
1303
+ "description": "Extra literal declarations merged into the effect class as-is."
1304
+ },
1305
+ "vars": {
1306
+ "type": "object",
1307
+ "propertyNames": {
1308
+ "type": "string"
1309
+ },
1310
+ "additionalProperties": {
1311
+ "anyOf": [
1312
+ {
1313
+ "type": "string"
1314
+ },
1315
+ {
1316
+ "type": "number"
1317
+ }
1318
+ ]
1319
+ },
1320
+ "description": "Effect endpoint variables (`--<key>: <value>`, e.g. opacity-from, translate-y-to) that override the keyframe defaults."
1321
+ }
1341
1322
  },
1342
- "additionalProperties": {
1343
- "anyOf": [
1344
- {
1323
+ "required": [
1324
+ "kf"
1325
+ ],
1326
+ "additionalProperties": false,
1327
+ "description": "A named animation effect class — a pure value layer (`--_anim` + `--<prop>-from/-to`) over the static keyframe engine."
1328
+ }
1329
+ },
1330
+ "journeys": {
1331
+ "description": "Multi-part journey classes composed from `base` building blocks.",
1332
+ "type": "object",
1333
+ "properties": {
1334
+ "base": {
1335
+ "description": "Named journey building blocks: part name → var map.",
1336
+ "type": "object",
1337
+ "propertyNames": {
1338
+ "type": "string"
1339
+ },
1340
+ "additionalProperties": {
1341
+ "type": "object",
1342
+ "propertyNames": {
1345
1343
  "type": "string"
1346
1344
  },
1347
- {
1348
- "type": "number"
1345
+ "additionalProperties": {
1346
+ "anyOf": [
1347
+ {
1348
+ "type": "string"
1349
+ },
1350
+ {
1351
+ "type": "number"
1352
+ }
1353
+ ]
1349
1354
  }
1350
- ]
1355
+ }
1356
+ },
1357
+ "compose": {
1358
+ "description": "Combinations of base parts, each emitted as a `.<parts>-journey` class.",
1359
+ "type": "array",
1360
+ "items": {
1361
+ "type": "array",
1362
+ "items": {
1363
+ "type": "string"
1364
+ }
1365
+ }
1351
1366
  }
1352
- }
1367
+ },
1368
+ "additionalProperties": false
1369
+ }
1370
+ },
1371
+ "additionalProperties": false,
1372
+ "description": "Animation effect + journey classes (pure value layers). The animation engine itself — keyframes, drivers, floats, utilities — is static framework CSS, not configured here."
1373
+ },
1374
+ "displayName": {
1375
+ "description": "Human-readable theme name (localisable). The label a theme picker would show — nothing reads it yet, because nothing builds more than one theme (see `themes`).",
1376
+ "anyOf": [
1377
+ {
1378
+ "type": "string"
1353
1379
  },
1354
- "compose": {
1355
- "description": "Combinations of base parts, each emitted as a `.<parts>-journey` class.",
1356
- "type": "array",
1357
- "items": {
1358
- "type": "array",
1359
- "items": {
1360
- "type": "string"
1361
- }
1380
+ {
1381
+ "type": "object",
1382
+ "propertyNames": {
1383
+ "type": "string"
1384
+ },
1385
+ "additionalProperties": {
1386
+ "type": "string"
1362
1387
  }
1363
1388
  }
1364
- },
1365
- "additionalProperties": false
1366
- }
1367
- },
1368
- "additionalProperties": false,
1369
- "description": "Animation effect + journey classes (pure value layers). The animation engine itself — keyframes, drivers, floats, utilities — is static framework CSS, not configured here."
1370
- },
1371
- "displayName": {
1372
- "description": "Human-readable theme name (localisable).",
1373
- "anyOf": [
1374
- {
1375
- "type": "string"
1389
+ ]
1376
1390
  },
1377
- {
1378
- "type": "object",
1379
- "propertyNames": {
1380
- "type": "string"
1381
- },
1382
- "additionalProperties": {
1383
- "type": "string"
1384
- }
1391
+ "extends": {
1392
+ "description": "Another `themes` key to inherit from; this entry then only supplies what it overrides.",
1393
+ "type": "string"
1385
1394
  }
1386
- ]
1395
+ },
1396
+ "additionalProperties": false
1387
1397
  },
1388
- "extends": {
1389
- "description": "Another `designSystem` key to inherit from; this entry then only supplies what it overrides.",
1390
- "type": "string"
1391
- }
1398
+ "description": "Named themes. `default` is the base; others may `extends` another and supply a partial patch. Light/dark is NOT a theme — the functional tokens flip per appearance within each one, so `default` already has both. Only the default theme is built today; multi-theme output and a picker are not wired yet."
1392
1399
  },
1393
- "additionalProperties": false
1400
+ "defaultTheme": {
1401
+ "description": "Which `themes` entry to use (convention: \"default\"). Validated against the map; selecting a non-default theme is not wired yet.",
1402
+ "type": "string"
1403
+ },
1404
+ "defaultColorScheme": {
1405
+ "description": "Initial appearance. `\"system\"` follows the OS via `prefers-color-scheme` and is what makes `<color-scheme-toggle>`'s \"System\" position resolve to anything — it removes the theme attribute, so without this the page falls through to light. It also gives a no-JS page the OS appearance. Defaults to `\"light\"`, because switching an existing site to `\"system\"` visibly flips it dark for dark-OS visitors.",
1406
+ "type": "string",
1407
+ "enum": [
1408
+ "light",
1409
+ "dark",
1410
+ "system"
1411
+ ]
1412
+ }
1394
1413
  },
1395
- "description": "Named-theme map of design systems. `default` is the base; other entries may `extends` another and supply a partial patch. Light/dark is automatic (functional tokens flip per appearance), not a separate theme entry. A bare design system (with `colors`) is shorthand for `{ default: … }`."
1396
- },
1397
- "defaultTheme": {
1398
- "description": "Which `designSystem` entry to use by default (convention: \"default\").",
1399
- "type": "string"
1400
- },
1401
- "defaultColorScheme": {
1402
- "description": "Initial appearance to render before any user/system preference applies.",
1403
- "type": "string",
1404
- "enum": [
1405
- "light",
1406
- "dark"
1407
- ]
1408
- },
1409
- "respectSystemPreference": {
1410
- "description": "Follow the user's OS light/dark preference.",
1411
- "type": "boolean"
1414
+ "required": [
1415
+ "themes"
1416
+ ],
1417
+ "additionalProperties": false,
1418
+ "description": "The design system: named themes plus the system-wide facts (which theme, which appearance). A bare design system, or a bare theme map, is accepted as shorthand for `{ themes: … }`."
1412
1419
  },
1413
1420
  "fonts": {
1414
- "description": "Font LOADING (a serialisable projection of the Astro Fonts API: provider, weights, subsets, preload). Font TOKENS live in `designSystem.<theme>.fonts` and reference the same `cssVariable`.",
1421
+ "description": "Font LOADING (a serialisable projection of the Astro Fonts API: provider, weights, subsets, preload). Font TOKENS live in `designSystem.themes.<theme>.fonts` and reference the same `cssVariable`.",
1415
1422
  "type": "array",
1416
1423
  "items": {
1417
1424
  "type": "object",
@@ -2219,7 +2226,7 @@
2219
2226
  "additionalProperties": false
2220
2227
  },
2221
2228
  "analytics": {
2222
- "description": "Analytics provider IDs (gated per environment via `environments.<env>.analytics`).",
2229
+ "description": "Analytics provider IDs (gated per environment via `environments.<env>.analytics`). Which provider is set is also what the generated privacy policy and cookie notice disclose — see `legal`.",
2223
2230
  "type": "object",
2224
2231
  "properties": {
2225
2232
  "googleAnalyticsId": {
@@ -2230,6 +2237,33 @@
2230
2237
  },
2231
2238
  "googleTagManagerId": {
2232
2239
  "type": "string"
2240
+ },
2241
+ "clarityId": {
2242
+ "description": "Microsoft Clarity project ID.",
2243
+ "type": "string"
2244
+ },
2245
+ "matomo": {
2246
+ "description": "Matomo instance.",
2247
+ "type": "object",
2248
+ "properties": {
2249
+ "url": {
2250
+ "type": "string",
2251
+ "description": "Instance base URL (self-hosted, or `*.matomo.cloud`)."
2252
+ },
2253
+ "siteId": {
2254
+ "type": "string",
2255
+ "description": "Site ID within that instance."
2256
+ },
2257
+ "cookies": {
2258
+ "description": "Default false — Matomo runs cookieless (`disableCookies`), which is what lets the cookie notice state positively that it sets none.",
2259
+ "type": "boolean"
2260
+ }
2261
+ },
2262
+ "required": [
2263
+ "url",
2264
+ "siteId"
2265
+ ],
2266
+ "additionalProperties": false
2233
2267
  }
2234
2268
  },
2235
2269
  "additionalProperties": false
@@ -2472,13 +2506,21 @@
2472
2506
  "type": "object",
2473
2507
  "properties": {
2474
2508
  "ui": {
2475
- "description": "Icon set used for UI chrome (e.g. \"lucide\").",
2509
+ "description": "Icon set used for UI chrome (e.g. \"lucide\", \"ph\").",
2476
2510
  "type": "string"
2477
2511
  },
2478
2512
  "brand": {
2479
2513
  "description": "Icon set used for brand marks.",
2480
2514
  "type": "string"
2481
2515
  },
2516
+ "weight": {
2517
+ "description": "Weight for suffix-weighted sets like Phosphor (\"regular\" | \"bold\" | \"duotone\" | \"fill\" | \"light\" | \"thin\"). Ignored by sets that split weights across collections, e.g. Font Awesome.",
2518
+ "type": "string"
2519
+ },
2520
+ "sprite": {
2521
+ "description": "Emit an SVG sprite (icons.svg) alongside the stylesheet, for consumers that cannot run an icon integration (Bricks/WordPress, EmDash renderers).",
2522
+ "type": "boolean"
2523
+ },
2482
2524
  "semantic": {
2483
2525
  "description": "Named semantic icons to include.",
2484
2526
  "type": "array",
@@ -2533,9 +2575,15 @@
2533
2575
  "items": {
2534
2576
  "type": "string"
2535
2577
  }
2578
+ },
2579
+ "ph": {
2580
+ "type": "array",
2581
+ "items": {
2582
+ "type": "string"
2583
+ }
2536
2584
  }
2537
2585
  },
2538
- "additionalProperties": false
2586
+ "additionalProperties": {}
2539
2587
  },
2540
2588
  "favicon": {
2541
2589
  "description": "Favicon/PWA asset generation (consumed by `@getvitops/utils` favicon tooling).",