@hewnui/themes 0.0.0-stage → 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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -2
  3. package/dist/authoring/css-vars.d.ts +10 -0
  4. package/dist/authoring/css-vars.js +83 -0
  5. package/dist/authoring/derive.d.ts +9 -0
  6. package/dist/authoring/derive.js +79 -0
  7. package/dist/authoring/export-css.d.ts +8 -0
  8. package/dist/authoring/export-css.js +60 -0
  9. package/dist/authoring/export-design-md.d.ts +5 -0
  10. package/dist/authoring/export-design-md.js +113 -0
  11. package/dist/authoring/export-dtcg.d.ts +6 -0
  12. package/dist/authoring/export-dtcg.js +85 -0
  13. package/dist/authoring/export-figma.d.ts +27 -0
  14. package/dist/authoring/export-figma.js +76 -0
  15. package/dist/authoring/export-formats.d.ts +27 -0
  16. package/dist/authoring/export-formats.js +62 -0
  17. package/dist/authoring/export-skill.d.ts +5 -0
  18. package/dist/authoring/export-skill.js +75 -0
  19. package/dist/authoring/export-sticker.d.ts +6 -0
  20. package/dist/authoring/export-sticker.js +54 -0
  21. package/dist/authoring/export-style-dictionary.d.ts +3 -0
  22. package/dist/authoring/export-style-dictionary.js +52 -0
  23. package/dist/authoring/figma-kit.d.ts +16 -0
  24. package/dist/authoring/figma-kit.js +41 -0
  25. package/dist/authoring/index.d.ts +21 -0
  26. package/dist/authoring/index.js +18 -0
  27. package/dist/authoring/oklch.d.ts +11 -0
  28. package/dist/authoring/oklch.js +116 -0
  29. package/dist/authoring/override-map.d.ts +9 -0
  30. package/dist/authoring/override-map.js +150 -0
  31. package/dist/authoring/pack-def.d.ts +7 -0
  32. package/dist/authoring/pack-def.js +3 -0
  33. package/dist/authoring/pack-meta.d.ts +10 -0
  34. package/dist/authoring/pack-meta.js +58 -0
  35. package/dist/authoring/packs-dual.d.ts +129 -0
  36. package/dist/authoring/packs-dual.js +130 -0
  37. package/dist/authoring/packs-single.d.ts +87 -0
  38. package/dist/authoring/packs-single.js +88 -0
  39. package/dist/authoring/packs.d.ts +8 -0
  40. package/dist/authoring/packs.js +26 -0
  41. package/dist/authoring/pro-apply.d.ts +8 -0
  42. package/dist/authoring/pro-apply.js +107 -0
  43. package/dist/authoring/propose.d.ts +21 -0
  44. package/dist/authoring/propose.js +162 -0
  45. package/dist/authoring/ramps.d.ts +13 -0
  46. package/dist/authoring/ramps.js +40 -0
  47. package/dist/authoring/status-ramps.d.ts +13 -0
  48. package/dist/authoring/status-ramps.js +76 -0
  49. package/dist/authoring/token-entries.d.ts +10 -0
  50. package/dist/authoring/token-entries.js +52 -0
  51. package/dist/authoring/types.d.ts +110 -0
  52. package/dist/authoring/types.js +11 -0
  53. package/exports/clay/DESIGN.md +138 -0
  54. package/exports/clay/SKILL.md +94 -0
  55. package/exports/clay/theme.css +37 -0
  56. package/exports/clay/tokens.dtcg.json +200 -0
  57. package/exports/dune/DESIGN.md +138 -0
  58. package/exports/dune/SKILL.md +94 -0
  59. package/exports/dune/theme.css +37 -0
  60. package/exports/dune/tokens.dtcg.json +200 -0
  61. package/exports/ember/DESIGN.md +138 -0
  62. package/exports/ember/SKILL.md +94 -0
  63. package/exports/ember/theme.css +37 -0
  64. package/exports/ember/tokens.dtcg.json +200 -0
  65. package/exports/forest/DESIGN.md +138 -0
  66. package/exports/forest/SKILL.md +94 -0
  67. package/exports/forest/theme.css +67 -0
  68. package/exports/forest/tokens.dtcg.json +348 -0
  69. package/exports/graphite/DESIGN.md +138 -0
  70. package/exports/graphite/SKILL.md +94 -0
  71. package/exports/graphite/theme.css +67 -0
  72. package/exports/graphite/tokens.dtcg.json +348 -0
  73. package/exports/index.json +45 -0
  74. package/exports/tide/DESIGN.md +138 -0
  75. package/exports/tide/SKILL.md +94 -0
  76. package/exports/tide/theme.css +67 -0
  77. package/exports/tide/tokens.dtcg.json +348 -0
  78. package/figma/README.md +16 -0
  79. package/figma/hewn.tokens.json +1308 -0
  80. package/figma/sticker-sheet.svg +93 -0
  81. package/package.json +98 -4
  82. package/src/authoring/css-vars.ts +92 -0
  83. package/src/authoring/derive.ts +106 -0
  84. package/src/authoring/export-css.ts +86 -0
  85. package/src/authoring/export-design-md.ts +134 -0
  86. package/src/authoring/export-dtcg.ts +116 -0
  87. package/src/authoring/export-figma.ts +109 -0
  88. package/src/authoring/export-formats.ts +101 -0
  89. package/src/authoring/export-skill.ts +93 -0
  90. package/src/authoring/export-sticker.ts +60 -0
  91. package/src/authoring/export-style-dictionary.ts +67 -0
  92. package/src/authoring/figma-kit.ts +60 -0
  93. package/src/authoring/index.ts +61 -0
  94. package/src/authoring/oklch.ts +137 -0
  95. package/src/authoring/override-map.ts +191 -0
  96. package/src/authoring/pack-def.ts +11 -0
  97. package/src/authoring/pack-meta.ts +66 -0
  98. package/src/authoring/packs-dual.ts +131 -0
  99. package/src/authoring/packs-single.ts +89 -0
  100. package/src/authoring/packs.ts +43 -0
  101. package/src/authoring/pro-apply.ts +156 -0
  102. package/src/authoring/propose.ts +181 -0
  103. package/src/authoring/ramps.ts +62 -0
  104. package/src/authoring/status-ramps.ts +98 -0
  105. package/src/authoring/token-entries.ts +59 -0
  106. package/src/authoring/types.ts +133 -0
  107. package/src/glass-buttons.css +46 -0
  108. package/src/glass.css +266 -0
  109. package/src/motion.css +60 -0
  110. package/src/packs/clay.css +45 -0
  111. package/src/packs/dune.css +45 -0
  112. package/src/packs/ember.css +45 -0
  113. package/src/packs/forest.css +80 -0
  114. package/src/packs/graphite.css +79 -0
  115. package/src/packs/tide.css +79 -0
  116. package/src/radius.css +25 -0
  117. package/src/tokens.css +104 -0
