learning_ai_common_plat/packages/generative-theme/src/apply.ts
saravanakumardb1 4af06f732b feat(generative-theme): @bytelyst/generative-theme@0.1.0 — Wave 13.E
Brand-prompt → Tier-2 token-override generator. Local deterministic
generator by default; pluggable async LLM hook. WCAG-correct
contrast enforcement with AA / AAA lock policies.

──────────────────────────────────────────────────────────────────
Module surface
──────────────────────────────────────────────────────────────────
  generateThemeFromPrompt(prompt, opts?)
    - opts.generate: optional async LLM hook (host wires real LLM)
    - opts.enforce:  'aa' (default) | 'aaa' | 'off'
    - Always runs the contrast pass when enforce !== 'off'
    - WAVE 13.E.1

  localGenerate(prompt)
    - Synchronous; 7 hand-curated palettes (midnight, citrus,
      forest, ocean, rose, graphite, violet) + default fallback
    - Keyword-matched via regex; deterministic for caching

  Contrast utilities (Wave 13.E.2):
    parseHex / toHex            — input + output round-trip safe
    relativeLuminance           — WCAG 2.x luminance
    contrast(a, b)              — pair contrast ratio
    report(fg, bg)              — { ratio, aa, aaa }
    auditTheme(theme)           — 4 canonical text/accent pairings
    adjustForContrast(fg,bg,t)  — iteratively darken/lighten until ≥ t
    enforceContrast(theme,'aa') — returns adjusted ThemeProposal

  applyTheme(theme, target?)
    - Writes 11 --bl-* custom properties onto the target element
    - Returns a tear-down fn (saved prior values, restored on call)

──────────────────────────────────────────────────────────────────
Quality gates
──────────────────────────────────────────────────────────────────
  ✓ 18/18 tests passing (hex / contrast math / generator /
    enforcement / applyTheme cleanup)
  ✓ tsc build clean
  ✓ Zero runtime deps, pure functions where possible

──────────────────────────────────────────────────────────────────
Roadmap (lands in subsequent commit)
──────────────────────────────────────────────────────────────────
  13.E.1  Deterministic prompt → palette generator
  13.E.2  Contrast checker + AA / AAA enforcement

Showcase route /futurism/theme-studio (MAG.5) lands in paired
showcase commit.
2026-05-27 17:23:06 -07:00

46 lines
1.4 KiB
TypeScript

import type { ThemeProposal } from './types.js';
/**
* Apply a `ThemeProposal` as CSS custom-property overrides on a
* target element. Defaults to `document.documentElement` so the
* entire page re-tints in one call.
*
* Returns a function that *removes* the overrides — useful for
* temporary preview surfaces (e.g. the showcase studio).
*/
export function applyTheme(
theme: ThemeProposal,
target?: HTMLElement,
): () => void {
if (typeof document === 'undefined') return () => {};
const el = target ?? document.documentElement;
const mapping: Array<[keyof ThemeProposal, string]> = [
['accent', '--bl-accent'],
['surface', '--bl-surface'],
['surfaceCard', '--bl-surface-card'],
['surfaceMuted', '--bl-surface-muted'],
['textPrimary', '--bl-text-primary'],
['textSecondary', '--bl-text-secondary'],
['success', '--bl-success'],
['warning', '--bl-warning'],
['danger', '--bl-danger'],
['info', '--bl-info'],
['border', '--bl-border'],
];
const previous = mapping.map(
([_, css]) => [css, el.style.getPropertyValue(css)] as const,
);
for (const [k, css] of mapping) {
const v = theme[k];
if (typeof v === 'string') {
el.style.setProperty(css, v);
}
}
return () => {
for (const [css, prev] of previous) {
if (prev) el.style.setProperty(css, prev);
else el.style.removeProperty(css);
}
};
}