@apollion-dsi/tokens 5.2.0 → 5.4.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.
@@ -1,3 +1,3 @@
1
- var h=Object.defineProperty;var r=(n,i)=>h(n,"name",{value:i,configurable:!0});import{readFile as w}from"node:fs/promises";import{resolve as x}from"node:path";import{createContext as y,Script as C}from"node:vm";import{z as o}from"zod";var e=o.string().regex(/^#([0-9a-fA-F]{3}){1,2}$|^transparent$|^rgb\(.*\)$|^oklch\(.*\)$/,{message:'Must be hex, rgb(), oklch(), or "transparent"'}),b=o.object({baseDark:o.string().optional(),baseLight:o.string().optional(),deepDark:e,deepLight:e,transparent:o.string().optional(),main:e.optional(),opposite:e.optional(),complementary:e.optional(),information:e.optional(),success:e.optional(),danger:e.optional(),warning:e.optional(),primary:e.optional(),secondary:e.optional(),tertiary:e.optional()}).passthrough(),u=o.object({brands:o.record(o.string(),b).refine(n=>Object.keys(n).length>0,{message:"apollion.config.mjs: brands must contain at least 1 entry"}),modes:o.array(o.enum(["light","dark"])).optional(),surfaces:o.array(o.enum(["positive","negative"])).optional(),dimensions:o.array(o.enum(["compact","normal","spacious"])).optional(),output:o.object({runtime:o.boolean().optional(),css:o.boolean().optional(),json:o.boolean().optional(),ts:o.boolean().optional()}).passthrough().optional()}).passthrough();var _=[/\brequire\s*\(/,/\bimport\s*\(/,/^\s*import\s+/m],k=1e3;function v(n){return n}r(v,"defineConfig");var f=class f extends Error{constructor(s,p){super(s);this.configPath=p;this.name="ConfigLoadError"}};r(f,"ConfigLoadError");var a=f;async function I(n){let i=x(n.configPath),s=await w(i,"utf8");for(let t of _)if(t.test(s))throw new a(`apollion.config: dynamic require/import not allowed (matched ${t}). Declarative config only \u2014 see ADR-006 \xA73.8 B3.`,i);let p=s.replace(/(^|\n)\s*export\s+default\s+/,(t,l)=>`${l}__apollion_config_export = `);if(p===s)throw new a("apollion.config: missing `export default <object>;` \u2014 config must export a default object literal.",i);let g={__apollion_config_export:void 0,console:j(),Symbol,Number,String,Boolean,Array,Object,JSON,Math,defineConfig:r(t=>t,"defineConfig")},d=y(g);try{new C(p,{filename:i}).runInContext(d,{timeout:k})}catch(t){throw new a(`apollion.config: evaluation failed \u2014 ${t instanceof Error?t.message:String(t)}`,i)}let m=g.__apollion_config_export;if(m===void 0)throw new a("apollion.config: `export default` produced undefined.",i);let c=u.safeParse(m);if(!c.success){let t=c.error.issues.map(l=>` ${l.path.join(".")||"(root)"}: ${l.message}`).join(`
1
+ var h=Object.defineProperty;var r=(n,i)=>h(n,"name",{value:i,configurable:!0});import{readFile as w}from"node:fs/promises";import{resolve as x}from"node:path";import{createContext as y,Script as C}from"node:vm";import{z as o}from"zod";var e=o.string().regex(/^#([0-9a-fA-F]{3}){1,2}$|^transparent$|^rgb\(.*\)$|^oklch\(.*\)$/,{message:'Must be hex, rgb(), oklch(), or "transparent"'}),b=o.object({baseDark:o.string().optional(),baseLight:o.string().optional(),deepDark:e,deepLight:e,transparent:o.string().optional(),main:e.optional(),opposite:e.optional(),complementary:e.optional(),information:e.optional(),success:e.optional(),danger:e.optional(),warning:e.optional(),primary:e.optional(),secondary:e.optional(),tertiary:e.optional()}).passthrough(),u=o.object({brands:o.record(o.string(),b).refine(n=>Object.keys(n).length>0,{message:"apollion.config.mjs: brands must contain at least 1 entry"}),modes:o.array(o.enum(["light","dark"])).optional(),surfaces:o.array(o.enum(["positive","negative"])).optional(),dimensions:o.array(o.enum(["compact","normal","spacious"])).optional(),output:o.object({runtime:o.boolean().optional(),css:o.boolean().optional(),json:o.boolean().optional(),ts:o.boolean().optional(),designMd:o.boolean().optional(),tailwind:o.boolean().optional()}).passthrough().optional()}).passthrough();var _=[/\brequire\s*\(/,/\bimport\s*\(/,/^\s*import\s+/m],k=1e3;function v(n){return n}r(v,"defineConfig");var f=class f extends Error{constructor(s,p){super(s);this.configPath=p;this.name="ConfigLoadError"}};r(f,"ConfigLoadError");var a=f;async function I(n){let i=x(n.configPath),s=await w(i,"utf8");for(let t of _)if(t.test(s))throw new a(`apollion.config: dynamic require/import not allowed (matched ${t}). Declarative config only \u2014 see ADR-006 \xA73.8 B3.`,i);let p=s.replace(/(^|\n)\s*export\s+default\s+/,(t,l)=>`${l}__apollion_config_export = `);if(p===s)throw new a("apollion.config: missing `export default <object>;` \u2014 config must export a default object literal.",i);let g={__apollion_config_export:void 0,console:j(),Symbol,Number,String,Boolean,Array,Object,JSON,Math,defineConfig:r(t=>t,"defineConfig")},d=y(g);try{new C(p,{filename:i}).runInContext(d,{timeout:k})}catch(t){throw new a(`apollion.config: evaluation failed \u2014 ${t instanceof Error?t.message:String(t)}`,i)}let m=g.__apollion_config_export;if(m===void 0)throw new a("apollion.config: `export default` produced undefined.",i);let c=u.safeParse(m);if(!c.success){let t=c.error.issues.map(l=>` ${l.path.join(".")||"(root)"}: ${l.message}`).join(`
2
2
  `);throw new a(`apollion.config: schema validation failed \u2014
3
3
  ${t}`,i)}return c.data}r(I,"loadConfig");function j(){return{log:r((...n)=>console.log("[apollion.config]",...n),"log"),warn:r((...n)=>console.warn("[apollion.config]",...n),"warn"),error:r((...n)=>console.error("[apollion.config]",...n),"error")}}r(j,"makeReadOnlyConsole");export{a as ConfigLoadError,v as defineConfig,I as loadConfig};
@@ -1 +1 @@
1
- "use strict";var t=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var c=(o,n)=>t(o,"name",{value:n,configurable:!0});var D=(o,n)=>{for(var s in n)t(o,s,{get:n[s],enumerable:!0})},S=(o,n,s,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let e of f(n))!b.call(o,e)&&e!==s&&t(o,e,{get:()=>n[e],enumerable:!(r=u(n,e))||r.enumerable});return o};var g=o=>S(t({},"__esModule",{value:!0}),o);var F={};D(F,{expandVariants:()=>C,variantName:()=>E});module.exports=g(F);var x=["light","dark"],A=["positive","negative"],y=["compact","normal","spacious"];function C(o){let n=o.modes??x,s=o.surfaces??A,r=o.dimensions??y,e=Object.entries(o.brands).sort(([i],[a])=>i.localeCompare(a)),d=[];for(let[i,a]of e)for(let l of n)for(let p of s)for(let m of r)d.push({brand:i,colors:a,mode:l,surface:p,dimension:m});return d}c(C,"expandVariants");function E(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}c(E,"variantName");0&&(module.exports={expandVariants,variantName});
1
+ "use strict";var t=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var c=(o,n)=>t(o,"name",{value:n,configurable:!0});var g=(o,n)=>{for(var s in n)t(o,s,{get:n[s],enumerable:!0})},D=(o,n,s,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let e of f(n))!b.call(o,e)&&e!==s&&t(o,e,{get:()=>n[e],enumerable:!(r=u(n,e))||r.enumerable});return o};var S=o=>D(t({},"__esModule",{value:!0}),o);var M={};g(M,{expandVariants:()=>C,variantName:()=>E});module.exports=S(M);var x=["light","dark"],A=["positive","negative"],y=["compact","normal","spacious"];function C(o){let n=o.modes??x,s=o.surfaces??A,r=o.dimensions??y,e=Object.entries(o.brands).sort(([i],[a])=>i.localeCompare(a)),d=[];for(let[i,a]of e)for(let l of n)for(let p of s)for(let m of r)d.push({brand:i,colors:a,mode:l,surface:p,dimension:m});return d}c(C,"expandVariants");function E(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}c(E,"variantName");0&&(module.exports={expandVariants,variantName});
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(o,n)=>m(o,"name",{value:n,configurable:!0});var u=["light","dark"],f=["positive","negative"],b=["compact","normal","spacious"];function S(o){let n=o.modes??u,i=o.surfaces??f,a=o.dimensions??b,c=Object.entries(o.brands).sort(([e],[s])=>e.localeCompare(s)),t=[];for(let[e,s]of c)for(let d of n)for(let l of i)for(let p of a)t.push({brand:e,colors:s,mode:d,surface:l,dimension:p});return t}r(S,"expandVariants");function g(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}r(g,"variantName");export{S as expandVariants,g as variantName};
1
+ var m=Object.defineProperty;var r=(o,n)=>m(o,"name",{value:n,configurable:!0});var u=["light","dark"],f=["positive","negative"],b=["compact","normal","spacious"];function D(o){let n=o.modes??u,i=o.surfaces??f,a=o.dimensions??b,c=Object.entries(o.brands).sort(([e],[s])=>e.localeCompare(s)),t=[];for(let[e,s]of c)for(let d of n)for(let l of i)for(let p of a)t.push({brand:e,colors:s,mode:d,surface:l,dimension:p});return t}r(D,"expandVariants");function S(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}r(S,"variantName");export{D as expandVariants,S as variantName};
@@ -1 +1 @@
1
- "use strict";var m=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var l=(t,e)=>m(t,"name",{value:e,configurable:!0});var D=(t,e)=>{for(var r in e)m(t,r,{get:e[r],enumerable:!0})},F=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of M(e))!P.call(t,n)&&n!==r&&m(t,n,{get:()=>e[n],enumerable:!(o=E(e,n))||o.enumerable});return t};var R=t=>F(m({},"__esModule",{value:!0}),t);var H={};D(H,{TOKENS_RUNTIME_API_VERSION:()=>B,createFoundation:()=>y.createFoundation,createSpacing:()=>O,getOppositeColor:()=>b,invertForSurface:()=>C.invertForSurface,mountGreyscaleColors:()=>L,mountNeutralColors:()=>A,mountSingleColor:()=>v});module.exports=R(H);var a=require("culori");var h=(0,a.converter)("oklch");function i(t,e,r){let o=(0,a.parse)(t),n=(0,a.parse)(e);if(!o||!n)return t;let c=(0,a.interpolate)([o,n],"oklch");return(0,a.formatHex)(c(r))??t}l(i,"mixOklch");function k(t){return t?typeof t=="string"?t:t.base:"#000"}l(k,"coerceToHex");function b(t){let e=k(t),r=h((0,a.parse)(e));if(!r)return e;let o={...r,h:((r.h??0)+180)%360};return(0,a.formatHex)(o)??e}l(b,"getOppositeColor");var d=1.3,G=.05,S=.04,u=l((t,e)=>{let r=(0,a.parse)(t),o=(0,a.parse)(e);return!r||!o?1:(0,a.wcagContrast)(r,o)},"contrastRatio"),g=l(t=>{let e=h((0,a.parse)(t));return e?e.l??0:null},"oklchLightness"),T=l((t,e)=>{if(u(t,e)>=d)return!0;let r=g(t),o=g(e);return r!==null&&o!==null&&Math.abs(r-o)>=G},"isPerceptibleState"),j=l(t=>t?u("#fff",t)>u("#000",t):!1,"isDarkSurface");function I(t,e,r){let o=h((0,a.parse)(t));if(!o)return null;let n={...o,l:r??o.l??0},c=e?S:-S;for(let s=0;s<25;s+=1){let f=Math.min(1,Math.max(0,(n.l??0)+c));if(f===(n.l??0))break;n={...n,l:f};let p=(0,a.formatHex)(n);if(p&&u(t,p)>=d)return p}return(0,a.formatHex)(n)??null}l(I,"derivePerceptibleState");function x(t,e,r){let o=I(t,e,r);if(o&&u(t,o)>=d)return o;let n=I(t,!e);return n&&u(t,n)>=d?n:o??n}l(x,"perceptibleStateFor");function v(t,e){let r=typeof t=="object"&&t!==null?t:void 0,o=k(t),n=e.deepDark,c=e.deepLight,s={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9)};if((0,a.parse)(o)){let f=j(e.baseLight),p=T(o,s.action);if(p||(s.action=x(o,f)??s.action),!T(o,s.active)){let _=p?void 0:g(s.action)??void 0;s.active=x(o,f,_)??s.active}}return r?{base:o,dark:r.dark??s.dark,action:r.action??s.action,active:r.active??s.active,light:r.light??s.light}:s}l(v,"mountSingleColor");function L(t){let e=t.deepLight,r=t.deepDark;return{0:e,5:i(e,r,.05),10:i(e,r,.1),20:i(e,r,.2),30:i(e,r,.3),40:i(e,r,.4),50:i(e,r,.5),60:i(e,r,.6),70:i(e,r,.7),80:i(e,r,.8),90:i(e,r,.9),100:i(e,r,1)}}l(L,"mountGreyscaleColors");function A(t){let e=t.deepLight,r=t.deepDark,o=typeof t.primary=="object"&&t.primary?t.primary.base:t.primary,n=l(s=>1/17*(s-1),"interpolation"),c=i(o,r,.5);return{0:i(c,e,n(18)),5:i(c,e,n(17.5)),10:i(c,e,n(17)),20:i(c,e,n(16)),30:i(c,e,n(15)),40:i(c,e,n(14)),50:i(c,e,n(13)),60:i(c,e,n(12)),70:i(c,e,n(11)),80:i(c,e,n(10)),90:i(c,e,n(9)),100:i(c,e,n(8)),110:i(c,e,n(7)),120:i(c,e,n(6)),130:i(c,e,n(5)),140:i(c,e,n(4)),150:i(c,e,n(3)),160:i(c,e,n(2)),170:c,180:i(c,r,.25)}}l(A,"mountNeutralColors");var N=require("@apollion-dsi/core/themes/dimension");function O({multiplier:t=.25,alias:e},r){let o=r!==void 0?N.DIMENSION_MULTIPLIERS[r]:t;return(...n)=>n.map(c=>{let s=typeof c=="string"?e[c]:c;return`${o*s}rem`}).join(" ")}l(O,"createSpacing");var y=require("@apollion-dsi/core/themes/foundation");var C=require("@apollion-dsi/core/themes/surface");var B="v1";0&&(module.exports={TOKENS_RUNTIME_API_VERSION,createFoundation,createSpacing,getOppositeColor,invertForSurface,mountGreyscaleColors,mountNeutralColors,mountSingleColor});
1
+ "use strict";var g=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var l=(e,t)=>g(e,"name",{value:t,configurable:!0});var R=(e,t)=>{for(var r in t)g(e,r,{get:t[r],enumerable:!0})},F=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of E(t))!D.call(e,n)&&n!==r&&g(e,n,{get:()=>t[n],enumerable:!(o=P(t,n))||o.enumerable});return e};var G=e=>F(g({},"__esModule",{value:!0}),e);var V={};R(V,{TOKENS_RUNTIME_API_VERSION:()=>U,createFoundation:()=>C.createFoundation,createSpacing:()=>O,getOppositeColor:()=>A,invertForSurface:()=>S.invertForSurface,mountGreyscaleColors:()=>N,mountNeutralColors:()=>M,mountSingleColor:()=>v});module.exports=G(V);var a=require("culori");var h=(0,a.converter)("oklch");function i(e,t,r){let o=(0,a.parse)(e),n=(0,a.parse)(t);if(!o||!n)return e;let c=(0,a.interpolate)([o,n],"oklch");return(0,a.formatHex)(c(r))??e}l(i,"mixOklch");function L(e){return e?typeof e=="string"?e:e.base:"#000"}l(L,"coerceToHex");function A(e){let t=L(e),r=h((0,a.parse)(t));if(!r)return t;let o={...r,h:((r.h??0)+180)%360};return(0,a.formatHex)(o)??t}l(A,"getOppositeColor");var d=1.3,j=.05,T=.04,f=l((e,t)=>{let r=(0,a.parse)(e),o=(0,a.parse)(t);return!r||!o?1:(0,a.wcagContrast)(r,o)},"contrastRatio"),y=l(e=>{let t=h((0,a.parse)(e));return t?t.l??0:null},"oklchLightness"),B=4.5;function x(e,t,r=B){let o=(0,a.parse)(t),n=h((0,a.parse)(e));if(!o||!n)return e;let c=f("#fff",t)>=f("#000",t)?.04:-.04;for(let s=0;s<25;s+=1){let u=(0,a.formatHex)(n)??e,p=(0,a.parse)(u);if(p&&(0,a.wcagContrast)(p,o)>=r)return u;let m=Math.min(1,Math.max(0,(n.l??0)+c));if(m===(n.l??0))break;n={...n,l:m}}return(0,a.formatHex)(n)??e}l(x,"readableColor");var I=l((e,t)=>{if(f(e,t)>=d)return!0;let r=y(e),o=y(t);return r!==null&&o!==null&&Math.abs(r-o)>=j},"isPerceptibleState"),H=l(e=>e?f("#fff",e)>f("#000",e):!1,"isDarkSurface");function b(e,t,r){let o=h((0,a.parse)(e));if(!o)return null;let n={...o,l:r??o.l??0},c=t?T:-T;for(let s=0;s<25;s+=1){let u=Math.min(1,Math.max(0,(n.l??0)+c));if(u===(n.l??0))break;n={...n,l:u};let p=(0,a.formatHex)(n);if(p&&f(e,p)>=d)return p}return(0,a.formatHex)(n)??null}l(b,"derivePerceptibleState");function k(e,t,r){let o=b(e,t,r);if(o&&f(e,o)>=d)return o;let n=b(e,!t);return n&&f(e,n)>=d?n:o??n}l(k,"perceptibleStateFor");function v(e,t){let r=typeof e=="object"&&e!==null?e:void 0,o=L(e),n=t.deepDark,c=t.deepLight,s={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9),onLight:o};if((0,a.parse)(o)){let u=H(t.baseLight),p=I(o,s.action);if(p||(s.action=k(o,u)??s.action),!I(o,s.active)){let m=p?void 0:y(s.action)??void 0;s.active=k(o,u,m)??s.active}}if(s.onLight=x(s.action,s.light),r){let u=r.action??s.action,p=r.light??s.light;return{base:o,dark:r.dark??s.dark,action:u,active:r.active??s.active,light:p,onLight:r.onLight??x(u,p)}}return s}l(v,"mountSingleColor");function N(e){let t=e.deepLight,r=e.deepDark;return{0:t,5:i(t,r,.05),10:i(t,r,.1),20:i(t,r,.2),30:i(t,r,.3),40:i(t,r,.4),50:i(t,r,.5),60:i(t,r,.6),70:i(t,r,.7),80:i(t,r,.8),90:i(t,r,.9),100:i(t,r,1)}}l(N,"mountGreyscaleColors");function M(e){let t=e.deepLight,r=e.deepDark,o=typeof e.primary=="object"&&e.primary?e.primary.base:e.primary,n=l(s=>1/17*(s-1),"interpolation"),c=i(o,r,.5);return{0:i(c,t,n(18)),5:i(c,t,n(17.5)),10:i(c,t,n(17)),20:i(c,t,n(16)),30:i(c,t,n(15)),40:i(c,t,n(14)),50:i(c,t,n(13)),60:i(c,t,n(12)),70:i(c,t,n(11)),80:i(c,t,n(10)),90:i(c,t,n(9)),100:i(c,t,n(8)),110:i(c,t,n(7)),120:i(c,t,n(6)),130:i(c,t,n(5)),140:i(c,t,n(4)),150:i(c,t,n(3)),160:i(c,t,n(2)),170:c,180:i(c,r,.25)}}l(M,"mountNeutralColors");var _=require("@apollion-dsi/core/themes/dimension");function O({multiplier:e=.25,alias:t},r){let o=r!==void 0?_.DIMENSION_MULTIPLIERS[r]:e;return(...n)=>n.map(c=>{let s=typeof c=="string"?t[c]:c;return`${o*s}rem`}).join(" ")}l(O,"createSpacing");var C=require("@apollion-dsi/core/themes/foundation");var S=require("@apollion-dsi/core/themes/surface");var U="v1";0&&(module.exports={TOKENS_RUNTIME_API_VERSION,createFoundation,createSpacing,getOppositeColor,invertForSurface,mountGreyscaleColors,mountNeutralColors,mountSingleColor});
@@ -1 +1 @@
1
- var k=Object.defineProperty;var s=(t,e)=>k(t,"name",{value:e,configurable:!0});import{converter as b,formatHex as m,interpolate as v,parse as l,wcagContrast as L}from"culori";var h=b("oklch");function i(t,e,r){let o=l(t),n=l(e);if(!o||!n)return t;let c=v([o,n],"oklch");return m(c(r))??t}s(i,"mixOklch");function I(t){return t?typeof t=="string"?t:t.base:"#000"}s(I,"coerceToHex");function A(t){let e=I(t),r=h(l(e));if(!r)return e;let o={...r,h:((r.h??0)+180)%360};return m(o)??e}s(A,"getOppositeColor");var d=1.3,N=.05,y=.04,u=s((t,e)=>{let r=l(t),o=l(e);return!r||!o?1:L(r,o)},"contrastRatio"),g=s(t=>{let e=h(l(t));return e?e.l??0:null},"oklchLightness"),C=s((t,e)=>{if(u(t,e)>=d)return!0;let r=g(t),o=g(e);return r!==null&&o!==null&&Math.abs(r-o)>=N},"isPerceptibleState"),O=s(t=>t?u("#fff",t)>u("#000",t):!1,"isDarkSurface");function S(t,e,r){let o=h(l(t));if(!o)return null;let n={...o,l:r??o.l??0},c=e?y:-y;for(let a=0;a<25;a+=1){let f=Math.min(1,Math.max(0,(n.l??0)+c));if(f===(n.l??0))break;n={...n,l:f};let p=m(n);if(p&&u(t,p)>=d)return p}return m(n)??null}s(S,"derivePerceptibleState");function T(t,e,r){let o=S(t,e,r);if(o&&u(t,o)>=d)return o;let n=S(t,!e);return n&&u(t,n)>=d?n:o??n}s(T,"perceptibleStateFor");function _(t,e){let r=typeof t=="object"&&t!==null?t:void 0,o=I(t),n=e.deepDark,c=e.deepLight,a={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9)};if(l(o)){let f=O(e.baseLight),p=C(o,a.action);if(p||(a.action=T(o,f)??a.action),!C(o,a.active)){let x=p?void 0:g(a.action)??void 0;a.active=T(o,f,x)??a.active}}return r?{base:o,dark:r.dark??a.dark,action:r.action??a.action,active:r.active??a.active,light:r.light??a.light}:a}s(_,"mountSingleColor");function E(t){let e=t.deepLight,r=t.deepDark;return{0:e,5:i(e,r,.05),10:i(e,r,.1),20:i(e,r,.2),30:i(e,r,.3),40:i(e,r,.4),50:i(e,r,.5),60:i(e,r,.6),70:i(e,r,.7),80:i(e,r,.8),90:i(e,r,.9),100:i(e,r,1)}}s(E,"mountGreyscaleColors");function M(t){let e=t.deepLight,r=t.deepDark,o=typeof t.primary=="object"&&t.primary?t.primary.base:t.primary,n=s(a=>1/17*(a-1),"interpolation"),c=i(o,r,.5);return{0:i(c,e,n(18)),5:i(c,e,n(17.5)),10:i(c,e,n(17)),20:i(c,e,n(16)),30:i(c,e,n(15)),40:i(c,e,n(14)),50:i(c,e,n(13)),60:i(c,e,n(12)),70:i(c,e,n(11)),80:i(c,e,n(10)),90:i(c,e,n(9)),100:i(c,e,n(8)),110:i(c,e,n(7)),120:i(c,e,n(6)),130:i(c,e,n(5)),140:i(c,e,n(4)),150:i(c,e,n(3)),160:i(c,e,n(2)),170:c,180:i(c,r,.25)}}s(M,"mountNeutralColors");import{DIMENSION_MULTIPLIERS as P}from"@apollion-dsi/core/themes/dimension";function D({multiplier:t=.25,alias:e},r){let o=r!==void 0?P[r]:t;return(...n)=>n.map(c=>{let a=typeof c=="string"?e[c]:c;return`${o*a}rem`}).join(" ")}s(D,"createSpacing");import{createFoundation as F}from"@apollion-dsi/core/themes/foundation";import{invertForSurface as R}from"@apollion-dsi/core/themes/surface";var z="v1";export{z as TOKENS_RUNTIME_API_VERSION,F as createFoundation,D as createSpacing,A as getOppositeColor,R as invertForSurface,E as mountGreyscaleColors,M as mountNeutralColors,_ as mountSingleColor};
1
+ var L=Object.defineProperty;var s=(e,t)=>L(e,"name",{value:t,configurable:!0});import{converter as A,formatHex as m,interpolate as v,parse as u,wcagContrast as b}from"culori";var h=A("oklch");function i(e,t,r){let o=u(e),n=u(t);if(!o||!n)return e;let c=v([o,n],"oklch");return m(c(r))??e}s(i,"mixOklch");function k(e){return e?typeof e=="string"?e:e.base:"#000"}s(k,"coerceToHex");function N(e){let t=k(e),r=h(u(t));if(!r)return t;let o={...r,h:((r.h??0)+180)%360};return m(o)??t}s(N,"getOppositeColor");var d=1.3,M=.05,C=.04,f=s((e,t)=>{let r=u(e),o=u(t);return!r||!o?1:b(r,o)},"contrastRatio"),y=s(e=>{let t=h(u(e));return t?t.l??0:null},"oklchLightness"),_=4.5;function S(e,t,r=_){let o=u(t),n=h(u(e));if(!o||!n)return e;let c=f("#fff",t)>=f("#000",t)?.04:-.04;for(let a=0;a<25;a+=1){let p=m(n)??e,l=u(p);if(l&&b(l,o)>=r)return p;let g=Math.min(1,Math.max(0,(n.l??0)+c));if(g===(n.l??0))break;n={...n,l:g}}return m(n)??e}s(S,"readableColor");var T=s((e,t)=>{if(f(e,t)>=d)return!0;let r=y(e),o=y(t);return r!==null&&o!==null&&Math.abs(r-o)>=M},"isPerceptibleState"),O=s(e=>e?f("#fff",e)>f("#000",e):!1,"isDarkSurface");function x(e,t,r){let o=h(u(e));if(!o)return null;let n={...o,l:r??o.l??0},c=t?C:-C;for(let a=0;a<25;a+=1){let p=Math.min(1,Math.max(0,(n.l??0)+c));if(p===(n.l??0))break;n={...n,l:p};let l=m(n);if(l&&f(e,l)>=d)return l}return m(n)??null}s(x,"derivePerceptibleState");function I(e,t,r){let o=x(e,t,r);if(o&&f(e,o)>=d)return o;let n=x(e,!t);return n&&f(e,n)>=d?n:o??n}s(I,"perceptibleStateFor");function P(e,t){let r=typeof e=="object"&&e!==null?e:void 0,o=k(e),n=t.deepDark,c=t.deepLight,a={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9),onLight:o};if(u(o)){let p=O(t.baseLight),l=T(o,a.action);if(l||(a.action=I(o,p)??a.action),!T(o,a.active)){let g=l?void 0:y(a.action)??void 0;a.active=I(o,p,g)??a.active}}if(a.onLight=S(a.action,a.light),r){let p=r.action??a.action,l=r.light??a.light;return{base:o,dark:r.dark??a.dark,action:p,active:r.active??a.active,light:l,onLight:r.onLight??S(p,l)}}return a}s(P,"mountSingleColor");function E(e){let t=e.deepLight,r=e.deepDark;return{0:t,5:i(t,r,.05),10:i(t,r,.1),20:i(t,r,.2),30:i(t,r,.3),40:i(t,r,.4),50:i(t,r,.5),60:i(t,r,.6),70:i(t,r,.7),80:i(t,r,.8),90:i(t,r,.9),100:i(t,r,1)}}s(E,"mountGreyscaleColors");function D(e){let t=e.deepLight,r=e.deepDark,o=typeof e.primary=="object"&&e.primary?e.primary.base:e.primary,n=s(a=>1/17*(a-1),"interpolation"),c=i(o,r,.5);return{0:i(c,t,n(18)),5:i(c,t,n(17.5)),10:i(c,t,n(17)),20:i(c,t,n(16)),30:i(c,t,n(15)),40:i(c,t,n(14)),50:i(c,t,n(13)),60:i(c,t,n(12)),70:i(c,t,n(11)),80:i(c,t,n(10)),90:i(c,t,n(9)),100:i(c,t,n(8)),110:i(c,t,n(7)),120:i(c,t,n(6)),130:i(c,t,n(5)),140:i(c,t,n(4)),150:i(c,t,n(3)),160:i(c,t,n(2)),170:c,180:i(c,r,.25)}}s(D,"mountNeutralColors");import{DIMENSION_MULTIPLIERS as R}from"@apollion-dsi/core/themes/dimension";function F({multiplier:e=.25,alias:t},r){let o=r!==void 0?R[r]:e;return(...n)=>n.map(c=>{let a=typeof c=="string"?t[c]:c;return`${o*a}rem`}).join(" ")}s(F,"createSpacing");import{createFoundation as G}from"@apollion-dsi/core/themes/foundation";import{invertForSurface as j}from"@apollion-dsi/core/themes/surface";var W="v1";export{W as TOKENS_RUNTIME_API_VERSION,G as createFoundation,F as createSpacing,N as getOppositeColor,j as invertForSurface,E as mountGreyscaleColors,D as mountNeutralColors,P as mountSingleColor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollion-dsi/tokens",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "description": "Apollion Design System — Framework-agnostic design tokens",
5
5
  "homepage": "https://www.apollion.com.br",
6
6
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  "./dist/json/sets/*": "./dist/json/sets/*",
38
38
  "./dist/json/*": "./dist/json/*",
39
39
  "./dist/ts/*": "./dist/ts/*",
40
+ "./dist/tailwind/*": "./dist/tailwind/*",
40
41
  "./dist/manifest.json": "./dist/manifest.json"
41
42
  },
42
43
  "sideEffects": false,
@@ -66,7 +67,7 @@
66
67
  },
67
68
  "devDependencies": {
68
69
  "@apollion-dsi/core": "workspace:*",
69
- "@apollion-dsi/eslint-config": "0.9.0",
70
+ "@apollion-dsi/eslint-config": "0.9.1",
70
71
  "@types/culori": "4.0.1",
71
72
  "@types/jest": "30.0.0",
72
73
  "@types/node": "25.7.0",
package/src/build.ts CHANGED
@@ -19,8 +19,11 @@
19
19
  * Verified by `__tests__/build.test.ts`.
20
20
  *
21
21
  * **`--check` mode (CI gate):** read existing `manifest.json`, recompute
22
- * the expected manifest from current config, compare. Exit 1 on drift.
23
- * Never writes anything. Suitable for pre-push hook or local lint.
22
+ * `configHash` from current config, then re-hash every file the manifest
23
+ * lists and compare against its recorded sha256 — `configHash` alone only
24
+ * proves "the input didn't change", not "dist/ still reflects it" (a file
25
+ * can be hand-edited or corrupted without touching config). Exit 1 on
26
+ * drift. Never writes anything. Suitable for pre-push hook or local lint.
24
27
  *
25
28
  * @see ADR-006 §3.5 (atomic+idempotent build)
26
29
  * @see PRD-002 §S5 acceptance (rerun byte-identical, --check blocks drift)
@@ -31,7 +34,9 @@ import { tmpdir } from 'node:os';
31
34
  import { dirname, join } from 'node:path';
32
35
 
33
36
  import { renderCss } from './renderers/css';
37
+ import { renderDesignMd } from './renderers/design-md';
34
38
  import { renderBaseSet, renderColorSet, renderFontSet, renderResolver, renderSpacingSet } from './renderers/resolver';
39
+ import { renderTailwindPreset, renderTwMergeExtension } from './renderers/tailwind';
35
40
  import { renderTs } from './renderers/ts';
36
41
  import type { ApollionConfig, Variant } from './config-schema';
37
42
  import { expandVariants, variantName } from './config-schema';
@@ -127,11 +132,37 @@ export async function build(opts: BuildOptions): Promise<BuildResult> {
127
132
  const ir = buildFullIR(foundation, colors, variant);
128
133
  const baseName = variantName(variant);
129
134
 
130
- if (output.css) await emit(`css/${baseName}.css`, renderCss(ir));
135
+ if (output.css) await emit(`css/${baseName}.css`, renderCss(ir, { rgbCompanions: output.tailwind }));
131
136
  if (output.ts) await emit(`ts/${baseName}.d.ts`, renderTs(ir));
132
137
  }
133
138
  }
134
139
 
140
+ // ── Tailwind surface: preset + tw-merge extension (naming layer) ─────────
141
+ // Token *topology* is variant-invariant (same groups/keys across the axes),
142
+ // and every entry maps through `var(--apollion-*)` indirection — so one
143
+ // preset serves all variants. The first expanded variant is the
144
+ // representative (deterministic: `expandVariants` ordering is stable).
145
+ if (output.tailwind) {
146
+ const variant = variants[0];
147
+ const { foundation, colors } = buildVariantThemes(variant);
148
+ const ir = buildFullIR(foundation, colors, variant);
149
+ await emit('tailwind/preset.mjs', renderTailwindPreset(ir));
150
+ await emit('tailwind/tw-merge.mjs', renderTwMergeExtension(ir));
151
+ }
152
+
153
+ // ── Design.md surface: resolved-values AI context, one doc per brand ─────
154
+ if (output.designMd) {
155
+ const brands = [...new Set(variants.map((v) => v.brand))];
156
+ for (const brand of brands) {
157
+ const brandVariants = variants.filter((v) => v.brand === brand);
158
+ const irs = brandVariants.map((variant) => {
159
+ const { foundation, colors } = buildVariantThemes(variant);
160
+ return buildFullIR(foundation, colors, variant);
161
+ });
162
+ await emit(`design/${brand}.design.md`, renderDesignMd(brand, irs));
163
+ }
164
+ }
165
+
135
166
  const manifest: BuildManifest = {
136
167
  configHash: hashConfig(config),
137
168
  files,
@@ -170,7 +201,22 @@ export async function check(opts: { config: ApollionConfig; outDir: string }): P
170
201
  } catch {
171
202
  return false; // no manifest = needs build
172
203
  }
173
- return current.configHash === hashConfig(config);
204
+ if (current.configHash !== hashConfig(config)) return false;
205
+
206
+ // configHash only proves the input didn't change — re-verify each emitted
207
+ // file's bytes still match what the manifest recorded, so a hand-edited or
208
+ // corrupted dist/ file (config untouched) is still caught as drift.
209
+ for (const entry of current.files) {
210
+ let content: Buffer;
211
+ try {
212
+ content = await readFile(join(outDir, entry.path));
213
+ } catch {
214
+ return false; // file missing
215
+ }
216
+ if (sha256(content) !== entry.sha256) return false;
217
+ }
218
+
219
+ return true;
174
220
  }
175
221
 
176
222
  async function writeAt(root: string, relativePath: string, content: string): Promise<void> {
@@ -67,13 +67,13 @@ export function getOppositeColor(color: AcceptColorType): string {
67
67
  return formatHex(rotated) ?? hex;
68
68
  }
69
69
 
70
- /** Piso perceptual base↔estado de interação (razão WCAG) — espelho do core (ADR-012). */
70
+ /** Perceptual state floor between base and interaction state (WCAG ratio) — mirror of the core (ADR-012). */
71
71
  const MIN_STATE_CONTRAST = 1.3;
72
72
 
73
- /** ΔL OKLch JND — critério alternativo onde a razão WCAG satura (quase-preto). */
73
+ /** ΔL OKLch JND — alternative criterion where the WCAG ratio saturates (near-black). */
74
74
  const STATE_DELTA_L_JND = 0.05;
75
75
 
76
- /** Passo de L por iteração na correção de estado. */
76
+ /** L step per iteration during state correction. */
77
77
  const STATE_L_STEP = 0.04;
78
78
 
79
79
  const contrastRatio = (a: string, b: string): number => {
@@ -88,6 +88,34 @@ const oklchLightness = (color: string): number | null => {
88
88
  return parsed ? (parsed.l ?? 0) : null;
89
89
  };
90
90
 
91
+ /** WCAG AA contrast floor (normal text) — mirror of the core. */
92
+ const MIN_CONTRAST_AA = 4.5;
93
+
94
+ /**
95
+ * Adjusts the OKLch L of `color` until it passes `minRatio` against `background`,
96
+ * preserving hue/chroma — **byte-for-byte mirror** of the core's `readableColor`
97
+ * (`Colors.helpers.ts`; parity.test.ts). Used to derive the tonal ink
98
+ * `onLight` (APOLLION-CONTRAST-GUARANTEE).
99
+ */
100
+ function readableColor(color: string, background: string, minRatio = MIN_CONTRAST_AA): string {
101
+ const bgParsed = parse(background);
102
+ let oklch = toOklch(parse(color));
103
+ if (!bgParsed || !oklch) return color;
104
+
105
+ const step = contrastRatio('#fff', background) >= contrastRatio('#000', background) ? 0.04 : -0.04;
106
+
107
+ for (let i = 0; i < 25; i += 1) {
108
+ const hex = formatHex(oklch) ?? color;
109
+ const parsed = parse(hex);
110
+ if (parsed && wcagContrast(parsed, bgParsed) >= minRatio) return hex;
111
+ const nextL = Math.min(1, Math.max(0, (oklch.l ?? 0) + step));
112
+ if (nextL === (oklch.l ?? 0)) break;
113
+ oklch = { ...oklch, l: nextL };
114
+ }
115
+
116
+ return formatHex(oklch) ?? color;
117
+ }
118
+
91
119
  const isPerceptibleState = (base: string, candidate: string): boolean => {
92
120
  if (contrastRatio(base, candidate) >= MIN_STATE_CONTRAST) return true;
93
121
  const baseL = oklchLightness(base);
@@ -129,9 +157,9 @@ function perceptibleStateFor(base: string, surfaceIsDark: boolean, startL?: numb
129
157
  }
130
158
 
131
159
  /**
132
- * Derive `{ base, dark, action, active, light }` via OKLch lerp, com piso
133
- * perceptual mode-aware nos estados de interação — espelho byte-a-byte do
134
- * core (ADR-012; parity.test.ts). Overrides por slot passam verbatim.
160
+ * Derive `{ base, dark, action, active, light }` via OKLch lerp, with a
161
+ * mode-aware perceptual state floor on the interaction states — byte-for-byte
162
+ * mirror of the core (ADR-012; parity.test.ts). Per-slot overrides pass through verbatim.
135
163
  */
136
164
  export function mountSingleColor(
137
165
  color: AcceptColorType | ColorPalleteOverrideInterface,
@@ -148,6 +176,8 @@ export function mountSingleColor(
148
176
  action: mixOklch(base, darken!, 0.2),
149
177
  active: mixOklch(base, lighten!, 0.6),
150
178
  light: mixOklch(base, lighten!, 0.9),
179
+ // Placeholder — recomputed from the FINAL `action` below (mirror of the core).
180
+ onLight: base,
151
181
  };
152
182
 
153
183
  if (parse(base)) {
@@ -164,13 +194,19 @@ export function mountSingleColor(
164
194
  }
165
195
  }
166
196
 
197
+ // Tonal ink AA by construction (APOLLION-CONTRAST-GUARANTEE) — mirror of the core.
198
+ derived.onLight = readableColor(derived.action, derived.light);
199
+
167
200
  if (override) {
201
+ const finalAction = override.action ?? derived.action;
202
+ const finalLight = override.light ?? derived.light;
168
203
  return {
169
204
  base,
170
205
  dark: override.dark ?? derived.dark,
171
- action: override.action ?? derived.action,
206
+ action: finalAction,
172
207
  active: override.active ?? derived.active,
173
- light: override.light ?? derived.light,
208
+ light: finalLight,
209
+ onLight: override.onLight ?? readableColor(finalAction, finalLight),
174
210
  };
175
211
  }
176
212
 
@@ -202,7 +238,7 @@ export function mountGreyscaleColors(colors: ColorsInput): Record<GrayscaleColor
202
238
  export function mountNeutralColors(colors: ColorsInput): Record<NeutralColorsType, string> {
203
239
  const lighter = colors.deepLight!;
204
240
  const darker = colors.deepDark!;
205
- // Slot pode ser override por slot (ADR-012) — a rampa ancora só no seed.
241
+ // The slot may be overridden per slot (ADR-012) — the ramp anchors on the seed only.
206
242
  const primary =
207
243
  typeof colors.primary === 'object' && colors.primary ? colors.primary.base : (colors.primary as string);
208
244
 
package/src/cli.ts CHANGED
@@ -103,7 +103,7 @@ async function main(): Promise<void> {
103
103
  const ok = await check({ config, outDir: resolve(args.outDir) });
104
104
  if (!ok) {
105
105
  console.error(
106
- `apollion-tokens: dist/ is stale or missing (configHash mismatch). Re-run \`apollion-tokens build\` without --check.`,
106
+ `apollion-tokens: dist/ is stale, missing, or has drifted from its manifest. Re-run \`apollion-tokens build\` without --check.`,
107
107
  );
108
108
  process.exit(1);
109
109
  }
