@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,21 @@
1
+ import { type BasePackId, type OverrideMap, type RadiusPreset } from "./types";
2
+ export type ThemeProposal = {
3
+ basePackId: BasePackId;
4
+ accentSeed: string;
5
+ radiusPreset: RadiusPreset;
6
+ notes: string[];
7
+ };
8
+ /** Pull hex / oklch color strings from CSS or Tailwind snippets. */
9
+ export declare function extractColorsFromCss(source: string): string[];
10
+ export declare function pickAccentFromColors(colors: string[]): string | null;
11
+ export declare function suggestBasePack(accentSeed: string, hint?: string): BasePackId;
12
+ export declare function suggestRadius(hint?: string): RadiusPreset;
13
+ /** Deterministic proposal from colors + optional free-text hint (no LLM). */
14
+ export declare function proposeFromPalette(colors: string[], hint?: string): ThemeProposal | null;
15
+ /** Offline prompt migration: keywords + embedded colors; never throws. */
16
+ export declare function proposeFromPrompt(prompt: string): ThemeProposal;
17
+ export declare function proposalToOverrideMap(proposal: ThemeProposal, packId?: string): OverrideMap;
18
+ /** Validate raw LLM/model JSON into a ThemeProposal via override normalize. */
19
+ export declare function parseAiProposal(raw: unknown): ThemeProposal | {
20
+ error: string;
21
+ };
@@ -0,0 +1,162 @@
1
+ import { formatHex, parseColor } from "./oklch.js";
2
+ import { normalizeOverrideMap } from "./override-map.js";
3
+ import { BASE_PACK_IDS, SCHEMA_VERSION, } from "./types.js";
4
+ const HEX_FIND = /#([0-9a-f]{3}|[0-9a-f]{6})\b/gi;
5
+ const OKLCH_FIND = /oklch\(\s*[0-9.]+\s+[0-9.]+\s+[0-9.]+(?:\s*\/\s*[0-9.]+%?)?\s*\)/gi;
6
+ const PACK_BY_HUE = [
7
+ { pack: "clay", min: 10, max: 45 },
8
+ { pack: "dune", min: 45, max: 85 },
9
+ { pack: "forest", min: 110, max: 165 },
10
+ { pack: "tide", min: 175, max: 230 },
11
+ { pack: "graphite", min: 230, max: 280 },
12
+ { pack: "ember", min: 0, max: 25 },
13
+ ];
14
+ const PACK_KEYWORDS = [
15
+ { pack: "forest", words: ["forest", "green", "nature", "leaf"] },
16
+ { pack: "tide", words: ["tide", "ocean", "sea", "blue", "cyan"] },
17
+ { pack: "graphite", words: ["graphite", "slate", "mono", "neutral", "gray", "grey"] },
18
+ { pack: "dune", words: ["dune", "sand", "desert", "beige", "warm"] },
19
+ { pack: "clay", words: ["clay", "terracotta", "earthy", "pottery"] },
20
+ { pack: "ember", words: ["ember", "fire", "night", "dark", "crimson"] },
21
+ ];
22
+ /** Pull hex / oklch color strings from CSS or Tailwind snippets. */
23
+ export function extractColorsFromCss(source) {
24
+ const found = [];
25
+ for (const match of source.matchAll(HEX_FIND)) {
26
+ const normalized = normalizeHex(match[0]);
27
+ if (normalized)
28
+ found.push(normalized);
29
+ }
30
+ for (const match of source.matchAll(OKLCH_FIND)) {
31
+ const parsed = parseColor(match[0]);
32
+ if (parsed)
33
+ found.push(formatHex(parsed));
34
+ }
35
+ return [...new Set(found)];
36
+ }
37
+ export function pickAccentFromColors(colors) {
38
+ let best = null;
39
+ for (const hex of colors) {
40
+ const color = parseColor(hex);
41
+ if (!color)
42
+ continue;
43
+ if (color.l < 0.2 || color.l > 0.9)
44
+ continue;
45
+ const score = color.c * (1 - Math.abs(color.l - 0.55));
46
+ if (!best || score > best.score)
47
+ best = { hex, score };
48
+ }
49
+ return best?.hex ?? colors[0] ?? null;
50
+ }
51
+ function normalizeHex(raw) {
52
+ const match = raw.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
53
+ if (!match?.[1])
54
+ return null;
55
+ const hex = match[1];
56
+ const full = hex.length === 3
57
+ ? hex
58
+ .split("")
59
+ .map((ch) => ch + ch)
60
+ .join("")
61
+ : hex;
62
+ return `#${full.toLowerCase()}`;
63
+ }
64
+ export function suggestBasePack(accentSeed, hint = "") {
65
+ const lower = hint.toLowerCase();
66
+ for (const entry of PACK_KEYWORDS) {
67
+ if (entry.words.some((w) => lower.includes(w)))
68
+ return entry.pack;
69
+ }
70
+ const color = parseColor(accentSeed);
71
+ if (!color || color.c < 0.02)
72
+ return "graphite";
73
+ const h = color.h;
74
+ for (const band of PACK_BY_HUE) {
75
+ if (h >= band.min && h < band.max)
76
+ return band.pack;
77
+ }
78
+ return "forest";
79
+ }
80
+ export function suggestRadius(hint = "") {
81
+ const lower = hint.toLowerCase();
82
+ if (/\b(sharp|square|brutal)\b/.test(lower))
83
+ return "sharp";
84
+ if (/\b(pill|full.?round|capsule)\b/.test(lower))
85
+ return "pill";
86
+ if (/\b(rounded|soft.?round|friendly)\b/.test(lower))
87
+ return "rounded";
88
+ return "soft";
89
+ }
90
+ /** Deterministic proposal from colors + optional free-text hint (no LLM). */
91
+ export function proposeFromPalette(colors, hint = "") {
92
+ const accent = pickAccentFromColors(colors);
93
+ if (!accent)
94
+ return null;
95
+ const basePackId = suggestBasePack(accent, hint);
96
+ const radiusPreset = suggestRadius(hint);
97
+ return {
98
+ basePackId,
99
+ accentSeed: accent,
100
+ radiusPreset,
101
+ notes: [
102
+ `Accent from palette (${colors.length} color${colors.length === 1 ? "" : "s"})`,
103
+ `Base pack suggested: ${basePackId}`,
104
+ ],
105
+ };
106
+ }
107
+ /** Offline prompt migration: keywords + embedded colors; never throws. */
108
+ export function proposeFromPrompt(prompt) {
109
+ const colors = extractColorsFromCss(prompt);
110
+ const fromPalette = proposeFromPalette(colors, prompt);
111
+ if (fromPalette)
112
+ return fromPalette;
113
+ const basePackId = suggestBasePack("#2f6b4f", prompt);
114
+ return {
115
+ basePackId,
116
+ accentSeed: defaultAccentFor(basePackId),
117
+ radiusPreset: suggestRadius(prompt),
118
+ notes: ["No colors in prompt — used keyword defaults"],
119
+ };
120
+ }
121
+ export function proposalToOverrideMap(proposal, packId = "studio-draft") {
122
+ return {
123
+ schemaVersion: SCHEMA_VERSION,
124
+ basePackId: proposal.basePackId,
125
+ packId,
126
+ accentSeed: proposal.accentSeed,
127
+ radiusPreset: proposal.radiusPreset,
128
+ };
129
+ }
130
+ /** Validate raw LLM/model JSON into a ThemeProposal via override normalize. */
131
+ export function parseAiProposal(raw) {
132
+ const normalized = normalizeOverrideMap({
133
+ schemaVersion: SCHEMA_VERSION,
134
+ ...(typeof raw === "object" && raw !== null ? raw : {}),
135
+ });
136
+ if (!normalized.ok) {
137
+ return { error: normalized.errors.join("; ") };
138
+ }
139
+ const { basePackId, accentSeed, radiusPreset } = normalized.map;
140
+ if (!accentSeed)
141
+ return { error: "accentSeed required" };
142
+ if (!BASE_PACK_IDS.includes(basePackId)) {
143
+ return { error: "invalid basePackId" };
144
+ }
145
+ return {
146
+ basePackId,
147
+ accentSeed,
148
+ radiusPreset: radiusPreset ?? "soft",
149
+ notes: ["Validated AI proposal"],
150
+ };
151
+ }
152
+ function defaultAccentFor(pack) {
153
+ const map = {
154
+ forest: "#2f6b4f",
155
+ tide: "#2f6f8f",
156
+ graphite: "#4a5568",
157
+ dune: "#b08968",
158
+ clay: "#a65d45",
159
+ ember: "#c45c3e",
160
+ };
161
+ return map[pack];
162
+ }
@@ -0,0 +1,13 @@
1
+ import type { Oklch, ThemeMode } from "./types";
2
+ export type AccentSet = {
3
+ accent: Oklch;
4
+ accentForeground: Oklch;
5
+ ring: Oklch;
6
+ glassTint: Oklch;
7
+ };
8
+ export declare function accentSetFromSeed(seedInput: string, mode: ThemeMode): {
9
+ set: AccentSet;
10
+ adjustments: string[];
11
+ } | {
12
+ error: string;
13
+ };
@@ -0,0 +1,40 @@
1
+ import { contrastForeground, parseColor } from "./oklch.js";
2
+ export function accentSetFromSeed(seedInput, mode) {
3
+ const seed = parseColor(seedInput);
4
+ if (!seed) {
5
+ return { error: `invalid accent seed: "${seedInput}"` };
6
+ }
7
+ const adjustments = [];
8
+ const accent = mode === "dark"
9
+ ? { ...seed, l: Math.min(0.82, Math.max(0.62, seed.l + 0.12)) }
10
+ : { ...seed, l: Math.min(0.62, Math.max(0.42, seed.l)) };
11
+ if (accent.l !== seed.l) {
12
+ adjustments.push(`${mode} accent lightness clamped to ${accent.l.toFixed(2)} for surface contrast`);
13
+ }
14
+ // Dark packs use light accents; prefer light ink so AA holds without
15
+ // brightening the accent beyond the pack character.
16
+ const preferFg = {
17
+ l: 0.98,
18
+ c: Math.min(0.02, accent.c),
19
+ h: accent.h,
20
+ };
21
+ const fg = contrastForeground(accent, preferFg);
22
+ if (fg.adjusted) {
23
+ adjustments.push(`${mode} accent-foreground adjusted to meet WCAG AA against accent`);
24
+ }
25
+ const ring = { ...accent, c: Math.min(0.14, accent.c + 0.01) };
26
+ const glassTint = {
27
+ ...accent,
28
+ l: mode === "dark" ? Math.min(0.7, accent.l) : Math.min(0.82, accent.l + 0.15),
29
+ alpha: mode === "dark" ? 0.24 : 0.28,
30
+ };
31
+ return {
32
+ set: {
33
+ accent,
34
+ accentForeground: fg.color,
35
+ ring,
36
+ glassTint,
37
+ },
38
+ adjustments,
39
+ };
40
+ }
@@ -0,0 +1,13 @@
1
+ import type { BasePackId, Oklch, StatusModeColors, ThemeMode } from "./types";
2
+ /** Pack-character hues for semantic status families (not accent clones). */
3
+ export declare const PACK_STATUS_HUES: Record<BasePackId, {
4
+ success: number;
5
+ warning: number;
6
+ info: number;
7
+ }>;
8
+ /** Derive danger/success/warning/info (+ fg/subtle/border) for a mode. */
9
+ export declare function statusColors(mode: ThemeMode, danger: Oklch, hues: {
10
+ success: number;
11
+ warning: number;
12
+ info: number;
13
+ }): StatusModeColors;
@@ -0,0 +1,76 @@
1
+ /** Pack-character hues for semantic status families (not accent clones). */
2
+ export const PACK_STATUS_HUES = {
3
+ forest: { success: 148, warning: 78, info: 220 },
4
+ tide: { success: 165, warning: 75, info: 230 },
5
+ graphite: { success: 150, warning: 75, info: 250 },
6
+ dune: { success: 140, warning: 70, info: 230 },
7
+ clay: { success: 145, warning: 55, info: 230 },
8
+ ember: { success: 145, warning: 70, info: 230 },
9
+ };
10
+ function o(l, c, h, alpha) {
11
+ return alpha === undefined ? { l, c, h } : { l, c, h, alpha };
12
+ }
13
+ function family(mode, hue, kind, dangerSeed) {
14
+ const dark = mode === "dark";
15
+ if (kind === "danger" && dangerSeed) {
16
+ const base = dangerSeed;
17
+ return {
18
+ base,
19
+ fg: o(0.985, 0.01, base.h),
20
+ subtle: dark ? o(0.32, 0.05, base.h) : o(0.94, 0.035, base.h),
21
+ border: dark ? o(0.72, 0.1, base.h, 0.4) : o(0.56, 0.12, base.h, 0.4),
22
+ };
23
+ }
24
+ if (kind === "warning") {
25
+ const base = dark ? o(0.78, 0.12, hue) : o(0.75, 0.14, hue);
26
+ const fg = dark ? o(0.22, 0.04, hue) : o(0.25, 0.05, hue);
27
+ return {
28
+ base,
29
+ fg,
30
+ subtle: dark ? o(0.34, 0.04, hue) : o(0.94, 0.04, hue),
31
+ border: dark ? o(0.78, 0.1, hue, 0.4) : o(0.72, 0.12, hue, 0.4),
32
+ };
33
+ }
34
+ if (kind === "info") {
35
+ const base = dark ? o(0.72, 0.1, hue) : o(0.52, 0.11, hue);
36
+ return {
37
+ base,
38
+ fg: o(0.985, 0.01, hue),
39
+ subtle: dark ? o(0.32, 0.035, hue) : o(0.94, 0.03, hue),
40
+ border: dark ? o(0.72, 0.08, hue, 0.4) : o(0.52, 0.09, hue, 0.4),
41
+ };
42
+ }
43
+ // success — tide dark needs a slightly deeper base for light-fg AA
44
+ const successL = dark ? (hue === 165 ? 0.71 : 0.72) : 0.55;
45
+ const base = dark ? o(successL, 0.14, hue) : o(0.55, 0.15, hue);
46
+ return {
47
+ base,
48
+ fg: o(0.985, 0.01, hue),
49
+ subtle: dark ? o(0.32, 0.045, hue) : o(0.94, 0.035, hue),
50
+ border: dark ? o(0.72, 0.1, hue, 0.4) : o(0.55, 0.12, hue, 0.4),
51
+ };
52
+ }
53
+ /** Derive danger/success/warning/info (+ fg/subtle/border) for a mode. */
54
+ export function statusColors(mode, danger, hues) {
55
+ const dangerSet = family(mode, danger.h, "danger", danger);
56
+ const success = family(mode, hues.success, "success");
57
+ const warning = family(mode, hues.warning, "warning");
58
+ const info = family(mode, hues.info, "info");
59
+ return {
60
+ dangerForeground: dangerSet.fg,
61
+ dangerSubtle: dangerSet.subtle,
62
+ dangerBorder: dangerSet.border,
63
+ success: success.base,
64
+ successForeground: success.fg,
65
+ successSubtle: success.subtle,
66
+ successBorder: success.border,
67
+ warning: warning.base,
68
+ warningForeground: warning.fg,
69
+ warningSubtle: warning.subtle,
70
+ warningBorder: warning.border,
71
+ info: info.base,
72
+ infoForeground: info.fg,
73
+ infoSubtle: info.subtle,
74
+ infoBorder: info.border,
75
+ };
76
+ }
@@ -0,0 +1,10 @@
1
+ import type { ResolvedMode, ResolvedPack, ThemeMode } from "./types";
2
+ export type TokenEntry = {
3
+ name: string;
4
+ value: string;
5
+ kind: "color" | "number" | "string";
6
+ };
7
+ export declare function modeOrder(pack: ResolvedPack): ThemeMode[];
8
+ export declare function colorEntries(mode: ResolvedMode): TokenEntry[];
9
+ export declare function structuralEntries(mode: ResolvedMode): TokenEntry[];
10
+ export declare function primaryMode(pack: ResolvedPack): ResolvedMode;
@@ -0,0 +1,52 @@
1
+ import { formatOklch } from "./oklch.js";
2
+ export function modeOrder(pack) {
3
+ const order = ["light", "dark"];
4
+ return order.filter((mode) => pack.modes[mode]);
5
+ }
6
+ export function colorEntries(mode) {
7
+ return [
8
+ { name: "background", value: formatOklch(mode.background), kind: "color" },
9
+ { name: "foreground", value: formatOklch(mode.foreground), kind: "color" },
10
+ { name: "surface", value: formatOklch(mode.surface), kind: "color" },
11
+ { name: "surface-raised", value: formatOklch(mode.surfaceRaised), kind: "color" },
12
+ { name: "muted", value: formatOklch(mode.muted), kind: "color" },
13
+ { name: "muted-foreground", value: formatOklch(mode.mutedForeground), kind: "color" },
14
+ { name: "accent", value: formatOklch(mode.accent), kind: "color" },
15
+ { name: "accent-foreground", value: formatOklch(mode.accentForeground), kind: "color" },
16
+ { name: "border", value: formatOklch(mode.border), kind: "color" },
17
+ { name: "ring", value: formatOklch(mode.ring), kind: "color" },
18
+ { name: "danger", value: formatOklch(mode.danger), kind: "color" },
19
+ { name: "danger-foreground", value: formatOklch(mode.dangerForeground), kind: "color" },
20
+ { name: "danger-subtle", value: formatOklch(mode.dangerSubtle), kind: "color" },
21
+ { name: "danger-border", value: formatOklch(mode.dangerBorder), kind: "color" },
22
+ { name: "success", value: formatOklch(mode.success), kind: "color" },
23
+ { name: "success-foreground", value: formatOklch(mode.successForeground), kind: "color" },
24
+ { name: "success-subtle", value: formatOklch(mode.successSubtle), kind: "color" },
25
+ { name: "success-border", value: formatOklch(mode.successBorder), kind: "color" },
26
+ { name: "warning", value: formatOklch(mode.warning), kind: "color" },
27
+ { name: "warning-foreground", value: formatOklch(mode.warningForeground), kind: "color" },
28
+ { name: "warning-subtle", value: formatOklch(mode.warningSubtle), kind: "color" },
29
+ { name: "warning-border", value: formatOklch(mode.warningBorder), kind: "color" },
30
+ { name: "info", value: formatOklch(mode.info), kind: "color" },
31
+ { name: "info-foreground", value: formatOklch(mode.infoForeground), kind: "color" },
32
+ { name: "info-subtle", value: formatOklch(mode.infoSubtle), kind: "color" },
33
+ { name: "info-border", value: formatOklch(mode.infoBorder), kind: "color" },
34
+ { name: "glass-tint", value: formatOklch(mode.glassTint), kind: "color" },
35
+ ];
36
+ }
37
+ export function structuralEntries(mode) {
38
+ return [
39
+ { name: "radius-sm", value: mode.radiusSm, kind: "string" },
40
+ { name: "radius-md", value: mode.radiusMd, kind: "string" },
41
+ { name: "radius-lg", value: mode.radiusLg, kind: "string" },
42
+ { name: "radius-panel", value: mode.radiusPanel, kind: "string" },
43
+ { name: "density", value: String(mode.density), kind: "number" },
44
+ { name: "motion-duration", value: mode.motionDuration, kind: "string" },
45
+ { name: "motion-stiffness", value: String(mode.motionStiffness), kind: "number" },
46
+ { name: "motion-damping", value: String(mode.motionDamping), kind: "number" },
47
+ ];
48
+ }
49
+ export function primaryMode(pack) {
50
+ const modes = modeOrder(pack);
51
+ return pack.modes[modes[0]];
52
+ }
@@ -0,0 +1,110 @@
1
+ export declare const SCHEMA_VERSION: 2;
2
+ /** Flip when monetization lands — Studio UI reads this stub gate. */
3
+ export declare const PRO_AUTHORING_UNLOCKED = false;
4
+ export declare const BASE_PACK_IDS: readonly ["forest", "tide", "graphite", "dune", "clay", "ember"];
5
+ export type BasePackId = (typeof BASE_PACK_IDS)[number];
6
+ export type ThemeMode = "light" | "dark";
7
+ export type RadiusPreset = "sharp" | "soft" | "rounded" | "pill";
8
+ export type Oklch = {
9
+ l: number;
10
+ c: number;
11
+ h: number;
12
+ alpha?: number;
13
+ };
14
+ /** Core semantic surfaces + accent + danger seed (curated per pack mode). */
15
+ export type CoreModeColors = {
16
+ background: Oklch;
17
+ foreground: Oklch;
18
+ surface: Oklch;
19
+ surfaceRaised: Oklch;
20
+ muted: Oklch;
21
+ mutedForeground: Oklch;
22
+ accent: Oklch;
23
+ accentForeground: Oklch;
24
+ border: Oklch;
25
+ ring: Oklch;
26
+ danger: Oklch;
27
+ glassTint: Oklch;
28
+ };
29
+ /** Status families — base + foreground / subtle / border variants. */
30
+ export type StatusModeColors = {
31
+ dangerForeground: Oklch;
32
+ dangerSubtle: Oklch;
33
+ dangerBorder: Oklch;
34
+ success: Oklch;
35
+ successForeground: Oklch;
36
+ successSubtle: Oklch;
37
+ successBorder: Oklch;
38
+ warning: Oklch;
39
+ warningForeground: Oklch;
40
+ warningSubtle: Oklch;
41
+ warningBorder: Oklch;
42
+ info: Oklch;
43
+ infoForeground: Oklch;
44
+ infoSubtle: Oklch;
45
+ infoBorder: Oklch;
46
+ };
47
+ /** Full semantic color tokens authored per mode. */
48
+ export type ModeColorTokens = CoreModeColors & StatusModeColors;
49
+ export type StructuralTokens = {
50
+ radiusSm: string;
51
+ radiusMd: string;
52
+ radiusLg: string;
53
+ radiusPanel: string;
54
+ density: number;
55
+ motionDuration: string;
56
+ motionStiffness: number;
57
+ motionDamping: number;
58
+ };
59
+ export type ResolvedMode = ModeColorTokens & StructuralTokens & {
60
+ mode: ThemeMode;
61
+ colorScheme: ThemeMode;
62
+ };
63
+ export type ResolvedPack = {
64
+ id: string;
65
+ basePackId: BasePackId;
66
+ supportedModes: readonly ThemeMode[];
67
+ modes: Partial<Record<ThemeMode, ResolvedMode>>;
68
+ adjustments: string[];
69
+ };
70
+ export type DensityPreset = "compact" | "default" | "comfortable";
71
+ export type MotionCharacter = "snappy" | "balanced" | "soft";
72
+ /** Pro — neutrals / surfaces (constrained bias, not free-form tokens). */
73
+ export type NeutralOverrides = {
74
+ /** Lightness shift −0.08…0.08 applied to surfaces. */
75
+ surfaceShift?: number;
76
+ /** Chroma multiplier 0.5…1.5 for tinted neutrals. */
77
+ chromaScale?: number;
78
+ };
79
+ /** Pro — semantic colors beyond accent. */
80
+ export type SemanticOverrides = {
81
+ /** Danger seed as `oklch(...)` or `#rrggbb`. */
82
+ dangerSeed?: string;
83
+ };
84
+ /** Pro — density (radius stays free via radiusPreset). */
85
+ export type ShapeOverrides = {
86
+ density?: DensityPreset;
87
+ };
88
+ /** Pro — glass tint strength. */
89
+ export type GlassOverrides = {
90
+ /** Alpha multiplier 0.5…1.5 on glassTint. */
91
+ tintStrength?: number;
92
+ };
93
+ /**
94
+ * Override map — free fields + optional Pro groups.
95
+ * schemaVersion migrations keep older maps valid.
96
+ */
97
+ export type OverrideMap = {
98
+ schemaVersion: typeof SCHEMA_VERSION | number;
99
+ basePackId: BasePackId;
100
+ /** Pack id for CSS `[data-theme="…"]` — kebab-case. */
101
+ packId?: string;
102
+ /** Accent seed as `oklch(...)` or `#rrggbb`. */
103
+ accentSeed?: string;
104
+ radiusPreset?: RadiusPreset;
105
+ neutrals?: NeutralOverrides;
106
+ semantic?: SemanticOverrides;
107
+ shape?: ShapeOverrides;
108
+ glass?: GlassOverrides;
109
+ motion?: MotionCharacter;
110
+ };
@@ -0,0 +1,11 @@
1
+ export const SCHEMA_VERSION = 2;
2
+ /** Flip when monetization lands — Studio UI reads this stub gate. */
3
+ export const PRO_AUTHORING_UNLOCKED = false;
4
+ export const BASE_PACK_IDS = [
5
+ "forest",
6
+ "tide",
7
+ "graphite",
8
+ "dune",
9
+ "clay",
10
+ "ember",
11
+ ];
@@ -0,0 +1,138 @@
1
+ # Clay — Design brief
2
+
3
+ > Generated from Hewn curated theme packs. Semantic OKLCH tokens; do not edit by hand.
4
+
5
+ ## Identity
6
+
7
+ Warm stone and terracotta.
8
+
9
+ - **Pack id:** `clay`
10
+ - **Base pack:** `clay`
11
+ - **Supported modes:** light
12
+ - **Color scheme (primary):** light
13
+
14
+ ## Palette
15
+
16
+ | Token | Role | light |
17
+ |---|---|---|
18
+ | `background` | Page and app canvas | `oklch(0.955 0.02 55)` |
19
+ | `foreground` | Primary text and icons | `oklch(0.27 0.045 40)` |
20
+ | `surface` | Cards, panels, and elevated chrome | `oklch(0.98 0.012 50)` |
21
+ | `surface-raised` | Overlays and nested elevation | `oklch(0.995 0.006 45)` |
22
+ | `muted` | Subtle fills and disabled wells | `oklch(0.915 0.025 50)` |
23
+ | `muted-foreground` | Secondary text and captions | `oklch(0.5 0.045 40)` |
24
+ | `accent` | Primary actions and focus accents | `oklch(0.55 0.12 40)` |
25
+ | `accent-foreground` | Text/icons on accent | `oklch(0.98 0.01 50)` |
26
+ | `border` | Separators and control outlines | `oklch(0.4 0.05 40 / 16%)` |
27
+ | `ring` | Focus ring | `oklch(0.6 0.13 40)` |
28
+ | `danger` | Destructive and error states | `oklch(0.54 0.17 28)` |
29
+ | `danger-foreground` | danger-foreground | `oklch(0.985 0.01 28)` |
30
+ | `danger-subtle` | danger-subtle | `oklch(0.94 0.035 28)` |
31
+ | `danger-border` | danger-border | `oklch(0.56 0.12 28 / 40%)` |
32
+ | `success` | success | `oklch(0.55 0.15 145)` |
33
+ | `success-foreground` | success-foreground | `oklch(0.985 0.01 145)` |
34
+ | `success-subtle` | success-subtle | `oklch(0.94 0.035 145)` |
35
+ | `success-border` | success-border | `oklch(0.55 0.12 145 / 40%)` |
36
+ | `warning` | warning | `oklch(0.75 0.14 55)` |
37
+ | `warning-foreground` | warning-foreground | `oklch(0.25 0.05 55)` |
38
+ | `warning-subtle` | warning-subtle | `oklch(0.94 0.04 55)` |
39
+ | `warning-border` | warning-border | `oklch(0.72 0.12 55 / 40%)` |
40
+ | `info` | info | `oklch(0.52 0.11 230)` |
41
+ | `info-foreground` | info-foreground | `oklch(0.985 0.01 230)` |
42
+ | `info-subtle` | info-subtle | `oklch(0.94 0.03 230)` |
43
+ | `info-border` | info-border | `oklch(0.52 0.09 230 / 40%)` |
44
+ | `glass-tint` | Tinted frosted glass material | `oklch(0.74 0.1 45 / 28%)` |
45
+
46
+ ## Typography
47
+
48
+ | Role | Stack | Usage |
49
+ |---|---|---|
50
+ | Sans | IBM Plex Sans, ui-sans-serif, system-ui | UI chrome, body, headings |
51
+ | Mono | IBM Plex Mono, ui-monospace | Labels, code, tabular metrics |
52
+ | Display | Space Grotesk → sans | Marketing hero H1 and brand wordmark ≥18px only |
53
+
54
+ Body text uses pretty wrapping; headings use balanced wrapping; dynamic numbers use `tabular-nums`.
55
+
56
+ ## Radius & density
57
+
58
+ - `radius-sm`: `0.5rem`
59
+ - `radius-md`: `0.8rem`
60
+ - `radius-lg`: `1.05rem`
61
+ - `radius-panel`: `1.05rem`
62
+ - `density`: `1`
63
+ - `motion-duration`: `270ms`
64
+ - `motion-stiffness`: `320`
65
+ - `motion-damping`: `27`
66
+
67
+ Density scales control spacing rhythm. Panel radius (`radius-panel`) caps menus and dialogs so they never go full-pill.
68
+
69
+ ## Motion
70
+
71
+ - Duration: `270ms`
72
+ - Spring stiffness: `320`
73
+ - Spring damping: `27`
74
+ - Reduced motion always wins over global motion toggles.
75
+ - Prefer transform / opacity / filter; never animate layout properties continuously.
76
+
77
+ ## Material
78
+
79
+ - `solid` — structured product surfaces and separators
80
+ - `glass` — accent-tinted frosted surfaces (blur, saturation, refraction, opaque fallback)
81
+
82
+ ## Do
83
+
84
+ - Use semantic OKLCH tokens (`--kp-*` / `--color-*`); never hard-code theme hues.
85
+ - Typography: IBM Plex Sans (UI), IBM Plex Mono (code/labels), Space Grotesk only for marketing hero H1 and brand wordmark ≥18px.
86
+ - Icons: Phosphor. Interactive hit targets ≥40×40px; press feedback `scale(0.96)`.
87
+ - Motion: animate transform, opacity, and filter; reduced motion always wins.
88
+ - Radius: concentric scale; panels use `--kp-radius-panel` (never full-pill menus/dialogs).
89
+ - Glass is a global material — tint, blur, saturation, refraction edge, opaque fallback.
90
+
91
+ ## Don't
92
+
93
+ - No purple/lila defaults, neon glow, pure black, Inter, emoji, or generic centered heroes.
94
+ - Never `transition: all` — list exact properties.
95
+ - Never `h-screen` for full-height sections — use `min-h-[100dvh]`.
96
+ - No per-component `glass` prop; material is global.
97
+ - Do not invent unsupported light/dark combinations for a pack.
98
+ - Do not put Space Grotesk in UI chrome, forms, or dense product copy.
99
+
100
+ ## Token reference (primary mode)
101
+
102
+ ```
103
+ --kp-background: oklch(0.955 0.02 55);
104
+ --kp-foreground: oklch(0.27 0.045 40);
105
+ --kp-surface: oklch(0.98 0.012 50);
106
+ --kp-surface-raised: oklch(0.995 0.006 45);
107
+ --kp-muted: oklch(0.915 0.025 50);
108
+ --kp-muted-foreground: oklch(0.5 0.045 40);
109
+ --kp-accent: oklch(0.55 0.12 40);
110
+ --kp-accent-foreground: oklch(0.98 0.01 50);
111
+ --kp-border: oklch(0.4 0.05 40 / 16%);
112
+ --kp-ring: oklch(0.6 0.13 40);
113
+ --kp-danger: oklch(0.54 0.17 28);
114
+ --kp-danger-foreground: oklch(0.985 0.01 28);
115
+ --kp-danger-subtle: oklch(0.94 0.035 28);
116
+ --kp-danger-border: oklch(0.56 0.12 28 / 40%);
117
+ --kp-success: oklch(0.55 0.15 145);
118
+ --kp-success-foreground: oklch(0.985 0.01 145);
119
+ --kp-success-subtle: oklch(0.94 0.035 145);
120
+ --kp-success-border: oklch(0.55 0.12 145 / 40%);
121
+ --kp-warning: oklch(0.75 0.14 55);
122
+ --kp-warning-foreground: oklch(0.25 0.05 55);
123
+ --kp-warning-subtle: oklch(0.94 0.04 55);
124
+ --kp-warning-border: oklch(0.72 0.12 55 / 40%);
125
+ --kp-info: oklch(0.52 0.11 230);
126
+ --kp-info-foreground: oklch(0.985 0.01 230);
127
+ --kp-info-subtle: oklch(0.94 0.03 230);
128
+ --kp-info-border: oklch(0.52 0.09 230 / 40%);
129
+ --kp-glass-tint: oklch(0.74 0.1 45 / 28%);
130
+ --kp-radius-sm: 0.5rem;
131
+ --kp-radius-md: 0.8rem;
132
+ --kp-radius-lg: 1.05rem;
133
+ --kp-radius-panel: 1.05rem;
134
+ --kp-density: 1;
135
+ --kp-motion-duration: 270ms;
136
+ --kp-motion-stiffness: 320;
137
+ --kp-motion-damping: 27;
138
+ ```