@kinetixui/cli 0.22.0 → 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 +2059 -55
  2. package/package.json +4 -2
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 "));
@@ -658,22 +658,283 @@ async function parity(names, options) {
658
658
  }
659
659
  console.log();
660
660
  console.log(`${rows.length} of ${components.length} component(s) shown.`);
661
+ if (!names.length) {
662
+ console.log(
663
+ pc7.dim("Registry items only \u2014 components that ship inside another item, or are documented compositions, have no entry of their own.")
664
+ );
665
+ console.log(pc7.dim("Full coverage: https://kinetixui.com/docs/platforms"));
666
+ }
661
667
  }
662
668
 
663
- // src/commands/theme.ts
664
- import { existsSync as existsSync9 } from "fs";
665
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
666
- import path9 from "path";
669
+ // src/commands/preset.ts
670
+ import { writeFile as writeFile3 } from "fs/promises";
667
671
  import pc8 from "picocolors";
668
672
 
669
- // 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
670
929
  function hexToRgb(hex) {
671
930
  const h = hex.replace("#", "");
672
931
  const n = parseInt(h.length === 3 ? h.replace(/(.)/g, "$1$1") : h, 16);
673
932
  return [n >> 16 & 255, n >> 8 & 255, n & 255];
674
933
  }
675
934
  function rgbToHsl(r, g, b) {
676
- r /= 255, g /= 255, b /= 255;
935
+ r /= 255;
936
+ g /= 255;
937
+ b /= 255;
677
938
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
678
939
  let h = 0, s = 0;
679
940
  const l = (max + min) / 2;
@@ -699,18 +960,46 @@ function hexToHslChannels(hex) {
699
960
  const [h, s, l] = rgbToHsl(...hexToRgb(hex));
700
961
  return `${h} ${s}% ${l}%`;
701
962
  }
702
- function relLuminance([r, g, b]) {
703
- const [cr, cg, cb] = [r, g, b].map((v) => {
704
- const s = v / 255;
705
- return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
706
- });
707
- 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);
708
966
  }
709
- function contrastRatio(hexA, hexB) {
710
- const la = relLuminance(hexToRgb(hexA)) + 0.05;
711
- 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;
712
971
  return la > lb ? la / lb : lb / la;
713
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
+ }
714
1003
  function bestTextHex(bgHex) {
715
1004
  return contrastRatio(bgHex, "#ffffff") >= contrastRatio(bgHex, "#000000") ? "#ffffff" : "#000000";
716
1005
  }
@@ -722,8 +1011,1672 @@ function normalizeHex(v) {
722
1011
  return `#${t.toLowerCase()}`;
723
1012
  }
724
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
+
725
2678
  // src/lib/theme.ts