@@ -57,6 +57,8 @@ export const ApollionConfigSchema = z
57
57
  css: z.boolean().optional(),
58
58
  json: z.boolean().optional(),
59
59
  ts: z.boolean().optional(),
60
+ designMd: z.boolean().optional(),
61
+ tailwind: z.boolean().optional(),
60
62
  })
61
63
  .passthrough()
62
64
  .optional(),
@@ -36,6 +36,15 @@ export interface ApollionOutputFlags {
36
36
  css?: boolean;
37
37
  json?: boolean;
38
38
  ts?: boolean;
39
+ /** `dist/design/<brand>.design.md` — resolved-values AI context per brand
40
+ * (YAML frontmatter + markdown body). See `renderers/design-md.ts`. */
41
+ designMd?: boolean;
42
+ /** `dist/tailwind/{preset,tw-merge}.mjs` — generated Tailwind preset mapping
43
+ * foundation tokens to theme keys (`var(--apollion-*)` indirection) plus a
44
+ * tailwind-merge extension derived from the same IR. Also adds `-rgb`
45
+ * channel-triplet companion vars to `dist/css/` so opaque colour tokens
46
+ * support Tailwind's alpha modifier. See `renderers/tailwind.ts`. */
47
+ tailwind?: boolean;
39
48
  }