@@ -0,0 +1,62 @@
1
+ import { exportDesignMd } from "./export-design-md.js";
2
+ import { exportDtcg } from "./export-dtcg.js";
3
+ import { exportTailwindTheme } from "./export-css.js";
4
+ import { exportSkillMd } from "./export-skill.js";
5
+ /** Free curated Theme Lab / docs site formats. */
6
+ export const THEME_EXPORT_FORMATS = [
7
+ {
8
+ id: "design-md",
9
+ label: "DESIGN.md",
10
+ description: "Human- and AI-readable design brief with OKLCH roles and rules",
11
+ filename: (packId) => `${packId}.DESIGN.md`,
12
+ mime: "text/markdown;charset=utf-8",
13
+ tier: "free",
14
+ render: exportDesignMd,
15
+ },
16
+ {
17
+ id: "dtcg",
18
+ label: "DTCG tokens",
19
+ description: "W3C Design Tokens JSON ($value / $type) with light and dark modes",
20
+ filename: (packId) => `${packId}.tokens.dtcg.json`,
21
+ mime: "application/json",
22
+ tier: "free",
23
+ render: exportDtcg,
24
+ },
25
+ {
26
+ id: "tailwind-theme",
27
+ label: "Tailwind @theme",
28
+ description: "Tailwind CSS v4 @theme file with mode remaps",
29
+ filename: (packId) => `${packId}.theme.css`,
30
+ mime: "text/css;charset=utf-8",
31
+ tier: "free",
32
+ render: exportTailwindTheme,
33
+ },
34
+ {
35
+ id: "skill-md",
36
+ label: "Agent skill",
37
+ description: "SKILL.md instructions for coding agents using this theme",
38
+ filename: (packId) => `${packId}.SKILL.md`,
39
+ mime: "text/markdown;charset=utf-8",
40
+ tier: "free",
41
+ render: exportSkillMd,
42
+ },
43
+ ];
44
+ export function getThemeExportFormat(id) {
45
+ const format = THEME_EXPORT_FORMATS.find((entry) => entry.id === id);
46
+ if (!format)
47
+ throw new Error(`Unknown theme export format: ${id}`);
48
+ return format;
49
+ }
50
+ export function renderThemeExport(id, pack) {
51
+ return getThemeExportFormat(id).render(pack);
52
+ }
53
+ export const THEME_EXPORT_BASENAMES = {
54
+ "design-md": "DESIGN.md",
55
+ dtcg: "tokens.dtcg.json",
56
+ "tailwind-theme": "theme.css",
57
+ "skill-md": "SKILL.md",
58
+ };
59
+ /** Static public path for a generated curated export. */
60
+ export function themeExportPublicPath(packId, id) {
61
+ return `/themes/${packId}/${THEME_EXPORT_BASENAMES[id]}`;
62
+ }
@@ -0,0 +1,5 @@
1
+ import type { ResolvedPack } from "./types";
2
+ /**
3
+ * Free — Cursor-style agent skill (SKILL.md) for applying this theme correctly.
4
+ */
5
+ export declare function exportSkillMd(pack: ResolvedPack): string;
@@ -0,0 +1,75 @@
1
+ import { COLOR_ROLES, DESIGN_DO, DESIGN_DONT, PACK_IDENTITY, } from "./pack-meta.js";
2
+ import { colorEntries, modeOrder, primaryMode, structuralEntries, } from "./token-entries.js";
3
+ function identity(pack) {
4
+ return (PACK_IDENTITY[pack.basePackId] ?? {
5
+ label: pack.id,
6
+ summary: `Custom pack based on ${pack.basePackId}.`,
7
+ });
8
+ }
9
+ /**
10
+ * Free — Cursor-style agent skill (SKILL.md) for applying this theme correctly.
11
+ */
12
+ export function exportSkillMd(pack) {
13
+ const { label, summary } = identity(pack);
14
+ const modes = modeOrder(pack);
15
+ const mode = primaryMode(pack);
16
+ const skillName = `hewn-${pack.id}-theme`;
17
+ const colorLines = colorEntries(mode)
18
+ .map((e) => `| \`--kp-${e.name}\` / \`--color-${e.name}\` | ${COLOR_ROLES[e.name] ?? e.name} | \`${e.value}\` |`)
19
+ .join("\n");
20
+ const structuralLines = structuralEntries(mode)
21
+ .map((e) => `- \`--kp-${e.name}\`: \`${e.value}\``)
22
+ .join("\n");
23
+ return `---
24
+ name: ${skillName}
25
+ description: Apply the Hewn ${label} theme tokens, typography, motion, and design rules when building or restyling UI.
26
+ ---
27
+
28
+ # ${label} theme (Hewn)
29
+
30
+ ${summary}
31
+
32
+ Use this skill whenever generating UI that should match the **${label}** pack
33
+ (\`${pack.id}\`). Supported modes: **${modes.join(", ")}**.
34
+
35
+ ## Quick rules
36
+
37
+ ${DESIGN_DO.map((item) => `- ${item}`).join("\n")}
38
+
39
+ ## Never
40
+
41
+ ${DESIGN_DONT.map((item) => `- ${item}`).join("\n")}
42
+
43
+ ## How to apply
44
+
45
+ 1. Set \`data-theme="${pack.id}"\` and \`data-mode="{${modes.join("|")}}"\` on a root element (or import the generated \`@theme\` CSS).
46
+ 2. Prefer semantic utilities / CSS variables (\`bg-background\`, \`text-foreground\`, \`bg-accent\`, \`border-border\`, \`--kp-*\`).
47
+ 3. Respect reduced motion — disable springs/shimmers when \`prefers-reduced-motion: reduce\`.
48
+ 4. Keep glass as a global material toggle; do not add per-component glass props.
49
+
50
+ ## Color tokens (primary mode: ${mode.mode})
51
+
52
+ | Token | Role | Value |
53
+ |---|---|---|
54
+ ${colorLines}
55
+
56
+ ## Structure
57
+
58
+ ${structuralLines}
59
+
60
+ ## Typography
61
+
62
+ - **Sans:** IBM Plex Sans — UI and body
63
+ - **Mono:** IBM Plex Mono — labels, code, metrics (\`tabular-nums\`)
64
+ - **Display:** Space Grotesk — hero H1 / brand wordmark ≥18px only
65
+
66
+ ## Export companions
67
+
68
+ - \`DESIGN.md\` — full design brief
69
+ - \`tokens.dtcg.json\` — DTCG (\`$value\` / \`$type\`) with light/dark modes
70
+ - \`theme.css\` — Tailwind v4 \`@theme\` block
71
+
72
+ When unsure, re-read \`DESIGN.md\` for this pack and keep accents within the
73
+ curated ramp — do not invent a new hue family.
74
+ `;
75
+ }
@@ -0,0 +1,6 @@
1
+ import type { ResolvedPack } from "./types";
2
+ /**
3
+ * Core sticker sheet SVG — surfaces, button, and input per pack.
4
+ * Use as a Figma community kit reference (paste or import SVG).
5
+ */
6
+ export declare function exportStickerSheetSvg(packs: readonly ResolvedPack[]): string;
@@ -0,0 +1,54 @@
1
+ import { formatOklch } from "./oklch.js";
2
+ import { primaryMode } from "./token-entries.js";
3
+ const ROW_H = 96;
4
+ const PAD = 24;
5
+ const COL_W = 120;
6
+ const LABEL_W = 88;
7
+ /**
8
+ * Core sticker sheet SVG — surfaces, button, and input per pack.
9
+ * Use as a Figma community kit reference (paste or import SVG).
10
+ */
11
+ export function exportStickerSheetSvg(packs) {
12
+ const width = PAD * 2 + LABEL_W + COL_W * 4;
13
+ const height = PAD * 2 + 40 + packs.length * ROW_H;
14
+ const rows = packs
15
+ .map((pack, index) => renderRow(pack, PAD + 40 + index * ROW_H))
16
+ .join("\n");
17
+ return `<?xml version="1.0" encoding="UTF-8"?>
18
+ <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="Hewn sticker sheet">
19
+ <rect width="100%" height="100%" fill="#f4f4f2"/>
20
+ <text x="${PAD}" y="${PAD + 16}" font-family="IBM Plex Sans, ui-sans-serif, system-ui, sans-serif" font-size="16" font-weight="600" fill="#1a1a18">Hewn · sticker sheet</text>
21
+ <text x="${PAD + LABEL_W}" y="${PAD + 34}" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" fill="#6b6b66">surface</text>
22
+ <text x="${PAD + LABEL_W + COL_W}" y="${PAD + 34}" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" fill="#6b6b66">raised</text>
23
+ <text x="${PAD + LABEL_W + COL_W * 2}" y="${PAD + 34}" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" fill="#6b6b66">button</text>
24
+ <text x="${PAD + LABEL_W + COL_W * 3}" y="${PAD + 34}" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" fill="#6b6b66">input</text>
25
+ ${rows}
26
+ </svg>
27
+ `;
28
+ }
29
+ function renderRow(pack, y) {
30
+ const mode = primaryMode(pack);
31
+ const bg = formatOklch(mode.background);
32
+ const surface = formatOklch(mode.surface);
33
+ const raised = formatOklch(mode.surfaceRaised);
34
+ const accent = formatOklch(mode.accent);
35
+ const accentFg = formatOklch(mode.accentForeground);
36
+ const fg = formatOklch(mode.foreground);
37
+ const border = formatOklch(mode.border);
38
+ const muted = formatOklch(mode.mutedForeground);
39
+ const x0 = PAD + LABEL_W;
40
+ return ` <g>
41
+ <text x="${PAD}" y="${y + 42}" font-family="IBM Plex Sans, ui-sans-serif, system-ui, sans-serif" font-size="13" fill="#1a1a18">${pack.id}</text>
42
+ <rect x="${x0}" y="${y}" width="100" height="72" rx="10" fill="${bg}" stroke="${border}" stroke-width="1"/>
43
+ <rect x="${x0 + 12}" y="${y + 14}" width="76" height="44" rx="8" fill="${surface}" stroke="${border}" stroke-width="1"/>
44
+ <rect x="${x0 + COL_W}" y="${y}" width="100" height="72" rx="10" fill="${bg}" stroke="${border}" stroke-width="1"/>
45
+ <rect x="${x0 + COL_W + 12}" y="${y + 14}" width="76" height="44" rx="8" fill="${raised}" stroke="${border}" stroke-width="1"/>
46
+ <rect x="${x0 + COL_W * 2}" y="${y}" width="100" height="72" rx="10" fill="${bg}" stroke="${border}" stroke-width="1"/>
47
+ <rect x="${x0 + COL_W * 2 + 14}" y="${y + 22}" width="72" height="28" rx="8" fill="${accent}"/>
48
+ <text x="${x0 + COL_W * 2 + 50}" y="${y + 40}" text-anchor="middle" font-family="IBM Plex Sans, ui-sans-serif, system-ui, sans-serif" font-size="11" fill="${accentFg}">Button</text>
49
+ <rect x="${x0 + COL_W * 3}" y="${y}" width="100" height="72" rx="10" fill="${bg}" stroke="${border}" stroke-width="1"/>
50
+ <rect x="${x0 + COL_W * 3 + 10}" y="${y + 22}" width="80" height="28" rx="8" fill="${surface}" stroke="${border}" stroke-width="1"/>
51
+ <text x="${x0 + COL_W * 3 + 18}" y="${y + 40}" font-family="IBM Plex Sans, ui-sans-serif, system-ui, sans-serif" font-size="11" fill="${muted}">Input</text>
52
+ <text x="${x0 + COL_W * 3 + 70}" y="${y + 40}" font-family="IBM Plex Sans, ui-sans-serif, system-ui, sans-serif" font-size="9" fill="${fg}" opacity="0.35">|</text>
53
+ </g>`;
54
+ }
@@ -0,0 +1,3 @@
1
+ import type { ResolvedPack } from "./types";
2
+ /** Pro — Style Dictionary–friendly nested token JSON (all modes). */
3
+ export declare function exportStyleDictionary(pack: ResolvedPack): string;
@@ -0,0 +1,52 @@
1
+ import { colorEntries, modeOrder, primaryMode, structuralEntries, } from "./token-entries.js";
2
+ function setPath(root, path, leaf) {
3
+ let cursor = root;
4
+ for (let i = 0; i < path.length - 1; i += 1) {
5
+ const key = path[i];
6
+ const existing = cursor[key];
7
+ if (!existing || typeof existing !== "object" || "value" in existing) {
8
+ cursor[key] = {};
9
+ }
10
+ cursor = cursor[key];
11
+ }
12
+ cursor[path[path.length - 1]] = leaf;
13
+ }
14
+ function parseNumeric(value) {
15
+ const asNum = Number(value);
16
+ return Number.isFinite(asNum) && value.trim() !== "" ? asNum : value;
17
+ }
18
+ /** Pro — Style Dictionary–friendly nested token JSON (all modes). */
19
+ export function exportStyleDictionary(pack) {
20
+ const tree = {};
21
+ const modes = modeOrder(pack);
22
+ for (const modeName of modes) {
23
+ const mode = pack.modes[modeName];
24
+ if (!mode)
25
+ continue;
26
+ for (const entry of colorEntries(mode)) {
27
+ setPath(tree, ["color", entry.name, modeName], {
28
+ value: entry.value,
29
+ type: "color",
30
+ });
31
+ }
32
+ }
33
+ const primary = primaryMode(pack);
34
+ for (const entry of structuralEntries(primary)) {
35
+ const [group, ...rest] = entry.name.split("-");
36
+ const path = rest.length > 0 ? [group, rest.join("-")] : [entry.name];
37
+ setPath(tree, path, {
38
+ value: parseNumeric(entry.value),
39
+ type: entry.kind === "number" ? "number" : "dimension",
40
+ });
41
+ }
42
+ setPath(tree, ["meta", "packId"], { value: pack.id, type: "other" });
43
+ setPath(tree, ["meta", "basePackId"], {
44
+ value: pack.basePackId,
45
+ type: "other",
46
+ });
47
+ setPath(tree, ["meta", "supportedModes"], {
48
+ value: modes.join(","),
49
+ type: "other",
50
+ });
51
+ return `${JSON.stringify(tree, null, 2)}\n`;
52
+ }
@@ -0,0 +1,16 @@
1
+ import { type ThemeExportFormatId } from "./export-formats";
2
+ import { type BasePackId, type ResolvedPack } from "./types";
3
+ /** Derive curated packs with no overrides (canonical kit source). */
4
+ export declare function curatedResolvedPacks(): ResolvedPack[];
5
+ export declare function curatedResolvedPack(basePackId: BasePackId): ResolvedPack;
6
+ export declare function exportCuratedFigmaKit(): string;
7
+ export declare function exportCuratedStickerSheet(): string;
8
+ export type CuratedThemeExportFile = {
9
+ packId: string;
10
+ formatId: ThemeExportFormatId;
11
+ /** Stable basename under `/themes/{packId}/` */
12
+ basename: string;
13
+ contents: string;
14
+ };
15
+ /** All free Theme Lab formats × six curated packs. */
16
+ export declare function exportCuratedThemeFiles(): CuratedThemeExportFile[];
@@ -0,0 +1,41 @@
1
+ import { derivePack } from "./derive.js";
2
+ import { exportFigmaKit } from "./export-figma.js";
3
+ import { THEME_EXPORT_BASENAMES, THEME_EXPORT_FORMATS, } from "./export-formats.js";
4
+ import { exportStickerSheetSvg } from "./export-sticker.js";
5
+ import { BASE_PACK_IDS } from "./types.js";
6
+ /** Derive curated packs with no overrides (canonical kit source). */
7
+ export function curatedResolvedPacks() {
8
+ return BASE_PACK_IDS.map((basePackId) => curatedResolvedPack(basePackId));
9
+ }
10
+ export function curatedResolvedPack(basePackId) {
11
+ const result = derivePack({
12
+ schemaVersion: 2,
13
+ basePackId,
14
+ packId: basePackId,
15
+ });
16
+ if (!result.ok) {
17
+ throw new Error(`failed to derive curated pack ${basePackId}: ${result.errors.join(", ")}`);
18
+ }
19
+ return result.pack;
20
+ }
21
+ export function exportCuratedFigmaKit() {
22
+ return exportFigmaKit(curatedResolvedPacks());
23
+ }
24
+ export function exportCuratedStickerSheet() {
25
+ return exportStickerSheetSvg(curatedResolvedPacks());
26
+ }
27
+ /** All free Theme Lab formats × six curated packs. */
28
+ export function exportCuratedThemeFiles() {
29
+ const files = [];
30
+ for (const pack of curatedResolvedPacks()) {
31
+ for (const format of THEME_EXPORT_FORMATS) {
32
+ files.push({
33
+ packId: pack.id,
34
+ formatId: format.id,
35
+ basename: THEME_EXPORT_BASENAMES[format.id],
36
+ contents: format.render(pack),
37
+ });
38
+ }
39
+ }
40
+ return files;
41
+ }
@@ -0,0 +1,21 @@
1
+ export { derivePack } from "./derive";
2
+ export type { DeriveResult } from "./derive";
3
+ export { exportPackCss, exportTailwindTheme } from "./export-css";
4
+ export { exportDesignMd, sampleAccent } from "./export-design-md";
5
+ export { exportDtcg } from "./export-dtcg";
6
+ export { getThemeExportFormat, renderThemeExport, themeExportPublicPath, THEME_EXPORT_BASENAMES, THEME_EXPORT_FORMATS, type ExportTier, type ThemeExportFormat, type ThemeExportFormatId, } from "./export-formats";
7
+ export { exportSkillMd } from "./export-skill";
8
+ export { modeToCssVars, modeToPreviewCss } from "./css-vars";
9
+ export { exportFigmaKit, exportFigmaVariables } from "./export-figma";
10
+ export { exportStyleDictionary } from "./export-style-dictionary";
11
+ export { exportStickerSheetSvg } from "./export-sticker";
12
+ export { curatedResolvedPack, curatedResolvedPacks, exportCuratedFigmaKit, exportCuratedStickerSheet, exportCuratedThemeFiles, type CuratedThemeExportFile, } from "./figma-kit";
13
+ export { contrastForeground, contrastRatio, formatHex, formatOklch, parseColor } from "./oklch";
14
+ export { normalizeOverrideMap } from "./override-map";
15
+ export type { NormalizeResult } from "./override-map";
16
+ export { PACK_IDENTITY, DESIGN_DO, DESIGN_DONT } from "./pack-meta";
17
+ export { BASE_PACKS, supportedModes } from "./packs";
18
+ export { extractColorsFromCss, parseAiProposal, pickAccentFromColors, proposeFromPalette, proposeFromPrompt, proposalToOverrideMap, suggestBasePack, suggestRadius, } from "./propose";
19
+ export type { ThemeProposal } from "./propose";
20
+ export { accentSetFromSeed } from "./ramps";
21
+ export { BASE_PACK_IDS, PRO_AUTHORING_UNLOCKED, SCHEMA_VERSION, type BasePackId, type DensityPreset, type GlassOverrides, type MotionCharacter, type NeutralOverrides, type OverrideMap, type RadiusPreset, type ResolvedPack, type SemanticOverrides, type ShapeOverrides, type ThemeMode, } from "./types";
@@ -0,0 +1,18 @@
1
+ export { derivePack } from "./derive.js";
2
+ export { exportPackCss, exportTailwindTheme } from "./export-css.js";
3
+ export { exportDesignMd, sampleAccent } from "./export-design-md.js";
4
+ export { exportDtcg } from "./export-dtcg.js";
5
+ export { getThemeExportFormat, renderThemeExport, themeExportPublicPath, THEME_EXPORT_BASENAMES, THEME_EXPORT_FORMATS, } from "./export-formats.js";
6
+ export { exportSkillMd } from "./export-skill.js";
7
+ export { modeToCssVars, modeToPreviewCss } from "./css-vars.js";
8
+ export { exportFigmaKit, exportFigmaVariables } from "./export-figma.js";
9
+ export { exportStyleDictionary } from "./export-style-dictionary.js";
10
+ export { exportStickerSheetSvg } from "./export-sticker.js";
11
+ export { curatedResolvedPack, curatedResolvedPacks, exportCuratedFigmaKit, exportCuratedStickerSheet, exportCuratedThemeFiles, } from "./figma-kit.js";
12
+ export { contrastForeground, contrastRatio, formatHex, formatOklch, parseColor } from "./oklch.js";
13
+ export { normalizeOverrideMap } from "./override-map.js";
14
+ export { PACK_IDENTITY, DESIGN_DO, DESIGN_DONT } from "./pack-meta.js";
15
+ export { BASE_PACKS, supportedModes } from "./packs.js";
16
+ export { extractColorsFromCss, parseAiProposal, pickAccentFromColors, proposeFromPalette, proposeFromPrompt, proposalToOverrideMap, suggestBasePack, suggestRadius, } from "./propose.js";
17
+ export { accentSetFromSeed } from "./ramps.js";
18
+ export { BASE_PACK_IDS, PRO_AUTHORING_UNLOCKED, SCHEMA_VERSION, } from "./types.js";
@@ -0,0 +1,11 @@
1
+ import type { Oklch } from "./types";
2
+ export declare function formatOklch(color: Oklch): string;
3
+ export declare function formatHex(color: Oklch): string;
4
+ export declare function parseColor(input: string): Oklch | null;
5
+ export declare function relativeLuminance(color: Oklch): number;
6
+ export declare function contrastRatio(a: Oklch, b: Oklch): number;
7
+ /** Pick black or white foreground that meets AA (4.5:1) against the background. */
8
+ export declare function contrastForeground(background: Oklch, prefer: Oklch, minRatio?: number): {
9
+ color: Oklch;
10
+ adjusted: boolean;
11
+ };
@@ -0,0 +1,116 @@
1
+ const OKLCH_RE = /^oklch\(\s*([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)(?:\s*\/\s*([0-9.]+)%?)?\s*\)$/i;
2
+ const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
3
+ export function formatOklch(color) {
4
+ const { l, c, h, alpha } = color;
5
+ const base = `oklch(${round(l)} ${round(c)} ${round(h)}`;
6
+ if (alpha === undefined || alpha >= 1)
7
+ return `${base})`;
8
+ return `${base} / ${Math.round(alpha * 100)}%)`;
9
+ }
10
+ export function formatHex(color) {
11
+ const [r, g, b] = oklchToSrgb(color);
12
+ const toByte = (channel) => Math.round(Math.min(1, Math.max(0, channel)) * 255);
13
+ return `#${[toByte(r), toByte(g), toByte(b)]
14
+ .map((n) => n.toString(16).padStart(2, "0"))
15
+ .join("")}`;
16
+ }
17
+ export function parseColor(input) {
18
+ const trimmed = input.trim();
19
+ const oklch = trimmed.match(OKLCH_RE);
20
+ if (oklch) {
21
+ return {
22
+ l: Number(oklch[1]),
23
+ c: Number(oklch[2]),
24
+ h: Number(oklch[3]),
25
+ ...(oklch[4] !== undefined
26
+ ? {
27
+ alpha: Number(oklch[4]) > 1
28
+ ? Number(oklch[4]) / 100
29
+ : Number(oklch[4]),
30
+ }
31
+ : {}),
32
+ };
33
+ }
34
+ const hex = trimmed.match(HEX_RE);
35
+ if (!hex?.[1])
36
+ return null;
37
+ return srgbToOklch(hexToSrgb(hex[1]));
38
+ }
39
+ export function relativeLuminance(color) {
40
+ const [r, g, b] = oklchToSrgb(color);
41
+ const lin = (channel) => {
42
+ const c = Math.min(1, Math.max(0, channel));
43
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
44
+ };
45
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
46
+ }
47
+ export function contrastRatio(a, b) {
48
+ const l1 = relativeLuminance(a);
49
+ const l2 = relativeLuminance(b);
50
+ const lighter = Math.max(l1, l2);
51
+ const darker = Math.min(l1, l2);
52
+ return (lighter + 0.05) / (darker + 0.05);
53
+ }
54
+ /** Pick black or white foreground that meets AA (4.5:1) against the background. */
55
+ export function contrastForeground(background, prefer, minRatio = 4.5) {
56
+ if (contrastRatio(prefer, background) >= minRatio) {
57
+ return { color: prefer, adjusted: false };
58
+ }
59
+ const white = { l: 0.99, c: 0, h: background.h };
60
+ const black = { l: 0.18, c: 0, h: background.h };
61
+ const whiteRatio = contrastRatio(white, background);
62
+ const blackRatio = contrastRatio(black, background);
63
+ return {
64
+ color: whiteRatio >= blackRatio ? white : black,
65
+ adjusted: true,
66
+ };
67
+ }
68
+ function round(value) {
69
+ return Math.round(value * 1000) / 1000;
70
+ }
71
+ function hexToSrgb(hex) {
72
+ const full = hex.length === 3
73
+ ? hex
74
+ .split("")
75
+ .map((ch) => ch + ch)
76
+ .join("")
77
+ : hex;
78
+ const n = Number.parseInt(full, 16);
79
+ return [(n >> 16) / 255, ((n >> 8) & 0xff) / 255, (n & 0xff) / 255];
80
+ }
81
+ /** Compact OKLCH ↔ sRGB (Björn Ottosson). Good enough for contrast floors. */
82
+ function oklchToSrgb(color) {
83
+ const a = color.c * Math.cos((color.h * Math.PI) / 180);
84
+ const b = color.c * Math.sin((color.h * Math.PI) / 180);
85
+ const l_ = color.l + 0.3963377774 * a + 0.2158037573 * b;
86
+ const m_ = color.l - 0.1055613458 * a - 0.0638541728 * b;
87
+ const s_ = color.l - 0.0894841775 * a - 1.291485548 * b;
88
+ const l = l_ ** 3;
89
+ const m = m_ ** 3;
90
+ const s = s_ ** 3;
91
+ return [
92
+ +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
93
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
94
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
95
+ ];
96
+ }
97
+ function srgbToOklch([r, g, b]) {
98
+ const toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
99
+ const rl = toLinear(r);
100
+ const gl = toLinear(g);
101
+ const bl = toLinear(b);
102
+ const l = 0.4122214708 * rl + 0.5363325363 * gl + 0.0514459929 * bl;
103
+ const m = 0.2119034982 * rl + 0.6806995451 * gl + 0.1073969566 * bl;
104
+ const s = 0.0883024619 * rl + 0.2817188376 * gl + 0.6299787005 * bl;
105
+ const l_ = Math.cbrt(l);
106
+ const m_ = Math.cbrt(m);
107
+ const s_ = Math.cbrt(s);
108
+ const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
109
+ const A = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
110
+ const B = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
111
+ const C = Math.sqrt(A * A + B * B);
112
+ let H = (Math.atan2(B, A) * 180) / Math.PI;
113
+ if (H < 0)
114
+ H += 360;
115
+ return { l: L, c: C, h: H };
116
+ }
@@ -0,0 +1,9 @@
1
+ import { type OverrideMap } from "./types";
2
+ export type NormalizeResult = {
3
+ ok: true;
4
+ map: OverrideMap;
5
+ } | {
6
+ ok: false;
7
+ errors: string[];
8
+ };
9
+ export declare function normalizeOverrideMap(value: unknown): NormalizeResult;
@@ -0,0 +1,150 @@
1
+ import { BASE_PACK_IDS, SCHEMA_VERSION, } from "./types.js";
2
+ const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3
+ const RADIUS = new Set(["sharp", "soft", "rounded", "pill"]);
4
+ const DENSITY = new Set(["compact", "default", "comfortable"]);
5
+ const MOTION = new Set(["snappy", "balanced", "soft"]);
6
+ function clamp(value, min, max) {
7
+ return Math.min(max, Math.max(min, value));
8
+ }
9
+ function readNeutrals(value, errors) {
10
+ if (value === undefined)
11
+ return undefined;
12
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
13
+ errors.push("neutrals must be an object");
14
+ return undefined;
15
+ }
16
+ const record = value;
17
+ const next = {};
18
+ if (record.surfaceShift !== undefined) {
19
+ if (typeof record.surfaceShift !== "number" || Number.isNaN(record.surfaceShift)) {
20
+ errors.push("neutrals.surfaceShift must be a number");
21
+ }
22
+ else {
23
+ next.surfaceShift = clamp(record.surfaceShift, -0.08, 0.08);
24
+ }
25
+ }
26
+ if (record.chromaScale !== undefined) {
27
+ if (typeof record.chromaScale !== "number" || Number.isNaN(record.chromaScale)) {
28
+ errors.push("neutrals.chromaScale must be a number");
29
+ }
30
+ else {
31
+ next.chromaScale = clamp(record.chromaScale, 0.5, 1.5);
32
+ }
33
+ }
34
+ return Object.keys(next).length > 0 ? next : undefined;
35
+ }
36
+ function readSemantic(value, errors) {
37
+ if (value === undefined)
38
+ return undefined;
39
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
40
+ errors.push("semantic must be an object");
41
+ return undefined;
42
+ }
43
+ const record = value;
44
+ if (record.dangerSeed === undefined)
45
+ return undefined;
46
+ if (typeof record.dangerSeed !== "string") {
47
+ errors.push("semantic.dangerSeed must be a string");
48
+ return undefined;
49
+ }
50
+ return { dangerSeed: record.dangerSeed };
51
+ }
52
+ function readShape(value, errors) {
53
+ if (value === undefined)
54
+ return undefined;
55
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
56
+ errors.push("shape must be an object");
57
+ return undefined;
58
+ }
59
+ const record = value;
60
+ if (record.density === undefined)
61
+ return undefined;
62
+ if (typeof record.density !== "string" || !DENSITY.has(record.density)) {
63
+ errors.push("shape.density must be compact | default | comfortable");
64
+ return undefined;
65
+ }
66
+ return { density: record.density };
67
+ }
68
+ function readGlass(value, errors) {
69
+ if (value === undefined)
70
+ return undefined;
71
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
72
+ errors.push("glass must be an object");
73
+ return undefined;
74
+ }
75
+ const record = value;
76
+ if (record.tintStrength === undefined)
77
+ return undefined;
78
+ if (typeof record.tintStrength !== "number" || Number.isNaN(record.tintStrength)) {
79
+ errors.push("glass.tintStrength must be a number");
80
+ return undefined;
81
+ }
82
+ return { tintStrength: clamp(record.tintStrength, 0.5, 1.5) };
83
+ }
84
+ export function normalizeOverrideMap(value) {
85
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
86
+ return { ok: false, errors: ["override map must be an object"] };
87
+ }
88
+ const record = value;
89
+ const errors = [];
90
+ let schemaVersion = typeof record.schemaVersion === "number" ? record.schemaVersion : 0;
91
+ if (schemaVersion < 1)
92
+ schemaVersion = SCHEMA_VERSION;
93
+ if (schemaVersion > SCHEMA_VERSION) {
94
+ errors.push(`unsupported schemaVersion ${schemaVersion} (max ${SCHEMA_VERSION})`);
95
+ }
96
+ if (typeof record.basePackId !== "string" ||
97
+ !BASE_PACK_IDS.includes(record.basePackId)) {
98
+ errors.push(`basePackId must be one of: ${BASE_PACK_IDS.join(", ")}`);
99
+ }
100
+ if (record.packId !== undefined) {
101
+ if (typeof record.packId !== "string" || !KEBAB.test(record.packId)) {
102
+ errors.push("packId must be kebab-case");
103
+ }
104
+ else if (BASE_PACK_IDS.includes(record.packId)) {
105
+ errors.push(`packId collides with curated pack "${record.packId}"`);
106
+ }
107
+ }
108
+ if (record.accentSeed !== undefined &&
109
+ typeof record.accentSeed !== "string") {
110
+ errors.push("accentSeed must be a string");
111
+ }
112
+ if (record.radiusPreset !== undefined &&
113
+ (typeof record.radiusPreset !== "string" ||
114
+ !RADIUS.has(record.radiusPreset))) {
115
+ errors.push("radiusPreset must be sharp | soft | rounded | pill");
116
+ }
117
+ if (record.motion !== undefined &&
118
+ (typeof record.motion !== "string" || !MOTION.has(record.motion))) {
119
+ errors.push("motion must be snappy | balanced | soft");
120
+ }
121
+ const neutrals = readNeutrals(record.neutrals, errors);
122
+ const semantic = readSemantic(record.semantic, errors);
123
+ const shape = readShape(record.shape, errors);
124
+ const glass = readGlass(record.glass, errors);
125
+ if (errors.length > 0)
126
+ return { ok: false, errors };
127
+ const map = {
128
+ schemaVersion: SCHEMA_VERSION,
129
+ basePackId: record.basePackId,
130
+ };
131
+ if (typeof record.packId === "string")
132
+ map.packId = record.packId;
133
+ if (typeof record.accentSeed === "string")
134
+ map.accentSeed = record.accentSeed;
135
+ if (typeof record.radiusPreset === "string" && RADIUS.has(record.radiusPreset)) {
136
+ map.radiusPreset = record.radiusPreset;
137
+ }
138
+ if (neutrals)
139
+ map.neutrals = neutrals;
140
+ if (semantic)
141
+ map.semantic = semantic;
142
+ if (shape)
143
+ map.shape = shape;
144
+ if (glass)
145
+ map.glass = glass;
146
+ if (typeof record.motion === "string" && MOTION.has(record.motion)) {
147
+ map.motion = record.motion;
148
+ }
149
+ return { ok: true, map };
150
+ }
@@ -0,0 +1,7 @@
1
+ import type { CoreModeColors, Oklch, StructuralTokens, ThemeMode } from "./types";
2
+ export declare function o(l: number, c: number, h: number, alpha?: number): Oklch;
3
+ /** Curated pack definition before status families are merged. */
4
+ export type PackDef = {
5
+ modes: Partial<Record<ThemeMode, CoreModeColors>>;
6
+ structural: StructuralTokens;
7
+ };