726
- var ACCEPTED_TOKENS = [
2679
+ var ACCEPTED_TOKENS2 = [
727
2680
  "background",
728
2681
  "foreground",
729
2682
  "card",
@@ -778,7 +2731,7 @@ var EXAMPLE_VALUES = {
778
2731
  brand: "#0f172a",
779
2732
  "brand-foreground": "#ffffff"
780
2733
  };
781
- var FOREGROUND_OF = {
2734
+ var FOREGROUND_OF2 = {
782
2735
  background: "foreground",
783
2736
  card: "card-foreground",
784
2737
  popover: "popover-foreground",
@@ -790,7 +2743,7 @@ var FOREGROUND_OF = {
790
2743
  action: "action-foreground",
791
2744
  brand: "brand-foreground"
792
2745
  };
793
- var CONTRAST_PAIRS = [
2746
+ var CONTRAST_PAIRS2 = [
794
2747
  ["background", "foreground"],
795
2748
  ["card", "card-foreground"],
796
2749
  ["popover", "popover-foreground"],
@@ -802,7 +2755,7 @@ var CONTRAST_PAIRS = [
802
2755
  ["action", "action-foreground"],
803
2756
  ["brand", "brand-foreground"]
804
2757
  ];
805
- var isAccepted = (t) => ACCEPTED_TOKENS.includes(t);
2758
+ var isAccepted = (t) => ACCEPTED_TOKENS2.includes(t);
806
2759
  function parseThemeFile(text) {
807
2760
  const values = {};
808
2761
  const errors = [];
@@ -828,22 +2781,22 @@ function parseThemeFile(text) {
828
2781
  });
829
2782
  return { values, errors, unknownTokens };
830
2783
  }
831
- function deriveForegrounds(values) {
2784
+ function deriveForegrounds2(values) {
832
2785
  const out = { ...values };
833
- for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
2786
+ for (const [base, fg] of Object.entries(FOREGROUND_OF2)) {
834
2787
  if (out[base] && !out[fg]) out[fg] = bestTextHex(out[base]);
835
2788
  }
836
2789
  return out;
837
2790
  }
838
- function toCssBlock(values) {
839
- 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])};`);
840
2793
  return `:root {
841
2794
  ${lines.join("\n")}
842
2795
  }
843
2796
  `;
844
2797
  }
845
- function checkContrast(values) {
846
- 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]) => {
847
2800
  const ratio = contrastRatio(values[bg], values[fg]);
848
2801
  return { pair: [bg, fg], ratio, pass: ratio >= 4.5 };
849
2802
  });
@@ -862,7 +2815,7 @@ function scaffoldThemeFile(name) {
862
2815
  "# don't have to set both unless you want a specific foreground.",
863
2816
  "#"
864
2817
  ];
865
- for (const token of ACCEPTED_TOKENS) {
2818
+ for (const token of ACCEPTED_TOKENS2) {
866
2819
  lines.push(`# ${token},${EXAMPLE_VALUES[token]}`);
867
2820
  }
868
2821
  return lines.join("\n") + "\n";
@@ -884,10 +2837,10 @@ async function themeCreate(name, options) {
884
2837
  throw new Error(`${path9.relative(cwd, file)} already exists. Pass --force to overwrite it.`);
885
2838
  }
886
2839
  await mkdir2(path9.dirname(file), { recursive: true });
887
- await writeFile3(file, scaffoldThemeFile(name), "utf8");
888
- 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)}`);
889
2842
  console.log(`Uncomment and edit the tokens you want to override, then run:`);
890
- console.log(pc8.dim(` kinetixui theme build ${name}`));
2843
+ console.log(pc9.dim(` kinetixui theme build ${name}`));
891
2844
  }
892
2845
  async function themeBuild(name, options) {
893
2846
  assertThemeName(name);
@@ -898,32 +2851,32 @@ async function themeBuild(name, options) {
898
2851
  }
899
2852
  const text = await readFile3(src, "utf8");
900
2853
  const { values: parsed, errors, unknownTokens } = parseThemeFile(text);
901
- for (const err of errors) console.log(pc8.yellow("!"), err);
902
- for (const unknown of unknownTokens) console.log(pc8.yellow("!"), `"${unknown}" isn't a known token \u2014 skipped.`);
903
- 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);
904
2857
  const setCount = Object.keys(values).length;
905
2858
  if (setCount === 0) {
906
2859
  throw new Error(`No tokens set in ${path9.relative(cwd, src)} \u2014 nothing to build. Uncomment at least one line.`);
907
2860
  }
908
2861
  const dest = cssPath(cwd, name);
909
- await writeFile3(dest, toCssBlock(values), "utf8");
910
- console.log(pc8.green("\u2714"), `Wrote ${path9.relative(cwd, dest)} (${setCount} token${setCount === 1 ? "" : "s"})`);
911
- 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.`);
912
2865
  console.log();
913
- console.log(pc8.dim("WCAG AA contrast (4.5:1)"));
914
- const results = checkContrast(values);
2866
+ console.log(pc9.dim("WCAG AA contrast (4.5:1)"));
2867
+ const results = checkContrast2(values);
915
2868
  let failed = 0;
916
2869
  for (const { pair, ratio, pass } of results) {
917
- const mark = pass ? pc8.green("\u2714") : pc8.red("\u2716");
2870
+ const mark = pass ? pc9.green("\u2714") : pc9.red("\u2716");
918
2871
  if (!pass) failed++;
919
- 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`));
920
2873
  }
921
2874
  if (results.length === 0) {
922
- 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)"));
923
2876
  }
924
2877
  console.log();
925
2878
  console.log("Native (SwiftUI/Compose/Flutter) theme output isn't built yet \u2014 this compiles to CSS only.");
926
- 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."));
927
2880
  if (failed > 0 && options.failOnContrast) {
928
2881
  process.exitCode = 1;
929
2882
  }