40
49
 
41
50
  export interface ApollionConfig {
@@ -102,7 +111,7 @@ export function expandVariants(config: ApollionConfig): Variant[] {
102
111
  }
103
112
 
104
113
  /**
105
- * Stable variant filename (sem extension).
114
+ * Stable variant filename (without extension).
106
115
  * Format: `<brand>.<mode>.<surface>.<dimension>` — kebab where needed.
107
116
  */
108
117
  export function variantName(v: Variant): string {
package/src/ir.ts CHANGED
@@ -258,6 +258,9 @@ function buildColorPrimitives(colors: ColorsThemeInterface): IRGroup {
258
258
  action: colorToken(palette.action),
259
259
  active: colorToken(palette.active),
260
260
  light: colorToken(palette.light),
261
+ // AA tonal pair (APOLLION-CONTRAST-GUARANTEE): ink legible over `light`,
262
+ // emitted as `--apollion-color-<palette>-on-light`.
263
+ onLight: colorToken(palette.onLight),
261
264
  };
262
265
  }
263
266
  out.grayscale = recordColorGroup(colors.grayscale);
package/src/manifest.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * **Threat-model fields (ADR-006 §3.9):** `gitCommit` and `buildEnv` give
10
10
  * provenance without requiring CI/SLSA-3 (decision Q-G9 — single-user org,
11
- * sem CI). `signedBy` slot reserved for sigstore in future PRD-003.
11
+ * no CI). `signedBy` slot reserved for sigstore in future PRD-003.
12
12
  *
