@kinetixui/cli 0.22.1 → 0.23.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.
Files changed (2) hide show
  1. package/dist/index.js +2052 -54
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc9 from "picocolors";
5
+ import pc10 from "picocolors";
6
6
 
7
7
  // src/commands/add.ts
8
8
  import { existsSync as existsSync4 } from "fs";
@@ -648,8 +648,8 @@ async function parity(names, options) {
648
648
  return item;
649
649
  }) : components;
650
650
  const nameWidth = Math.max(9, ...rows.map((r) => r.name.length));
651
- const header = `${"COMPONENT".padEnd(nameWidth)} ${PLATFORMS.map((p) => PLATFORM_ABBR[p]).join(" ")} STATUS`;
652
- console.log(pc7.dim(header));
651
+ const header4 = `${"COMPONENT".padEnd(nameWidth)} ${PLATFORMS.map((p) => PLATFORM_ABBR[p]).join(" ")} STATUS`;
652
+ console.log(pc7.dim(header4));
653
653
  for (const item of rows) {
654
654
  const on = new Set(item.platforms ?? PLATFORMS);
655
655
  const marks = PLATFORMS.map((p) => on.has(p) ? pc7.green("\u2713 ") : pc7.dim("\u2014 "));
@@ -666,20 +666,275 @@ async function parity(names, options) {
666
666
  }
667
667
  }
668
668
 
669
- // src/commands/theme.ts
670
- import { existsSync as existsSync9 } from "fs";
671
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
672
- import path9 from "path";
669
+ // src/commands/preset.ts
670
+ import { writeFile as writeFile3 } from "fs/promises";
673
671
  import pc8 from "picocolors";
674
672
 
675
- // src/lib/color-math.ts
673
+ // ../create-preset/src/index.ts
674
+ var PRESET_VERSION = 1;
675
+ var SUPPORTED_VERSIONS = [1];
676
+ var NEUTRALS = ["kinetix", "neutral", "cool", "warm", "stone"];
677
+ var RADII = ["square", "small", "default", "rounded", "soft"];
678
+ var SURFACES = ["flat", "bordered", "soft", "elevated"];
679
+ var CHART_PALETTES = ["kinetix", "brand", "categorical", "cool", "warm"];
680
+ var ACCEPTED_TOKENS = [
681
+ "background",
682
+ "foreground",
683
+ "card",
684
+ "card-foreground",
685
+ "popover",
686
+ "popover-foreground",
687
+ "primary",
688
+ "primary-foreground",
689
+ "secondary",
690
+ "secondary-foreground",
691
+ "muted",
692
+ "muted-foreground",
693
+ "accent",
694
+ "accent-foreground",
695
+ "destructive",
696
+ "destructive-foreground",
697
+ "border",
698
+ "input",
699
+ "ring",
700
+ "action",
701
+ "action-foreground",
702
+ "link",
703
+ "focus",
704
+ "brand",
705
+ "brand-foreground"
706
+ ];
707
+ var DEFAULT_BRAND = "#1d4ed8";
708
+ var DEFAULT_PRESET = {
709
+ brand: DEFAULT_BRAND,
710
+ neutral: "kinetix",
711
+ radius: "default",
712
+ surface: "soft",
713
+ chartPalette: "kinetix",
714
+ manualOverrides: {}
715
+ };
716
+ var LIMITS = {
717
+ /** Longest code accepted, prefix included. A maximal legitimate preset is ~700 characters. */
718
+ code: 4096,
719
+ /** Longest decoded JSON. Base64 expands by 4/3, so this is the real guard. */
720
+ json: 4096,
721
+ /** A hex colour, at most. */
722
+ colour: 7,
723
+ /** More override entries than there are tokens means the payload is not one of ours. */
724
+ overrides: 64
725
+ };
726
+ var HEX2 = /^#[0-9a-f]{6}$/;
727
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
728
+ var fail = (error) => ({ ok: false, error });
729
+ var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
730
+ function normalizeColour(value) {
731
+ const trimmed = value.trim().toLowerCase();
732
+ const hashed = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
733
+ return HEX2.test(hashed) ? hashed : null;
734
+ }
735
+ function readEnum(value, allowed, field) {
736
+ if (typeof value !== "string" || !allowed.includes(value)) {
737
+ return { code: "bad-field", field, message: `${field} must be one of: ${allowed.join(", ")}` };
738
+ }
739
+ return value;
740
+ }
741
+ function validate(raw) {
742
+ if (!isPlainObject(raw)) return fail({ code: "bad-json", message: "A preset must be a JSON object." });
743
+ const version = raw.v;
744
+ if (typeof version !== "number" || !Number.isInteger(version)) {
745
+ return fail({ code: "bad-json", message: "A preset must carry an integer version." });
746
+ }
747
+ if (!SUPPORTED_VERSIONS.includes(version)) {
748
+ return fail({
749
+ code: "unsupported-version",
750
+ version,
751
+ message: `This preset is version ${version}; this build reads version ${SUPPORTED_VERSIONS.join(", ")}. Update KinetixUI and try again.`
752
+ });
753
+ }
754
+ const config = { ...DEFAULT_PRESET, manualOverrides: {} };
755
+ if (raw.brand !== void 0) {
756
+ if (typeof raw.brand !== "string" || raw.brand.length > LIMITS.colour) {
757
+ return fail({ code: "bad-field", field: "brand", message: "brand must be a hex colour." });
758
+ }
759
+ const colour = normalizeColour(raw.brand);
760
+ if (!colour) return fail({ code: "bad-field", field: "brand", message: `"${raw.brand}" is not a six-digit hex colour.` });
761
+ config.brand = colour;
762
+ }
763
+ for (const [field, allowed] of [
764
+ ["neutral", NEUTRALS],
765
+ ["radius", RADII],
766
+ ["surface", SURFACES],
767
+ ["chartPalette", CHART_PALETTES]
768
+ ]) {
769
+ if (raw[field] === void 0) continue;
770
+ const value = readEnum(raw[field], allowed, field);
771
+ if (typeof value !== "string") return fail(value);
772
+ config[field] = value;
773
+ }
774
+ if (raw.manualOverrides !== void 0) {
775
+ if (!isPlainObject(raw.manualOverrides)) {
776
+ return fail({ code: "bad-field", field: "manualOverrides", message: "manualOverrides must be an object." });
777
+ }
778
+ const entries = Object.entries(raw.manualOverrides);
779
+ if (entries.length > LIMITS.overrides) {
780
+ return fail({ code: "bad-field", field: "manualOverrides", message: "Too many overrides." });
781
+ }
782
+ for (const [token, value] of entries) {
783
+ if (FORBIDDEN_KEYS.has(token)) {
784
+ return fail({ code: "bad-field", field: "manualOverrides", message: `"${token}" is not a theme colour.` });
785
+ }
786
+ if (!ACCEPTED_TOKENS.includes(token)) {
787
+ return fail({ code: "bad-field", field: "manualOverrides", message: `"${token}" is not a theme colour.` });
788
+ }
789
+ if (typeof value !== "string" || value.length > LIMITS.colour) {
790
+ return fail({ code: "bad-field", field: "manualOverrides", message: `${token} must be a hex colour.` });
791
+ }
792
+ const colour = normalizeColour(value);
793
+ if (!colour) {
794
+ return fail({ code: "bad-field", field: "manualOverrides", message: `${token}: "${value}" is not a six-digit hex colour.` });
795
+ }
796
+ config.manualOverrides[token] = colour;
797
+ }
798
+ }
799
+ return { ok: true, config };
800
+ }
801
+ function canonicalize(config) {
802
+ const envelope = { v: PRESET_VERSION };
803
+ const brand = normalizeColour(config.brand) ?? DEFAULT_BRAND;
804
+ if (brand !== DEFAULT_PRESET.brand) envelope.brand = brand;
805
+ if (config.neutral !== DEFAULT_PRESET.neutral) envelope.neutral = config.neutral;
806
+ if (config.radius !== DEFAULT_PRESET.radius) envelope.radius = config.radius;
807
+ if (config.surface !== DEFAULT_PRESET.surface) envelope.surface = config.surface;
808
+ if (config.chartPalette !== DEFAULT_PRESET.chartPalette) envelope.chartPalette = config.chartPalette;
809
+ const overrides = {};
810
+ for (const token of ACCEPTED_TOKENS) {
811
+ const value = config.manualOverrides[token];
812
+ if (!value) continue;
813
+ const colour = normalizeColour(value);
814
+ if (colour) overrides[token] = colour;
815
+ }
816
+ if (Object.keys(overrides).length > 0) envelope.manualOverrides = overrides;
817
+ return envelope;
818
+ }
819
+ function isDefaultPreset(config) {
820
+ const envelope = canonicalize(config);
821
+ return Object.keys(envelope).length === 1;
822
+ }
823
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
824
+ var REVERSE = new Map([...ALPHABET].map((c, i) => [c, i]));
825
+ function toBase64Url(text) {
826
+ const bytes = [];
827
+ for (let i = 0; i < text.length; i++) {
828
+ const code = text.charCodeAt(i);
829
+ if (code > 127) return null;
830
+ bytes.push(code);
831
+ }
832
+ let out = "";
833
+ for (let i = 0; i < bytes.length; i += 3) {
834
+ const a = bytes[i];
835
+ const b = bytes[i + 1];
836
+ const c = bytes[i + 2];
837
+ out += ALPHABET[a >> 2];
838
+ out += ALPHABET[(a & 3) << 4 | (b ?? 0) >> 4];
839
+ if (b === void 0) break;
840
+ out += ALPHABET[(b & 15) << 2 | (c ?? 0) >> 6];
841
+ if (c === void 0) break;
842
+ out += ALPHABET[c & 63];
843
+ }
844
+ return out;
845
+ }
846
+ function fromBase64Url(code) {
847
+ let out = "";
848
+ let buffer = 0;
849
+ let bits = 0;
850
+ for (const char of code) {
851
+ const value = REVERSE.get(char);
852
+ if (value === void 0) return null;
853
+ buffer = buffer << 6 | value;
854
+ bits += 6;
855
+ if (bits >= 8) {
856
+ bits -= 8;
857
+ const byte = buffer >> bits & 255;
858
+ if (byte > 127) return null;
859
+ out += String.fromCharCode(byte);
860
+ }
861
+ }
862
+ return out;
863
+ }
864
+ var PRESET_PREFIX = "KX";
865
+ function encodePreset(config) {
866
+ const payload = toBase64Url(JSON.stringify(canonicalize(config)));
867
+ if (payload === null) throw new Error("Preset contains a value that cannot be encoded.");
868
+ return `${PRESET_PREFIX}${PRESET_VERSION}_${payload}`;
869
+ }
870
+ function decodePreset(input) {
871
+ const code = extractCode(input);
872
+ if (!code) return fail({ code: "empty", message: "No preset code found." });
873
+ if (code.length > LIMITS.code) return fail({ code: "too-long", message: "That preset code is too long to be one of ours." });
874
+ const match = /^KX(\d+)_(.*)$/s.exec(code);
875
+ if (!match) {
876
+ return fail({ code: "bad-prefix", message: "A preset code starts with KX1_." });
877
+ }
878
+ const version = Number(match[1]);
879
+ if (!SUPPORTED_VERSIONS.includes(version)) {
880
+ return fail({
881
+ code: "unsupported-version",
882
+ version,
883
+ message: `This preset is version ${version}; this build reads version ${SUPPORTED_VERSIONS.join(", ")}. Update KinetixUI and try again.`
884
+ });
885
+ }
886
+ const json = fromBase64Url(match[2] ?? "");
887
+ if (json === null) return fail({ code: "bad-encoding", message: "That preset code is not valid base64url." });
888
+ if (json.length > LIMITS.json) return fail({ code: "too-long", message: "That preset is larger than any real one." });
889
+ let parsed;
890
+ try {
891
+ parsed = JSON.parse(json);
892
+ } catch {
893
+ return fail({ code: "bad-json", message: "That preset code does not contain a readable preset." });
894
+ }
895
+ if (isPlainObject(parsed) && parsed.v === void 0) parsed.v = version;
896
+ const result = validate(parsed);
897
+ return result.ok ? { ok: true, config: migrate(result.config, version) } : result;
898
+ }
899
+ function migrate(config, from) {
900
+ switch (from) {
901
+ case 1:
902
+ return config;
903
+ default:
904
+ return config;
905
+ }
906
+ }
907
+ function extractCode(input) {
908
+ const text = input.trim();
909
+ if (!text) return null;
910
+ if (text.length > LIMITS.code * 2) return null;
911
+ if (/^https?:\/\//i.test(text)) {
912
+ try {
913
+ const url = new URL(text);
914
+ const values = url.searchParams.getAll(PRESET_PARAM);
915
+ const first = values[0];
916
+ return first === void 0 ? null : first.trim();
917
+ } catch {
918
+ return null;
919
+ }
920
+ }
921
+ return text;
922
+ }
923
+ var PRESET_PARAM = "preset";
924
+ function presetUrl(config, origin) {
925
+ return `${origin.replace(/\/$/, "")}/create?${PRESET_PARAM}=${encodePreset(config)}`;
926
+ }
927
+
928
+ // ../create-theme/src/color-math.ts
676
929
  function hexToRgb(hex) {
677
930
  const h = hex.replace("#", "");
678
931
  const n = parseInt(h.length === 3 ? h.replace(/(.)/g, "$1$1") : h, 16);
679
932
  return [n >> 16 & 255, n >> 8 & 255, n & 255];
680
933
  }
