@theme-registry/refract 0.1.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_createTheme-255ff48e.esm.js +2 -0
  3. package/dist/_createTheme-255ff48e.esm.js.map +1 -0
  4. package/dist/_createTheme-41c01033.cjs.js +2 -0
  5. package/dist/_createTheme-41c01033.cjs.js.map +1 -0
  6. package/dist/_errors-be768b25.cjs.js +2 -0
  7. package/dist/_errors-be768b25.cjs.js.map +1 -0
  8. package/dist/_errors-d75a5c74.esm.js +2 -0
  9. package/dist/_errors-d75a5c74.esm.js.map +1 -0
  10. package/dist/adapter-kit.cjs.js +2 -0
  11. package/dist/adapter-kit.cjs.js.map +1 -0
  12. package/dist/adapter-kit.d.ts +88 -0
  13. package/dist/adapter-kit.esm.js +2 -0
  14. package/dist/adapter-kit.esm.js.map +1 -0
  15. package/dist/build/auditCommand.d.ts +19 -0
  16. package/dist/build/buildCommand.d.ts +23 -0
  17. package/dist/build/cli.d.ts +2 -0
  18. package/dist/build/config.d.ts +58 -0
  19. package/dist/build/diff.d.ts +56 -0
  20. package/dist/build/diffCommand.d.ts +29 -0
  21. package/dist/build/emit.d.ts +20 -0
  22. package/dist/build/emitTheme.d.ts +39 -0
  23. package/dist/build/guide.d.ts +34 -0
  24. package/dist/build/importCommand.d.ts +42 -0
  25. package/dist/build/index.d.ts +33 -0
  26. package/dist/build/init.d.ts +32 -0
  27. package/dist/build/paths.d.ts +42 -0
  28. package/dist/build/skillsCommand.d.ts +53 -0
  29. package/dist/build/tokensCommand.d.ts +17 -0
  30. package/dist/build/vendor.d.ts +39 -0
  31. package/dist/build.cjs.js +2 -0
  32. package/dist/build.cjs.js.map +1 -0
  33. package/dist/build.esm.js +2 -0
  34. package/dist/build.esm.js.map +1 -0
  35. package/dist/cli.js +8250 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/color-math.cjs.js +2 -0
  38. package/dist/color-math.cjs.js.map +1 -0
  39. package/dist/color-math.esm.js +2 -0
  40. package/dist/color-math.esm.js.map +1 -0
  41. package/dist/core/ThemeAdapter.d.ts +234 -0
  42. package/dist/core/createTheme.d.ts +96 -0
  43. package/dist/core/cssProperties.d.ts +8 -0
  44. package/dist/core/defineAdapter.d.ts +25 -0
  45. package/dist/core/derive/crossProperty.d.ts +31 -0
  46. package/dist/core/derive/index.d.ts +11 -0
  47. package/dist/core/derive/resolveTokens.d.ts +27 -0
  48. package/dist/core/errors.d.ts +24 -0
  49. package/dist/core/index.d.ts +16 -0
  50. package/dist/core/media/containers.d.ts +20 -0
  51. package/dist/core/media/defaults.d.ts +3 -0
  52. package/dist/core/media/descriptors.d.ts +12 -0
  53. package/dist/core/media/index.d.ts +11 -0
  54. package/dist/core/media/queries.d.ts +20 -0
  55. package/dist/core/model/buildModel.d.ts +116 -0
  56. package/dist/core/model/index.d.ts +14 -0
  57. package/dist/core/model/interpreted.d.ts +37 -0
  58. package/dist/core/model/mergeRuleSet.d.ts +68 -0
  59. package/dist/core/model/model.d.ts +260 -0
  60. package/dist/core/noopAdapter.d.ts +26 -0
  61. package/dist/core/normalize/index.d.ts +15 -0
  62. package/dist/core/normalize/properties.d.ts +20 -0
  63. package/dist/core/normalize/recipeResolver.d.ts +19 -0
  64. package/dist/core/normalize/recipes.d.ts +38 -0
  65. package/dist/core/normalize/responsive.d.ts +40 -0
  66. package/dist/core/normalize/types.d.ts +261 -0
  67. package/dist/core/rawTheme.d.ts +53 -0
  68. package/dist/core/ref.d.ts +13 -0
  69. package/dist/core/subsystem.d.ts +115 -0
  70. package/dist/core/types.d.ts +31 -0
  71. package/dist/core/units.d.ts +93 -0
  72. package/dist/dtcg/export.d.ts +48 -0
  73. package/dist/dtcg/import.d.ts +43 -0
  74. package/dist/dtcg/index.d.ts +21 -0
  75. package/dist/dtcg/parse.d.ts +6 -0
  76. package/dist/dtcg/types.d.ts +69 -0
  77. package/dist/dtcg.cjs.js +2 -0
  78. package/dist/dtcg.cjs.js.map +1 -0
  79. package/dist/dtcg.esm.js +2 -0
  80. package/dist/dtcg.esm.js.map +1 -0
  81. package/dist/index.cjs.js +2 -0
  82. package/dist/index.cjs.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.esm.js +2 -0
  85. package/dist/index.esm.js.map +1 -0
  86. package/dist/subsystems/animation/index.d.ts +13 -0
  87. package/dist/subsystems/animation/recipes.d.ts +20 -0
  88. package/dist/subsystems/animation/types.d.ts +52 -0
  89. package/dist/subsystems/borders/index.d.ts +10 -0
  90. package/dist/subsystems/borders/recipes.d.ts +29 -0
  91. package/dist/subsystems/borders/types.d.ts +50 -0
  92. package/dist/subsystems/colors/audit.d.ts +67 -0
  93. package/dist/subsystems/colors/colorInput.d.ts +17 -0
  94. package/dist/subsystems/colors/index.d.ts +12 -0
  95. package/dist/subsystems/colors/keywords.d.ts +9 -0
  96. package/dist/subsystems/colors/normalize.d.ts +26 -0
  97. package/dist/subsystems/colors/recipes.d.ts +21 -0
  98. package/dist/subsystems/colors/types.d.ts +98 -0
  99. package/dist/subsystems/colors/utils.d.ts +104 -0
  100. package/dist/subsystems/components/index.d.ts +13 -0
  101. package/dist/subsystems/components/recipes.d.ts +22 -0
  102. package/dist/subsystems/components/types.d.ts +46 -0
  103. package/dist/subsystems/effects/index.d.ts +9 -0
  104. package/dist/subsystems/effects/recipes.d.ts +24 -0
  105. package/dist/subsystems/effects/types.d.ts +63 -0
  106. package/dist/subsystems/effects/utils.d.ts +57 -0
  107. package/dist/subsystems/globals/index.d.ts +21 -0
  108. package/dist/subsystems/globals/presets.d.ts +33 -0
  109. package/dist/subsystems/globals/types.d.ts +48 -0
  110. package/dist/subsystems/index.d.ts +21 -0
  111. package/dist/subsystems/layout/index.d.ts +14 -0
  112. package/dist/subsystems/layout/normalize.d.ts +35 -0
  113. package/dist/subsystems/layout/recipes.d.ts +16 -0
  114. package/dist/subsystems/layout/structural.d.ts +37 -0
  115. package/dist/subsystems/layout/types.d.ts +154 -0
  116. package/dist/subsystems/typography/index.d.ts +9 -0
  117. package/dist/subsystems/typography/normalize.d.ts +17 -0
  118. package/dist/subsystems/typography/recipes.d.ts +21 -0
  119. package/dist/subsystems/typography/types.d.ts +49 -0
  120. package/package.json +83 -0
  121. package/skills/adapter-scaffold/SKILL.md +85 -0
  122. package/skills/adapter-usage/SKILL.md +75 -0
  123. package/skills/build-config/SKILL.md +77 -0
  124. package/skills/colors/SKILL.md +134 -0
  125. package/skills/consuming-the-output/SKILL.md +62 -0
  126. package/skills/dtcg-import/SKILL.md +76 -0
  127. package/skills/layout/SKILL.md +65 -0
  128. package/skills/overrides-and-child-themes/SKILL.md +70 -0
  129. package/skills/recipes-and-composition/SKILL.md +110 -0
  130. package/skills/theme-authoring/SKILL.md +107 -0
  131. package/skills/theme-foundations/SKILL.md +127 -0
  132. package/skills/troubleshooting/SKILL.md +97 -0
  133. package/skills/typography/SKILL.md +66 -0
  134. package/skills/visual-effects/SKILL.md +96 -0
  135. package/src/subsystems/colors/utils.ts +299 -0