13
13
  * @see ADR-006 §3.9 + §8.5 risk T1/R2
14
14
  * @see PRD-002 §S5
@@ -13,6 +13,8 @@
13
13
  * @see ../ir.ts (source of truth) · ADR-006 §3.3 + §3.10 · tech-radar R3
14
14
  */
15
15
 
16
+ import { colorPayload, isOpaqueColor, rgbTriplet } from './tailwind';
17
+
16
18
  import type { IRDocument, IRGroup } from '../ir';
17
19
  import { isToken, kebab, rawValue } from '../ir';
18
20
 
@@ -26,6 +28,28 @@ function emitVars(group: IRGroup, prefix: string[], lines: string[]): void {
26
28
  }
27
29
  }
28
30
 
31
+ /**
32
+ * `-rgb` channel-triplet companions for opaque colour tokens (Tailwind
33
+ * `<alpha-value>` support — `output.tailwind` only). Projection rule shared
34
+ * with the preset renderer ({@link colorPayload}/{@link isOpaqueColor}/
35
+ * {@link rgbTriplet}) so preset entries and companions cannot drift.
36
+ *
37
+ * NEVER mirrored into the `@property` blocks — `syntax: '<color>'` is invalid
38
+ * for a channel triplet (`emitProperties` walks tokens, companions are not
39
+ * tokens, so the exclusion is structural).
40
+ */
41
+ function emitRgbVars(group: IRGroup, prefix: string[], lines: string[]): void {
42
+ for (const [key, node] of Object.entries(group)) {
43
+ const path = [...prefix, kebab(key)];
44
+ if (!isToken(node)) {
45
+ emitRgbVars(node, path, lines);
46
+ continue;
47
+ }
48
+ const color = colorPayload(node);
49
+ if (color && isOpaqueColor(color)) lines.push(` --apollion-${path.join('-')}-rgb: ${rgbTriplet(color)};`);
50
+ }
51
+ }
52
+
29
53
  function emitProperties(group: IRGroup, prefix: string[], lines: string[]): void {
30
54
  for (const [key, node] of Object.entries(group)) {
31
55
  const path = [...prefix, kebab(key)];
@@ -39,13 +63,24 @@ function emitProperties(group: IRGroup, prefix: string[], lines: string[]): void
39
63
  }
40
64
  }