@@ -932,7 +2885,7 @@ async function themeBuild(name, options) {
932
2885
  // package.json
933
2886
  var package_default = {
934
2887
  name: "@kinetixui/cli",
935
- version: "0.22.0",
2888
+ version: "0.23.0",
936
2889
  description: "Add KinetixUI components and tokens to your project.",
937
2890
  type: "module",
938
2891
  license: "MIT",
@@ -955,7 +2908,9 @@ var package_default = {
955
2908
  prompts: "^2.4.2"
956
2909
  },
957
2910
  devDependencies: {
958
- "@types/node": "^22.20.2",
2911
+ "@kinetixui/create-preset": "workspace:*",
2912
+ "@kinetixui/create-theme": "workspace:*",
2913
+ "@types/node": "^22.20.3",
959
2914
  "@types/prompts": "^2.4.9",
960
2915
  tsup: "^8.5.1",
961
2916
  typescript: "^5.9.3"
@@ -976,7 +2931,7 @@ program.command("init").description("Set up kinetixui.json and pull in the token
976
2931
  try {
977
2932
  await init({ registry: opts.registry, yes: opts.yes });
978
2933
  } catch (err) {
979
- console.error(pc9.red("\u2716"), err.message);
2934
+ console.error(pc10.red("\u2716"), err.message);
980
2935
  process.exitCode = 1;
981
2936
  }
982
2937
  });
@@ -984,7 +2939,7 @@ program.command("add").description("Add one or more components to your project."
984
2939
  try {
985
2940
  await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
986
2941
  } catch (err) {
987
- console.error(pc9.red("\u2716"), err.message);
2942
+ console.error(pc10.red("\u2716"), err.message);
988
2943
  process.exitCode = 1;
989
2944
  }
990
2945
  });
@@ -992,7 +2947,7 @@ program.command("list").description("List every component available in the regis
992
2947
  try {
993
2948
  await list({ registry: opts.registry });
994
2949
  } catch (err) {
995
- console.error(pc9.red("\u2716"), err.message);
2950
+ console.error(pc10.red("\u2716"), err.message);
996
2951
  process.exitCode = 1;
997
2952
  }
998
2953
  });
@@ -1000,7 +2955,7 @@ program.command("inspect").description("Show everything the registry knows about
1000
2955
  try {
1001
2956
  await inspect(name, { registry: opts.registry });
1002
2957
  } catch (err) {
1003
- console.error(pc9.red("\u2716"), err.message);
2958
+ console.error(pc10.red("\u2716"), err.message);
1004
2959
  process.exitCode = 1;
1005
2960
  }
1006
2961
  });
@@ -1008,7 +2963,7 @@ program.command("parity").description("Show which platforms carry each component
1008
2963
  try {
1009
2964
  await parity(components, { registry: opts.registry });
1010
2965
  } catch (err) {
1011
- console.error(pc9.red("\u2716"), err.message);
2966
+ console.error(pc10.red("\u2716"), err.message);
1012
2967
  process.exitCode = 1;
1013
2968
  }
1014
2969
  });
@@ -1016,7 +2971,7 @@ program.command("lint").description("Scan for hardcoded colors and spacing that
1016
2971
  try {
1017
2972
  await lint(target, { fail: opts.fail });
1018
2973
  } catch (err) {
1019
- console.error(pc9.red("\u2716"), err.message);
2974
+ console.error(pc10.red("\u2716"), err.message);
1020
2975
  process.exitCode = 1;
1021
2976
  }
1022
2977
  });
@@ -1024,7 +2979,7 @@ program.command("doctor").description("Check kinetixui.json, its aliases, and th
1024
2979
  try {
1025
2980
  await doctor();
1026
2981
  } catch (err) {
1027
- console.error(pc9.red("\u2716"), err.message);
2982
+ console.error(pc10.red("\u2716"), err.message);
1028
2983
  process.exitCode = 1;
1029
2984
  }
1030
2985
  });
@@ -1033,7 +2988,7 @@ theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with e
1033
2988
  try {
1034
2989
  await themeCreate(name, { force: opts.force });
1035
2990
  } catch (err) {
1036
- console.error(pc9.red("\u2716"), err.message);
2991
+ console.error(pc10.red("\u2716"), err.message);
1037
2992
  process.exitCode = 1;
1038
2993
  }
1039
2994
  });
@@ -1041,7 +2996,56 @@ theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS a
1041
2996
  try {
1042
2997
  await themeBuild(name, { failOnContrast: opts.failOnContrast });
1043
2998
  } catch (err) {
1044
- 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);
1045
3049
  process.exitCode = 1;
1046
3050
  }
1047
3051
  });