This repository has been archived on 2026-06-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
master-ai/vibn-frontend/lib/design-kits/resolve.ts

106 lines
3.0 KiB
TypeScript

import type { DesignKitOverrides, StarterKitDefinition } from "./types";
export interface ResolvedKitTokens {
accentHex: string;
accentScale: string[];
grayScale: string[];
radiusMdPx: number;
radiusXs: number;
radiusSm: number;
radiusXl: number;
radiusXxl: number;
fontFamily: string;
density: "compact" | "comfortable";
}
/** Fixed CRM-neutral ramp (custom neutral tint could merge later). */
export const GRAY_TWELVE: string[] = [
"#ffffff",
"#fafafa",
"#f5f5f5",
"#ebebeb",
"#e0e0e0",
"#d4d4d4",
"#c4c4c4",
"#a3a3a3",
"#737373",
"#525252",
"#3f3f3f",
"#1a1a1a",
];
function parseHex(hex: string): [number, number, number] | null {
const n = hex.trim().replace(/^#/, "");
if (n.length !== 6 || !/^[0-9a-fA-F]+$/.test(n)) return null;
return [parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16)];
}
function rgbToHex(r: number, g: number, b: number): string {
const x = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0");
return `#${x(r)}${x(g)}${x(b)}`;
}
/** Linear RGB mix (good enough for UI ramps). */
export function mixHex(a: string, b: string, t: number): string {
const A = parseHex(a);
const B = parseHex(b);
if (!A || !B) return a;
const r = Math.round(A[0] + (B[0] - A[0]) * t);
const g = Math.round(A[1] + (B[1] - A[1]) * t);
const bl = Math.round(A[2] + (B[2] - A[2]) * t);
return rgbToHex(r, g, bl);
}
/** Twelve-step accent ramp: light tints → base → deep shadows. */
export function buildAccentScale(base: string): string[] {
const steps: string[] = [];
for (let i = 0; i < 12; i++) {
const t = i / 11;
if (t <= 0.45) {
steps.push(mixHex("#ffffff", base, t / 0.45 * 0.92));
} else {
steps.push(mixHex(base, "#0c0c12", (t - 0.45) / 0.55 * 0.92));
}
}
return steps;
}
export function mergeOverrides(
kit: StarterKitDefinition,
saved?: DesignKitOverrides,
): Required<
Pick<DesignKitOverrides, "accentHex" | "radiusMdPx" | "fontPreset" | "density">
> {
const d = kit.defaults;
const o = saved ?? {};
return {
accentHex: o.accentHex ?? d.accentHex ?? "#5f6bf5",
radiusMdPx: o.radiusMdPx ?? d.radiusMdPx ?? 8,
fontPreset: o.fontPreset ?? d.fontPreset ?? "inter",
density: o.density ?? d.density ?? "comfortable",
};
}
export function resolveKitTokens(
kit: StarterKitDefinition,
saved?: DesignKitOverrides,
): ResolvedKitTokens {
const m = mergeOverrides(kit, saved);
const md = m.radiusMdPx;
return {
accentHex: m.accentHex,
accentScale: buildAccentScale(m.accentHex),
grayScale: GRAY_TWELVE,
radiusMdPx: md,
radiusXs: Math.max(2, Math.round(md * 0.25)),
radiusSm: Math.max(4, Math.round(md * 0.5)),
radiusXl: Math.max(md + 4, Math.round(md * 2.25)),
radiusXxl: Math.max(md + 8, Math.round(md * 4.5)),
fontFamily:
m.fontPreset === "system"
? 'system-ui, -apple-system, "Segoe UI", sans-serif'
: "var(--font-inter), ui-sans-serif, system-ui, sans-serif",
density: m.density,
};
}