681
934
  function rgbToHsl(r, g, b) {
682
- r /= 255, g /= 255, b /= 255;
935
+ r /= 255;
936
+ g /= 255;
937
+ b /= 255;
683
938
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
684
939
  let h = 0, s = 0;
685
940
  const l = (max + min) / 2;
@@ -705,18 +960,46 @@ function hexToHslChannels(hex) {
705
960
  const [h, s, l] = rgbToHsl(...hexToRgb(hex));
706
961
  return `${h} ${s}% ${l}%`;
707
962
  }
708
- function relLuminance([r, g, b]) {
709
- const [cr, cg, cb] = [r, g, b].map((v) => {
710
- const s = v / 255;
711
- return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
712
- });
713
- return 0.2126 * cr + 0.7152 * cg + 0.0722 * cb;
963
+ function relLuminanceNormalized([r, g, b]) {
964
+ const linear = (s) => s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
965
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
714
966
  }
715
- function contrastRatio(hexA, hexB) {
716
- const la = relLuminance(hexToRgb(hexA)) + 0.05;
717
- const lb = relLuminance(hexToRgb(hexB)) + 0.05;
967
+ var normalize = ([r, g, b]) => [r / 255, g / 255, b / 255];
968
+ function contrastOfNormalized(a, b) {
969
+ const la = relLuminanceNormalized(a) + 0.05;
970
+ const lb = relLuminanceNormalized(b) + 0.05;
718
971
  return la > lb ? la / lb : lb / la;
719
972
  }
973
+ function contrastRatio(hexA, hexB) {
974
+ return contrastOfNormalized(normalize(hexToRgb(hexA)), normalize(hexToRgb(hexB)));
975
+ }
976
+ function hslChannelsToHex(channels) {
977
+ const [h = 0, s = 0, l = 0] = channels.trim().split(/\s+/).map((v) => parseFloat(v));
978
+ const S = s / 100;
979
+ const L = l / 100;
980
+ const c = (1 - Math.abs(2 * L - 1)) * S;
981
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
982
+ const m = L - c / 2;
983
+ const sector = Math.floor((h % 360 + 360) % 360 / 60);
984
+ const rgb = [
985
+ [c, x, 0],
986
+ [x, c, 0],
987
+ [0, c, x],
988
+ [0, x, c],
989
+ [x, 0, c],
990
+ [c, 0, x]
991
+ ][sector];
992
+ return `#${rgb.map((v) => Math.round((v + m) * 255).toString(16).padStart(2, "0")).join("")}`;
993
+ }
994
+ function swiftUiChannels(hex) {
995
+ return hexToRgb(hex).map((v) => Number((v / 255).toFixed(3)));
996
+ }
997
+ function guaranteedContrast(hexA, hexB) {
998
+ const exact = contrastRatio(hexA, hexB);
999
+ const asCss = contrastRatio(hslChannelsToHex(hexToHslChannels(hexA)), hslChannelsToHex(hexToHslChannels(hexB)));
1000
+ const asSwift = contrastOfNormalized(swiftUiChannels(hexA), swiftUiChannels(hexB));
1001
+ return Math.min(exact, asCss, asSwift);
1002
+ }
720
1003
  function bestTextHex(bgHex) {
721
1004
  return contrastRatio(bgHex, "#ffffff") >= contrastRatio(bgHex, "#000000") ? "#ffffff" : "#000000";
722
1005
  }
@@ -728,8 +1011,1672 @@ function normalizeHex(v) {
728
1011
  return `#${t.toLowerCase()}`;
729
1012
  }
730
1013
 
1014
+ // ../create-theme/src/oklch.ts
1015
+ var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
1016
+ var wrapHue = (h) => h >= 0 && h < 360 ? h : (h % 360 + 360) % 360;
1017
+ var MAX_CHROMA = 0.37;
1018
+ var toLinear = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
1019
+ var toGamma = (v) => v <= 31308e-7 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055;
1020
+ function srgbToOklab([r, g, b]) {
1021
+ const lr = toLinear(r / 255);
1022
+ const lg = toLinear(g / 255);
1023
+ const lb = toLinear(b / 255);
1024
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
1025
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
1026
+ const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
1027
+ return {
1028
+ L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
1029
+ a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
1030
+ b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
1031
+ };
1032
+ }
1033
+ function oklabToSrgbRaw({ L, a, b }) {
1034
+ const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
1035
+ const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
1036
+ const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
1037
+ return [
1038
+ toGamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s) * 255,
1039
+ toGamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s) * 255,
1040
+ toGamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s) * 255
1041
+ ];
1042
+ }
1043
+ function oklabToOklch({ L, a, b }) {
1044
+ const c = Math.sqrt(a * a + b * b);
1045
+ if (c < 1e-6) return { l: L, c: 0, h: 0 };
1046
+ return { l: L, c, h: (Math.atan2(b, a) * 180 / Math.PI + 360) % 360 };
1047
+ }
1048
+ function oklchToOklab({ l, c, h }) {
1049
+ const rad = h * Math.PI / 180;
1050
+ return { L: l, a: c * Math.cos(rad), b: c * Math.sin(rad) };
1051
+ }
1052
+ var EPS = 0.5 / 255;
1053
+ var fitsSrgb = (rgb) => rgb.every((v) => v >= -EPS * 255 && v <= 255 + EPS * 255);
1054
+ function gamutMapOklch(colour) {
1055
+ const h = wrapHue(colour.h);
1056
+ const l = clamp(colour.l, 0, 1);
1057
+ const c = Math.max(0, colour.c);
1058
+ if (fitsSrgb(oklabToSrgbRaw(oklchToOklab({ l, c, h })))) return { l, c, h };
1059
+ let lo = 0;
1060
+ let hi = c;
1061
+ for (let i = 0; i < 24; i++) {
1062
+ const mid = (lo + hi) / 2;
1063
+ if (fitsSrgb(oklabToSrgbRaw(oklchToOklab({ l, c: mid, h })))) lo = mid;
1064
+ else hi = mid;
1065
+ }
1066
+ return { l, c: lo, h };
1067
+ }
1068
+ function rgbToHex([r, g, b]) {
1069
+ const ch = (v) => Math.round(clamp(v, 0, 255)).toString(16).padStart(2, "0");
1070
+ return `#${ch(r)}${ch(g)}${ch(b)}`;
1071
+ }
1072
+ function hexToOklch(hex) {
1073
+ if (!isHex(hex)) return null;
1074
+ return oklabToOklch(srgbToOklab(hexToRgb(normalizeHex(hex))));
1075
+ }
1076
+ function oklchToHex(colour) {
1077
+ return rgbToHex(oklabToSrgbRaw(oklchToOklab(gamutMapOklch(colour))));
1078
+ }
1079
+ function adjust(colour, { dl = 0, dc = 0, dh = 0 }) {
1080
+ return gamutMapOklch({
1081
+ l: clamp(colour.l + dl, 0, 1),
1082
+ c: Math.max(0, colour.c + dc),
1083
+ h: wrapHue(colour.h + dh)
1084
+ });
1085
+ }
1086
+
1087
+ // ../tokens/dist/web/tokens.js
1088
+ var tokens = {
1089
+ "color": {
1090
+ "azure": {
1091
+ "0": "#f7faff",
1092
+ "50": "#eff6ff",
1093
+ "100": "#dbeafe",
1094
+ "150": "#cde1fd",
1095
+ "200": "#bfdbfe",
1096
+ "300": "#93c5fd",
1097
+ "400": "#60a5fa",
1098
+ "500": "#3b82f6",
1099
+ "600": "#2563eb",
1100
+ "700": "#1d4ed8",
1101
+ "800": "#1e40af",
1102
+ "850": "#1b3897",
1103
+ "900": "#1e3a8a",
1104
+ "950": "#172554",
1105
+ "1000": "#0f172a"
1106
+ },
1107
+ "blue": {
1108
+ "0": "#f6fbff",
1109
+ "50": "#f0f7ff",
1110
+ "100": "#92b2c8",
1111
+ "150": "#83a4b9",
1112
+ "200": "#7495ab",
1113
+ "300": "#57778d",
1114
+ "400": "#395a70",
1115
+ "500": "#1b3c53",
1116
+ "600": "#163042",
1117
+ "700": "#102432",
1118
+ "800": "#0b1821",
1119
+ "850": "#081219",
1120
+ "900": "#050c11",
1121
+ "950": "#030608",
1122
+ "1000": "#000000"
1123
+ },
1124
+ "green": {
1125
+ "0": "#ffffff",
1126
+ "50": "#f1f3f1",
1127
+ "100": "#e3e7e3",
1128
+ "150": "#d5dbd5",
1129
+ "200": "#c7cfc7",
1130
+ "300": "#acb8ab",
1131
+ "400": "#90a08f",
1132
+ "500": "#748873",
1133
+ "600": "#5d6d5c",
1134
+ "700": "#465245",
1135
+ "800": "#2e362e",
1136
+ "850": "#232923",
1137
+ "900": "#171b17",
1138
+ "950": "#0c0e0c",
1139
+ "1000": "#000000"
1140
+ },
1141
+ "taupe": {
1142
+ "0": "#ffffff",
1143
+ "50": "#fbf9f8",
1144
+ "100": "#f6f3f0",
1145
+ "150": "#f2ece9",
1146
+ "200": "#ede6e2",
1147
+ "300": "#e4dad3",
1148
+ "400": "#dbcdc5",
1149
+ "500": "#d2c1b6",
1150
+ "600": "#b4a499",
1151
+ "700": "#96867c",
1152
+ "800": "#78695f",
1153
+ "850": "#695a51",
1154
+ "900": "#5a4b42",
1155
+ "950": "#4b3d34",
1156
+ "1000": "#3c2e25"
1157
+ },
1158
+ "cream": {
1159
+ "0": "#ffffff",
1160
+ "50": "#fefefd",
1161
+ "100": "#fefdfc",
1162
+ "150": "#fdfbfa",
1163
+ "200": "#fdfaf9",
1164
+ "300": "#fbf8f5",
1165
+ "400": "#faf5f2",
1166
+ "500": "#f9f3ef",
1167
+ "600": "#e3d4ca",
1168
+ "700": "#cdb5a4",
1169
+ "800": "#b8957f",
1170
+ "850": "#ad866c",
1171
+ "900": "#a27659",
1172
+ "950": "#976747",
1173
+ "1000": "#8c5734"
1174
+ },
1175
+ "amber": {
1176
+ "0": "#ffffff",
1177
+ "50": "#fff8ee",
1178
+ "100": "#fff2dd",
1179
+ "150": "#ffebcb",
1180
+ "200": "#ffe4ba",
1181
+ "300": "#ffd798",
1182
+ "400": "#ffc975",
1183
+ "500": "#ffbc53",
1184
+ "600": "#d49c42",
1185
+ "700": "#aa7b32",
1186
+ "800": "#7f5b21",
1187
+ "850": "#6a4b19",
1188
+ "900": "#553a11",
1189
+ "950": "#3f2a08",
1190
+ "1000": "#2a1a00"
1191
+ },
1192
+ "red": {
1193
+ "0": "#fff9f9",
1194
+ "50": "#f9e1e1",
1195
+ "100": "#f4c9c9",
1196
+ "150": "#eeb1b1",
1197
+ "200": "#e89999",
1198
+ "300": "#dd6a6a",
1199
+ "400": "#d13a3a",
1200
+ "500": "#c60a0a",
1201
+ "600": "#9e0808",
1202
+ "700": "#770606",
1203
+ "800": "#4f0404",
1204
+ "850": "#3b0303",
1205
+ "900": "#280202",
1206
+ "950": "#140101",
1207
+ "1000": "#000000"
1208
+ },
1209
+ "neutral": {
1210
+ "0": "#ffffff",
1211
+ "50": "#fafafa",
1212
+ "100": "#f6f6f6",
1213
+ "200": "#ededed",
1214
+ "300": "#e0e0e0",
1215
+ "400": "#c2c2c2",
1216
+ "500": "#9e9e9e",
1217
+ "600": "#6d6d6d",
1218
+ "700": "#4f4f4f",
1219
+ "800": "#333333",
1220
+ "900": "#1f1f1f",
1221
+ "1000": "#000000"
1222
+ },
1223
+ "static": {
1224
+ "white": "#ffffff",
1225
+ "black": "#000000"
1226
+ },
1227
+ "background": "#ffffff",
1228
+ "foreground": "#050c11",
1229
+ "card": "#ffffff",
1230
+ "card-foreground": "#050c11",
1231
+ "popover": "#ffffff",
1232
+ "popover-foreground": "#050c11",
1233
+ "primary": "#1d4ed8",
1234
+ "primary-foreground": "#f0f7ff",
1235
+ "secondary": "#c7cfc7",
1236
+ "secondary-foreground": "#465245",
1237
+ "muted": "#f6f6f6",
1238
+ "muted-foreground": "#6d6d6d",
1239
+ "accent": "#f0f7ff",
1240
+ "accent-foreground": "#1d4ed8",
1241
+ "destructive": "#c60a0a",
1242
+ "destructive-foreground": "#fef3f2",
1243
+ "success": "#5d6d5c",
1244
+ "success-foreground": "#f1f3f1",
1245
+ "warning": "#7f5b21",
1246
+ "warning-foreground": "#fff8eb",
1247
+ "border": "#92b2c8",
1248
+ "input": "#92b2c8",
1249
+ "ring": "#1d4ed8",
1250
+ "semantic": {
1251
+ "error": "#ec5047",
1252
+ "on-error": "#fef3f2",
1253
+ "error-container": "#fef3f2",
1254
+ "on-error-container": "#ec5047",
1255
+ "warning-container": "#fff8eb",
1256
+ "on-warning-container": "#f97907",
1257
+ "success-container": "#f1f3f1",
1258
+ "on-success-container": "#465245",
1259
+ "warning": "#f97907",
1260
+ "info-container": "#f0f7ff",
1261
+ "on-info-container": "#395a70"
1262
+ },
1263
+ "chart-1": "#1b3c53",
1264
+ "chart-2": "#748873",
1265
+ "chart-3": "#ffbc53",
1266
+ "chart-4": "#d13a3a",
1267
+ "chart-5": "#b4a499",
1268
+ "chart-6": "#57778d",
1269
+ "chart-7": "#9e0808",
1270
+ "chart-8": "#465245",
1271
+ "sidebar": "#fafafa",
1272
+ "sidebar-foreground": "#050c11",
1273
+ "sidebar-primary": "#1d4ed8",
1274
+ "sidebar-primary-foreground": "#f0f7ff",
1275
+ "sidebar-accent": "#f0f7ff",
1276
+ "sidebar-accent-foreground": "#1d4ed8",
1277
+ "sidebar-border": "#e0e0e0",
1278
+ "sidebar-ring": "#1d4ed8",
1279
+ "tertiary": "#b0b0b0",
1280
+ "tertiary-foreground": "#f0f7ff",
1281
+ "info": "#57778d",
1282
+ "info-foreground": "#f0f7ff",
1283
+ "brand": "#1b3c53",
1284
+ "brand-foreground": "#f0f7ff",
1285
+ "action": "#1d4ed8",
1286
+ "action-foreground": "#f0f7ff",
1287
+ "action-hover": "#3460dc",
1288
+ "action-pressed": "#3f69de",
1289
+ "focus": "#1d4ed8",
1290
+ "link": "#1d4ed8"
1291
+ },
1292
+ "spacing": {
1293
+ "0": "0",
1294
+ "1": "4px",
1295
+ "2": "8px",
1296
+ "3": "12px",
1297
+ "4": "16px",
1298
+ "5": "20px",
1299
+ "6": "24px",
1300
+ "7": "28px",
1301
+ "8": "32px",
1302
+ "10": "40px",
1303
+ "12": "48px",
1304
+ "16": "64px",
1305
+ "20": "80px",
1306
+ "24": "96px",
1307
+ "32": "128px"
1308
+ },
1309
+ "radius": {
1310
+ "none": "0",
1311
+ "sm": "4px",
1312
+ "md": "8px",
1313
+ "lg": "12px",
1314
+ "xl": "16px",
1315
+ "xxl": "24px",
1316
+ "full": "9999px",
1317
+ "field": "4px",
1318
+ "control": "8px",
1319
+ "container": "12px",
1320
+ "surface": "16px"
1321
+ },
1322
+ "borderWidth": {
1323
+ "default": "1px",
1324
+ "focus": "2px"
1325
+ },
1326
+ "interaction": {
1327
+ "target": {
1328
+ "minimum": "44px",
1329
+ "default": "40px"
1330
+ },
1331
+ "focus": {
1332
+ "width": "1px",
1333
+ "offset": "2px"
1334
+ },
1335
+ "press": {
1336
+ "opacity": 0.8
1337
+ },
1338
+ "drag": {
1339
+ "threshold": 4
1340
+ }
1341
+ },
1342
+ "duration": {
1343
+ "instant": "100ms",
1344
+ "fast": "200ms",
1345
+ "base": "300ms",
1346
+ "slow": "500ms",
1347
+ "slower": "1000ms"
1348
+ },
1349
+ "easing": {
1350
+ "linear": [
1351
+ 0,
1352
+ 0,
1353
+ 1,
1354
+ 1
1355
+ ],
1356
+ "standard": [
1357
+ 0.4,
1358
+ 0,
1359
+ 0.2,
1360
+ 1
1361
+ ],
1362
+ "enter": [
1363
+ 0,
1364
+ 0,
1365
+ 0.2,
1366
+ 1
1367
+ ],
1368
+ "exit": [
1369
+ 0.4,
1370
+ 0,
1371
+ 1,
1372
+ 1
1373
+ ],
1374
+ "emphasized": [
1375
+ 0.2,
1376
+ 0,
1377
+ 0,
1378
+ 1
1379
+ ]
1380
+ },
1381
+ "opacity": {
1382
+ "hidden": 0,
1383
+ "disabled": 0.5,
1384
+ "muted": 0.7,
1385
+ "visible": 1
1386
+ },
1387
+ "font": {
1388
+ "family": {
1389
+ "sans": "Montserrat, ui-sans-serif, system-ui, sans-serif"
1390
+ },
1391
+ "weight": {
1392
+ "regular": "400",
1393
+ "medium": "500",
1394
+ "semibold": "600"
1395
+ }
1396
+ },
1397
+ "fontSize": {
1398
+ "display-lg": "57px",
1399
+ "display-md": "45px",
1400
+ "display-sm": "36px",
1401
+ "headline-lg": "32px",
1402
+ "headline-md": "28px",
1403
+ "headline-sm": "24px",
1404
+ "title-lg": "22px",
1405
+ "title-md": "16px",
1406
+ "title-sm": "14px",
1407
+ "title-dialog": "18px",
1408
+ "label-lg": "14px",
1409
+ "label-md": "12px",
1410
+ "label-sm": "11px",
1411
+ "body-lg": "16px",
1412
+ "body-md": "14px",
1413
+ "body-sm": "12px"
1414
+ },
1415
+ "lineHeight": {
1416
+ "display-lg": "64px",
1417
+ "display-md": "52px",
1418
+ "display-sm": "44px",
1419
+ "headline-lg": "40px",
1420
+ "headline-md": "36px",
1421
+ "headline-sm": "32px",
1422
+ "title-lg": "28px",
1423
+ "title-md": "24px",
1424
+ "title-sm": "20px",
1425
+ "title-dialog": "24px",
1426
+ "label-lg": "20px",
1427
+ "label-md": "16px",
1428
+ "label-sm": "16px",
1429
+ "body-lg": "24px",
1430
+ "body-md": "20px",
1431
+ "body-sm": "16px"
1432
+ },
1433
+ "letterSpacing": {
1434
+ "tighter": "-0.25px",
1435
+ "normal": "0",
1436
+ "wide-1": "0.1px",
1437
+ "wide-15": "0.15px",
1438
+ "wide-25": "0.25px",
1439
+ "wide-5": "0.5px"
1440
+ },
1441
+ "zIndex": {
1442
+ "base": 0,
1443
+ "raised": 1,
1444
+ "docked": 10,
1445
+ "focus": 20,
1446
+ "sticky": 40,
1447
+ "overlay": 50
1448
+ },
1449
+ "shadow": {
1450
+ "sm": [
1451
+ {
1452
+ "color": "#0000000d",
1453
+ "offsetX": "0",
1454
+ "offsetY": "1",
1455
+ "blur": "2",
1456
+ "spread": "0"
1457
+ }
1458
+ ],
1459
+ "md": [
1460
+ {
1461
+ "color": "#0000001f",
1462
+ "offsetX": "0",
1463
+ "offsetY": "1",
1464
+ "blur": "1",
1465
+ "spread": "0"
1466
+ },
1467
+ {
1468
+ "color": "#676e7614",
1469
+ "offsetX": "0",
1470
+ "offsetY": "2",
1471
+ "blur": "5",
1472
+ "spread": "0"
1473
+ }
1474
+ ],
1475
+ "lg": [
1476
+ {
1477
+ "color": "#00000012",
1478
+ "offsetX": "0",
1479
+ "offsetY": "4",
1480
+ "blur": "6",
1481
+ "spread": "-1"
1482
+ },
1483
+ {
1484
+ "color": "#0000000f",
1485
+ "offsetX": "0",
1486
+ "offsetY": "2",
1487
+ "blur": "4",
1488
+ "spread": "-2"
1489
+ }
1490
+ ],
1491
+ "xl": [
1492
+ {
1493
+ "color": "#0000001f",
1494
+ "offsetX": "0",
1495
+ "offsetY": "4",
1496
+ "blur": "24",
1497
+ "spread": "0"
1498
+ }
1499
+ ],
1500
+ "focus": [
1501
+ {
1502
+ "color": "#1d4ed8",
1503
+ "offsetX": "0",
1504
+ "offsetY": "0",
1505
+ "blur": "0",
1506
+ "spread": "1px"
1507
+ },
1508
+ {
1509
+ "color": "#1d4ed833",
1510
+ "offsetX": "0",
1511
+ "offsetY": "0",
1512
+ "blur": "0",
1513
+ "spread": "4"
1514
+ }
1515
+ ],
1516
+ "focus-destructive": [
1517
+ {
1518
+ "color": "#ec5047",
1519
+ "offsetX": "0",
1520
+ "offsetY": "0",
1521
+ "blur": "0",
1522
+ "spread": "1px"
1523
+ },
1524
+ {
1525
+ "color": "#ec504733",
1526
+ "offsetX": "0",
1527
+ "offsetY": "0",
1528
+ "blur": "0",
1529
+ "spread": "4"
1530
+ }
1531
+ ],
1532
+ "focus-success": [
1533
+ {
1534
+ "color": "#5d6d5c",
1535
+ "offsetX": "0",
1536
+ "offsetY": "0",
1537
+ "blur": "0",
1538
+ "spread": "1px"
1539
+ },
1540
+ {
1541
+ "color": "#5d6d5c33",
1542
+ "offsetX": "0",
1543
+ "offsetY": "0",
1544
+ "blur": "0",
1545
+ "spread": "4"
1546
+ }
1547
+ ],
1548
+ "focus-warning": [
1549
+ {
1550
+ "color": "#7f5b21",
1551
+ "offsetX": "0",
1552
+ "offsetY": "0",
1553
+ "blur": "0",
1554
+ "spread": "1px"
1555
+ },
1556
+ {
1557
+ "color": "#7f5b2133",
1558
+ "offsetX": "0",
1559
+ "offsetY": "0",
1560
+ "blur": "0",
1561
+ "spread": "4"
1562
+ }
1563
+ ]
1564
+ },
1565
+ "text": {
1566
+ "display-lg": {
1567
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1568
+ "fontWeight": "500",
1569
+ "fontSize": "57px",
1570
+ "lineHeight": "64px",
1571
+ "letterSpacing": "-0.25px"
1572
+ },
1573
+ "display-md": {
1574
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1575
+ "fontWeight": "500",
1576
+ "fontSize": "45px",
1577
+ "lineHeight": "52px",
1578
+ "letterSpacing": "0"
1579
+ },
1580
+ "display-sm": {
1581
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1582
+ "fontWeight": "400",
1583
+ "fontSize": "36px",
1584
+ "lineHeight": "44px",
1585
+ "letterSpacing": "0"
1586
+ },
1587
+ "headline-lg": {
1588
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1589
+ "fontWeight": "600",
1590
+ "fontSize": "32px",
1591
+ "lineHeight": "40px",
1592
+ "letterSpacing": "0"
1593
+ },
1594
+ "headline-md": {
1595
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1596
+ "fontWeight": "400",
1597
+ "fontSize": "28px",
1598
+ "lineHeight": "36px",
1599
+ "letterSpacing": "0"
1600
+ },
1601
+ "headline-sm": {
1602
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1603
+ "fontWeight": "400",
1604
+ "fontSize": "24px",
1605
+ "lineHeight": "32px",
1606
+ "letterSpacing": "0"
1607
+ },
1608
+ "title-lg": {
1609
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1610
+ "fontWeight": "400",
1611
+ "fontSize": "22px",
1612
+ "lineHeight": "28px",
1613
+ "letterSpacing": "0"
1614
+ },
1615
+ "title-md": {
1616
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1617
+ "fontWeight": "500",
1618
+ "fontSize": "16px",
1619
+ "lineHeight": "24px",
1620
+ "letterSpacing": "0.15px"
1621
+ },
1622
+ "title-sm": {
1623
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1624
+ "fontWeight": "500",
1625
+ "fontSize": "14px",
1626
+ "lineHeight": "20px",
1627
+ "letterSpacing": "0.1px"
1628
+ },
1629
+ "title-dialog": {
1630
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1631
+ "fontWeight": "600",
1632
+ "fontSize": "18px",
1633
+ "lineHeight": "24px",
1634
+ "letterSpacing": "0"
1635
+ },
1636
+ "label-lg": {
1637
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1638
+ "fontWeight": "500",
1639
+ "fontSize": "14px",
1640
+ "lineHeight": "20px",
1641
+ "letterSpacing": "0.1px"
1642
+ },
1643
+ "label-md": {
1644
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1645
+ "fontWeight": "500",
1646
+ "fontSize": "12px",
1647
+ "lineHeight": "16px",
1648
+ "letterSpacing": "0.5px"
1649
+ },
1650
+ "label-sm": {
1651
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1652
+ "fontWeight": "500",
1653
+ "fontSize": "11px",
1654
+ "lineHeight": "16px",
1655
+ "letterSpacing": "0.5px"
1656
+ },
1657
+ "body-lg": {
1658
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1659
+ "fontWeight": "400",
1660
+ "fontSize": "16px",
1661
+ "lineHeight": "24px",
1662
+ "letterSpacing": "0.5px"
1663
+ },
1664
+ "body-md": {
1665
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1666
+ "fontWeight": "400",
1667
+ "fontSize": "14px",
1668
+ "lineHeight": "20px",
1669
+ "letterSpacing": "0.25px"
1670
+ },
1671
+ "body-sm": {
1672
+ "fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
1673
+ "fontWeight": "400",
1674
+ "fontSize": "12px",
1675
+ "lineHeight": "16px",
1676
+ "letterSpacing": "0"
1677
+ }
1678
+ }
1679
+ };
1680
+ var tokens_default = tokens;
1681
+
1682
+ // ../create-theme/src/contract.ts
1683
+ var TOKEN_CONTRACT = [
1684
+ { token: "background", light: "#ffffff", dark: "#050c11" },
1685
+ { token: "foreground", light: "#050c11", dark: "#f0f7ff" },
1686
+ { token: "card", light: "#ffffff", dark: "#0b1821", note: "synthesized" },
1687
+ { token: "popover", light: "#ffffff", dark: "#0b1821", note: "synthesized" },
1688
+ { token: "primary", light: "#1d4ed8", dark: "#60a5fa" },
1689
+ { token: "primary-foreground", light: "#f0f7ff", dark: "#050c11" },
1690
+ { token: "secondary", light: "#c7cfc7", dark: "#2e362e" },
1691
+ { token: "secondary-foreground", light: "#465245", dark: "#e3e7e3" },
1692
+ { token: "muted", light: "#f6f6f6", dark: "#102432" },
1693
+ { token: "muted-foreground", light: "#6d6d6d", dark: "#92b2c8" },
1694
+ { token: "accent", light: "#f0f7ff", dark: "#102432" },
1695
+ { token: "accent-foreground", light: "#1d4ed8", dark: "#f0f7ff" },
1696
+ { token: "destructive", light: "#c60a0a", dark: "#dd6a6a", note: "darkened for AA" },
1697
+ { token: "success", light: "#5d6d5c", dark: "#90a08f" },
1698
+ { token: "warning", light: "#7f5b21", dark: "#ffc975", note: "darkened for AA" },
1699
+ { token: "border", light: "#92b2c8", dark: "#395a70" },
1700
+ { token: "ring", light: "#1d4ed8", dark: "#60a5fa", note: "synthesized" },
1701
+ // Role tokens layered over primary / ring (added after the shadcn-compatible set on purpose: the
1702
+ // first row for a given hex names it in tokenNameForHex, so `primary` stays the label).
1703
+ { token: "brand", light: "#1b3c53", dark: "#7495ab", note: "identity \u2014 the Figma navy" },
1704
+ { token: "brand-foreground", light: "#f0f7ff", dark: "#050c11" },
1705
+ { token: "action", light: "#1d4ed8", dark: "#60a5fa", note: "defaults to primary" },
1706
+ { token: "action-foreground", light: "#f0f7ff", dark: "#050c11", note: "defaults to primary-foreground" },
1707
+ { token: "action-hover", light: "#3460dc", dark: "#5796e3", note: "action @ 90% \u2014 native" },
1708
+ { token: "action-pressed", light: "#3f69de", dark: "#528ed7", note: "action @ 85% \u2014 native" },
1709
+ { token: "link", light: "#1d4ed8", dark: "#60a5fa", note: "defaults to primary" },
1710
+ { token: "focus", light: "#1d4ed8", dark: "#60a5fa", note: "defaults to ring" }
1711
+ ];
1712
+ var RADIUS_STEP_NAMES = ["sm", "md", "lg", "xl"];
1713
+ var ELEVATION_STEP_NAMES = ["sm", "md", "lg", "xl"];
1714
+ var px = (value) => Number(value.replace("px", ""));
1715
+ var SHIPPED_RADIUS = {
1716
+ sm: px(tokens_default.radius.sm),
1717
+ md: px(tokens_default.radius.md),
1718
+ lg: px(tokens_default.radius.lg),
1719
+ xl: px(tokens_default.radius.xl)
1720
+ };
1721
+ var SHADOWS = tokens_default.shadow;
1722
+ var SHIPPED_ELEVATION = {
1723
+ sm: SHADOWS.sm,
1724
+ md: SHADOWS.md,
1725
+ lg: SHADOWS.lg,
1726
+ xl: SHADOWS.xl
1727
+ };
1728
+
1729
+ // ../create-theme/src/palette.ts
1730
+ var CONTRAST_PAIRS = [
1731
+ ["background", "foreground"],
1732
+ ["card", "card-foreground"],
1733
+ ["popover", "popover-foreground"],
1734
+ ["primary", "primary-foreground"],
1735
+ ["secondary", "secondary-foreground"],
1736
+ ["muted", "muted-foreground"],
1737
+ ["accent", "accent-foreground"],
1738
+ ["destructive", "destructive-foreground"],
1739
+ ["action", "action-foreground"],
1740
+ ["brand", "brand-foreground"]
1741
+ ];
1742
+
1743
+ // ../create-theme/src/engine.ts
1744
+ var ACTION_BAND = {
1745
+ light: [0.42, 0.62],
1746
+ dark: [0.66, 0.84]
1747
+ };
1748
+ var BACKDROP_L = { light: 1, dark: 0.15 };
1749
+ var clamp2 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
1750
+ function intoBand(colour, mode) {
1751
+ const [lo, hi] = ACTION_BAND[mode];
1752
+ return adjust(colour, { dl: clamp2(colour.l, lo, hi) - colour.l });
1753
+ }
1754
+ function foregroundFor(surfaceHex) {
1755
+ return foregroundForAll([surfaceHex]);
1756
+ }
1757
+ function foregroundForAll(surfaces) {
1758
+ const primary = surfaces[0];
1759
+ if (primary === void 0) return "#000000";
1760
+ const s = hexToOklch(primary);
1761
+ if (!s) return "#000000";
1762
+ const tint = Math.min(s.c, 0.03);
1763
+ const worst = (candidate) => Math.min(...surfaces.map((surface) => guaranteedContrast(surface, candidate)));
1764
+ const tinted = [oklchToHex({ l: 0.97, c: tint, h: s.h }), oklchToHex({ l: 0.09, c: tint, h: s.h })];
1765
+ const best = worst(tinted[0]) >= worst(tinted[1]) ? tinted[0] : tinted[1];
1766
+ if (worst(best) >= AA) return best;
1767
+ return worst("#ffffff") >= worst("#000000") ? "#ffffff" : "#000000";
1768
+ }
1769
+ var AA = 4.5;
1770
+ function stateOf(action, mode, fraction) {
1771
+ return adjust(action, { dl: (BACKDROP_L[mode] - action.l) * fraction });
1772
+ }
1773
+ var HOVER_FRACTION = 0.1;
1774
+ var PRESSED_FRACTION = 0.16;
1775
+ var STATE_SCALE_STEPS = 20;
1776
+ function interactionStates(action, mode) {
1777
+ const actionHex = oklchToHex(action);
1778
+ for (let step = STATE_SCALE_STEPS; step >= 0; step--) {
1779
+ const scale = step / STATE_SCALE_STEPS;
1780
+ const hover = oklchToHex(stateOf(action, mode, HOVER_FRACTION * scale));
1781
+ const pressed = oklchToHex(stateOf(action, mode, PRESSED_FRACTION * scale));
1782
+ const foreground2 = foregroundForAll([actionHex, hover, pressed]);
1783
+ const readable = [actionHex, hover, pressed].every((surface) => guaranteedContrast(surface, foreground2) >= AA);
1784
+ if (readable) return { hover, pressed, foreground: foreground2, scale };
1785
+ }
1786
+ const foreground = foregroundForAll([actionHex]);
1787
+ return { hover: actionHex, pressed: actionHex, foreground, scale: 0 };
1788
+ }
1789
+ function readableOnSurface(surfaceHex, preferredHex) {
1790
+ if (guaranteedContrast(surfaceHex, preferredHex) >= AA) return preferredHex;
1791
+ const surface = hexToOklch(surfaceHex);
1792
+ const preferred = hexToOklch(preferredHex);
1793
+ if (!surface || !preferred) return foregroundFor(surfaceHex);
1794
+ const step = surface.l > 0.5 ? -0.02 : 0.02;
1795
+ for (let i = 1; i <= 50; i++) {
1796
+ const candidate = oklchToHex(adjust(preferred, { dl: step * i }));
1797
+ if (guaranteedContrast(surfaceHex, candidate) >= AA) return candidate;
1798
+ }
1799
+ return foregroundFor(surfaceHex);
1800
+ }
1801
+ function deriveBrandRoles(brandHex, mode) {
1802
+ const brand = hexToOklch(brandHex);
1803
+ if (!brand) return {};
1804
+ const action = intoBand(brand, mode);
1805
+ const actionHex = oklchToHex(action);
1806
+ const brandOwn = oklchToHex(brand);
1807
+ const states = interactionStates(action, mode);
1808
+ const actionFg = states.foreground;
1809
+ const accent = oklchToHex({
1810
+ l: mode === "light" ? 0.96 : 0.24,
1811
+ c: Math.min(brand.c, 0.04),
1812
+ h: brand.h
1813
+ });
1814
+ return {
1815
+ brand: brandOwn,
1816
+ "brand-foreground": foregroundFor(brandOwn),
1817
+ action: actionHex,
1818
+ "action-foreground": actionFg,
1819
+ "action-hover": states.hover,
1820
+ "action-pressed": states.pressed,
1821
+ link: actionHex,
1822
+ focus: actionHex,
1823
+ // `primary` and `ring` are the pre-role names the contract says `action` and `focus` default to.
1824
+ // Create writes both so a component that still reads the older name follows the theme.
1825
+ primary: actionHex,
1826
+ "primary-foreground": actionFg,
1827
+ ring: actionHex,
1828
+ accent,
1829
+ // The action colour if it is readable on its own tint, stepped toward readable if not.
1830
+ "accent-foreground": readableOnSurface(accent, actionHex)
1831
+ };
1832
+ }
1833
+ var NEUTRAL_TINT = {
1834
+ neutral: { h: 0, c: 0 },
1835
+ cool: { h: 250, c: 0.012 },
1836
+ warm: { h: 70, c: 0.012 },
1837
+ stone: { h: 40, c: 8e-3 }
1838
+ };
1839
+ var NEUTRAL_LADDER = {
1840
+ light: {
1841
+ background: 1,
1842
+ card: 1,
1843
+ popover: 1,
1844
+ muted: 0.975,
1845
+ border: 0.86,
1846
+ input: 0.86,
1847
+ "muted-foreground": 0.535,
1848
+ foreground: 0.15
1849
+ },
1850
+ dark: {
1851
+ background: 0.16,
1852
+ card: 0.22,
1853
+ popover: 0.22,
1854
+ muted: 0.27,
1855
+ border: 0.4,
1856
+ input: 0.4,
1857
+ "muted-foreground": 0.72,
1858
+ foreground: 0.97
1859
+ }
1860
+ };
1861
+ function deriveNeutrals(neutral, mode) {
1862
+ if (neutral === "kinetix") return {};
1863
+ const { h, c } = NEUTRAL_TINT[neutral];
1864
+ const ladder = NEUTRAL_LADDER[mode];
1865
+ const at = (l) => oklchToHex({ l, c, h });
1866
+ const out = {};
1867
+ for (const [token, l] of Object.entries(ladder)) out[token] = at(l);
1868
+ out["card-foreground"] = out.foreground;
1869
+ out["popover-foreground"] = out.foreground;
1870
+ return out;
1871
+ }
1872
+ var RADIUS_STEPS = {
1873
+ square: { sm: 0, md: 0, lg: 0, xl: 0 },
1874
+ small: { sm: 4, md: 4, lg: 8, xl: 12 },
1875
+ default: { sm: 4, md: 8, lg: 12, xl: 16 },
1876
+ rounded: { sm: 8, md: 12, lg: 16, xl: 24 },
1877
+ soft: { sm: 12, md: 20, lg: 28, xl: 36 }
1878
+ };
1879
+ function deriveRadius(radius) {
1880
+ return { ...RADIUS_STEPS[radius] };
1881
+ }
1882
+ function deriveElevation(surface) {
1883
+ switch (surface) {
1884
+ case "soft":
1885
+ return { ...SHIPPED_ELEVATION };
1886
+ case "elevated":
1887
+ return {
1888
+ sm: SHIPPED_ELEVATION.md,
1889
+ md: SHIPPED_ELEVATION.lg,
1890
+ lg: SHIPPED_ELEVATION.xl,
1891
+ xl: SHIPPED_ELEVATION.xl
1892
+ };
1893
+ case "flat":
1894
+ case "bordered":
1895
+ return { sm: [], md: [], lg: [], xl: [] };
1896
+ }
1897
+ }
1898
+ function deriveSurfaceBorder(surface, mode, borderHex) {
1899
+ if (surface !== "bordered") return null;
1900
+ const border = hexToOklch(borderHex);
1901
+ return border ? oklchToHex(adjust(border, { dl: mode === "light" ? -0.14 : 0.14 })) : null;
1902
+ }
1903
+ var CHART_HUES = {
1904
+ categorical: [255, 60, 145, 330, 200],
1905
+ cool: [200, 250, 290, 170, 225],
1906
+ // A warm band is only ~100° wide, so the hues are ordered against the lightness ladder below rather
1907
+ // than run in sequence: the two closest hues (45 and 55) sit on the two most separated lightnesses.
1908
+ // Running them in order gave two oranges a reader could not tell apart.
1909
+ warm: [20, 55, 45, 115, 90]
1910
+ };
1911
+ var CHART_L = {
1912
+ light: [0.55, 0.68, 0.46, 0.62, 0.74],
1913
+ dark: [0.7, 0.62, 0.78, 0.55, 0.85]
1914
+ };
1915
+ function deriveChart(palette, brandHex, mode) {
1916
+ if (palette === "kinetix") return {};
1917
+ const hues = palette === "brand" ? (() => {
1918
+ const h = hexToOklch(brandHex)?.h ?? 255;
1919
+ return [0, 50, -50, 100, -100].map((d) => ((h + d) % 360 + 360) % 360);
1920
+ })() : CHART_HUES[palette];
1921
+ const ls = CHART_L[mode];
1922
+ const out = {};
1923
+ hues.forEach((h, i) => {
1924
+ out[`chart-${i + 1}`] = oklchToHex({ l: ls[i], c: Math.min(0.16, MAX_CHROMA), h });
1925
+ });
1926
+ return out;
1927
+ }
1928
+
1929
+ // ../create-theme/src/resolve.ts
1930
+ var ALIASED_FROM = {
1931
+ "card-foreground": "foreground",
1932
+ "popover-foreground": "foreground",
1933
+ input: "border"
1934
+ };
1935
+ var FOREGROUND_OF = Object.fromEntries(CONTRAST_PAIRS);
1936
+ function shippedColorsFor(mode) {
1937
+ const out = {};
1938
+ for (const row2 of TOKEN_CONTRACT) out[row2.token] = mode === "dark" ? row2.dark : row2.light;
1939
+ for (const [token, source] of Object.entries(ALIASED_FROM)) {
1940
+ const value = out[source];
1941
+ if (value) out[token] = value;
1942
+ }
1943
+ for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
1944
+ const baseHex = out[base];
1945
+ if (baseHex && !out[fg]) out[fg] = foregroundFor(baseHex);
1946
+ }
1947
+ return out;
1948
+ }
1949
+ var SHIPPED_COLORS = {
1950
+ light: shippedColorsFor("light"),
1951
+ dark: shippedColorsFor("dark")
1952
+ };
1953
+ var SHIPPED_THEME = {
1954
+ light: { colors: SHIPPED_COLORS.light, radius: SHIPPED_RADIUS, elevation: SHIPPED_ELEVATION },
1955
+ dark: { colors: SHIPPED_COLORS.dark, radius: SHIPPED_RADIUS, elevation: SHIPPED_ELEVATION }
1956
+ };
1957
+ function resolveMode(design, mode) {
1958
+ const colors = { ...SHIPPED_COLORS[mode] };
1959
+ Object.assign(colors, deriveNeutrals(design.neutral, mode));
1960
+ if (design.brand.toLowerCase() !== DEFAULT_PRESET.brand) {
1961
+ Object.assign(colors, deriveBrandRoles(design.brand, mode));
1962
+ }
1963
+ Object.assign(colors, deriveChart(design.chartPalette, design.brand, mode));
1964
+ const border = deriveSurfaceBorder(design.surface, mode, colors.border ?? "#000000");
1965
+ if (border) colors.border = border;
1966
+ for (const [token, hex] of Object.entries(design.manualOverrides)) {
1967
+ if (hex) colors[token] = hex;
1968
+ }
1969
+ for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
1970
+ if (design.manualOverrides[base] && !design.manualOverrides[fg]) {
1971
+ colors[fg] = foregroundFor(colors[base]);
1972
+ }
1973
+ }
1974
+ return {
1975
+ colors,
1976
+ radius: deriveRadius(design.radius),
1977
+ elevation: deriveElevation(design.surface)
1978
+ };
1979
+ }
1980
+ function canonicalDesign(design) {
1981
+ const result = validate(canonicalize(design));
1982
+ return result.ok ? result.config : design;
1983
+ }
1984
+ function resolveCreateTheme(input) {
1985
+ const design = canonicalDesign(input);
1986
+ return {
1987
+ light: resolveMode(design, "light"),
1988
+ dark: resolveMode(design, "dark"),
1989
+ meta: {
1990
+ design,
1991
+ // In token order, not insertion order. `Object.keys` would hand back whatever order the overrides
1992
+ // happened to be written in, so a decoded preset (canonicalized, sorted) and the design it was
1993
+ // encoded from would resolve to two theme objects that are not equal — which breaks the one
1994
+ // property the whole pipeline rests on. Token order, rather than alphabetical, because that is the
1995
+ // order the raw editor and the CLI already list overrides in.
1996
+ manualTokens: ACCEPTED_TOKENS.filter((token) => design.manualOverrides[token]),
1997
+ isDefault: isDefaultPreset(design)
1998
+ }
1999
+ };
2000
+ }
2001
+
2002
+ // ../create-theme/src/exporters/css.ts
2003
+ var px2 = (value) => Number(value) === 0 ? "0" : `${value}px`;
2004
+ function shadowCss(layers) {
2005
+ if (layers.length === 0) return "none";
2006
+ return layers.map((l) => `${px2(l.offsetX)} ${px2(l.offsetY)} ${px2(l.blur)} ${px2(l.spread)} ${l.color}`).join(", ");
2007
+ }
2008
+ var declaration = (token, value) => ` --${token}: ${value};`;
2009
+ var block = (selector, lines) => lines.length ? `${selector} {
2010
+ ${lines.join("\n")}
2011
+ }` : "";
2012
+ function colorLines(resolved, base) {
2013
+ return Object.keys(resolved.colors).filter((token) => {
2014
+ const value = resolved.colors[token];
2015
+ return value && value.toLowerCase() !== base.colors[token]?.toLowerCase();
2016
+ }).sort().map((token) => declaration(token, hexToHslChannels(resolved.colors[token])));
2017
+ }
2018
+ function cssVarOverrides(resolved, base = SHIPPED_THEME.light) {
2019
+ const out = {};
2020
+ for (const step of RADIUS_STEP_NAMES) {
2021
+ if (resolved.radius[step] !== base.radius[step]) out[`radius-${step}`] = `${resolved.radius[step]}px`;
2022
+ }
2023
+ for (const step of ELEVATION_STEP_NAMES) {
2024
+ const value = shadowCss(resolved.elevation[step]);
2025
+ if (value !== shadowCss(base.elevation[step])) out[`shadow-${step}`] = value;
2026
+ }
2027
+ return out;
2028
+ }
2029
+ var varLines = (resolved, base) => Object.entries(cssVarOverrides(resolved, base)).map(([token, value]) => declaration(token, value));
2030
+ function exportCss(theme2, options = {}) {
2031
+ const rootSelector = options.rootSelector ?? ":root";
2032
+ const darkSelector = options.darkSelector ?? ".dark";
2033
+ const rootVars = varLines(theme2.light, SHIPPED_THEME.light);
2034
+ const root = [...colorLines(theme2.light, SHIPPED_THEME.light), ...rootVars];
2035
+ const dark = [
2036
+ ...colorLines(theme2.dark, SHIPPED_THEME.dark),
2037
+ ...varLines(theme2.dark, SHIPPED_THEME.dark).filter((line) => !rootVars.includes(line))
2038
+ ];
2039
+ return [block(rootSelector, root), block(darkSelector, dark)].filter(Boolean).join("\n\n");
2040
+ }
2041
+
2042
+ // ../create-theme/src/exporters/flutter.ts
2043
+ var DEFAULT_FLUTTER_SYMBOL = "CreateTheme";
2044
+ var DART_RESERVED_WORDS = /* @__PURE__ */ new Set([
2045
+ "assert",
2046
+ "break",
2047
+ "case",
2048
+ "catch",
2049
+ "class",
2050
+ "const",
2051
+ "continue",
2052
+ "default",
2053
+ "do",
2054
+ "else",
2055
+ "enum",
2056
+ "extends",
2057
+ "false",
2058
+ "final",
2059
+ "finally",
2060
+ "for",
2061
+ "if",
2062
+ "in",
2063
+ "is",
2064
+ "new",
2065
+ "null",
2066
+ "rethrow",
2067
+ "return",
2068
+ "super",
2069
+ "switch",
2070
+ "this",
2071
+ "throw",
2072
+ "true",
2073
+ "try",
2074
+ "var",
2075
+ "void",
2076
+ "while",
2077
+ "with"
2078
+ ]);
2079
+ function dartSymbolError(symbol) {
2080
+ if (symbol.length === 0) return "A theme name cannot be empty.";
2081
+ if (symbol.length > 64) return "A theme name must be 64 characters or fewer.";
2082
+ if (/[\p{Cc}\p{Cf}]/u.test(symbol)) return "A theme name cannot contain control characters.";
2083
+ if (/^[0-9]/.test(symbol)) return `"${symbol}" cannot start with a digit \u2014 Dart type names begin with a letter or underscore.`;
2084
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) {
2085
+ return `"${symbol}" is not a Dart identifier \u2014 use letters, digits and underscores only.`;
2086
+ }
2087
+ if (DART_RESERVED_WORDS.has(symbol)) return `"${symbol}" is a Dart reserved word and cannot be a type name.`;
2088
+ return null;
2089
+ }
2090
+ function dartColor(hex) {
2091
+ const h = hex.replace("#", "").trim();
2092
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
2093
+ return `Color(0xFF${h.toUpperCase()})`;
2094
+ }
2095
+ var FLUTTER_COLOR_FIELDS = [
2096
+ ["primary", "primary"],
2097
+ ["primaryForeground", "primary-foreground"],
2098
+ ["action", "action"],
2099
+ ["actionForeground", "action-foreground"],
2100
+ ["actionHover", "action-hover"],
2101
+ ["actionPressed", "action-pressed"],
2102
+ ["link", "link"],
2103
+ ["focus", "focus"],
2104
+ ["brand", "brand"],
2105
+ ["brandForeground", "brand-foreground"],
2106
+ ["secondary", "secondary"],
2107
+ ["secondaryForeground", "secondary-foreground"],
2108
+ ["destructive", "destructive"],
2109
+ ["destructiveForeground", "destructive-foreground"],
2110
+ ["foreground", "foreground"],
2111
+ ["background", "background"],
2112
+ ["border", "border"],
2113
+ ["input", "input"],
2114
+ ["ring", "ring"],
2115
+ ["muted", "muted"],
2116
+ ["mutedForeground", "muted-foreground"],
2117
+ ["accent", "accent"],
2118
+ ["accentForeground", "accent-foreground"],
2119
+ ["tertiary", "tertiary"],
2120
+ ["tertiaryForeground", "tertiary-foreground"],
2121
+ ["warning", "warning"],
2122
+ ["warningForeground", "warning-foreground"],
2123
+ ["card", "card"],
2124
+ ["cardForeground", "card-foreground"],
2125
+ ["success", "success"],
2126
+ ["successForeground", "success-foreground"],
2127
+ ["info", "info"],
2128
+ ["infoForeground", "info-foreground"],
2129
+ ["popover", "popover"],
2130
+ ["popoverForeground", "popover-foreground"]
2131
+ ];
2132
+ var FLUTTER_CHART_STOPS = [1, 2, 3, 4, 5];
2133
+ var shippedRef = (appearance, field) => `KinetixColors.${appearance}.${field}`;
2134
+ function fieldValue(mode, appearance, field, token) {
2135
+ const resolved = mode.colors[token];
2136
+ if (!resolved) return shippedRef(appearance, field);
2137
+ const shipped = SHIPPED_COLORS[appearance][token];
2138
+ if (shipped && shipped.toLowerCase() === resolved.toLowerCase()) return shippedRef(appearance, field);
2139
+ return dartColor(resolved) ?? shippedRef(appearance, field);
2140
+ }
2141
+ function colorSet(mode, appearance) {
2142
+ const fields = FLUTTER_COLOR_FIELDS.map(
2143
+ ([field, token]) => ` ${field}: ${fieldValue(mode, appearance, field, token)},`
2144
+ );
2145
+ const chart = FLUTTER_CHART_STOPS.map(
2146
+ (stop) => ` ${fieldValue(mode, appearance, `chart${stop}`, `chart-${stop}`)},`
2147
+ );
2148
+ return [...fields, " chart: <Color>[", ...chart, " ],"].join("\n");
2149
+ }
2150
+ function header(symbol, theme2) {
2151
+ const design = theme2.meta.design;
2152
+ return [
2153
+ "//",
2154
+ `// ${symbol} \u2014 a KinetixUI Create theme.`,
2155
+ "//",
2156
+ "// Generated by `kinetixui preset flutter`. Edit the design in Create and re-export rather than",
2157
+ "// editing this file: it is a rendering of a preset, not a source of one.",
2158
+ "//",
2159
+ `// Theme colour ${design.brand} \xB7 ${design.neutral} neutral \xB7 ${design.chartPalette} charts`,
2160
+ "//",
2161
+ "// Every semantic colour KinetixColors declares is carried here, light and dark \u2014 Flutter's contract",
2162
+ "// is the complete one, so no role is left behind.",
2163
+ "//",
2164
+ "// COLOURS ONLY. Radius and elevation are not themeable at runtime: widgets read `KinetixRadius`",
2165
+ "// constants directly and `KinetixTheme` carries neither them nor `KinetixShadows`, so this design's",
2166
+ `// radius (${design.radius}) and surface treatment (${design.surface}) have nowhere to be installed.`,
2167
+ "// They apply on the web. That is the only thing this file does not carry.",
2168
+ "//",
2169
+ "// A field written as `KinetixColors.light.\u2026` is one this design did not change, and it keeps",
2170
+ "// following the library. Six of them \u2014 tertiary, tertiaryForeground, info, infoForeground,",
2171
+ "// successForeground and warningForeground \u2014 are always references, because Create does not model",
2172
+ "// those tokens.",
2173
+ "//",
2174
+ "// These are `static final` rather than `static const`: Dart's constant expressions do not include",
2175
+ "// instance field access, so a `static const` could not reference the shipped palette. The theme is",
2176
+ "// still usable everywhere a const one would be \u2014 `KinetixTheme.custom` takes it fine \u2014 you just",
2177
+ "// cannot write `const KinetixTheme.custom(\u2026)` around it.",
2178
+ "//",
2179
+ "// Apply it to Kinetix widgets:",
2180
+ "//",
2181
+ `// KinetixTheme.custom(light: ${symbol}.light, dark: ${symbol}.dark, child: App())`,
2182
+ "//",
2183
+ "// \u2026and to Material or Cupertino widgets, whose own theme APIs represent the subset they can:",
2184
+ "//",
2185
+ `// MaterialApp(theme: KinetixMaterialTheme.fromColors(Brightness.light, ${symbol}.light))`,
2186
+ "//"
2187
+ ].join("\n");
2188
+ }
2189
+ function exportFlutter(theme2, options = {}) {
2190
+ const symbol = options.symbol ?? DEFAULT_FLUTTER_SYMBOL;
2191
+ const reason = dartSymbolError(symbol);
2192
+ if (reason) throw new Error(reason);
2193
+ return [
2194
+ header(symbol, theme2),
2195
+ "",
2196
+ "import 'package:flutter/widgets.dart';",
2197
+ "import 'package:kinetix_ui/kinetix_ui.dart';",
2198
+ "",
2199
+ // `abstract final class` is Dart 3 and the package floor is >=3.6, so it is available. It is the
2200
+ // idiomatic way to say "namespace, never instantiated, never extended" — the same intent SwiftUI's
2201
+ // `enum` and Compose's `object` express.
2202
+ `abstract final class ${symbol} {`,
2203
+ " static final KinetixColors light = KinetixColors(",
2204
+ colorSet(theme2.light, "light"),
2205
+ " );",
2206
+ "",
2207
+ " static final KinetixColors dark = KinetixColors(",
2208
+ colorSet(theme2.dark, "dark"),
2209
+ " );",
2210
+ "}",
2211
+ ""
2212
+ ].join("\n");
2213
+ }
2214
+
2215
+ // ../create-theme/src/exporters/compose.ts
2216
+ var DEFAULT_COMPOSE_SYMBOL = "CreateTheme";
2217
+ var KOTLIN_KEYWORDS = /* @__PURE__ */ new Set([
2218
+ "as",
2219
+ "break",
2220
+ "class",
2221
+ "continue",
2222
+ "do",
2223
+ "else",
2224
+ "false",
2225
+ "for",
2226
+ "fun",
2227
+ "if",
2228
+ "in",
2229
+ "interface",
2230
+ "is",
2231
+ "null",
2232
+ "object",
2233
+ "package",
2234
+ "return",
2235
+ "super",
2236
+ "this",
2237
+ "throw",
2238
+ "true",
2239
+ "try",
2240
+ "typealias",
2241
+ "typeof",
2242
+ "val",
2243
+ "var",
2244
+ "when",
2245
+ "while"
2246
+ ]);
2247
+ function kotlinSymbolError(symbol) {
2248
+ if (symbol.length === 0) return "A theme name cannot be empty.";
2249
+ if (symbol.length > 64) return "A theme name must be 64 characters or fewer.";
2250
+ if (/[\p{Cc}\p{Cf}]/u.test(symbol)) return "A theme name cannot contain control characters.";
2251
+ if (/^[0-9]/.test(symbol)) return `"${symbol}" cannot start with a digit \u2014 Kotlin type names begin with a letter or underscore.`;
2252
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) {
2253
+ return `"${symbol}" is not a Kotlin identifier \u2014 use letters, digits and underscores only.`;
2254
+ }
2255
+ if (KOTLIN_KEYWORDS.has(symbol)) return `"${symbol}" is a Kotlin keyword and cannot be a type name.`;
2256
+ return null;
2257
+ }
2258
+ function kotlinFieldName(token) {
2259
+ return token.replace(/[-_]([a-z0-9])/g, (_, c) => c.toUpperCase());
2260
+ }
2261
+ function composeColor(hex) {
2262
+ const h = hex.replace("#", "").trim();
2263
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
2264
+ return `Color(0xff${h.toLowerCase()})`;
2265
+ }
2266
+ var COMPOSE_COLOR_FIELDS = [
2267
+ ["primary", "primary"],
2268
+ ["primaryForeground", "primary-foreground"],
2269
+ ["action", "action"],
2270
+ ["actionForeground", "action-foreground"],
2271
+ ["actionHover", "action-hover"],
2272
+ ["actionPressed", "action-pressed"],
2273
+ ["link", "link"],
2274
+ ["focus", "focus"],
2275
+ ["brand", "brand"],
2276
+ ["brandForeground", "brand-foreground"],
2277
+ ["secondary", "secondary"],
2278
+ ["secondaryForeground", "secondary-foreground"],
2279
+ ["destructive", "destructive"],
2280
+ ["destructiveForeground", "destructive-foreground"],
2281
+ ["foreground", "foreground"],
2282
+ ["background", "background"],
2283
+ ["border", "border"],
2284
+ ["muted", "muted"],
2285
+ ["mutedForeground", "muted-foreground"],
2286
+ ["accent", "accent"],
2287
+ ["accentForeground", "accent-foreground"],
2288
+ ["tertiary", "tertiary"],
2289
+ ["warning", "warning"],
2290
+ ["warningForeground", "warning-foreground"],
2291
+ ["card", "card"],
2292
+ ["cardForeground", "card-foreground"],
2293
+ ["success", "success"],
2294
+ ["successForeground", "success-foreground"],
2295
+ ["info", "info"],
2296
+ ["infoForeground", "info-foreground"],
2297
+ ["popover", "popover"],
2298
+ ["popoverForeground", "popover-foreground"]
2299
+ ];
2300
+ var COMPOSE_CHART_STOPS = [1, 2, 3, 4, 5];
2301
+ var COMPOSE_UNMAPPED_PRESET_ROLES = ["input", "ring"];
2302
+ var COMPOSE_NATIVE_THEME_GAPS = ["tertiary-foreground"];
2303
+ var SHIPPED_OBJECT = { light: "KinetixTokensLight", dark: "KinetixTokensDark" };
2304
+ var shippedRef2 = (appearance, token) => `${SHIPPED_OBJECT[appearance]}.color${kotlinFieldName(token).replace(/^./, (c) => c.toUpperCase())}`;
2305
+ function fieldValue2(mode, appearance, token) {
2306
+ const resolved = mode.colors[token];
2307
+ if (!resolved) return shippedRef2(appearance, token);
2308
+ const shipped = SHIPPED_COLORS[appearance][token];
2309
+ if (shipped && shipped.toLowerCase() === resolved.toLowerCase()) return shippedRef2(appearance, token);
2310
+ return composeColor(resolved) ?? shippedRef2(appearance, token);
2311
+ }
2312
+ function colorSet2(mode, appearance) {
2313
+ const fields = COMPOSE_COLOR_FIELDS.map(
2314
+ ([field, token]) => ` ${field} = ${fieldValue2(mode, appearance, token)},`
2315
+ );
2316
+ const chart = COMPOSE_CHART_STOPS.map(
2317
+ (stop) => ` ${fieldValue2(mode, appearance, `chart-${stop}`)},`
2318
+ );
2319
+ return [...fields, " chart = listOf(", ...chart, " ),"].join("\n");
2320
+ }
2321
+ function header2(symbol, theme2) {
2322
+ const design = theme2.meta.design;
2323
+ return [
2324
+ "//",
2325
+ `// ${symbol} \u2014 a KinetixUI Create theme.`,
2326
+ "//",
2327
+ "// Generated by `kinetixui preset compose`. Edit the design in Create and re-export rather than",
2328
+ "// editing this file: it is a rendering of a preset, not a source of one.",
2329
+ "//",
2330
+ `// Theme colour ${design.brand} \xB7 ${design.neutral} neutral \xB7 ${design.chartPalette} charts`,
2331
+ "//",
2332
+ "// COLOURS ONLY. KinetixUI for Compose is themeable through `KinetixColors`, and this file carries",
2333
+ "// every colour it has a field for. What this design can express and Compose cannot receive:",
2334
+ "//",
2335
+ `// - radius (${design.radius}) and surface treatment (${design.surface}). Compose reads these from`,
2336
+ "// generated constants, not from a runtime theme, so nothing written here could override them.",
2337
+ "// Both apply on the web.",
2338
+ `// - \`${COMPOSE_UNMAPPED_PRESET_ROLES.join("` and `")}\`. Create resolves both, and a preset can override either by`,
2339
+ "// hand, but `KinetixColors` has no field for them.",
2340
+ "//",
2341
+ "// That is the complete list for this design. Nothing else was dropped.",
2342
+ "//",
2343
+ `// Separately, and not a Create limitation: \`${COMPOSE_NATIVE_THEME_GAPS.join("`, `")}\` is in the Kinetix`,
2344
+ "// token contract and in the SwiftUI theme, but Compose's `KinetixColors` does not expose it. Create",
2345
+ "// does not model that token at all, so no preset can set it and nothing here is affected by it.",
2346
+ "//",
2347
+ "// A field written as `KinetixTokensLight.\u2026` is one this design did not change, and it keeps",
2348
+ "// following the library.",
2349
+ "//"
2350
+ ].join("\n");
2351
+ }
2352
+ function exportCompose(theme2, options = {}) {
2353
+ const symbol = options.symbol ?? DEFAULT_COMPOSE_SYMBOL;
2354
+ const reason = kotlinSymbolError(symbol);
2355
+ if (reason) throw new Error(reason);
2356
+ const light = colorSet2(theme2.light, "light");
2357
+ const dark = colorSet2(theme2.dark, "dark");
2358
+ const usesColor = `${light}${dark}`.includes("Color(0x");
2359
+ return [
2360
+ header2(symbol, theme2),
2361
+ "",
2362
+ "package com.kinetixui.create",
2363
+ "",
2364
+ // Alphabetical, and aliased exactly as the package's own Theme.kt does it:
2365
+ // `com.kinetixui.tokens.KinetixTheme` is the generated token object while `com.kinetixui.ui.KinetixTheme`
2366
+ // is the composable, so the bare name is ambiguous in any file that wants both.
2367
+ ...usesColor ? ["import androidx.compose.ui.graphics.Color"] : [],
2368
+ "import com.kinetixui.tokens.KinetixTheme as KinetixTokensLight",
2369
+ "import com.kinetixui.tokens.KinetixThemeDark as KinetixTokensDark",
2370
+ "import com.kinetixui.ui.KinetixColors",
2371
+ "",
2372
+ `object ${symbol} {`,
2373
+ " /** Apply with `KinetixTheme(light = light, dark = dark) { \u2026 }`. */",
2374
+ " val light = KinetixColors(",
2375
+ light,
2376
+ " )",
2377
+ "",
2378
+ " val dark = KinetixColors(",
2379
+ dark,
2380
+ " )",
2381
+ "}",
2382
+ ""
2383
+ ].join("\n");
2384
+ }
2385
+
2386
+ // ../create-theme/src/exporters/swiftui.ts
2387
+ var DEFAULT_SWIFT_SYMBOL = "CreateTheme";
2388
+ var SWIFT_KEYWORDS = /* @__PURE__ */ new Set([
2389
+ "associatedtype",
2390
+ "borrowing",
2391
+ "class",
2392
+ "consuming",
2393
+ "deinit",
2394
+ "enum",
2395
+ "extension",
2396
+ "fileprivate",
2397
+ "func",
2398
+ "import",
2399
+ "init",
2400
+ "inout",
2401
+ "internal",
2402
+ "let",
2403
+ "macro",
2404
+ "nonisolated",
2405
+ "open",
2406
+ "operator",
2407
+ "private",
2408
+ "precedencegroup",
2409
+ "protocol",
2410
+ "public",
2411
+ "rethrows",
2412
+ "static",
2413
+ "struct",
2414
+ "subscript",
2415
+ "typealias",
2416
+ "var",
2417
+ "break",
2418
+ "case",
2419
+ "catch",
2420
+ "continue",
2421
+ "default",
2422
+ "defer",
2423
+ "do",
2424
+ "else",
2425
+ "fallthrough",
2426
+ "for",
2427
+ "guard",
2428
+ "if",
2429
+ "in",
2430
+ "repeat",
2431
+ "return",
2432
+ "throw",
2433
+ "switch",
2434
+ "where",
2435
+ "while",
2436
+ "Any",
2437
+ "as",
2438
+ "await",
2439
+ "false",
2440
+ "is",
2441
+ "nil",
2442
+ "rethrows",
2443
+ "self",
2444
+ "Self",
2445
+ "super",
2446
+ "throw",
2447
+ "throws",
2448
+ "true",
2449
+ "try"
2450
+ ]);
2451
+ function swiftSymbolError(symbol) {
2452
+ if (symbol.length === 0) return "A theme name cannot be empty.";
2453
+ if (symbol.length > 64) return "A theme name must be 64 characters or fewer.";
2454
+ if (/[\p{Cc}\p{Cf}]/u.test(symbol)) return "A theme name cannot contain control characters.";
2455
+ if (/^[0-9]/.test(symbol)) return `"${symbol}" cannot start with a digit \u2014 Swift type names begin with a letter or underscore.`;
2456
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) {
2457
+ return `"${symbol}" is not a Swift identifier \u2014 use letters, digits and underscores only.`;
2458
+ }
2459
+ if (SWIFT_KEYWORDS.has(symbol)) return `"${symbol}" is a Swift keyword and cannot be a type name.`;
2460
+ return null;
2461
+ }
2462
+ function swiftFieldName(token) {
2463
+ return token.replace(/[-_]([a-z0-9])/g, (_, c) => c.toUpperCase());
2464
+ }
2465
+ function swiftColor(hex) {
2466
+ const h = hex.replace("#", "").trim();
2467
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
2468
+ const [r, g, b] = swiftUiChannels(`#${h}`);
2469
+ return `Color(red: ${r}, green: ${g}, blue: ${b})`;
2470
+ }
2471
+ var SWIFT_COLOR_FIELDS = [
2472
+ "primary",
2473
+ "primary-foreground",
2474
+ "action",
2475
+ "action-foreground",
2476
+ "action-hover",
2477
+ "action-pressed",
2478
+ "link",
2479
+ "focus",
2480
+ "brand",
2481
+ "brand-foreground",
2482
+ "secondary",
2483
+ "secondary-foreground",
2484
+ "destructive",
2485
+ "destructive-foreground",
2486
+ "foreground",
2487
+ "background",
2488
+ "border",
2489
+ "input",
2490
+ "ring",
2491
+ "muted",
2492
+ "muted-foreground",
2493
+ "accent",
2494
+ "accent-foreground",
2495
+ "tertiary",
2496
+ "tertiary-foreground",
2497
+ "warning",
2498
+ "warning-foreground",
2499
+ "card",
2500
+ "card-foreground",
2501
+ "success",
2502
+ "success-foreground",
2503
+ "info",
2504
+ "info-foreground",
2505
+ "popover",
2506
+ "popover-foreground"
2507
+ ];
2508
+ var SWIFT_CHART_STOPS = [1, 2, 3, 4, 5];
2509
+ var SHIPPED_ENUM = { light: "KinetixColorsSwiftUI", dark: "KinetixColorsSwiftUIDark" };
2510
+ function fieldValue3(mode, appearance, token) {
2511
+ const reference = `${SHIPPED_ENUM[appearance]}.${swiftFieldName(token)}`;
2512
+ const resolved = mode.colors[token];
2513
+ if (!resolved) return reference;
2514
+ const shipped = SHIPPED_COLORS[appearance][token];
2515
+ if (shipped && shipped.toLowerCase() === resolved.toLowerCase()) return reference;
2516
+ return swiftColor(resolved) ?? reference;
2517
+ }
2518
+ function colorSet3(mode, appearance) {
2519
+ const fields = SWIFT_COLOR_FIELDS.map(
2520
+ (token) => ` ${swiftFieldName(token)}: ${fieldValue3(mode, appearance, token)},`
2521
+ );
2522
+ const chart = SWIFT_CHART_STOPS.map(
2523
+ (stop) => ` ${fieldValue3(mode, appearance, `chart-${stop}`)},`
2524
+ );
2525
+ return [...fields, " chart: [", ...chart, " ]"].join("\n");
2526
+ }
2527
+ function header3(symbol, theme2) {
2528
+ const design = theme2.meta.design;
2529
+ return [
2530
+ "//",
2531
+ `// ${symbol} \u2014 a KinetixUI Create theme.`,
2532
+ "//",
2533
+ "// Generated by `kinetixui preset swiftui`. Edit the design in Create and re-export rather than",
2534
+ "// editing this file: it is a rendering of a preset, not a source of one.",
2535
+ "//",
2536
+ `// Theme colour ${design.brand} \xB7 ${design.neutral} neutral \xB7 ${design.chartPalette} charts`,
2537
+ "//",
2538
+ "// COLOURS ONLY. KinetixUI for SwiftUI is themeable through `KinetixColors`; corner radius and",
2539
+ "// elevation are literals inside each view, with no token to override. This design's radius",
2540
+ `// (${design.radius}) and surface treatment (${design.surface}) therefore are NOT carried here \u2014 they`,
2541
+ "// apply on the web. Nothing is silently dropped; there is nowhere for them to go yet.",
2542
+ "//",
2543
+ "// A field written as `KinetixColorsSwiftUI.\u2026` is one this design did not change, and it keeps",
2544
+ "// following the library.",
2545
+ "//"
2546
+ ].join("\n");
2547
+ }
2548
+ function exportSwiftUi(theme2, options = {}) {
2549
+ const symbol = options.symbol ?? DEFAULT_SWIFT_SYMBOL;
2550
+ const reason = swiftSymbolError(symbol);
2551
+ if (reason) throw new Error(reason);
2552
+ return [
2553
+ header3(symbol, theme2),
2554
+ "",
2555
+ "import SwiftUI",
2556
+ "import KinetixUI",
2557
+ "",
2558
+ `public enum ${symbol} {`,
2559
+ " /// Apply with `KinetixTheme(light: .light, dark: .dark) { \u2026 }`, or set",
2560
+ " /// `\\.kinetixColors` directly to pin one appearance.",
2561
+ ` public static let light = KinetixColors(`,
2562
+ colorSet3(theme2.light, "light"),
2563
+ " )",
2564
+ "",
2565
+ ` public static let dark = KinetixColors(`,
2566
+ colorSet3(theme2.dark, "dark"),
2567
+ " )",
2568
+ "}",
2569
+ ""
2570
+ ].join("\n");
2571
+ }
2572
+
2573
+ // src/commands/preset.ts
2574
+ function read(input) {
2575
+ const result = decodePreset(input);
2576
+ if (!result.ok) {
2577
+ throw new Error(result.error.message);
2578
+ }
2579
+ return result.config;
2580
+ }
2581
+ var row = (label, value) => ` ${pc8.dim(label.padEnd(16))} ${value}`;
2582
+ function presetDecode(input, opts) {
2583
+ const config = read(input);
2584
+ if (opts.json) {
2585
+ console.log(JSON.stringify(config, null, 2));
2586
+ return;
2587
+ }
2588
+ console.log();
2589
+ console.log(pc8.bold("Kinetix Create preset"));
2590
+ console.log();
2591
+ console.log(row("Theme colour", config.brand));
2592
+ console.log(row("Neutral", config.neutral));
2593
+ console.log(row("Radius", config.radius));
2594
+ console.log(row("Surface", config.surface));
2595
+ console.log(row("Chart palette", config.chartPalette));
2596
+ const overrides = ACCEPTED_TOKENS.filter((token) => config.manualOverrides[token]);
2597
+ console.log();
2598
+ if (overrides.length === 0) {
2599
+ console.log(pc8.dim(" No manual overrides \u2014 every colour is generated from the theme colour."));
2600
+ } else {
2601
+ console.log(pc8.bold(` Manual overrides (${overrides.length})`));
2602
+ for (const token of overrides) console.log(row(` ${token}`, config.manualOverrides[token]));
2603
+ }
2604
+ if (isDefaultPreset(config)) {
2605
+ console.log();
2606
+ console.log(pc8.dim(" This preset is the shipped Kinetix default \u2014 it changes nothing."));
2607
+ }
2608
+ console.log();
2609
+ console.log(
2610
+ pc8.dim(
2611
+ // Wrapped so that no command name is split across lines — a reader scanning for `theme build`
2612
+ // should find it, and so should a test.
2613
+ " A preset describes a design, not a stylesheet.\n\n kinetixui preset css <code> the same web CSS the workspace's Copy CSS produces\n kinetixui preset swiftui <code> a SwiftUI colour theme\n kinetixui preset compose <code> a Jetpack Compose colour theme\n kinetixui preset flutter <code> a Flutter colour theme\n\n For a file of literal colours, `kinetixui theme build` is unchanged."
2614
+ )
2615
+ );
2616
+ console.log();
2617
+ }
2618
+ async function presetCss(input, opts) {
2619
+ const css = exportCss(resolveCreateTheme(read(input)));
2620
+ if (opts.output) {
2621
+ await writeFile3(opts.output, css ? `${css}
2622
+ ` : "", "utf8");
2623
+ console.log(pc8.green("\u2714"), css ? `Wrote ${opts.output}` : `Wrote ${opts.output} (empty \u2014 the preset is the Kinetix default)`);
2624
+ return;
2625
+ }
2626
+ if (!css) {
2627
+ console.error(pc8.dim("This preset is the shipped Kinetix default \u2014 there is nothing to override."));
2628
+ return;
2629
+ }
2630
+ console.log(css);
2631
+ }
2632
+ async function presetSwiftUi(input, opts) {
2633
+ const symbol = opts.name ?? DEFAULT_SWIFT_SYMBOL;
2634
+ const reason = swiftSymbolError(symbol);
2635
+ if (reason) throw new Error(reason);
2636
+ const swift = exportSwiftUi(resolveCreateTheme(read(input)), { symbol });
2637
+ if (opts.output) {
2638
+ await writeFile3(opts.output, swift, "utf8");
2639
+ console.log(pc8.green("\u2714"), `Wrote ${opts.output} \u2014 apply it with KinetixTheme(light: ${symbol}.light, dark: ${symbol}.dark)`);
2640
+ return;
2641
+ }
2642
+ process.stdout.write(swift);
2643
+ }
2644
+ async function presetCompose(input, opts) {
2645
+ const symbol = opts.name ?? DEFAULT_COMPOSE_SYMBOL;
2646
+ const reason = kotlinSymbolError(symbol);
2647
+ if (reason) throw new Error(reason);
2648
+ const kotlin = exportCompose(resolveCreateTheme(read(input)), { symbol });
2649
+ if (opts.output) {
2650
+ await writeFile3(opts.output, kotlin, "utf8");
2651
+ console.log(pc8.green("\u2714"), `Wrote ${opts.output} \u2014 apply it with KinetixTheme(light = ${symbol}.light, dark = ${symbol}.dark)`);
2652
+ return;
2653
+ }
2654
+ process.stdout.write(kotlin);
2655
+ }
2656
+ async function presetFlutter(input, opts) {
2657
+ const symbol = opts.name ?? DEFAULT_FLUTTER_SYMBOL;
2658
+ const reason = dartSymbolError(symbol);
2659
+ if (reason) throw new Error(reason);
2660
+ const dart = exportFlutter(resolveCreateTheme(read(input)), { symbol });
2661
+ if (opts.output) {
2662
+ await writeFile3(opts.output, dart, "utf8");
2663
+ console.log(pc8.green("\u2714"), `Wrote ${opts.output} \u2014 apply it with KinetixTheme.custom(light: ${symbol}.light, dark: ${symbol}.dark, child: \u2026)`);
2664
+ return;
2665
+ }
2666
+ process.stdout.write(dart);
2667
+ }
2668
+ function presetUrlCommand(input, opts) {
2669
+ console.log(presetUrl(read(input), opts.site));
2670
+ }
2671
+
2672
+ // src/commands/theme.ts
2673
+ import { existsSync as existsSync9 } from "fs";
2674
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2675
+ import path9 from "path";
2676
+ import pc9 from "picocolors";
2677
+
731
2678
  // src/lib/theme.ts