41
65
 
42
- export function renderCss(ir: IRDocument): string {
66
+ export interface RenderCssOptions {
67
+ /** Emit `-rgb` channel-triplet companions for opaque colour tokens
68
+ * (`output.tailwind` — see {@link emitRgbVars}). Default false: the byte
69
+ * output without the flag is unchanged. */
70
+ rgbCompanions?: boolean;
71
+ }
72
+
73
+ export function renderCss(ir: IRDocument, options: RenderCssOptions = {}): string {
43
74
  const { brand, mode, surface, dimension } = ir.meta;
44
75
  const lines: string[] = [HEADER_LINE, ''];
45
76
 
46
77
  // Variant marker as a comment (NOT a var — keep cascade clean).
47
78
  lines.push(`/* Variant: brand=${brand} mode=${mode} surface=${surface} dimension=${dimension} */`, '', ':root {');
48
79
  emitVars(ir.groups, [], lines);
80
+ if (options.rgbCompanions) {
81
+ lines.push('', ' /* -rgb channel triplets — Tailwind <alpha-value> companions (opaque colour tokens only). */');
82
+ emitRgbVars(ir.groups, [], lines);
83
+ }
49
84
  lines.push('}', '');
50
85
 
51
86
  // @property declarations for color tokens — opt-in via @supports.
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Design.md renderer — resolved-values AI context per brand.
3
+ *
4
+ * The rules layer (`llms.txt`) teaches *which* token to reach
5
+ * for; this emission supplies the *resolved values* those rules reference,
6
+ * generated from the consumer's own build so it can never drift from the
7
+ * shipped theme. YAML frontmatter for machines + a markdown body for humans.
8
+ *
9
+ * One document per brand, covering every (mode × surface × dimension) variant
10
+ * of that brand. Pure projection over per-variant `IRDocument`s — no value is
11
+ * computed here; a missing value is an IR gap by contract.
12
+ *
13
+ * @see ../../../../docs/references/design-md-decision.md (adoption decision)
14
+ * @see ../ir.ts (source of truth) · ./ts.ts (sibling resolved-surface renderer)
15
+ */
16
+
17
+ import type { IRDocument, IRGroup } from '../ir';
18
+ import { isToken, rawValue } from '../ir';
19
+
20
+ const HEADER_LINE = '<!-- Generated by apollion-tokens build — DO NOT EDIT. See apollion.config.mjs. -->';
21
+
22
+ /** Flatten a resolved IR group into dot-path → raw value pairs. */
23
+ function flatten(group: IRGroup, prefix: string, out: Array<[string, string | number]>): void {
24
+ for (const [key, node] of Object.entries(group)) {
25
+ const path = prefix ? `${prefix}.${key}` : key;
26
+ if (isToken(node)) {
27
+ out.push([path, rawValue(node.value) as string | number]);
28
+ } else {
29
+ flatten(node, path, out);
30
+ }
31
+ }
32
+ }
33
+
34
+ function yamlEscape(value: string | number): string {
35
+ if (typeof value === 'number') return String(value);
36
+ // hex colors and px/rem strings are safe unquoted except when starting with
37
+ // YAML-significant characters — quote everything for uniformity/safety.
38
+ return `'${String(value).replace(/'/g, "''")}'`;
39
+ }
40
+
41
+ function variantKey(ir: IRDocument): string {
42
+ const { mode, surface, dimension } = ir.meta;
43
+ return `${mode}.${surface}.${dimension}`;
44
+ }
45
+
46
+ /**
47
+ * Render one Design.md for a brand from its variant IRDocuments.
48
+ * Deterministic: variant order is the caller's (build order), path order is
49
+ * IR insertion order — both stable for a given config.
50
+ */
51
+ export function renderDesignMd(brand: string, variants: readonly IRDocument[]): string {
52
+ if (variants.length === 0) {
53
+ throw new Error(`renderDesignMd: no variants for brand "${brand}"`);
54
+ }
55
+
56
+ // ── YAML frontmatter (machine surface) ────────────────────────────────────
57
+ const fm: string[] = ['---', `brand: '${brand}'`, 'generator: apollion-tokens', 'variants:'];
58
+ for (const ir of variants) {
59
+ const flat: Array<[string, string | number]> = [];
60
+ flatten(ir.groups, '', flat);
61
+ fm.push(` ${variantKey(ir)}:`);
62
+ for (const [path, value] of flat) {
63
+ fm.push(` ${path}: ${yamlEscape(value)}`);
64
+ }
65
+ }
66
+ fm.push('---');
67
+
68
+ // ── Markdown body (human surface) ─────────────────────────────────────────
69
+ const body: string[] = [
70
+ '',
71
+ HEADER_LINE,
72
+ '',
73
+ `# Design.md — ${brand}`,
74
+ '',
75
+ 'Resolved design-token values for this brand, one section per variant.',
76
+ 'Consumption rules (which token to reach for, layer boundaries) live in the',
77
+ 'package `llms.txt`; this file carries the values those rules reference.',
78
+ 'Regenerate with `apollion-tokens build` — never edit.',
79
+ '',
80
+ ];
81
+
82
+ for (const ir of variants) {
83
+ const { mode, surface, dimension } = ir.meta;
84
+ body.push(`## Variant \`${mode} · ${surface} · ${dimension}\``, '');
85
+ const flat: Array<[string, string | number]> = [];
86
+ flatten(ir.groups, '', flat);
87
+
88
+ // group rows by top-level namespace for readable tables
89
+ const byNs = new Map<string, Array<[string, string | number]>>();
90
+ for (const [path, value] of flat) {
91
+ const ns = path.split('.')[0] ?? path;
92
+ const rows = byNs.get(ns) ?? [];
93
+ rows.push([path, value]);
94
+ byNs.set(ns, rows);
95
+ }
96
+
97
+ for (const [ns, rows] of byNs) {
98
+ body.push(`### ${ns}`, '', '| Token | Value |', '| --- | --- |');
99
+ for (const [path, value] of rows) {
100
+ body.push(`| \`${path}\` | \`${value}\` |`);
101
+ }
102
+ body.push('');
103
+ }
104
+ }
105
+
106
+ return `${fm.join('\n')}\n${body.join('\n')}`;
107
+ }
@@ -10,9 +10,10 @@
10
10
  * manifest + O(brand·mode·surface + 2·dimension + 1) sets (spacing + font per
11
11
  * dimension).
12
12
  *
13
- * **S5 mode caveat:** `mode` is emitted as a real modifier (values + default)
14
- * even though palette generation is currently mode-agnostic (dark == light).
15
- * The topology is therefore correct ahead of S6 wiring real dark-mode logic.
13
+ * **Mode modifier:** `mode` is emitted as a real modifier (values + default),
14
+ * and since ADR-019 the dark contexts carry real values — palette generation
15
+ * derives dark through the canonical seed swap in `theme-factory.ts` (the S5
16
+ * dark == light placeholder is retired).
16
17
  *
17
18
  * Set documents are projected from `IRSlice`s via the shared `dtcg-project`
18
19
  * mapping, so a composed (base + color + spacing + font) tree is byte-equivalent