@@ -0,0 +1,2 @@
1
+ "use strict";const t=new Set([3,6]),r=(t,r,e)=>Math.min(e,Math.max(r,t)),e=t=>r(t,0,255),o=(t,r)=>{const e=10**r;return Math.round(t*e)/e},n=t=>Math.round(1e3*r(t,0,1))/1e3,s=r=>{const e=r.replace(/^\s*#|\s*$/g,"");if(!t.has(e.length))throw new Error(`Unsupported hex length: "${e}". Use 3 or 6 digits.`);const o=3===e.length?e.replace(/(.)/g,"$1$1"):e;return[parseInt(o.substring(0,2),16),parseInt(o.substring(2,4),16),parseInt(o.substring(4,6),16)]},a=t=>{if(3!==t.length)throw new Error(`Expected RGB tuple of length 3, received ${t.length}`);return`#${t.map(t=>{const r=e(Math.round(t)).toString(16);return 1===r.length?`0${r}`:r}).join("")}`},h=/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i,c=t=>{const r=t.trim(),e=h.exec(r);return e?{rgb:[Number(e[1]),Number(e[2]),Number(e[3])],a:void 0===e[4]?1:n(Number(e[4]))}:{rgb:s(r),a:1}},u=(t,r=1)=>{const o=n(r),[s,a,h]=t,c=e(Math.round(s)),u=e(Math.round(a)),p=e(Math.round(h));return o>=1?`rgb(${c}, ${u}, ${p})`:`rgba(${c}, ${u}, ${p}, ${o})`},p=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,l=180/Math.PI,g=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),i=t=>t>=.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,b=t=>{const[r,e,o]=t,n=((t,r,e)=>{const o=.4122214708*t+.5363325363*r+.0514459929*e,n=.2119034982*t+.6806995451*r+.1073969566*e,s=.0883024619*t+.2817188376*r+.6299787005*e,a=Math.cbrt(o),h=Math.cbrt(n),c=Math.cbrt(s);return{L:.2104542553*a+.793617785*h-.0040720468*c,a:1.9779984951*a-2.428592205*h+.4505937099*c,b:.0259040371*a+.7827717662*h-.808675766*c}})(g(r/255),g(e/255),g(o/255)),s=Math.sqrt(n.a*n.a+n.b*n.b);let a=Math.atan2(n.b,n.a)*l;return a<0&&(a+=360),{L:100*n.L,C:s,h:a}},x=t=>{const e=r(t.L,0,100)/100,o=t.h/l,n=Math.cos(o),s=Math.sin(o),a=t=>((t,r,e)=>{const o=t+.3963377774*r+.2158037573*e,n=t-.1055613458*r-.0638541728*e,s=t-.0894841775*r-1.291485548*e,a=o*o*o,h=n*n*n,c=s*s*s;return[4.0767416621*a-3.3077115913*h+.2309699292*c,-1.2684380046*a+2.6097574011*h-.3413193965*c,-.0041960863*a-.7034186147*h+1.707614701*c]})(e,t*n,t*s),h=t=>t.every(t=>t>=-1e-6&&t<=1.000001);let c=a(t.C);if(!h(c)){let r=0,e=t.C;for(let t=0;t<24;t++){const t=(r+e)/2;h(a(t))?r=t:e=t}c=a(r)}return[255*i(r(c[0],0,1)),255*i(r(c[1],0,1)),255*i(r(c[2],0,1))]},$=(t,r)=>{const{rgb:e,a:o}=c(t);return u(x(r(b(e))),o)},d=(t,r)=>$(t,t=>({...t,h:((t.h+r)%360+360)%360}));exports.adjust=(t,e)=>$(t,t=>({L:void 0===e.l?t.L:r(e.l,0,100),C:void 0===e.c?t.C:Math.max(0,t.C*e.c),h:void 0===e.h?t.h:((t.h+e.h)%360+360)%360})),exports.alpha=(t,e)=>{const{rgb:o}=c(t);return u(o,(t=>r(t,0,100))(e)/100)},exports.complement=t=>d(t,180),exports.convertHexToRGB=s,exports.convertRgbToHex=a,exports.darken=(t,e)=>$(t,t=>({...t,L:r(t.L-e,0,100)})),exports.isHexColor=t=>p.test(t.trim()),exports.lighten=(t,e)=>$(t,t=>({...t,L:r(t.L+e,0,100)})),exports.oklchToRgb=x,exports.parseColor=c,exports.rgbToOklch=b,exports.rotateHue=d,exports.serializeColor=u,exports.setL=(t,e)=>$(t,t=>({...t,L:r(e,0,100)})),exports.toHexColor=t=>{const{rgb:r,a:o}=c(t),n=a(r);if(o>=1)return n;return`${n}${e(Math.round(255*o)).toString(16).padStart(2,"0")}`},exports.toOklchColor=t=>{const{rgb:r,a:e}=c(t),{L:s,C:a,h:h}=b(r),u=`${o(s,2)}% ${o(a,4)} ${o(h,2)}`;return e>=1?`oklch(${u})`:`oklch(${u} / ${n(e)})`};
2
+ //# sourceMappingURL=color-math.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-math.cjs.js","sources":["../src/subsystems/colors/utils.ts"],"sourcesContent":["/**\n * Pure colour math for palette step + variant synthesis.\n *\n * Lightness / hue / chroma work runs in **OKLCH** (a perceptual space) via Björn Ottosson's\n * public-domain matrices — parse → sRGB → linear → OKLab → OKLCH → apply → gamut-map → linear →\n * sRGB → quantize → serialize — so equal lightness steps look even and one lightness reads the same\n * across hues. hex / `rgba()` strings are only touched at the boundary (`parseColor` in,\n * `serializeColor` out). `lighten` / `darken` shift OKLCH lightness by ΔL points; `setL` places an\n * absolute lightness; `rotateHue` / `complement` turn the hue; `alpha` sets opacity. All are\n * string-in / string-out so the same fns run at normalize time (baking the cached value) and later\n * in the derivation registry — AND are vendored to consumers (see `build/vendor.ts`) so a value\n * computed live in the app matches the emitted CSS variable. Kept dependency-free for that vendoring.\n */\n\n/** An `[r, g, b]` channel triple, each `0–255`. */\nexport type RGBTuple = readonly [number, number, number];\n\n/** An `[r, g, b, a]` quad — channels `0–255`, alpha `0–1`. */\nexport type RGBATuple = readonly [number, number, number, number];\n\n/** A colour authored/worked as a tuple — opaque `[r,g,b]` or translucent `[r,g,b,a]`. */\nexport type ColorTuple = RGBTuple | RGBATuple;\n\nconst HEX_LENGTHS = new Set([3, 6]);\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\nconst clampChannel = (value: number): number => clamp(value, 0, 255);\nconst clampAlpha = (value: number): number => clamp(value, 0, 1);\nconst clampPercent = (percent: number): number => clamp(percent, 0, 100);\n\n/** Round to `dp` decimal places (tidy CSS output — `71.8`, not `71.79999`). */\nconst roundTo = (value: number, dp: number): number => {\n const f = 10 ** dp;\n return Math.round(value * f) / f;\n};\n\n/** Round an alpha to 3 decimals so serialized `rgba()` stays tidy (`0.4`, not `0.4000001`). */\nconst roundAlpha = (a: number): number => Math.round(clampAlpha(a) * 1000) / 1000;\n\n/** Parse a 3- or 6-digit hex string (with or without `#`) into an `[r, g, b]` tuple. */\nexport const convertHexToRGB = (hex: string): RGBTuple => {\n const sanitized = hex.replace(/^\\s*#|\\s*$/g, \"\");\n\n if (!HEX_LENGTHS.has(sanitized.length)) {\n // Plain Error on purpose: this module is vendored import-free (build/vendor.ts) so it can be\n // transpiled standalone into consumer bundles — it cannot import RefractError. Its parse errors are\n // re-thrown as REFRACT_E_COLOR_INPUT by colors/normalize.ts, which is the coded authoring surface.\n throw new Error(`Unsupported hex length: \"${sanitized}\". Use 3 or 6 digits.`);\n }\n\n const normalized = sanitized.length === 3 ? sanitized.replace(/(.)/g, \"$1$1\") : sanitized;\n\n return [\n parseInt(normalized.substring(0, 2), 16),\n parseInt(normalized.substring(2, 4), 16),\n parseInt(normalized.substring(4, 6), 16),\n ] as RGBTuple;\n};\n\n/** Serialize an `[r, g, b]` tuple back to a `#rrggbb` string, clamping each channel to `0–255`. */\nexport const convertRgbToHex = (rgb: RGBTuple): string => {\n if (rgb.length !== 3) {\n throw new Error(`Expected RGB tuple of length 3, received ${rgb.length}`);\n }\n\n const hex = rgb\n .map(value => {\n const next = clampChannel(Math.round(value)).toString(16);\n return next.length === 1 ? `0${next}` : next;\n })\n .join(\"\");\n\n return `#${hex}`;\n};\n\nconst RGB_FN = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/i;\n\n/**\n * Parse any canonical colour string this subsystem produces — a 3/6-digit hex **or** an\n * `rgb()` / `rgba()` string — into `{ rgb, a }` (alpha defaults to `1`). This is the boundary\n * gate; a value that isn't parseable rgb (e.g. a bare CSS keyword or `var(--…)`) throws, so\n * callers can surface a friendly \"this colour can't be derived from\" error.\n */\nexport const parseColor = (value: string): { rgb: RGBTuple; a: number } => {\n const trimmed = value.trim();\n\n const fn = RGB_FN.exec(trimmed);\n if (fn) {\n return {\n rgb: [Number(fn[1]), Number(fn[2]), Number(fn[3])] as RGBTuple,\n a: fn[4] === undefined ? 1 : roundAlpha(Number(fn[4])),\n };\n }\n\n return { rgb: convertHexToRGB(trimmed), a: 1 };\n};\n\n/**\n * Serialize `[r, g, b]` (+ optional alpha) to the canonical CSS string — always `rgb(r, g, b)`\n * when fully opaque, else `rgba(r, g, b, a)`. The single serialization point for the subsystem, so\n * the Model (and every adapter + `resolveToken`) always carries one canonical `rgb()/rgba()` form\n * per colour — never hex. (`convertRgbToHex` remains for the vendored consumer helper + input parsing.)\n */\nexport const serializeColor = (rgb: RGBTuple, a = 1): string => {\n const alpha = roundAlpha(a);\n const [r, g, b] = rgb;\n const R = clampChannel(Math.round(r));\n const G = clampChannel(Math.round(g));\n const B = clampChannel(Math.round(b));\n return alpha >= 1 ? `rgb(${R}, ${G}, ${B})` : `rgba(${R}, ${G}, ${B}, ${alpha})`;\n};\n\nconst HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n\n/** Whether a string is a 3- or 6-digit `#`-prefixed hex colour. */\nexport const isHexColor = (value: string): boolean => HEX_RE.test(value.trim());\n\n\n// ─── OKLCH synthesis (Björn Ottosson public-domain matrices) ──────────────────────────────────\n// Lightness / hue / chroma adjustments happen here, in a perceptual space, so equal ΔL steps look\n// even and one lightness reads consistently across hues. `L` is carried on a 0–100 scale (matching\n// the authoring dials); OKLab's native `L` is 0–1. sRGB channels are 0–255 only at the boundary.\n\n/** An OKLCH colour — perceptual lightness `L` (0–100), chroma `C` (≥ 0), hue `h` (degrees, 0–360). */\nexport type OKLCH = { L: number; C: number; h: number };\n\nconst DEG = 180 / Math.PI;\nconst GAMUT_EPS = 1e-6;\nconst GAMUT_ITERATIONS = 24;\n\n/** sRGB channel (0–1) → linear-light (0–1). */\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n\n/** Linear-light channel (0–1) → sRGB (0–1). */\nconst linearToSrgb = (c: number): number =>\n c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c;\n\n/** Linear-sRGB `[r, g, b]` (0–1) → OKLab `{ L: 0–1, a, b }`. */\nconst linearRgbToOklab = (\n lr: number,\n lg: number,\n lb: number,\n): { L: number; a: number; b: number } => {\n const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;\n const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;\n const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;\n const l_ = Math.cbrt(l);\n const m_ = Math.cbrt(m);\n const s_ = Math.cbrt(s);\n return {\n L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,\n a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,\n b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,\n };\n};\n\n/** OKLab `{ L: 0–1, a, b }` → linear-sRGB `[r, g, b]` (may fall outside 0–1 when out of gamut). */\nconst oklabToLinearRgb = (L: number, a: number, b: number): [number, number, number] => {\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ];\n};\n\n/** Convert an sRGB `[r, g, b]` (0–255) triple to OKLCH (`L` on a 0–100 scale). */\nexport const rgbToOklch = (rgb: RGBTuple): OKLCH => {\n const [r, g, b] = rgb;\n const lab = linearRgbToOklab(srgbToLinear(r / 255), srgbToLinear(g / 255), srgbToLinear(b / 255));\n const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);\n let h = Math.atan2(lab.b, lab.a) * DEG;\n if (h < 0) h += 360;\n return { L: lab.L * 100, C, h };\n};\n\n/**\n * Convert OKLCH back to an sRGB `[r, g, b]` triple (**unquantized** 0–255 floats). Holds `L` and\n * `h`; when the requested chroma is outside sRGB it binary-searches `C` downward — fixed iterations,\n * no data-dependent early exit, so build- and runtime-computed values agree (§20.8) — until the\n * linear result is in `[0, 1]`. Lightness and hue are never adjusted, so a synthesized ramp keeps\n * its hue and even lightness spacing; only saturation gives way at the gamut boundary. The floats\n * are quantized exactly once, later, by `serializeColor`, so the 0–255 channel clamp never fires.\n */\nexport const oklchToRgb = (color: OKLCH): RGBTuple => {\n const L = clamp(color.L, 0, 100) / 100;\n const hr = color.h / DEG;\n const cosh = Math.cos(hr);\n const sinh = Math.sin(hr);\n\n const linearAt = (c: number): [number, number, number] => oklabToLinearRgb(L, c * cosh, c * sinh);\n const inGamut = (lin: [number, number, number]): boolean =>\n lin.every(v => v >= -GAMUT_EPS && v <= 1 + GAMUT_EPS);\n\n let lin = linearAt(color.C);\n if (!inGamut(lin)) {\n // `C = 0` (a neutral grey at this L) is always in gamut, so `lo` starts from a valid floor.\n let lo = 0;\n let hi = color.C;\n for (let i = 0; i < GAMUT_ITERATIONS; i++) {\n const mid = (lo + hi) / 2;\n if (inGamut(linearAt(mid))) lo = mid;\n else hi = mid;\n }\n lin = linearAt(lo);\n }\n\n return [\n linearToSrgb(clamp(lin[0], 0, 1)) * 255,\n linearToSrgb(clamp(lin[1], 0, 1)) * 255,\n linearToSrgb(clamp(lin[2], 0, 1)) * 255,\n ] as RGBTuple;\n};\n\n/** Run a colour string through OKLCH, apply `transform`, and re-serialize (alpha preserved). */\nconst withOklch = (value: string, transform: (lch: OKLCH) => OKLCH): string => {\n const { rgb, a } = parseColor(value);\n return serializeColor(oklchToRgb(transform(rgbToOklch(rgb))), a);\n};\n\n/** Lighten a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const lighten = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L + delta, 0, 100) }));\n\n/** Darken a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const darken = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L - delta, 0, 100) }));\n\n/** Set a colour's absolute OKLCH lightness to `L` (0–100); hue, chroma, alpha preserved. */\nexport const setL = (value: string, L: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(L, 0, 100) }));\n\n/** Rotate a colour's OKLCH hue by `deg` (signed) around the perceptual wheel; L, C, alpha preserved. */\nexport const rotateHue = (value: string, deg: number): string =>\n withOklch(value, lch => ({ ...lch, h: (((lch.h + deg) % 360) + 360) % 360 }));\n\n/** The perceptual complement — a 180° hue rotation at the same lightness and chroma. */\nexport const complement = (value: string): string => rotateHue(value, 180);\n\n/**\n * An `adjust` dial set — any subset. `l` = **absolute** OKLCH lightness target (0–100, raw L, the\n * opposite direction from a numeric-`steps` label); `c` = chroma **multiplier** (`1` keep, `0` grey,\n * `>1` more saturated where the gamut allows); `h` = **signed** hue rotation in degrees. Numbers\n * only — keeps the vendored math string-free.\n */\nexport type AdjustDials = { l?: number; c?: number; h?: number };\n\n/**\n * One-shot OKLCH placement — set an absolute lightness, scale chroma, and/or rotate hue in a single\n * derivation (each dial optional; an omitted dial leaves that component untouched). Alpha preserved.\n */\nexport const adjust = (value: string, dials: AdjustDials): string =>\n withOklch(value, lch => ({\n L: dials.l === undefined ? lch.L : clamp(dials.l, 0, 100),\n C: dials.c === undefined ? lch.C : Math.max(0, lch.C * dials.c),\n h: dials.h === undefined ? lch.h : (((lch.h + dials.h) % 360) + 360) % 360,\n }));\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string)\n * to hex — 6-digit `#rrggbb` when opaque, 8-digit `#rrggbbaa` when translucent. Used by the DTCG\n * exporter, whose `color` token type is conventionally hex (every other output stays `rgb()`).\n */\nexport const toHexColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const hex = convertRgbToHex(rgb);\n if (a >= 1) return hex;\n const aa = clampChannel(Math.round(a * 255)).toString(16).padStart(2, \"0\");\n return `${hex}${aa}`;\n};\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string) to\n * a CSS Color 4 `oklch(L% C H)` string — `oklch(L% C H / a)` when translucent. `L` is a percentage\n * (0–100), `C` the absolute chroma, `H` in degrees, derived from the stored (quantized) rgb. Used by\n * the CSS adapter's `colorFormat: \"oklch\"` output; the colour is identical to the `rgb()` form.\n */\nexport const toOklchColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const { L, C, h } = rgbToOklch(rgb);\n const body = `${roundTo(L, 2)}% ${roundTo(C, 4)} ${roundTo(h, 2)}`;\n return a >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(a)})`;\n};\n\n/**\n * Set a colour's opacity to `percent` (0–100) — an **absolute** alpha, not a relative fade\n * (`alpha(x, 40)` ⇒ 40% opaque). The rgb channels are untouched; only the alpha channel is\n * replaced. Result serializes to `rgba(…)` (or hex when `percent` is 100).\n */\nexport const alpha = (value: string, percent: number): string => {\n const { rgb } = parseColor(value);\n return serializeColor(rgb, clampPercent(percent) / 100);\n};\n"],"names":["HEX_LENGTHS","Set","clamp","value","min","max","Math","clampChannel","roundTo","dp","f","round","roundAlpha","a","convertHexToRGB","hex","sanitized","replace","has","length","Error","normalized","parseInt","substring","convertRgbToHex","rgb","map","next","toString","join","RGB_FN","parseColor","trimmed","trim","fn","exec","Number","undefined","serializeColor","alpha","r","g","b","R","G","B","HEX_RE","DEG","PI","srgbToLinear","c","pow","linearToSrgb","rgbToOklch","lab","lr","lg","lb","l","m","s","l_","cbrt","m_","s_","L","linearRgbToOklab","C","sqrt","h","atan2","oklchToRgb","color","hr","cosh","cos","sinh","sin","linearAt","oklabToLinearRgb","inGamut","lin","every","v","lo","hi","i","mid","withOklch","transform","rotateHue","deg","lch","dials","percent","clampPercent","delta","test","padStart","body"],"mappings":"aAuBA,MAAMA,EAAc,IAAIC,IAAI,CAAC,EAAG,IAE1BC,EAAQ,CAACC,EAAeC,EAAaC,IAAwBC,KAAKF,IAAIC,EAAKC,KAAKD,IAAID,EAAKD,IACzFI,EAAgBJ,GAA0BD,EAAMC,EAAO,EAAG,KAK1DK,EAAU,CAACL,EAAeM,KAC9B,MAAMC,EAAI,IAAMD,EAChB,OAAOH,KAAKK,MAAMR,EAAQO,GAAKA,GAI3BE,EAAcC,GAAsBP,KAAKK,MAAsB,IAVvBT,EAUkBW,EAVL,EAAG,IAUe,IAGhEC,EAAmBC,IAC9B,MAAMC,EAAYD,EAAIE,QAAQ,cAAe,IAE7C,IAAKjB,EAAYkB,IAAIF,EAAUG,QAI7B,MAAM,IAAIC,MAAM,4BAA4BJ,0BAG9C,MAAMK,EAAkC,IAArBL,EAAUG,OAAeH,EAAUC,QAAQ,OAAQ,QAAUD,EAEhF,MAAO,CACLM,SAASD,EAAWE,UAAU,EAAG,GAAI,IACrCD,SAASD,EAAWE,UAAU,EAAG,GAAI,IACrCD,SAASD,EAAWE,UAAU,EAAG,GAAI,MAK5BC,EAAmBC,IAC9B,GAAmB,IAAfA,EAAIN,OACN,MAAM,IAAIC,MAAM,4CAA4CK,EAAIN,UAUlE,MAAO,IAPKM,EACTC,IAAIvB,IACH,MAAMwB,EAAOpB,EAAaD,KAAKK,MAAMR,IAAQyB,SAAS,IACtD,OAAuB,IAAhBD,EAAKR,OAAe,IAAIQ,IAASA,IAEzCE,KAAK,OAKJC,EAAS,+EAQFC,EAAc5B,IACzB,MAAM6B,EAAU7B,EAAM8B,OAEhBC,EAAKJ,EAAOK,KAAKH,GACvB,OAAIE,EACK,CACLT,IAAK,CAACW,OAAOF,EAAG,IAAKE,OAAOF,EAAG,IAAKE,OAAOF,EAAG,KAC9CrB,OAAawB,IAAVH,EAAG,GAAmB,EAAItB,EAAWwB,OAAOF,EAAG,MAI/C,CAAET,IAAKX,EAAgBkB,GAAUnB,EAAG,IAShCyB,EAAiB,CAACb,EAAeZ,EAAI,KAChD,MAAM0B,EAAQ3B,EAAWC,IAClB2B,EAAGC,EAAGC,GAAKjB,EACZkB,EAAIpC,EAAaD,KAAKK,MAAM6B,IAC5BI,EAAIrC,EAAaD,KAAKK,MAAM8B,IAC5BI,EAAItC,EAAaD,KAAKK,MAAM+B,IAClC,OAAOH,GAAS,EAAI,OAAOI,MAAMC,MAAMC,KAAO,QAAQF,MAAMC,MAAMC,MAAMN,MAGpEO,EAAS,qCAcTC,EAAM,IAAMzC,KAAK0C,GAKjBC,EAAgBC,GACpBA,GAAK,OAAUA,EAAI,MAAQ5C,KAAK6C,KAAKD,EAAI,MAAS,MAAO,KAGrDE,EAAgBF,GACpBA,GAAK,SAAY,MAAQ5C,KAAK6C,IAAID,EAAG,EAAI,KAAO,KAAQ,MAAQA,EAqCrDG,EAAc5B,IACzB,MAAOe,EAAGC,EAAGC,GAAKjB,EACZ6B,EApCiB,EACvBC,EACAC,EACAC,KAEA,MAAMC,EAAI,YAAeH,EAAK,YAAeC,EAAK,YAAeC,EAC3DE,EAAI,YAAeJ,EAAK,YAAeC,EAAK,YAAeC,EAC3DG,EAAI,YAAeL,EAAK,YAAeC,EAAK,YAAeC,EAC3DI,EAAKvD,KAAKwD,KAAKJ,GACfK,EAAKzD,KAAKwD,KAAKH,GACfK,EAAK1D,KAAKwD,KAAKF,GACrB,MAAO,CACLK,EAAG,YAAeJ,EAAK,WAAcE,EAAK,YAAeC,EACzDnD,EAAG,aAAegD,EAAK,YAAcE,EAAK,YAAeC,EACzDtB,EAAG,YAAemB,EAAK,YAAeE,EAAK,WAAcC,IAsB/CE,CAAiBjB,EAAaT,EAAI,KAAMS,EAAaR,EAAI,KAAMQ,EAAaP,EAAI,MACtFyB,EAAI7D,KAAK8D,KAAKd,EAAIzC,EAAIyC,EAAIzC,EAAIyC,EAAIZ,EAAIY,EAAIZ,GAChD,IAAI2B,EAAI/D,KAAKgE,MAAMhB,EAAIZ,EAAGY,EAAIzC,GAAKkC,EAEnC,OADIsB,EAAI,IAAGA,GAAK,KACT,CAAEJ,EAAW,IAARX,EAAIW,EAASE,IAAGE,MAWjBE,EAAcC,IACzB,MAAMP,EAAI/D,EAAMsE,EAAMP,EAAG,EAAG,KAAO,IAC7BQ,EAAKD,EAAMH,EAAItB,EACf2B,EAAOpE,KAAKqE,IAAIF,GAChBG,EAAOtE,KAAKuE,IAAIJ,GAEhBK,EAAY5B,GAtCK,EAACe,EAAWpD,EAAW6B,KAC9C,MAAMmB,EAAKI,EAAI,YAAepD,EAAI,YAAe6B,EAC3CqB,EAAKE,EAAI,YAAepD,EAAI,YAAe6B,EAC3CsB,EAAKC,EAAI,YAAepD,EAAI,YAAc6B,EAC1CgB,EAAIG,EAAKA,EAAKA,EACdF,EAAII,EAAKA,EAAKA,EACdH,EAAII,EAAKA,EAAKA,EACpB,MAAO,CACL,aAAeN,EAAI,aAAeC,EAAI,YAAeC,GACpD,aAAeF,EAAI,aAAeC,EAAI,YAAeC,GACrD,YAAeF,EAAI,YAAeC,EAAI,YAAcC,IA4BGmB,CAAiBd,EAAGf,EAAIwB,EAAMxB,EAAI0B,GACtFI,EAAWC,GACfA,EAAIC,MAAMC,GAAKA,IAAK,MAAcA,GAAK,UAEzC,IAAIF,EAAMH,EAASN,EAAML,GACzB,IAAKa,EAAQC,GAAM,CAEjB,IAAIG,EAAK,EACLC,EAAKb,EAAML,EACf,IAAK,IAAImB,EAAI,EAAGA,EA7EK,GA6EiBA,IAAK,CACzC,MAAMC,GAAOH,EAAKC,GAAM,EACpBL,EAAQF,EAASS,IAAOH,EAAKG,EAC5BF,EAAKE,CACX,CACDN,EAAMH,EAASM,EAChB,CAED,MAAO,CAC+B,IAApChC,EAAalD,EAAM+E,EAAI,GAAI,EAAG,IACM,IAApC7B,EAAalD,EAAM+E,EAAI,GAAI,EAAG,IACM,IAApC7B,EAAalD,EAAM+E,EAAI,GAAI,EAAG,MAK5BO,EAAY,CAACrF,EAAesF,KAChC,MAAMhE,IAAEA,EAAGZ,EAAEA,GAAMkB,EAAW5B,GAC9B,OAAOmC,EAAeiC,EAAWkB,EAAUpC,EAAW5B,KAAQZ,IAgBnD6E,EAAY,CAACvF,EAAewF,IACvCH,EAAUrF,EAAOyF,IAAQ,IAAKA,EAAKvB,IAAMuB,EAAIvB,EAAIsB,GAAO,IAAO,KAAO,sBAiBlD,CAACxF,EAAe0F,IACpCL,EAAUrF,EAAOyF,IAAQ,CACvB3B,OAAe5B,IAAZwD,EAAMnC,EAAkBkC,EAAI3B,EAAI/D,EAAM2F,EAAMnC,EAAG,EAAG,KACrDS,OAAe9B,IAAZwD,EAAM3C,EAAkB0C,EAAIzB,EAAI7D,KAAKD,IAAI,EAAGuF,EAAIzB,EAAI0B,EAAM3C,GAC7DmB,OAAehC,IAAZwD,EAAMxB,EAAkBuB,EAAIvB,IAAOuB,EAAIvB,EAAIwB,EAAMxB,GAAK,IAAO,KAAO,qBAkCtD,CAAClE,EAAe2F,KACnC,MAAMrE,IAAEA,GAAQM,EAAW5B,GAC3B,OAAOmC,EAAeb,EA7QH,CAACqE,GAA4B5F,EAAM4F,EAAS,EAAG,KA6QvCC,CAAaD,GAAW,yBAtD1B3F,GAA0BuF,EAAUvF,EAAO,wEAZhD,CAACA,EAAe6F,IACpCR,EAAUrF,EAAOyF,IAAG,IAAUA,EAAK3B,EAAG/D,EAAM0F,EAAI3B,EAAI+B,EAAO,EAAG,2BArHrC7F,GAA2B2C,EAAOmD,KAAK9F,EAAM8B,wBAgHjD,CAAC9B,EAAe6F,IACrCR,EAAUrF,EAAOyF,IAAG,IAAUA,EAAK3B,EAAG/D,EAAM0F,EAAI3B,EAAI+B,EAAO,EAAG,iIAO5C,CAAC7F,EAAe8D,IAClCuB,EAAUrF,EAAOyF,IAAQ,IAAKA,EAAK3B,EAAG/D,EAAM+D,EAAG,EAAG,2BAiCzB9D,IACzB,MAAMsB,IAAEA,EAAGZ,EAAEA,GAAMkB,EAAW5B,GACxBY,EAAMS,EAAgBC,GAC5B,GAAIZ,GAAK,EAAG,OAAOE,EAEnB,MAAO,GAAGA,IADCR,EAAaD,KAAKK,MAAU,IAAJE,IAAUe,SAAS,IAAIsE,SAAS,EAAG,6BAU3C/F,IAC3B,MAAMsB,IAAEA,EAAGZ,EAAEA,GAAMkB,EAAW5B,IACxB8D,EAAEA,EAACE,EAAEA,EAACE,EAAEA,GAAMhB,EAAW5B,GACzB0E,EAAO,GAAG3F,EAAQyD,EAAG,OAAOzD,EAAQ2D,EAAG,MAAM3D,EAAQ6D,EAAG,KAC9D,OAAOxD,GAAK,EAAI,SAASsF,KAAU,SAASA,OAAUvF,EAAWC"}
@@ -0,0 +1,2 @@
1
+ const t=new Set([3,6]),r=(t,r,n)=>Math.min(n,Math.max(r,t)),n=t=>r(t,0,255),e=(t,r)=>{const n=10**r;return Math.round(t*n)/n},a=t=>Math.round(1e3*r(t,0,1))/1e3,o=r=>{const n=r.replace(/^\s*#|\s*$/g,"");if(!t.has(n.length))throw new Error(`Unsupported hex length: "${n}". Use 3 or 6 digits.`);const e=3===n.length?n.replace(/(.)/g,"$1$1"):n;return[parseInt(e.substring(0,2),16),parseInt(e.substring(2,4),16),parseInt(e.substring(4,6),16)]},h=t=>{if(3!==t.length)throw new Error(`Expected RGB tuple of length 3, received ${t.length}`);return`#${t.map(t=>{const r=n(Math.round(t)).toString(16);return 1===r.length?`0${r}`:r}).join("")}`},s=/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i,u=t=>{const r=t.trim(),n=s.exec(r);return n?{rgb:[Number(n[1]),Number(n[2]),Number(n[3])],a:void 0===n[4]?1:a(Number(n[4]))}:{rgb:o(r),a:1}},c=(t,r=1)=>{const e=a(r),[o,h,s]=t,u=n(Math.round(o)),c=n(Math.round(h)),g=n(Math.round(s));return e>=1?`rgb(${u}, ${c}, ${g})`:`rgba(${u}, ${c}, ${g}, ${e})`},g=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,$=t=>g.test(t.trim()),b=180/Math.PI,i=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),d=t=>t>=.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,M=t=>{const[r,n,e]=t,a=((t,r,n)=>{const e=.4122214708*t+.5363325363*r+.0514459929*n,a=.2119034982*t+.6806995451*r+.1073969566*n,o=.0883024619*t+.2817188376*r+.6299787005*n,h=Math.cbrt(e),s=Math.cbrt(a),u=Math.cbrt(o);return{L:.2104542553*h+.793617785*s-.0040720468*u,a:1.9779984951*h-2.428592205*s+.4505937099*u,b:.0259040371*h+.7827717662*s-.808675766*u}})(i(r/255),i(n/255),i(e/255)),o=Math.sqrt(a.a*a.a+a.b*a.b);let h=Math.atan2(a.b,a.a)*b;return h<0&&(h+=360),{L:100*a.L,C:o,h:h}},l=t=>{const n=r(t.L,0,100)/100,e=t.h/b,a=Math.cos(e),o=Math.sin(e),h=t=>((t,r,n)=>{const e=t+.3963377774*r+.2158037573*n,a=t-.1055613458*r-.0638541728*n,o=t-.0894841775*r-1.291485548*n,h=e*e*e,s=a*a*a,u=o*o*o;return[4.0767416621*h-3.3077115913*s+.2309699292*u,-1.2684380046*h+2.6097574011*s-.3413193965*u,-.0041960863*h-.7034186147*s+1.707614701*u]})(n,t*a,t*o),s=t=>t.every(t=>t>=-1e-6&&t<=1.000001);let u=h(t.C);if(!s(u)){let r=0,n=t.C;for(let t=0;t<24;t++){const t=(r+n)/2;s(h(t))?r=t:n=t}u=h(r)}return[255*d(r(u[0],0,1)),255*d(r(u[1],0,1)),255*d(r(u[2],0,1))]},p=(t,r)=>{const{rgb:n,a:e}=u(t);return c(l(r(M(n))),e)},L=(t,n)=>p(t,t=>({...t,L:r(t.L+n,0,100)})),m=(t,n)=>p(t,t=>({...t,L:r(t.L-n,0,100)})),f=(t,n)=>p(t,t=>({...t,L:r(n,0,100)})),w=(t,r)=>p(t,t=>({...t,h:((t.h+r)%360+360)%360})),C=t=>w(t,180),v=(t,n)=>p(t,t=>({L:void 0===n.l?t.L:r(n.l,0,100),C:void 0===n.c?t.C:Math.max(0,t.C*n.c),h:void 0===n.h?t.h:((t.h+n.h)%360+360)%360})),x=t=>{const{rgb:r,a:e}=u(t),a=h(r);if(e>=1)return a;return`${a}${n(Math.round(255*e)).toString(16).padStart(2,"0")}`},I=t=>{const{rgb:r,a:n}=u(t),{L:o,C:h,h:s}=M(r),c=`${e(o,2)}% ${e(h,4)} ${e(s,2)}`;return n>=1?`oklch(${c})`:`oklch(${c} / ${a(n)})`},N=(t,n)=>{const{rgb:e}=u(t);return c(e,(t=>r(t,0,100))(n)/100)};export{v as adjust,N as alpha,C as complement,o as convertHexToRGB,h as convertRgbToHex,m as darken,$ as isHexColor,L as lighten,l as oklchToRgb,u as parseColor,M as rgbToOklch,w as rotateHue,c as serializeColor,f as setL,x as toHexColor,I as toOklchColor};
2
+ //# sourceMappingURL=color-math.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-math.esm.js","sources":["../src/subsystems/colors/utils.ts"],"sourcesContent":["/**\n * Pure colour math for palette step + variant synthesis.\n *\n * Lightness / hue / chroma work runs in **OKLCH** (a perceptual space) via Björn Ottosson's\n * public-domain matrices — parse → sRGB → linear → OKLab → OKLCH → apply → gamut-map → linear →\n * sRGB → quantize → serialize — so equal lightness steps look even and one lightness reads the same\n * across hues. hex / `rgba()` strings are only touched at the boundary (`parseColor` in,\n * `serializeColor` out). `lighten` / `darken` shift OKLCH lightness by ΔL points; `setL` places an\n * absolute lightness; `rotateHue` / `complement` turn the hue; `alpha` sets opacity. All are\n * string-in / string-out so the same fns run at normalize time (baking the cached value) and later\n * in the derivation registry — AND are vendored to consumers (see `build/vendor.ts`) so a value\n * computed live in the app matches the emitted CSS variable. Kept dependency-free for that vendoring.\n */\n\n/** An `[r, g, b]` channel triple, each `0–255`. */\nexport type RGBTuple = readonly [number, number, number];\n\n/** An `[r, g, b, a]` quad — channels `0–255`, alpha `0–1`. */\nexport type RGBATuple = readonly [number, number, number, number];\n\n/** A colour authored/worked as a tuple — opaque `[r,g,b]` or translucent `[r,g,b,a]`. */\nexport type ColorTuple = RGBTuple | RGBATuple;\n\nconst HEX_LENGTHS = new Set([3, 6]);\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\nconst clampChannel = (value: number): number => clamp(value, 0, 255);\nconst clampAlpha = (value: number): number => clamp(value, 0, 1);\nconst clampPercent = (percent: number): number => clamp(percent, 0, 100);\n\n/** Round to `dp` decimal places (tidy CSS output — `71.8`, not `71.79999`). */\nconst roundTo = (value: number, dp: number): number => {\n const f = 10 ** dp;\n return Math.round(value * f) / f;\n};\n\n/** Round an alpha to 3 decimals so serialized `rgba()` stays tidy (`0.4`, not `0.4000001`). */\nconst roundAlpha = (a: number): number => Math.round(clampAlpha(a) * 1000) / 1000;\n\n/** Parse a 3- or 6-digit hex string (with or without `#`) into an `[r, g, b]` tuple. */\nexport const convertHexToRGB = (hex: string): RGBTuple => {\n const sanitized = hex.replace(/^\\s*#|\\s*$/g, \"\");\n\n if (!HEX_LENGTHS.has(sanitized.length)) {\n // Plain Error on purpose: this module is vendored import-free (build/vendor.ts) so it can be\n // transpiled standalone into consumer bundles — it cannot import RefractError. Its parse errors are\n // re-thrown as REFRACT_E_COLOR_INPUT by colors/normalize.ts, which is the coded authoring surface.\n throw new Error(`Unsupported hex length: \"${sanitized}\". Use 3 or 6 digits.`);\n }\n\n const normalized = sanitized.length === 3 ? sanitized.replace(/(.)/g, \"$1$1\") : sanitized;\n\n return [\n parseInt(normalized.substring(0, 2), 16),\n parseInt(normalized.substring(2, 4), 16),\n parseInt(normalized.substring(4, 6), 16),\n ] as RGBTuple;\n};\n\n/** Serialize an `[r, g, b]` tuple back to a `#rrggbb` string, clamping each channel to `0–255`. */\nexport const convertRgbToHex = (rgb: RGBTuple): string => {\n if (rgb.length !== 3) {\n throw new Error(`Expected RGB tuple of length 3, received ${rgb.length}`);\n }\n\n const hex = rgb\n .map(value => {\n const next = clampChannel(Math.round(value)).toString(16);\n return next.length === 1 ? `0${next}` : next;\n })\n .join(\"\");\n\n return `#${hex}`;\n};\n\nconst RGB_FN = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/i;\n\n/**\n * Parse any canonical colour string this subsystem produces — a 3/6-digit hex **or** an\n * `rgb()` / `rgba()` string — into `{ rgb, a }` (alpha defaults to `1`). This is the boundary\n * gate; a value that isn't parseable rgb (e.g. a bare CSS keyword or `var(--…)`) throws, so\n * callers can surface a friendly \"this colour can't be derived from\" error.\n */\nexport const parseColor = (value: string): { rgb: RGBTuple; a: number } => {\n const trimmed = value.trim();\n\n const fn = RGB_FN.exec(trimmed);\n if (fn) {\n return {\n rgb: [Number(fn[1]), Number(fn[2]), Number(fn[3])] as RGBTuple,\n a: fn[4] === undefined ? 1 : roundAlpha(Number(fn[4])),\n };\n }\n\n return { rgb: convertHexToRGB(trimmed), a: 1 };\n};\n\n/**\n * Serialize `[r, g, b]` (+ optional alpha) to the canonical CSS string — always `rgb(r, g, b)`\n * when fully opaque, else `rgba(r, g, b, a)`. The single serialization point for the subsystem, so\n * the Model (and every adapter + `resolveToken`) always carries one canonical `rgb()/rgba()` form\n * per colour — never hex. (`convertRgbToHex` remains for the vendored consumer helper + input parsing.)\n */\nexport const serializeColor = (rgb: RGBTuple, a = 1): string => {\n const alpha = roundAlpha(a);\n const [r, g, b] = rgb;\n const R = clampChannel(Math.round(r));\n const G = clampChannel(Math.round(g));\n const B = clampChannel(Math.round(b));\n return alpha >= 1 ? `rgb(${R}, ${G}, ${B})` : `rgba(${R}, ${G}, ${B}, ${alpha})`;\n};\n\nconst HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n\n/** Whether a string is a 3- or 6-digit `#`-prefixed hex colour. */\nexport const isHexColor = (value: string): boolean => HEX_RE.test(value.trim());\n\n\n// ─── OKLCH synthesis (Björn Ottosson public-domain matrices) ──────────────────────────────────\n// Lightness / hue / chroma adjustments happen here, in a perceptual space, so equal ΔL steps look\n// even and one lightness reads consistently across hues. `L` is carried on a 0–100 scale (matching\n// the authoring dials); OKLab's native `L` is 0–1. sRGB channels are 0–255 only at the boundary.\n\n/** An OKLCH colour — perceptual lightness `L` (0–100), chroma `C` (≥ 0), hue `h` (degrees, 0–360). */\nexport type OKLCH = { L: number; C: number; h: number };\n\nconst DEG = 180 / Math.PI;\nconst GAMUT_EPS = 1e-6;\nconst GAMUT_ITERATIONS = 24;\n\n/** sRGB channel (0–1) → linear-light (0–1). */\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n\n/** Linear-light channel (0–1) → sRGB (0–1). */\nconst linearToSrgb = (c: number): number =>\n c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c;\n\n/** Linear-sRGB `[r, g, b]` (0–1) → OKLab `{ L: 0–1, a, b }`. */\nconst linearRgbToOklab = (\n lr: number,\n lg: number,\n lb: number,\n): { L: number; a: number; b: number } => {\n const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;\n const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;\n const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;\n const l_ = Math.cbrt(l);\n const m_ = Math.cbrt(m);\n const s_ = Math.cbrt(s);\n return {\n L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,\n a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,\n b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,\n };\n};\n\n/** OKLab `{ L: 0–1, a, b }` → linear-sRGB `[r, g, b]` (may fall outside 0–1 when out of gamut). */\nconst oklabToLinearRgb = (L: number, a: number, b: number): [number, number, number] => {\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ];\n};\n\n/** Convert an sRGB `[r, g, b]` (0–255) triple to OKLCH (`L` on a 0–100 scale). */\nexport const rgbToOklch = (rgb: RGBTuple): OKLCH => {\n const [r, g, b] = rgb;\n const lab = linearRgbToOklab(srgbToLinear(r / 255), srgbToLinear(g / 255), srgbToLinear(b / 255));\n const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);\n let h = Math.atan2(lab.b, lab.a) * DEG;\n if (h < 0) h += 360;\n return { L: lab.L * 100, C, h };\n};\n\n/**\n * Convert OKLCH back to an sRGB `[r, g, b]` triple (**unquantized** 0–255 floats). Holds `L` and\n * `h`; when the requested chroma is outside sRGB it binary-searches `C` downward — fixed iterations,\n * no data-dependent early exit, so build- and runtime-computed values agree (§20.8) — until the\n * linear result is in `[0, 1]`. Lightness and hue are never adjusted, so a synthesized ramp keeps\n * its hue and even lightness spacing; only saturation gives way at the gamut boundary. The floats\n * are quantized exactly once, later, by `serializeColor`, so the 0–255 channel clamp never fires.\n */\nexport const oklchToRgb = (color: OKLCH): RGBTuple => {\n const L = clamp(color.L, 0, 100) / 100;\n const hr = color.h / DEG;\n const cosh = Math.cos(hr);\n const sinh = Math.sin(hr);\n\n const linearAt = (c: number): [number, number, number] => oklabToLinearRgb(L, c * cosh, c * sinh);\n const inGamut = (lin: [number, number, number]): boolean =>\n lin.every(v => v >= -GAMUT_EPS && v <= 1 + GAMUT_EPS);\n\n let lin = linearAt(color.C);\n if (!inGamut(lin)) {\n // `C = 0` (a neutral grey at this L) is always in gamut, so `lo` starts from a valid floor.\n let lo = 0;\n let hi = color.C;\n for (let i = 0; i < GAMUT_ITERATIONS; i++) {\n const mid = (lo + hi) / 2;\n if (inGamut(linearAt(mid))) lo = mid;\n else hi = mid;\n }\n lin = linearAt(lo);\n }\n\n return [\n linearToSrgb(clamp(lin[0], 0, 1)) * 255,\n linearToSrgb(clamp(lin[1], 0, 1)) * 255,\n linearToSrgb(clamp(lin[2], 0, 1)) * 255,\n ] as RGBTuple;\n};\n\n/** Run a colour string through OKLCH, apply `transform`, and re-serialize (alpha preserved). */\nconst withOklch = (value: string, transform: (lch: OKLCH) => OKLCH): string => {\n const { rgb, a } = parseColor(value);\n return serializeColor(oklchToRgb(transform(rgbToOklch(rgb))), a);\n};\n\n/** Lighten a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const lighten = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L + delta, 0, 100) }));\n\n/** Darken a colour by `delta` OKLCH lightness points (0–100 scale); hue, chroma, alpha preserved. */\nexport const darken = (value: string, delta: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(lch.L - delta, 0, 100) }));\n\n/** Set a colour's absolute OKLCH lightness to `L` (0–100); hue, chroma, alpha preserved. */\nexport const setL = (value: string, L: number): string =>\n withOklch(value, lch => ({ ...lch, L: clamp(L, 0, 100) }));\n\n/** Rotate a colour's OKLCH hue by `deg` (signed) around the perceptual wheel; L, C, alpha preserved. */\nexport const rotateHue = (value: string, deg: number): string =>\n withOklch(value, lch => ({ ...lch, h: (((lch.h + deg) % 360) + 360) % 360 }));\n\n/** The perceptual complement — a 180° hue rotation at the same lightness and chroma. */\nexport const complement = (value: string): string => rotateHue(value, 180);\n\n/**\n * An `adjust` dial set — any subset. `l` = **absolute** OKLCH lightness target (0–100, raw L, the\n * opposite direction from a numeric-`steps` label); `c` = chroma **multiplier** (`1` keep, `0` grey,\n * `>1` more saturated where the gamut allows); `h` = **signed** hue rotation in degrees. Numbers\n * only — keeps the vendored math string-free.\n */\nexport type AdjustDials = { l?: number; c?: number; h?: number };\n\n/**\n * One-shot OKLCH placement — set an absolute lightness, scale chroma, and/or rotate hue in a single\n * derivation (each dial optional; an omitted dial leaves that component untouched). Alpha preserved.\n */\nexport const adjust = (value: string, dials: AdjustDials): string =>\n withOklch(value, lch => ({\n L: dials.l === undefined ? lch.L : clamp(dials.l, 0, 100),\n C: dials.c === undefined ? lch.C : Math.max(0, lch.C * dials.c),\n h: dials.h === undefined ? lch.h : (((lch.h + dials.h) % 360) + 360) % 360,\n }));\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string)\n * to hex — 6-digit `#rrggbb` when opaque, 8-digit `#rrggbbaa` when translucent. Used by the DTCG\n * exporter, whose `color` token type is conventionally hex (every other output stays `rgb()`).\n */\nexport const toHexColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const hex = convertRgbToHex(rgb);\n if (a >= 1) return hex;\n const aa = clampChannel(Math.round(a * 255)).toString(16).padStart(2, \"0\");\n return `${hex}${aa}`;\n};\n\n/**\n * Convert any canonical colour string (the subsystem's `rgb()` / `rgba()` form, or a hex string) to\n * a CSS Color 4 `oklch(L% C H)` string — `oklch(L% C H / a)` when translucent. `L` is a percentage\n * (0–100), `C` the absolute chroma, `H` in degrees, derived from the stored (quantized) rgb. Used by\n * the CSS adapter's `colorFormat: \"oklch\"` output; the colour is identical to the `rgb()` form.\n */\nexport const toOklchColor = (value: string): string => {\n const { rgb, a } = parseColor(value);\n const { L, C, h } = rgbToOklch(rgb);\n const body = `${roundTo(L, 2)}% ${roundTo(C, 4)} ${roundTo(h, 2)}`;\n return a >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(a)})`;\n};\n\n/**\n * Set a colour's opacity to `percent` (0–100) — an **absolute** alpha, not a relative fade\n * (`alpha(x, 40)` ⇒ 40% opaque). The rgb channels are untouched; only the alpha channel is\n * replaced. Result serializes to `rgba(…)` (or hex when `percent` is 100).\n */\nexport const alpha = (value: string, percent: number): string => {\n const { rgb } = parseColor(value);\n return serializeColor(rgb, clampPercent(percent) / 100);\n};\n"],"names":["HEX_LENGTHS","Set","clamp","value","min","max","Math","clampChannel","roundTo","dp","f","round","roundAlpha","a","convertHexToRGB","hex","sanitized","replace","has","length","Error","normalized","parseInt","substring","convertRgbToHex","rgb","map","next","toString","join","RGB_FN","parseColor","trimmed","trim","fn","exec","Number","undefined","serializeColor","alpha","r","g","b","R","G","B","HEX_RE","isHexColor","test","DEG","PI","srgbToLinear","c","pow","linearToSrgb","rgbToOklch","lab","lr","lg","lb","l","m","s","l_","cbrt","m_","s_","L","linearRgbToOklab","C","sqrt","h","atan2","oklchToRgb","color","hr","cosh","cos","sinh","sin","linearAt","oklabToLinearRgb","inGamut","lin","every","v","lo","hi","i","mid","withOklch","transform","lighten","delta","lch","darken","setL","rotateHue","deg","complement","adjust","dials","toHexColor","padStart","toOklchColor","body","percent","clampPercent"],"mappings":"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"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * The adapter contract — the ONE interface `src/next/` core commits to.
3
+ *
4
+ * An adapter turns the format-neutral `ThemeModel` into some output format (CSS,
5
+ * SCSS, styled-components, JSON, React-Native). Core builds and enumerates the
6
+ * Model; it knows no output format — the adapter does. Core imports only this
7
+ * interface, never a concrete adapter.
8
+ *
9
+ * Two-level shape:
10
+ * ThemeAdapter identity (`name`/`version`) + `bind(model, ctx)`
11
+ * BoundAdapter the render surface, with `model`/`ctx` already bound so the
12
+ * methods read cleanly: `renderRecipe(subsystem, group, variant)`.
13
+ *
14
+ * `createTheme` builds the Model, calls `bind(model, ctx)` ONCE, and curries the
15
+ * bound surface onto the public theme (`theme.renderRecipe(group, variant)`, …).
16
+ *
17
+ * @typeParam TUnit the output unit. CSS/SCSS → `string`; JSON → a doc fragment;
18
+ * React-Native → a style object. Only `join` + the render methods vary by it;
19
+ * `recipeName` is always `string` (identity is format-independent).
20
+ */
21
+ import type { ThemeModel, Literal } from "./model";
22
+ import type { MediaDescriptor, ContainerDescriptors } from "./media";
23
+ /**
24
+ * Build-time emit output — self-contained files an adapter writes to disk.
25
+ *
26
+ * `files` are the static artifacts a downstream project consumes directly (a CSS stylesheet, a
27
+ * theme module, …). `vendorHelpers` are **live** helper modules the adapter vendors for the app's
28
+ * runtime needs (color-math for adjusting a colour live, an SC media module, …); each MUST be
29
+ * self-contained ES-module source — zero refract import — so the app keeps working after it drops
30
+ * refract at runtime. The build layer (`emitTheme`) writes both to disk.
31
+ */
32
+ export interface EmitOutput {
33
+ /** Static consume-as-is artifacts, keyed by filename (`"theme.css"`). */
34
+ files: Record<string, string>;
35
+ /** Self-contained live helper modules, keyed by filename (`"color-math.js"`, `"media.ts"`). */
36
+ vendorHelpers?: Record<string, string>;
37
+ }
38
+ /**
39
+ * `emit` — the per-target directive for HOW a theme is written to disk (§9). Core owns only this
40
+ * **vocabulary** (so config authors get autocomplete) + light normalization (see `resolveEmitPlan`
41
+ * in `src/build/emit.ts`); each ADAPTER decides which modes it honors and how it realizes them.
42
+ * An adapter that doesn't recognize a mode throws a clear error rather than mis-emitting.
43
+ *
44
+ * The four intents:
45
+ * - `single` — one file (default `theme.css`); `file` renames it.
46
+ * - `split` — two files: rules (`file`, default `styles.css`) + variables (`variables`,
47
+ * default `variables.css`). Load-order contract, no `@import`.
48
+ * - `subsystem` — a styles+variables pair per subsystem (`filename(subsystem, kind)` names both).
49
+ * - `components` — the merged/flattened per-component export (`inline` defaults true).
50
+ *
51
+ * Discriminator inference (only `single`/`split` may omit `type`): `undefined`/`{}`/`{ file }` →
52
+ * single; presence of the `variables` key → split. `subsystem`/`components` always need `type` (or
53
+ * the string form).
54
+ *
55
+ * ---
56
+ * **CSS adapter semantics** (the reference realization — the only adapter shipping `emit` today; SC
57
+ * and future adapters may honor a subset or map the intent onto their own shape):
58
+ *
59
+ * - **single** — one stylesheet: all subsystems' `:root` variables + every rule, in one file.
60
+ * The global adapter `inline` option bakes resolved values instead of `var(--…)` whole-file.
61
+ *
62
+ * - **split / subsystem** — every multi-file mode obeys a **load-order contract, NO `@import`**: the
63
+ * styles file(s) reference vars by name and assume the variables file loads first. Responsive
64
+ * `:root` var overrides route to the variables side, responsive rule overrides to the styles side.
65
+ * The global `inline` option is **rejected** here (baking values leaves no variables to split out —
66
+ * the adapter throws). In `subsystem` mode the `components` subsystem owns no properties, so it
67
+ * emits **styles only** (no `components.variables.css`), and that file is the normal option-C
68
+ * composition (delta classes referencing sibling-subsystem classes) — NOT flattened.
69
+ *
70
+ * - **components** — the ONLY mode that flattens each component variant into one self-contained
71
+ * rule-set (its referenced recipes' declarations + own `css` delta, delta winning on conflict).
72
+ * `inline` **defaults true** → baked literal values, zero `var(`, fully portable/dependency-free.
73
+ * `inline: false` → the same rules with `var(--…)` refs PLUS a **tree-shaken** variables file
74
+ * containing ONLY the tokens the exported components reference. `variables` names that file
75
+ * (default `variables.css`); `variables: false` **suppresses** it (the consumer supplies the vars).
76
+ * **File assignment = the `filename` return value** — variants returning the same name are
77
+ * concatenated into that file (default `` `${group}-${variant}.css` `` = one file per variant;
78
+ * `() => "components.css"` collapses all; a per-group fn bundles a group while others stay split).
79
+ *
80
+ * NOTE (docs location): post-cutover author docs (AGENT / examples) were removed in the clean-room
81
+ * rebuild and not yet re-authored, so this TSDoc + the `tmp-build-example/theme.config.ts` comments
82
+ * are the current author-facing reference for `emit`.
83
+ */
84
+ export type Emit = undefined | "single" | "split" | "subsystem" | "components" | {
85
+ type?: "single";
86
+ file?: string;
87
+ } | {
88
+ type?: "split";
89
+ file?: string;
90
+ variables?: string;
91
+ } | {
92
+ type: "subsystem";
93
+ filename?: (subsystem: string, kind: "styles" | "variables") => string;
94
+ } | {
95
+ type: "components";
96
+ inline?: boolean;
97
+ filename?: (c: {
98
+ group: string;
99
+ variant: string;
100
+ }) => string;
101
+ variables?: string | false;
102
+ };
103
+ /**
104
+ * The normalized, discriminated form of {@link Emit} — every default filled, `type` always present.
105
+ * `resolveEmitPlan` (build layer) produces it; an adapter's `emit(plan)` switches on `plan.type`.
106
+ */
107
+ export type NormalizedEmit = {
108
+ type: "single";
109
+ file: string;
110
+ } | {
111
+ type: "split";
112
+ file: string;
113
+ variables: string;
114
+ } | {
115
+ type: "subsystem";
116
+ filename: (subsystem: string, kind: "styles" | "variables") => string;
117
+ } | {
118
+ type: "components";
119
+ inline: boolean;
120
+ filename: (c: {
121
+ group: string;
122
+ variant: string;
123
+ }) => string;
124
+ variables: string | false;
125
+ };
126
+ /** One recipe's real identity in a format — a CSS class, an SC export, a JSON key. */
127
+ export interface UsageRecipe {
128
+ readonly subsystem: string;
129
+ readonly group: string;
130
+ readonly variant: string;
131
+ /** The name a downstream consumer actually uses (`recipeName` for this rule-set). */
132
+ readonly name: string;
133
+ }
134
+ /**
135
+ * How to consume an adapter's built output — the source the build-time self-documenting guide
136
+ * (`llms.txt` + `manifest.json`) is rendered from. The adapter that produced the format is the
137
+ * thing that knows its consumption shape, so it describes it here from its REAL naming surface;
138
+ * `defineAdapter` supplies a generic default (recipe identities via `recipeName`) for adapters that
139
+ * don't override it.
140
+ */
141
+ export interface UsageDescriptor {
142
+ /** The output format id (the adapter's `name`). */
143
+ readonly format: string;
144
+ /** Prose lines describing how to consume the output — the `llms.txt` narrative. Relative-path first. */
145
+ readonly summary: readonly string[];
146
+ /** The real recipe identities in this format, so a downstream agent never guesses a name. */
147
+ readonly recipes: readonly UsageRecipe[];
148
+ }
149
+ /** The Model-derived context bound alongside the Model. */
150
+ export interface RenderContext<TBreakpoint extends string = string> {
151
+ /** Core-built media descriptor (breakpoints → `@media` builder) for responsive rules. */
152
+ readonly media: MediaDescriptor<TBreakpoint>;
153
+ /** Per-named-container descriptors (§10.5) — `name → @container builder`. Empty when no `containers`. */
154
+ readonly containers: ContainerDescriptors;
155
+ /** Resolve a token path to its concrete literal — for inline / value-mode output. */
156
+ readonly resolve: (path: string) => Literal;
157
+ }
158
+ /**
159
+ * The render surface an author's `bind` returns: the required primitives, plus
160
+ * optional overrides / lifecycle. All methods are already bound to `model`/`ctx`,
161
+ * so none of them take those. `defineAdapter` fills in the defaulted aggregators.
162
+ */
163
+ export interface BoundSpec<TUnit = string> {
164
+ /** A recipe's identity in this format (CSS class, SCSS mixin, RN key, …). Always a string. */
165
+ recipeName(subsystem: string, group: string, variant: string): string;
166
+ /** Render ONE rule-set: base declarations + all its state/responsive rules. */
167
+ renderRecipe(subsystem: string, group: string, variant: string): TUnit;
168
+ /** Render ONE subsystem's tokens (its `:root` variables in CSS). */
169
+ renderVariables(subsystem: string): TUnit;
170
+ /** Combine units — the one primitive the defaulted aggregators need.
171
+ * CSS/SCSS: `parts.join("\n\n")`; JSON: merge fragments; RN: merge objects. */
172
+ join(parts: TUnit[]): TUnit;
173
+ /** Render ONE variable by token path, e.g. `"colors.primary"` → `"--dt-color--primary: #4dabf7;"`.
174
+ * The finer-grained sibling of `renderVariables`; feeds the core `renderRecipeStandalone`
175
+ * helper. Optional until lazy delivery lands. (*Which* paths a recipe needs is core
176
+ * knowledge — a Model walk — not an adapter concern.) */
177
+ renderToken?(path: string): TUnit;
178
+ renderAllRecipes?(): TUnit;
179
+ renderAllVariables?(): TUnit;
180
+ renderAll?(): TUnit;
181
+ /** Attach runtime-only helpers to the theme root (the `theme.media` pattern). */
182
+ extend?(theme: Record<string, unknown>): Record<string, unknown>;
183
+ /**
184
+ * Describe how to consume this output — feeds the build-time self-documenting guide (`llms.txt` +
185
+ * `manifest.json`) so a published/zipped theme travels with instructions for a downstream agent
186
+ * that has neither refract nor its skills. Optional: `defineAdapter` supplies a generic default
187
+ * (recipe identities via `recipeName`); an adapter overrides it to add format-specific import prose. */
188
+ describeUsage?(): UsageDescriptor;
189
+ /**
190
+ * Build-time emit: self-contained files (adapter vendors its own runtime helpers). `plan` is the
191
+ * normalized output-shape directive (§9); omitted ⇒ `single`/`theme.css` (back-compat). The
192
+ * adapter switches on `plan.type` and throws a clear error for modes it doesn't support. */
193
+ emit?(plan?: NormalizedEmit): EmitOutput;
194
+ }
195
+ /**
196
+ * The full bound render surface — a `BoundSpec` with the DEFAULTED aggregators
197
+ * guaranteed present (supplied by `defineAdapter`). This is what `createTheme`
198
+ * curries onto the public theme.
199
+ */
200
+ export interface BoundAdapter<TUnit = string> extends BoundSpec<TUnit> {
201
+ /** All rule-sets combined (default: `join` over every `renderRecipe`). */
202
+ renderAllRecipes(): TUnit;
203
+ /** All subsystems' variables combined (default: `join` over every `renderVariables`). */
204
+ renderAllVariables(): TUnit;
205
+ /** The full output (default: `join([renderAllVariables, renderAllRecipes])`). */
206
+ renderAll(): TUnit;
207
+ /** How to consume the output (default: format + recipe identities from `recipeName`). */
208
+ describeUsage(): UsageDescriptor;
209
+ }
210
+ /** What an adapter AUTHOR writes: identity + a `bind` that returns the render surface. */
211
+ export interface AdapterSpec<TUnit = string> {
212
+ /** Stable adapter id, e.g. `"css"`, `"scss"`, `"styled-components"`, `"json"`. */
213
+ readonly name: string;
214
+ /** Contract version external packages pin to. */
215
+ readonly version: number;
216
+ /**
217
+ * The states this adapter can render (`["hover", "disabled", …]`) — the validation set for recipe
218
+ * `state:` refs. **The adapter owns state knowledge** (CSS knows `:hover`/`[disabled]`; an inline
219
+ * or JSON adapter may know none). Surfaced at the adapter level (not `bind`) because recipe
220
+ * normalization runs *before* `bind`; core threads it into `normalizeRecipeGroup` generically,
221
+ * with no CSS import. Absent ⇒ no state validation (any state name accepted).
222
+ */
223
+ readonly allowedStates?: ReadonlyArray<string>;
224
+ /** Bind the Model + context once; return the render primitives. */
225
+ bind(model: ThemeModel, ctx: RenderContext): BoundSpec<TUnit>;
226
+ }
227
+ /** The full adapter core/`createTheme` consumes: `bind` returns a complete `BoundAdapter`. */
228
+ export interface ThemeAdapter<TUnit = string> {
229
+ readonly name: string;
230
+ readonly version: number;
231
+ /** The adapter's known-state set (see {@link AdapterSpec.allowedStates}); core validates against it. */
232
+ readonly allowedStates?: ReadonlyArray<string>;
233
+ bind(model: ThemeModel, ctx: RenderContext): BoundAdapter<TUnit>;
234
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The clean-room `createTheme` — the public entry point.
3
+ *
4
+ * A **walking skeleton** (Step 0d): a live spine that grows one seam per step, so every
5
+ * later step is reviewed through the real entry point rather than isolated unit tests.
6
+ * The control flow is fixed; coverage grows by pushing a {@link Subsystem} onto `SUBSYSTEMS`
7
+ * or filling a hook — never by editing the spine.
8
+ *
9
+ * `adapter` is a **required** option (no `createCssAdapter()` default), which is what lets
10
+ * this module stay in core importing nothing but the `ThemeAdapter` interface.
11
+ *
12
+ * Flow: read breakpoints → build the media descriptor → normalize each subsystem's slice
13
+ * into a per-subsystem Model input (`buildSubsystemInput`) → `buildThemeModel` → assemble the
14
+ * theme surface (`assembleTheme`: token map + `resolveToken` + `adapter.bind` + `extend`).
15
+ *
16
+ * `theme.override(partial)` (Step 5) is a **delta merge**, not a re-run: it normalizes ONLY the
17
+ * partial's changed subsystem slices (through the very same `buildSubsystemInput`), immutably
18
+ * merges those Model fragments into the current Model at property / rule-set-group granularity,
19
+ * and re-assembles onto the **new** Model. The parent Model (and its bound outputs) are untouched
20
+ * — real child themes. There is no raw retention: the Model is the only held state, and the
21
+ * reference-resolution context recipe interpretation needs is reconstructed from it
22
+ * (`reconstructNormalizedProperties`). Synthesized steps are derived refs, so overriding a base
23
+ * re-derives its steps for free; the merge code itself carries no subsystem-specific logic.
24
+ */
25
+ import type { ThemeAdapter } from "./ThemeAdapter";
26
+ import type { Literal, Ref, ThemeModel, PropertyModel, RuleSetGroup, Keyframe, ContainerModel } from "./model";
27
+ import { type UnitsConfig } from "./units";
28
+ import type { RawTheme } from "./rawTheme";
29
+ import { type MediaConfig } from "./media";
30
+ export interface CreateThemeOptions<TAdapter extends ThemeAdapter = ThemeAdapter> {
31
+ /** Required — the format target. Core ships no default adapter. */
32
+ readonly adapter: TAdapter;
33
+ /**
34
+ * Media-query output config (§10.5) — `{ unit: "px" | "em" | "rem"; baseFontSize }`. Breakpoint and
35
+ * container thresholds are authored as px numbers; `unit` controls the emitted unit (em/rem = value ÷
36
+ * baseFontSize). Defaults to px. Stable across `override()` children.
37
+ */
38
+ readonly media?: MediaConfig;
39
+ /**
40
+ * Declaration-value length units (§21) — a token-path-prefix role map. `units.default` (global),
41
+ * `units["<subsystem>"]`, `units["<subsystem>.<property>"]`; most-specific wins, over a built-in seed
42
+ * (length subsystems → px, `lineHeight` → none, `letterSpacing` → em). A bare authored number is
43
+ * deferred (resolved here); an explicit unit (`"1.5rem"`) is pinned. Unit intent is a theme fact, so it
44
+ * lives here (not on the adapter): every adapter receives a Model with fully-resolved `{ value, unit }`.
45
+ */
46
+ readonly units?: UnitsConfig;
47
+ /** Divisor for a deferred length resolving to `rem` (§21). Defaults to 16. */
48
+ readonly baseFontSize?: number;
49
+ /**
50
+ * DTCG round-trip (§12 Phase 2) — prebuilt property Model to splice in **after** property build,
51
+ * keyed `subsystem → property`. Carries the lossless bits the resolved DTCG token surface can't
52
+ * (appearance `modes` / `responsive` / derivation refs / `external`); a property present here
53
+ * REPLACES the one the standard tokens rebuilt (property-level merge). `fromDTCGTheme` sets it from
54
+ * the `com.theme-registry.refract` extension. Absent → the standard build is untouched.
55
+ */
56
+ readonly propertiesOverlay?: Record<string, Record<string, PropertyModel>>;
57
+ /**
58
+ * DTCG round-trip — prebuilt, already-interpreted rule-set IR to splice into the Model **after**
59
+ * property build, keyed `subsystem → group → variant`. Bypasses authoring/normalization (the IR is
60
+ * a built Model slice, lengths already resolved), then flows through the normal `{ ref }` validation.
61
+ * `fromDTCGTheme` sets this to restore recipes a DTCG document carried in its
62
+ * `com.theme-registry.refract` extension. Rarely set by hand. Absent → the standard build is untouched.
63
+ */
64
+ readonly ruleSetsOverlay?: Record<string, Record<string, RuleSetGroup>>;
65
+ /** DTCG round-trip — prebuilt keyframes to splice in, keyed `subsystem → name`. See {@link ruleSetsOverlay}. */
66
+ readonly keyframesOverlay?: Record<string, Record<string, Keyframe>>;
67
+ /** DTCG round-trip — prebuilt query containers to splice into the Model root. See {@link ruleSetsOverlay}. */
68
+ readonly containersOverlay?: Record<string, ContainerModel>;
69
+ }
70
+ /** The public theme surface. Grows: `css`/recipe helpers (step 1). */
71
+ export interface Theme {
72
+ /** The format-neutral held state — the single source of truth. */
73
+ readonly model: ThemeModel;
74
+ /**
75
+ * Flat, lazy, cached `path -> Ref` view of the Model's property tokens
76
+ * (`"<subsystem>.<property>[.<variant|extra>]"`). Aliases / derived steps stay as refs
77
+ * (`{ ref }` / `{ ref, fn, arg }`), so the map is override-safe. Rule-sets are excluded.
78
+ */
79
+ readonly tokens: Record<string, Ref>;
80
+ /**
81
+ * Resolve one token path to its concrete literal — following aliases and running derivations
82
+ * (`lighten` / `darken` / …) through the subsystem derivation registry. Throws on unknown paths.
83
+ */
84
+ readonly resolveToken: (path: string) => Literal;
85
+ /**
86
+ * Derive a **child** theme by immutably merging `partial` (a partial raw theme) into this theme's
87
+ * Model. Only the partial's subsystem slices are re-normalized; the merge is a property /
88
+ * rule-set-variant replace. The parent theme (Model, tokens, css) is untouched — call it again to
89
+ * derive siblings. Overriding a colour base re-derives its synthesized steps automatically.
90
+ *
91
+ * Accepts a (partial) {@link RawTheme} — every key is already optional, so a delta touching one
92
+ * subsystem slice is a valid `RawTheme`.
93
+ */
94
+ readonly override: (partial: RawTheme) => Theme;
95
+ }
96
+ export declare function createTheme(rawTheme: RawTheme, options: CreateThemeOptions): Theme;
@@ -0,0 +1,8 @@
1
+ /** Known CSS property names (kebab-case). Membership test drives {@link isKnownCssProperty}. */
2
+ export declare const KNOWN_CSS_PROPERTIES: ReadonlySet<string>;
3
+ /**
4
+ * Is `name` a real CSS property? Accepts camelCase or kebab-case (`borderColor` / `border-color`),
5
+ * custom properties (`--brand`), and vendor-prefixed names (`-webkit-mask`). Everything else — a
6
+ * reserved recipe key that leaked, or a typo — is rejected so the caller can fail loud.
7
+ */
8
+ export declare const isKnownCssProperty: (name: string) => boolean;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `defineAdapter` — turns an `AdapterSpec` (identity + a `bind` returning the four
3
+ * required primitives) into a full `ThemeAdapter` whose `bind` yields a complete
4
+ * `BoundAdapter` with the DEFAULTED aggregators supplied.
5
+ *
6
+ * The defaults are pure Model walks: the Model enumerates every subsystem and every
7
+ * (group, variant), so core loops it, calls the author's per-unit renderers, and
8
+ * `join`s the results. Model/ctx are already bound, so the aggregators take no args.
9
+ * An author can still override any aggregator (a format whose full document isn't a
10
+ * flat concatenation).
11
+ */
12
+ import type { AdapterSpec, ThemeAdapter } from "./ThemeAdapter";
13
+ /**
14
+ * Complete an `AdapterSpec` into a `ThemeAdapter`.
15
+ *
16
+ * Wraps the author's `bind` so the returned `BoundAdapter` gains the defaulted
17
+ * aggregators (`renderAllRecipes` / `renderAllVariables` / `renderAll`) built from
18
+ * the required primitives — unless the spec already provides its own override.
19
+ * `name` / `version` pass through unchanged.
20
+ *
21
+ * @param spec identity (`name`/`version`) + a `bind(model, ctx)` returning the
22
+ * required render primitives (and any optional overrides).
23
+ * @returns a full adapter whose `bind(model, ctx)` yields a complete `BoundAdapter`.
24
+ */
25
+ export declare function defineAdapter<TUnit>(spec: AdapterSpec<TUnit>): ThemeAdapter<TUnit>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * dec.4 — cross-property derivation resolve pass.
3
+ *
4
+ * A variant / mode value may derive from ANOTHER property (`surface.modes.dark =
5
+ * { ref: "colors.brand", modifiers: [{ adjust: { l: 12 } }] }`). Colours bakes derivations one
6
+ * property at a time, so a cross-property source isn't available at bake time — the owning subsystem
7
+ * emits such a derivation **unbaked** (a `Ref` carrying `ref` + `modifiers`/`fn` but no cached
8
+ * `value`). This post-build pass runs once the full `path -> Ref` token map exists and fills each
9
+ * cross-property derived `Ref`'s `.value` by resolving its source and folding its chain.
10
+ *
11
+ * - **Variants** are addressable tokens, so their source resolves through `resolveToken` (which itself
12
+ * follows the ref + folds modifiers across the whole map — chains of cross-property derivations
13
+ * resolve for free, no ordering needed).
14
+ * - **Modes are NOT tokens** (they're emit-only), so a mode's source is resolved via `resolveToken`
15
+ * on its `ref` and the chain folded here by hand, rather than through a mode token that doesn't exist.
16
+ *
17
+ * The pass is **structural, not value-based**: it re-bakes any derived Ref whose source property
18
+ * differs from the owner, regardless of whether a value is already present. That makes it correct on
19
+ * `override()` too — re-running it against the merged token map re-derives a child's cross-property
20
+ * values when the source changed, exactly like intra-property derivations. It rebuilds immutably
21
+ * (new `Ref`s only where it re-bakes; every untouched branch keeps its reference), so a shared parent
22
+ * Model is never mutated, and a Model with no cross-property derivation is returned by reference
23
+ * (goldens byte-identical).
24
+ */
25
+ import type { ThemeModel } from "../model";
26
+ import { type DerivationRegistry } from "./resolveTokens";
27
+ /**
28
+ * Fill every cross-property derived variant / mode Ref's `.value` against the Model's token map.
29
+ * Immutable — returns the Model by reference when nothing is cross-property (byte-identical output).
30
+ */
31
+ export declare const bakeCrossPropertyDerivations: (model: ThemeModel, registry: DerivationRegistry) => ThemeModel;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Derivation layer for the clean-room tree (Step 0e).
3
+ *
4
+ * The cycle-safe derivation resolver (`resolveToken` / `resolveTokenMap`) + the registry
5
+ * merge (`buildDerivationRegistry`). Ported from `core/common/resolveTokens.ts`; pure
6
+ * Model/Ref data → literals, no CSS. Not yet wired into `theme.tokens` / `theme.resolveToken`
7
+ * — that's Step 0f.
8
+ */
9
+ export type { DerivationFn, DerivationRegistry } from "./resolveTokens";
10
+ export { buildDerivationRegistry, resolveToken, resolveTokenMap } from "./resolveTokens";
11
+ export { bakeCrossPropertyDerivations } from "./crossProperty";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Derivation resolver (model-first).
3
+ *
4
+ * Resolves a `path -> Ref` token map to concrete literals, running **value derivations** — a
5
+ * `Ref` of the form `{ ref, fn, arg }` means `value = registry[fn](resolve(ref), arg)`. Plain
6
+ * `{ ref }` is an alias (`= resolve(ref)`), `{ value }` is a terminal literal. Recursive (so
7
+ * chained derivations like `darker = darken(dark)` resolve), memoized per pass, and cycle-safe.
8
+ *
9
+ * Subsystems contribute derivation fns (colors → `lighten`/`darken`, typography → `scale`);
10
+ * core assembles them into one {@link DerivationRegistry}. Used by `theme.tokens` (resolved
11
+ * view) and by the CSS adapter's lowering (concrete `:root` / inline values). See
12
+ * `.notes/reference/derivation-resolver-and-merge.md`.
13
+ */
14
+ import type { Literal, Ref } from "../model";
15
+ /** A value-derivation: derive a literal from a source literal + optional argument. */
16
+ export type DerivationFn = (value: Literal, arg?: unknown) => Literal;
17
+ /** `fn name -> DerivationFn`, assembled from the subsystems (unique names; collisions throw). */
18
+ export type DerivationRegistry = Record<string, DerivationFn>;
19
+ /** Merge subsystem derivation-fn maps into one registry; a duplicate name is a hard error. */
20
+ export declare const buildDerivationRegistry: (contributions: ReadonlyArray<Readonly<DerivationRegistry>>) => DerivationRegistry;
21
+ /**
22
+ * Resolve one token `path` in `map` to a concrete literal, running any derivation. `cache` and
23
+ * `resolving` are threaded through recursion (chaining + cycle detection); callers may omit them.
24
+ */
25
+ export declare const resolveToken: (map: Record<string, Ref>, registry: DerivationRegistry, path: string, cache?: Map<string, Literal>, resolving?: Set<string>) => Literal;
26
+ /** Resolve every entry in a token `map` to its concrete literal (one shared memo pass). */
27
+ export declare const resolveTokenMap: (map: Record<string, Ref>, registry: DerivationRegistry) => Record<string, Literal>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /
3
+ * build problem — everything reachable from {@link createTheme} (subsystem normalization, token
4
+ * derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:
5
+ * a stable identifier an agent (or a catch block) can branch on without string-matching the human
6
+ * message. The message text stays human-first and unchanged; the code is additive.
7
+ *
8
+ * Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing
9
+ * file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a
10
+ * problem with the theme an agent is editing.
11
+ *
12
+ * Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one
13
+ * without pulling in the core graph.
14
+ */
15
+ export type RefractErrorCode = "REFRACT_E_COLOR_INPUT" | "REFRACT_E_COLOR_TUPLE" | "REFRACT_E_STEPS" | "REFRACT_E_VARIANT" | "REFRACT_E_VARIANT_REF" | "REFRACT_E_CYCLE" | "REFRACT_E_ADJUST" | "REFRACT_E_HARMONY" | "REFRACT_E_VALIDATION" | "REFRACT_E_RECIPE_PROPERTY" | "REFRACT_E_TOKEN_PATH" | "REFRACT_E_BREAKPOINT" | "REFRACT_E_STATE" | "REFRACT_E_CONTAINER" | "REFRACT_E_RESPONSIVE" | "REFRACT_E_MEDIA" | "REFRACT_E_MODE" | "REFRACT_E_PRESET" | "REFRACT_E_EFFECTS" | "REFRACT_E_LAYOUT" | "REFRACT_E_UNITS" | "REFRACT_E_PROPERTY" | "REFRACT_E_REFERENCE" | "REFRACT_E_NAMING" | "REFRACT_E_DTCG_VERSION" | "REFRACT_E_AUDIT";
16
+ /**
17
+ * An authoring / build error with a stable {@link RefractErrorCode}. For an aggregate (collect-all)
18
+ * error, `failures` lists each individual message so a caller can report them all at once.
19
+ */
20
+ export declare class RefractError extends Error {
21
+ readonly code: RefractErrorCode;
22
+ readonly failures?: readonly string[];
23
+ constructor(code: RefractErrorCode, message: string, failures?: readonly string[]);
24
+ }
@@ -0,0 +1,16 @@
1
+ /** Clean-room core barrel. */
2
+ export * from "./errors";
3
+ export * from "./cssProperties";
4
+ export * from "./model";
5
+ export * from "./media";
6
+ export * from "./units";
7
+ export * from "./ref";
8
+ export * from "./normalize";
9
+ export * from "./derive";
10
+ export * from "./types";
11
+ export * from "./subsystem";
12
+ export * from "./rawTheme";
13
+ export * from "./ThemeAdapter";
14
+ export * from "./defineAdapter";
15
+ export * from "./noopAdapter";
16
+ export * from "./createTheme";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Container-query descriptors (§10.5).
3
+ *
4
+ * A named query container has a *size* scale (`sizeName → px`), exactly the shape a breakpoint scale
5
+ * has — so each container reuses {@link buildMediaDescriptor} with an `@container`-prefixing resolver.
6
+ * The result is a per-container descriptor whose `.min/.max/.exact(sizeName)` (and `[sizeName][kind]`)
7
+ * yield `@container <name> (…)` preludes with the same min/max/exact + band-to-next semantics the
8
+ * viewport axis uses. Threshold widths honor the shared media unit config (px default, em/rem).
9
+ */
10
+ import { type MediaDescriptor } from "./descriptors";
11
+ import { type MediaConfig } from "./queries";
12
+ import type { ContainerModel } from "../model/model";
13
+ /** One descriptor per named container: `containerName → MediaDescriptor` over that container's size scale. */
14
+ export type ContainerDescriptors = Record<string, MediaDescriptor<string>>;
15
+ /**
16
+ * Build a {@link ContainerDescriptors} map from the theme's `containers` config. Each container's
17
+ * `sizes` become the descriptor's "breakpoints"; the resolver emits `@container <name> (…)`. The
18
+ * width unit follows `config` (shared with the viewport media descriptor).
19
+ */
20
+ export declare const buildContainerDescriptors: (containers: Record<string, ContainerModel> | undefined, config?: MediaConfig) => ContainerDescriptors;