732
- var ACCEPTED_TOKENS = [
2679
+ var ACCEPTED_TOKENS2 = [
733
2680
  "background",
734
2681
  "foreground",
735
2682
  "card",
@@ -784,7 +2731,7 @@ var EXAMPLE_VALUES = {
784
2731
  brand: "#0f172a",
785
2732
  "brand-foreground": "#ffffff"
786
2733
  };
787
- var FOREGROUND_OF = {
2734
+ var FOREGROUND_OF2 = {
788
2735
  background: "foreground",
789
2736
  card: "card-foreground",
790
2737
  popover: "popover-foreground",
@@ -796,7 +2743,7 @@ var FOREGROUND_OF = {
796
2743
  action: "action-foreground",
797
2744
  brand: "brand-foreground"
798
2745
  };
799
- var CONTRAST_PAIRS = [
2746
+ var CONTRAST_PAIRS2 = [
800
2747
  ["background", "foreground"],
801
2748
  ["card", "card-foreground"],
802
2749
  ["popover", "popover-foreground"],
@@ -808,7 +2755,7 @@ var CONTRAST_PAIRS = [
808
2755
  ["action", "action-foreground"],
809
2756
  ["brand", "brand-foreground"]
810
2757
  ];
811
- var isAccepted = (t) => ACCEPTED_TOKENS.includes(t);
2758
+ var isAccepted = (t) => ACCEPTED_TOKENS2.includes(t);
812
2759
  function parseThemeFile(text) {
813
2760
  const values = {};
814
2761
  const errors = [];
@@ -834,22 +2781,22 @@ function parseThemeFile(text) {
834
2781
  });
835
2782
  return { values, errors, unknownTokens };
836
2783
  }
837
- function deriveForegrounds(values) {
2784
+ function deriveForegrounds2(values) {
838
2785
  const out = { ...values };
839
- for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
2786
+ for (const [base, fg] of Object.entries(FOREGROUND_OF2)) {
840
2787
  if (out[base] && !out[fg]) out[fg] = bestTextHex(out[base]);
841
2788
  }
842
2789
  return out;
843
2790
  }
844
- function toCssBlock(values) {
845
- const lines = ACCEPTED_TOKENS.filter((t) => values[t]).map((t) => ` --${t}: ${hexToHslChannels(values[t])};`);
2791
+ function toCssBlock2(values) {
2792
+ const lines = ACCEPTED_TOKENS2.filter((t) => values[t]).map((t) => ` --${t}: ${hexToHslChannels(values[t])};`);
846
2793
  return `:root {
847
2794
  ${lines.join("\n")}
848
2795
  }
849
2796
  `;
850
2797
  }
851
- function checkContrast(values) {
852
- return CONTRAST_PAIRS.filter(([bg, fg]) => values[bg] && values[fg]).map(([bg, fg]) => {
2798
+ function checkContrast2(values) {
2799
+ return CONTRAST_PAIRS2.filter(([bg, fg]) => values[bg] && values[fg]).map(([bg, fg]) => {
853
2800
  const ratio = contrastRatio(values[bg], values[fg]);
854
2801
  return { pair: [bg, fg], ratio, pass: ratio >= 4.5 };
855
2802
  });
@@ -868,7 +2815,7 @@ function scaffoldThemeFile(name) {
868
2815
  "# don't have to set both unless you want a specific foreground.",
869
2816
  "#"
870
2817
  ];
871
- for (const token of ACCEPTED_TOKENS) {
2818
+ for (const token of ACCEPTED_TOKENS2) {
872
2819
  lines.push(`# ${token},${EXAMPLE_VALUES[token]}`);
873
2820
  }
874
2821
  return lines.join("\n") + "\n";
@@ -890,10 +2837,10 @@ async function themeCreate(name, options) {
890
2837
  throw new Error(`${path9.relative(cwd, file)} already exists. Pass --force to overwrite it.`);
891
2838
  }
892
2839
  await mkdir2(path9.dirname(file), { recursive: true });
893
- await writeFile3(file, scaffoldThemeFile(name), "utf8");
894
- console.log(pc8.green("\u2714"), `Wrote ${path9.relative(cwd, file)}`);
2840
+ await writeFile4(file, scaffoldThemeFile(name), "utf8");
2841
+ console.log(pc9.green("\u2714"), `Wrote ${path9.relative(cwd, file)}`);
895
2842
  console.log(`Uncomment and edit the tokens you want to override, then run:`);
896
- console.log(pc8.dim(` kinetixui theme build ${name}`));
2843
+ console.log(pc9.dim(` kinetixui theme build ${name}`));
897
2844
  }
898
2845
  async function themeBuild(name, options) {
899
2846
  assertThemeName(name);
@@ -904,32 +2851,32 @@ async function themeBuild(name, options) {
904
2851
  }
905
2852
  const text = await readFile3(src, "utf8");
906
2853
  const { values: parsed, errors, unknownTokens } = parseThemeFile(text);
907
- for (const err of errors) console.log(pc8.yellow("!"), err);
908
- for (const unknown of unknownTokens) console.log(pc8.yellow("!"), `"${unknown}" isn't a known token \u2014 skipped.`);
909
- const values = deriveForegrounds(parsed);
2854
+ for (const err of errors) console.log(pc9.yellow("!"), err);
2855
+ for (const unknown of unknownTokens) console.log(pc9.yellow("!"), `"${unknown}" isn't a known token \u2014 skipped.`);
2856
+ const values = deriveForegrounds2(parsed);
910
2857
  const setCount = Object.keys(values).length;
911
2858
  if (setCount === 0) {
912
2859
  throw new Error(`No tokens set in ${path9.relative(cwd, src)} \u2014 nothing to build. Uncomment at least one line.`);
913
2860
  }
914
2861
  const dest = cssPath(cwd, name);
915
- await writeFile3(dest, toCssBlock(values), "utf8");
916
- console.log(pc8.green("\u2714"), `Wrote ${path9.relative(cwd, dest)} (${setCount} token${setCount === 1 ? "" : "s"})`);
917
- console.log(`Paste it after ${pc8.dim('@import "@kinetixui/tokens/css";')} in your global CSS.`);
2862
+ await writeFile4(dest, toCssBlock2(values), "utf8");
2863
+ console.log(pc9.green("\u2714"), `Wrote ${path9.relative(cwd, dest)} (${setCount} token${setCount === 1 ? "" : "s"})`);
2864
+ console.log(`Paste it after ${pc9.dim('@import "@kinetixui/tokens/css";')} in your global CSS.`);
918
2865
  console.log();
919
- console.log(pc8.dim("WCAG AA contrast (4.5:1)"));
920
- const results = checkContrast(values);
2866
+ console.log(pc9.dim("WCAG AA contrast (4.5:1)"));
2867
+ const results = checkContrast2(values);
921
2868
  let failed = 0;
922
2869
  for (const { pair, ratio, pass } of results) {
923
- const mark = pass ? pc8.green("\u2714") : pc8.red("\u2716");
2870
+ const mark = pass ? pc9.green("\u2714") : pc9.red("\u2716");
924
2871
  if (!pass) failed++;
925
- console.log(`${mark} ${pair[0]} / ${pair[1]}`, pc8.dim(`${ratio.toFixed(2)}:1`));
2872
+ console.log(`${mark} ${pair[0]} / ${pair[1]}`, pc9.dim(`${ratio.toFixed(2)}:1`));
926
2873
  }
927
2874
  if (results.length === 0) {
928
- console.log(pc8.dim("(no checkable pairs \u2014 set at least one base + foreground, or just a base to auto-derive one)"));
2875
+ console.log(pc9.dim("(no checkable pairs \u2014 set at least one base + foreground, or just a base to auto-derive one)"));
929
2876
  }
930
2877
  console.log();
931
2878
  console.log("Native (SwiftUI/Compose/Flutter) theme output isn't built yet \u2014 this compiles to CSS only.");
932
- console.log(pc8.dim("See https://kinetixui.com/docs/cli#theme for the current scope."));
2879
+ console.log(pc9.dim("See https://kinetixui.com/docs/cli#theme for the current scope."));
933
2880
  if (failed > 0 && options.failOnContrast) {
934
2881
  process.exitCode = 1;
935
2882
  }
@@ -938,7 +2885,7 @@ async function themeBuild(name, options) {
938
2885
  // package.json
939
2886
  var package_default = {
940
2887
  name: "@kinetixui/cli",
941
- version: "0.22.1",
2888
+ version: "0.23.0",
942
2889
  description: "Add KinetixUI components and tokens to your project.",
943
2890
  type: "module",
944
2891
  license: "MIT",
@@ -961,6 +2908,8 @@ var package_default = {
961
2908
  prompts: "^2.4.2"
962
2909
  },
963
2910
  devDependencies: {
2911
+ "@kinetixui/create-preset": "workspace:*",
2912
+ "@kinetixui/create-theme": "workspace:*",
964
2913
  "@types/node": "^22.20.3",
965
2914
  "@types/prompts": "^2.4.9",
966
2915
  tsup: "^8.5.1",
@@ -982,7 +2931,7 @@ program.command("init").description("Set up kinetixui.json and pull in the token
982
2931
  try {
983
2932
  await init({ registry: opts.registry, yes: opts.yes });
984
2933
  } catch (err) {
985
- console.error(pc9.red("\u2716"), err.message);
2934
+ console.error(pc10.red("\u2716"), err.message);
986
2935
  process.exitCode = 1;
987
2936
  }
988
2937
  });
@@ -990,7 +2939,7 @@ program.command("add").description("Add one or more components to your project."
990
2939
  try {
991
2940
  await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
992
2941
  } catch (err) {
993
- console.error(pc9.red("\u2716"), err.message);
2942
+ console.error(pc10.red("\u2716"), err.message);
994
2943
  process.exitCode = 1;
995
2944
  }
996
2945
  });
@@ -998,7 +2947,7 @@ program.command("list").description("List every component available in the regis
998
2947
  try {
999
2948
  await list({ registry: opts.registry });
1000
2949
  } catch (err) {
1001
- console.error(pc9.red("\u2716"), err.message);
2950
+ console.error(pc10.red("\u2716"), err.message);
1002
2951
  process.exitCode = 1;
1003
2952
  }
1004
2953
  });
@@ -1006,7 +2955,7 @@ program.command("inspect").description("Show everything the registry knows about
1006
2955
  try {
1007
2956
  await inspect(name, { registry: opts.registry });
1008
2957
  } catch (err) {
1009
- console.error(pc9.red("\u2716"), err.message);
2958
+ console.error(pc10.red("\u2716"), err.message);
1010
2959
  process.exitCode = 1;
1011
2960
  }
1012
2961
  });
@@ -1014,7 +2963,7 @@ program.command("parity").description("Show which platforms carry each component
1014
2963
  try {
1015
2964
  await parity(components, { registry: opts.registry });
1016
2965
  } catch (err) {
1017
- console.error(pc9.red("\u2716"), err.message);
2966
+ console.error(pc10.red("\u2716"), err.message);
1018
2967
  process.exitCode = 1;
1019
2968
  }
1020
2969
  });
@@ -1022,7 +2971,7 @@ program.command("lint").description("Scan for hardcoded colors and spacing that
1022
2971
  try {
1023
2972
  await lint(target, { fail: opts.fail });
1024
2973
  } catch (err) {
1025
- console.error(pc9.red("\u2716"), err.message);
2974
+ console.error(pc10.red("\u2716"), err.message);
1026
2975
  process.exitCode = 1;
1027
2976
  }
1028
2977
  });
@@ -1030,7 +2979,7 @@ program.command("doctor").description("Check kinetixui.json, its aliases, and th
1030
2979
  try {
1031
2980
  await doctor();
1032
2981
  } catch (err) {
1033
- console.error(pc9.red("\u2716"), err.message);
2982
+ console.error(pc10.red("\u2716"), err.message);
1034
2983
  process.exitCode = 1;
1035
2984
  }
1036
2985
  });
@@ -1039,7 +2988,7 @@ theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with e
1039
2988
  try {
1040
2989
  await themeCreate(name, { force: opts.force });
1041
2990
  } catch (err) {
1042
- console.error(pc9.red("\u2716"), err.message);
2991
+ console.error(pc10.red("\u2716"), err.message);
1043
2992
  process.exitCode = 1;
1044
2993
  }
1045
2994
  });
@@ -1047,7 +2996,56 @@ theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS a
1047
2996
  try {
1048
2997
  await themeBuild(name, { failOnContrast: opts.failOnContrast });
1049
2998
  } catch (err) {
1050
- console.error(pc9.red("\u2716"), err.message);
2999
+ console.error(pc10.red("\u2716"), err.message);
3000
+ process.exitCode = 1;
3001
+ }
3002
+ });
3003
+ var preset = program.command("preset").description("Read a Kinetix Create preset code (KX1_\u2026) produced by kinetixui.com/create.");
3004
+ preset.command("decode").description("Show what a preset contains. Accepts a KX1_ code or a full /create?preset=\u2026 URL.").argument("<preset>", "a KX1_ code, or a share URL").option("--json", "print the configuration as JSON", false).action((input, opts) => {
3005
+ try {
3006
+ presetDecode(input, { json: opts.json });
3007
+ } catch (err) {
3008
+ console.error(pc10.red("\u2716"), err.message);
3009
+ process.exitCode = 1;
3010
+ }
3011
+ });
3012
+ preset.command("css").description("Resolve a preset into its web CSS override block \u2014 the same output as Copy CSS in the workspace. Web CSS only; no native (SwiftUI/Compose/Flutter) output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").action(async (input, opts) => {
3013
+ try {
3014
+ await presetCss(input, { output: opts.output });
3015
+ } catch (err) {
3016
+ console.error(pc10.red("\u2716"), err.message);
3017
+ process.exitCode = 1;
3018
+ }
3019
+ });
3020
+ preset.command("swiftui").description("Resolve a preset into a SwiftUI theme file (KinetixColors, light and dark). Colours only \u2014 SwiftUI has no radius or elevation token to carry. No Compose or Flutter output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Swift enum to generate", DEFAULT_SWIFT_SYMBOL).action(async (input, opts) => {
3021
+ try {
3022
+ await presetSwiftUi(input, { output: opts.output, name: opts.name });
3023
+ } catch (err) {
3024
+ console.error(pc10.red("\u2716"), err.message);
3025
+ process.exitCode = 1;
3026
+ }
3027
+ });
3028
+ preset.command("compose").description("Resolve a preset into a Jetpack Compose theme file (KinetixColors, light and dark). Colours only \u2014 Compose has no runtime radius or elevation token to carry. No Flutter or Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Kotlin object to generate", DEFAULT_COMPOSE_SYMBOL).action(async (input, opts) => {
3029
+ try {
3030
+ await presetCompose(input, { output: opts.output, name: opts.name });
3031
+ } catch (err) {
3032
+ console.error(pc10.red("\u2716"), err.message);
3033
+ process.exitCode = 1;
3034
+ }
3035
+ });
3036
+ preset.command("flutter").description("Resolve a preset into a Flutter theme file (KinetixColors, light and dark). Colours only \u2014 Flutter's radius and elevation are not runtime-themeable. No Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Dart class to generate", DEFAULT_FLUTTER_SYMBOL).action(async (input, opts) => {
3037
+ try {
3038
+ await presetFlutter(input, { output: opts.output, name: opts.name });
3039
+ } catch (err) {
3040
+ console.error(pc10.red("\u2716"), err.message);
3041
+ process.exitCode = 1;
3042
+ }
3043
+ });
3044
+ preset.command("url").description("Print the canonical share URL for a preset. Prints it \u2014 piping it to your opener is up to you.").argument("<preset>", "a KX1_ code, or a share URL").option("-s, --site <origin>", "site origin", "https://kinetixui.com").action((input, opts) => {
3045
+ try {
3046
+ presetUrlCommand(input, { site: opts.site });
3047
+ } catch (err) {
3048
+ console.error(pc10.red("\u2716"), err.message);
1051
3049
  process.exitCode = 1;
1052
3050
  }
1053
3051
  });