/*
Single source of truth for the biome-YAML-field to --ds-* CSS custom property
projection (RFC-0071). Consumed by biome.css.generate (codegen), biome.contract.validate
(contract check), and biome.tokens.validate (drift detection). Replaces three
divergent copies of the same mapping.
- Do not validate token names against @warpgogol/werkstatt-site/tokens — that is the checks package's job.
- Do not format CSS for emission — callers handle formatting.
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
// ---------------------------------------------------------------------------
// Primary mapping: biome dotted field path to CSS custom property name
// ---------------------------------------------------------------------------
export const BIOME_TO_TOKEN_MAP = {
// Palette — semantic color slots (RFC-0071)
"palette.brand": "--ds-color-primary",
"palette.brandHover": "--ds-color-secondary",
"palette.brandContrast": "--ds-color-text-inverse",
"palette.accent": "--ds-color-accent",
"palette.surface": "--ds-color-bg",
"palette.surfaceMuted": "--ds-color-surface",
"palette.ink": "--ds-color-text",
"palette.inkSoft": "--ds-color-text-muted",
"palette.inkMuted": "--ds-color-text-quiet",
"palette.divider": "--ds-color-border",
"palette.success": "--ds-color-success",
"palette.warning": "--ds-color-warning-strong",
"palette.danger": "--ds-color-danger",
"palette.info": "--ds-color-info",
// Components
"components.heroOverlay": "--ds-color-hero-overlay",
"components.heroText": "--ds-color-hero-text",
"components.surfaceGlass": "--ds-color-surface-glass",
"components.sectionAltBase": "--ds-color-section-alt-base",
"components.sectionAltText": "--ds-color-section-alt-text",
"components.sectionAltTextSoft": "--ds-color-section-alt-text-soft",
"components.sectionAltLink": "--ds-color-section-alt-link",
"components.sectionAltLinkVisited": "--ds-color-section-alt-link-visited",
"components.sectionAltLinkHover": "--ds-color-section-alt-link-hover",
"components.sectionAltAccent": "--ds-color-section-alt-accent",
"components.statsBg": "--ds-color-stats-bg",
"components.footerBg": "--ds-color-footer-bg",
"components.footerText": "--ds-color-footer-text",
"components.footerTextMuted": "--ds-color-footer-text-muted",
"components.cardBg": "--ds-color-card-bg",
"components.cardBorder": "--ds-color-card-border",
"components.cta": "--ds-color-cta",
"components.ctaHover": "--ds-color-cta-hover",
"components.ctaText": "--ds-color-cta-text",
"components.surfaceAlt": "--ds-color-surface-alt",
"components.textSoftOnDark": "--ds-color-text-soft-on-dark",
"components.textInverse84": "--ds-color-text-inverse-84",
"components.surfaceHover": "--ds-color-surface-hover",
"components.surfaceRaised": "--ds-color-surface-raised",
"components.accentContrast": "--ds-color-accent-contrast",
"components.accentHover": "--ds-color-accent-hover",
"components.bodyBg": "--ds-color-bg",
// Typography — full family + scale (RFC-0071 expansion)
"typography.headingFamily": "--ds-font-heading",
"typography.bodyFamily": "--ds-font-body",
"typography.monoFamily": "--ds-font-mono",
"typography.baseSize": "--ds-text-base",
"typography.lineHeightBody": "--ds-line-height-body",
"typography.lineHeightHeading": "--ds-line-height-heading",
"typography.lineHeightRelaxed": "--ds-line-height-relaxed",
"typography.measureBody": "--ds-size-container-measure-body",
"typography.measureHeading": "--ds-size-container-measure-heading",
// Spacing — base + layout (RFC-0071)
"spacing.base": "--ds-space-4",
"spacing.sectionPaddingY": "--ds-size-section-padding-y",
"spacing.containerMaxWidth": "--ds-size-container-max",
"spacing.containerNarrow": "--ds-size-container-narrow",
"spacing.gutter": "--ds-size-section-padding-x",
// Motion — duration set + easing (RFC-0071 expansion)
"motion.durationFast": "--ds-duration-fast",
"motion.durationMedium": "--ds-duration-medium",
"motion.durationSlow": "--ds-duration-slow",
"motion.easing": "--ds-ease-biome",
// Axes that surface as live tokens
"axes.cornerRadius": "--ds-radius-xs",
"axes.cornerRadiusFull": "--ds-radius-full",
"axes.borderWeight": "--ds-border-1",
// Font weights
"fontWeights.bold": "--ds-font-weight-bold",
// Geometry — diagram styling (RFC-0071)
"geometry.diagramLineWeight": "--ds-border-diagram-line",
"geometry.diagramAccentColor": "--ds-color-accent",
// RFC-0098: biome-scoped shadows (override studio defaults from tokens.css)
"shadows.sm": "--ds-shadow-sm",
"shadows.md": "--ds-shadow-md",
"shadows.lg": "--ds-shadow-lg",
"shadows.xl": "--ds-shadow-xl",
"shadows.glass": "--ds-shadow-glass",
"shadows.glow": "--ds-shadow-glow",
"shadows.header": "--ds-shadow-header",
"shadows.appeal": "--ds-shadow-appeal",
// RFC-0098: biome-scoped gradients (brand-derived; studio fallback is uncolored)
"gradients.accent": "--ds-gradient-accent",
"gradients.primary": "--ds-gradient-primary",
"gradients.vignetteDark": "--ds-gradient-vignette-dark",
} as const satisfies Record;
// ---------------------------------------------------------------------------
// Alias entries: one biome field produces an additional CSS var beyond the primary
// ---------------------------------------------------------------------------
export interface BiomeTokenAlias {
field: string;
token: string;
}
export const BIOME_TOKEN_ALIASES: readonly BiomeTokenAlias[] = [
{ field: "palette.ink", token: "--ds-color-text-primary" },
{ field: "palette.inkSoft", token: "--ds-color-text-secondary" },
{ field: "typography.lineHeightBody", token: "--ds-line-height-normal" },
{ field: "spacing.base", token: "--ds-size-1" },
{ field: "axes.cornerRadius", token: "--ds-radius-md" },
];
// ---------------------------------------------------------------------------
// Derived entries: computed from multiple biome fields
// ---------------------------------------------------------------------------
export interface BiomeTokenDerived {
token: string;
compute: (biome: Record) => string | undefined;
}
export const BIOME_TOKEN_DERIVED: readonly BiomeTokenDerived[] = [
{
token: "--ds-text-sm",
compute: (biome) => {
const t = biome.typography as Record | undefined;
if (!t?.baseSize || !t?.scaleRatio) return undefined;
const base = parseFloat(String(t.baseSize));
const ratio = parseFloat(String(t.scaleRatio));
if (isNaN(base) || isNaN(ratio)) return undefined;
return `${(base / ratio).toFixed(2)}px`;
},
},
{
token: "--ds-text-lg",
compute: (biome) => {
const t = biome.typography as Record | undefined;
if (!t?.baseSize || !t?.scaleRatio) return undefined;
const base = parseFloat(String(t.baseSize));
const ratio = parseFloat(String(t.scaleRatio));
if (isNaN(base) || isNaN(ratio)) return undefined;
return `${(base * ratio).toFixed(2)}px`;
},
},
{
token: "--ds-text-xl",
compute: (biome) => {
const t = biome.typography as Record | undefined;
if (!t?.baseSize || !t?.scaleRatio) return undefined;
const base = parseFloat(String(t.baseSize));
const ratio = parseFloat(String(t.scaleRatio));
if (isNaN(base) || isNaN(ratio)) return undefined;
return `${(base * ratio * ratio).toFixed(2)}px`;
},
},
];
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Get a value from a nested biome object using a dotted path like "palette.brand". */
export function getBiomeField(biome: Record, field: string): unknown {
return field.split(".").reduce((obj, key) => {
return obj && typeof obj === "object" ? (obj as Record)[key] : undefined;
}, biome);
}
/**
* Project a parsed biome object into a Map of token name to value.
*
* Iterates the primary mapping, then aliases, then derived entries.
* Only includes tokens whose source field is present in the biome.
*/
export function projectBiomeToTokens(biome: Record): Map {
const result = new Map();
for (const [field, token] of Object.entries(BIOME_TO_TOKEN_MAP)) {
const value = getBiomeField(biome, field);
if (value !== undefined && value !== null) {
result.set(token, String(value));
}
}
for (const { field, token } of BIOME_TOKEN_ALIASES) {
const value = getBiomeField(biome, field);
if (value !== undefined && value !== null) {
result.set(token, String(value));
}
}
for (const { token, compute } of BIOME_TOKEN_DERIVED) {
const value = compute(biome);
if (value !== undefined) {
result.set(token, value);
}
}
return result;
}
/**
* All token names produced by the projection (primary + aliases + derived).
* Used by biome.contract.validate to check that every mapped token exists in
* the @warpgogol/werkstatt-site/tokens TOKEN_NAME_SET.
*/
export function getAllProjectedTokenNames(): readonly string[] {
const names = new Set();
for (const token of Object.values(BIOME_TO_TOKEN_MAP)) names.add(token);
for (const { token } of BIOME_TOKEN_ALIASES) names.add(token);
for (const { token } of BIOME_TOKEN_DERIVED) names.add(token);
return [...names];
}