@portel/photon-core 2.8.0 → 2.8.2
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.
- package/dist/base.js +1 -1
- package/dist/base.js.map +1 -1
- package/dist/channels/registry.js +7 -7
- package/dist/channels/registry.js.map +1 -1
- package/dist/collections/Collection.js +1 -1
- package/dist/collections/Collection.js.map +1 -1
- package/dist/collections/ReactiveArray.d.ts +7 -2
- package/dist/collections/ReactiveArray.d.ts.map +1 -1
- package/dist/collections/ReactiveArray.js +21 -0
- package/dist/collections/ReactiveArray.js.map +1 -1
- package/dist/decorators.js +1 -1
- package/dist/decorators.js.map +1 -1
- package/dist/design-system/index.js +1 -1
- package/dist/design-system/index.js.map +1 -1
- package/dist/design-system/oklch.d.ts +119 -0
- package/dist/design-system/oklch.d.ts.map +1 -0
- package/dist/design-system/oklch.js +354 -0
- package/dist/design-system/oklch.js.map +1 -0
- package/dist/design-system/tokens.d.ts +5 -4
- package/dist/design-system/tokens.d.ts.map +1 -1
- package/dist/design-system/tokens.js +16 -14
- package/dist/design-system/tokens.js.map +1 -1
- package/dist/generator.d.ts +0 -48
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +0 -26
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-apps.d.ts.map +1 -1
- package/dist/mcp-apps.js +1 -2
- package/dist/mcp-apps.js.map +1 -1
- package/dist/memory.d.ts +4 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +49 -25
- package/dist/memory.js.map +1 -1
- package/dist/rendering/layout-selector.js +2 -2
- package/dist/rendering/layout-selector.js.map +1 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +13 -1
- package/dist/schema-extractor.js.map +1 -1
- package/dist/stateful.d.ts +1 -2
- package/dist/stateful.d.ts.map +1 -1
- package/dist/stateful.js +30 -13
- package/dist/stateful.js.map +1 -1
- package/dist/types.d.ts +7 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/base.ts +1 -1
- package/src/channels/registry.ts +7 -7
- package/src/collections/Collection.ts +4 -4
- package/src/collections/ReactiveArray.ts +24 -2
- package/src/decorators.ts +1 -1
- package/src/design-system/index.ts +1 -1
- package/src/design-system/oklch.ts +493 -0
- package/src/design-system/tokens.ts +17 -14
- package/src/generator.ts +0 -69
- package/src/index.ts +0 -14
- package/src/mcp-apps.ts +2 -3
- package/src/memory.ts +43 -24
- package/src/rendering/layout-selector.ts +5 -5
- package/src/schema-extractor.ts +16 -1
- package/src/stateful.ts +28 -13
- package/src/types.ts +9 -3
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
export type Emitter = (event: string, data: unknown) => void;
|
|
29
29
|
|
|
30
30
|
export class ReactiveArray<T> extends Array<T> {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
protected _propertyName: string = '';
|
|
32
|
+
protected _emitter: Emitter = () => {};
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Create a new ReactiveArray bound to a property name and emitter function.
|
|
@@ -50,11 +50,28 @@ export class ReactiveArray<T> extends Array<T> {
|
|
|
50
50
|
return arr;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Auto-stamp plain objects with `_addedAt` timestamp on add operations.
|
|
55
|
+
* Only stamps if no recognized timestamp field already exists.
|
|
56
|
+
*/
|
|
57
|
+
private _stampAdded(items: T[]): void {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
61
|
+
const rec = item as Record<string, unknown>;
|
|
62
|
+
if (rec._addedAt === undefined && rec.createdAt === undefined && rec.created_at === undefined) {
|
|
63
|
+
rec._addedAt = now;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
/**
|
|
54
70
|
* Add one or more elements to the end of the array.
|
|
55
71
|
* Emits `{prop}:added` for each item.
|
|
56
72
|
*/
|
|
57
73
|
push(...items: T[]): number {
|
|
74
|
+
this._stampAdded(items);
|
|
58
75
|
const result = super.push(...items);
|
|
59
76
|
items.forEach((item) => this._emitter(`${this._propertyName}:added`, item));
|
|
60
77
|
return result;
|
|
@@ -89,6 +106,7 @@ export class ReactiveArray<T> extends Array<T> {
|
|
|
89
106
|
* Emits `{prop}:added` for each item.
|
|
90
107
|
*/
|
|
91
108
|
unshift(...items: T[]): number {
|
|
109
|
+
this._stampAdded(items);
|
|
92
110
|
const result = super.unshift(...items);
|
|
93
111
|
items.forEach((item) => this._emitter(`${this._propertyName}:added`, item));
|
|
94
112
|
return result;
|
|
@@ -99,6 +117,7 @@ export class ReactiveArray<T> extends Array<T> {
|
|
|
99
117
|
* Emits `{prop}:removed` for deleted items and `{prop}:added` for inserted items.
|
|
100
118
|
*/
|
|
101
119
|
splice(start: number, deleteCount?: number, ...items: T[]): T[] {
|
|
120
|
+
this._stampAdded(items);
|
|
102
121
|
const removed = super.splice(start, deleteCount ?? 0, ...items);
|
|
103
122
|
removed.forEach((item) =>
|
|
104
123
|
this._emitter(`${this._propertyName}:removed`, item)
|
|
@@ -112,6 +131,9 @@ export class ReactiveArray<T> extends Array<T> {
|
|
|
112
131
|
* Emits `{prop}:updated` with `{ index, value, previous }`.
|
|
113
132
|
*/
|
|
114
133
|
set(index: number, value: T): void {
|
|
134
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
135
|
+
(value as Record<string, unknown>)._updatedAt = Date.now();
|
|
136
|
+
}
|
|
115
137
|
const previous = this[index];
|
|
116
138
|
this[index] = value;
|
|
117
139
|
this._emitter(`${this._propertyName}:updated`, { index, value, previous });
|
package/src/decorators.ts
CHANGED
|
@@ -70,7 +70,7 @@ export async function withLock<T>(
|
|
|
70
70
|
const lockManager = getLockManager();
|
|
71
71
|
|
|
72
72
|
if (!lockManager) {
|
|
73
|
-
|
|
73
|
+
console.warn(`[photon] withLock('${lockName}'): no lock manager configured, running without lock`);
|
|
74
74
|
return fn();
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Apple HIG: https://developer.apple.com/design/human-interface-guidelines/layout
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export * from './tokens.js';
|
|
12
|
+
export * from './tokens.js'; // includes oklch.js re-exports
|
|
13
13
|
export * from './transaction-ui.js';
|
|
14
14
|
|
|
15
15
|
import { generateTokensCSS } from './tokens.js';
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKLCH Color Engine
|
|
3
|
+
*
|
|
4
|
+
* Pure-math OKLCH → sRGB conversion for perceptually uniform theme generation.
|
|
5
|
+
* No external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* References:
|
|
8
|
+
* - Björn Ottosson's Oklab: https://bottosson.github.io/posts/oklab/
|
|
9
|
+
* - CSS Color Level 4: https://www.w3.org/TR/css-color-4/#ok-lab
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// TYPES
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface ThemeConfig {
|
|
17
|
+
/** Color hue: 0-360 */
|
|
18
|
+
hue: number;
|
|
19
|
+
/** Color saturation/vibrancy: 0-0.4 */
|
|
20
|
+
chroma: number;
|
|
21
|
+
/** Base lightness: 0-1 */
|
|
22
|
+
lightness: number;
|
|
23
|
+
/** Dark or light mode */
|
|
24
|
+
theme: 'dark' | 'light';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ThemePreset {
|
|
28
|
+
name: string;
|
|
29
|
+
config: Omit<ThemeConfig, 'theme'>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** All generated theme colors as hex strings */
|
|
33
|
+
export interface GeneratedThemeColors {
|
|
34
|
+
// Surfaces
|
|
35
|
+
surface: string;
|
|
36
|
+
surfaceContainer: string;
|
|
37
|
+
surfaceContainerHigh: string;
|
|
38
|
+
surfaceContainerHighest: string;
|
|
39
|
+
surfaceBright: string;
|
|
40
|
+
|
|
41
|
+
// Text
|
|
42
|
+
onSurface: string;
|
|
43
|
+
onSurfaceVariant: string;
|
|
44
|
+
onSurfaceMuted: string;
|
|
45
|
+
|
|
46
|
+
// Primary
|
|
47
|
+
primary: string;
|
|
48
|
+
primaryContainer: string;
|
|
49
|
+
onPrimary: string;
|
|
50
|
+
onPrimaryContainer: string;
|
|
51
|
+
|
|
52
|
+
// Status - derived from hue offsets
|
|
53
|
+
success: string;
|
|
54
|
+
successContainer: string;
|
|
55
|
+
onSuccess: string;
|
|
56
|
+
onSuccessContainer: string;
|
|
57
|
+
|
|
58
|
+
warning: string;
|
|
59
|
+
warningContainer: string;
|
|
60
|
+
onWarning: string;
|
|
61
|
+
onWarningContainer: string;
|
|
62
|
+
|
|
63
|
+
error: string;
|
|
64
|
+
errorContainer: string;
|
|
65
|
+
onError: string;
|
|
66
|
+
onErrorContainer: string;
|
|
67
|
+
|
|
68
|
+
// Outline
|
|
69
|
+
outline: string;
|
|
70
|
+
outlineVariant: string;
|
|
71
|
+
|
|
72
|
+
// Scrim
|
|
73
|
+
scrim: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Beam-specific CSS variables generated from OKLCH */
|
|
77
|
+
export interface BeamThemeColors {
|
|
78
|
+
bgApp: string;
|
|
79
|
+
bgGlass: string;
|
|
80
|
+
bgGlassStrong: string;
|
|
81
|
+
bgPanel: string;
|
|
82
|
+
tPrimary: string;
|
|
83
|
+
tMuted: string;
|
|
84
|
+
borderGlass: string;
|
|
85
|
+
accentPrimary: string;
|
|
86
|
+
accentSecondary: string;
|
|
87
|
+
glowPrimary: string;
|
|
88
|
+
// Status
|
|
89
|
+
colorError: string;
|
|
90
|
+
colorErrorGlow: string;
|
|
91
|
+
colorErrorBg: string;
|
|
92
|
+
colorSuccess: string;
|
|
93
|
+
colorSuccessBg: string;
|
|
94
|
+
colorWarning: string;
|
|
95
|
+
colorWarningBg: string;
|
|
96
|
+
colorWarningGlow: string;
|
|
97
|
+
colorInfo: string;
|
|
98
|
+
// CLI preview
|
|
99
|
+
cliBg: string;
|
|
100
|
+
cliBorder: string;
|
|
101
|
+
cliText: string;
|
|
102
|
+
cliMuted: string;
|
|
103
|
+
cliHoverBg: string;
|
|
104
|
+
// Syntax highlighting
|
|
105
|
+
syntaxComment: string;
|
|
106
|
+
syntaxKeyword: string;
|
|
107
|
+
syntaxString: string;
|
|
108
|
+
syntaxNumber: string;
|
|
109
|
+
syntaxFunction: string;
|
|
110
|
+
syntaxOperator: string;
|
|
111
|
+
syntaxVariable: string;
|
|
112
|
+
syntaxPunctuation: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// OKLCH → sRGB CONVERSION
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert OKLCH to linear sRGB.
|
|
121
|
+
* L: 0-1 (lightness), C: 0-0.4+ (chroma), H: 0-360 (hue in degrees)
|
|
122
|
+
*/
|
|
123
|
+
export function oklchToSRGB(L: number, C: number, H: number): [number, number, number] {
|
|
124
|
+
// Convert hue to radians
|
|
125
|
+
const hRad = (H * Math.PI) / 180;
|
|
126
|
+
|
|
127
|
+
// OKLCH → OKLab
|
|
128
|
+
const a = C * Math.cos(hRad);
|
|
129
|
+
const b = C * Math.sin(hRad);
|
|
130
|
+
|
|
131
|
+
// OKLab → linear LMS (via inverse of the matrix)
|
|
132
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
133
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
134
|
+
const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
|
|
135
|
+
|
|
136
|
+
// Cube the values (inverse of cube root)
|
|
137
|
+
const l = l_ * l_ * l_;
|
|
138
|
+
const m = m_ * m_ * m_;
|
|
139
|
+
const s = s_ * s_ * s_;
|
|
140
|
+
|
|
141
|
+
// Linear LMS → linear sRGB
|
|
142
|
+
const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
143
|
+
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
144
|
+
const bv = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
|
145
|
+
|
|
146
|
+
// Apply sRGB gamma (linear → sRGB)
|
|
147
|
+
return [gammaEncode(r), gammaEncode(g), gammaEncode(bv)];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** sRGB gamma encoding (linear → sRGB) */
|
|
151
|
+
function gammaEncode(c: number): number {
|
|
152
|
+
if (c <= 0.0031308) {
|
|
153
|
+
return 12.92 * c;
|
|
154
|
+
}
|
|
155
|
+
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Clamp a value to [0, 1] */
|
|
159
|
+
function clamp01(v: number): number {
|
|
160
|
+
return Math.max(0, Math.min(1, v));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Convert sRGB [0-1] to hex color string.
|
|
165
|
+
*/
|
|
166
|
+
export function srgbToHex(r: number, g: number, b: number): string {
|
|
167
|
+
const toHex = (c: number) => {
|
|
168
|
+
const v = Math.round(clamp01(c) * 255);
|
|
169
|
+
return v.toString(16).padStart(2, '0');
|
|
170
|
+
};
|
|
171
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Convert OKLCH directly to hex color string.
|
|
176
|
+
* Gamut-clamps out-of-range values.
|
|
177
|
+
*/
|
|
178
|
+
export function oklchToHex(L: number, C: number, H: number): string {
|
|
179
|
+
const [r, g, b] = oklchToSRGB(L, C, H);
|
|
180
|
+
return srgbToHex(r, g, b);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Convert OKLCH to rgba() string with alpha.
|
|
185
|
+
*/
|
|
186
|
+
function oklchToRgba(L: number, C: number, H: number, alpha: number): string {
|
|
187
|
+
const [r, g, b] = oklchToSRGB(L, C, H);
|
|
188
|
+
return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${alpha})`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// THEME PRESETS
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
export const themePresets: ThemePreset[] = [
|
|
196
|
+
{ name: 'Default Violet', config: { hue: 260, chroma: 0.15, lightness: 0.65 } },
|
|
197
|
+
{ name: 'Ocean Blue', config: { hue: 220, chroma: 0.12, lightness: 0.60 } },
|
|
198
|
+
{ name: 'Emerald', config: { hue: 155, chroma: 0.14, lightness: 0.60 } },
|
|
199
|
+
{ name: 'Amber', config: { hue: 75, chroma: 0.14, lightness: 0.70 } },
|
|
200
|
+
{ name: 'Rose', config: { hue: 350, chroma: 0.14, lightness: 0.62 } },
|
|
201
|
+
{ name: 'Monochrome', config: { hue: 0, chroma: 0.0, lightness: 0.65 } },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// THEME COLOR GENERATION
|
|
206
|
+
// =============================================================================
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate Material Design 3-compatible theme colors from OKLCH parameters.
|
|
210
|
+
*/
|
|
211
|
+
export function generateThemeColors(config: ThemeConfig): GeneratedThemeColors {
|
|
212
|
+
const { hue: H, chroma: C, lightness: L, theme } = config;
|
|
213
|
+
const isDark = theme === 'dark';
|
|
214
|
+
|
|
215
|
+
// --- Surface colors ---
|
|
216
|
+
// Dark: very low lightness, minimal chroma for subtle tinting
|
|
217
|
+
// Light: very high lightness, minimal chroma
|
|
218
|
+
const surface = isDark
|
|
219
|
+
? oklchToHex(0.15, C * 0.03, H)
|
|
220
|
+
: oklchToHex(0.97, C * 0.02, H);
|
|
221
|
+
const surfaceContainer = isDark
|
|
222
|
+
? oklchToHex(0.19, C * 0.04, H)
|
|
223
|
+
: oklchToHex(0.94, C * 0.025, H);
|
|
224
|
+
const surfaceContainerHigh = isDark
|
|
225
|
+
? oklchToHex(0.23, C * 0.05, H)
|
|
226
|
+
: oklchToHex(0.91, C * 0.03, H);
|
|
227
|
+
const surfaceContainerHighest = isDark
|
|
228
|
+
? oklchToHex(0.27, C * 0.06, H)
|
|
229
|
+
: oklchToHex(0.88, C * 0.035, H);
|
|
230
|
+
const surfaceBright = isDark
|
|
231
|
+
? oklchToHex(0.95, C * 0.01, H)
|
|
232
|
+
: oklchToHex(1.0, 0, H);
|
|
233
|
+
|
|
234
|
+
// --- Text colors ---
|
|
235
|
+
const onSurface = isDark
|
|
236
|
+
? oklchToHex(0.93, C * 0.02, H)
|
|
237
|
+
: oklchToHex(0.22, C * 0.02, H);
|
|
238
|
+
const onSurfaceVariant = isDark
|
|
239
|
+
? oklchToHex(0.75, C * 0.04, H)
|
|
240
|
+
: oklchToHex(0.38, C * 0.04, H);
|
|
241
|
+
const onSurfaceMuted = isDark
|
|
242
|
+
? oklchToHex(0.55, C * 0.05, H)
|
|
243
|
+
: oklchToHex(0.50, C * 0.05, H);
|
|
244
|
+
|
|
245
|
+
// --- Primary accent ---
|
|
246
|
+
const primary = isDark
|
|
247
|
+
? oklchToHex(L, C, H)
|
|
248
|
+
: oklchToHex(L * 0.7, C, H);
|
|
249
|
+
const primaryContainer = isDark
|
|
250
|
+
? oklchToHex(0.30, C * 0.5, H)
|
|
251
|
+
: oklchToHex(0.90, C * 0.3, H);
|
|
252
|
+
const onPrimary = isDark
|
|
253
|
+
? oklchToHex(0.15, C * 0.1, H)
|
|
254
|
+
: oklchToHex(0.98, C * 0.01, H);
|
|
255
|
+
const onPrimaryContainer = isDark
|
|
256
|
+
? oklchToHex(0.90, C * 0.2, H)
|
|
257
|
+
: oklchToHex(0.20, C * 0.15, H);
|
|
258
|
+
|
|
259
|
+
// --- Status colors (hue offsets from base) ---
|
|
260
|
+
// Success: green tones
|
|
261
|
+
const successH = (H + 145) % 360;
|
|
262
|
+
const success = isDark
|
|
263
|
+
? oklchToHex(0.70, 0.15, successH)
|
|
264
|
+
: oklchToHex(0.45, 0.15, successH);
|
|
265
|
+
const successContainer = isDark
|
|
266
|
+
? oklchToHex(0.25, 0.06, successH)
|
|
267
|
+
: oklchToHex(0.92, 0.06, successH);
|
|
268
|
+
const onSuccess = isDark
|
|
269
|
+
? oklchToHex(0.15, 0.05, successH)
|
|
270
|
+
: oklchToHex(0.98, 0.01, successH);
|
|
271
|
+
const onSuccessContainer = isDark
|
|
272
|
+
? oklchToHex(0.90, 0.08, successH)
|
|
273
|
+
: oklchToHex(0.20, 0.08, successH);
|
|
274
|
+
|
|
275
|
+
// Warning: amber/yellow tones
|
|
276
|
+
const warningH = (H + 100) % 360;
|
|
277
|
+
const warning = isDark
|
|
278
|
+
? oklchToHex(0.75, 0.15, warningH)
|
|
279
|
+
: oklchToHex(0.50, 0.15, warningH);
|
|
280
|
+
const warningContainer = isDark
|
|
281
|
+
? oklchToHex(0.25, 0.06, warningH)
|
|
282
|
+
: oklchToHex(0.92, 0.06, warningH);
|
|
283
|
+
const onWarning = isDark
|
|
284
|
+
? oklchToHex(0.15, 0.05, warningH)
|
|
285
|
+
: oklchToHex(0.98, 0.01, warningH);
|
|
286
|
+
const onWarningContainer = isDark
|
|
287
|
+
? oklchToHex(0.90, 0.08, warningH)
|
|
288
|
+
: oklchToHex(0.20, 0.08, warningH);
|
|
289
|
+
|
|
290
|
+
// Error: red tones
|
|
291
|
+
const errorH = (H + 210) % 360;
|
|
292
|
+
const error = isDark
|
|
293
|
+
? oklchToHex(0.65, 0.18, errorH)
|
|
294
|
+
: oklchToHex(0.48, 0.18, errorH);
|
|
295
|
+
const errorContainer = isDark
|
|
296
|
+
? oklchToHex(0.25, 0.07, errorH)
|
|
297
|
+
: oklchToHex(0.92, 0.07, errorH);
|
|
298
|
+
const onError = isDark
|
|
299
|
+
? oklchToHex(0.15, 0.05, errorH)
|
|
300
|
+
: oklchToHex(0.98, 0.01, errorH);
|
|
301
|
+
const onErrorContainer = isDark
|
|
302
|
+
? oklchToHex(0.90, 0.08, errorH)
|
|
303
|
+
: oklchToHex(0.20, 0.08, errorH);
|
|
304
|
+
|
|
305
|
+
// --- Outline ---
|
|
306
|
+
const outline = isDark
|
|
307
|
+
? oklchToHex(0.40, C * 0.06, H)
|
|
308
|
+
: oklchToHex(0.65, C * 0.06, H);
|
|
309
|
+
const outlineVariant = isDark
|
|
310
|
+
? oklchToHex(0.28, C * 0.04, H)
|
|
311
|
+
: oklchToHex(0.82, C * 0.04, H);
|
|
312
|
+
|
|
313
|
+
// --- Scrim ---
|
|
314
|
+
const scrim = isDark ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
surface, surfaceContainer, surfaceContainerHigh, surfaceContainerHighest, surfaceBright,
|
|
318
|
+
onSurface, onSurfaceVariant, onSurfaceMuted,
|
|
319
|
+
primary, primaryContainer, onPrimary, onPrimaryContainer,
|
|
320
|
+
success, successContainer, onSuccess, onSuccessContainer,
|
|
321
|
+
warning, warningContainer, onWarning, onWarningContainer,
|
|
322
|
+
error, errorContainer, onError, onErrorContainer,
|
|
323
|
+
outline, outlineVariant,
|
|
324
|
+
scrim,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Generate Beam-specific CSS variable values from OKLCH parameters.
|
|
330
|
+
* These map to the --bg-app, --accent-primary, etc. variables used in beam-app.ts.
|
|
331
|
+
*/
|
|
332
|
+
export function generateBeamThemeColors(config: ThemeConfig): BeamThemeColors {
|
|
333
|
+
const { hue: H, chroma: C, lightness: L, theme } = config;
|
|
334
|
+
const isDark = theme === 'dark';
|
|
335
|
+
|
|
336
|
+
// Secondary accent: shifted hue, reduced chroma
|
|
337
|
+
const secondaryH = (H + 70) % 360;
|
|
338
|
+
|
|
339
|
+
// === Surfaces ===
|
|
340
|
+
const bgApp = isDark
|
|
341
|
+
? oklchToHex(0.15, C * 0.04, H)
|
|
342
|
+
: oklchToHex(0.92, C * 0.03, H);
|
|
343
|
+
const bgGlass = isDark
|
|
344
|
+
? oklchToRgba(0.19, C * 0.04, H, 0.6)
|
|
345
|
+
: oklchToRgba(0.98, C * 0.02, H, 0.75);
|
|
346
|
+
const bgGlassStrong = isDark
|
|
347
|
+
? oklchToRgba(0.19, C * 0.04, H, 0.85)
|
|
348
|
+
: oklchToRgba(0.98, C * 0.02, H, 0.9);
|
|
349
|
+
const bgPanel = isDark
|
|
350
|
+
? oklchToHex(0.18, C * 0.05, H)
|
|
351
|
+
: oklchToHex(0.96, C * 0.02, H);
|
|
352
|
+
|
|
353
|
+
// === Text ===
|
|
354
|
+
const tPrimary = isDark
|
|
355
|
+
? oklchToHex(0.93, C * 0.02, H)
|
|
356
|
+
: oklchToHex(0.22, C * 0.03, H);
|
|
357
|
+
const tMuted = isDark
|
|
358
|
+
? oklchToHex(0.58, C * 0.04, H)
|
|
359
|
+
: oklchToHex(0.48, C * 0.04, H);
|
|
360
|
+
|
|
361
|
+
// === Borders ===
|
|
362
|
+
const borderGlass = isDark
|
|
363
|
+
? oklchToRgba(0.75, C * 0.03, H, 0.1)
|
|
364
|
+
: oklchToRgba(0.40, C * 0.05, H, 0.12);
|
|
365
|
+
|
|
366
|
+
// === Accents ===
|
|
367
|
+
const accentPrimary = isDark
|
|
368
|
+
? oklchToHex(L, C, H)
|
|
369
|
+
: oklchToHex(L * 0.72, C * 0.9, H);
|
|
370
|
+
const accentSecondary = isDark
|
|
371
|
+
? oklchToHex(L * 0.9, C * 0.8, secondaryH)
|
|
372
|
+
: oklchToHex(L * 0.6, C * 0.7, secondaryH);
|
|
373
|
+
const glowPrimary = isDark
|
|
374
|
+
? oklchToRgba(L, C, H, 0.3)
|
|
375
|
+
: oklchToRgba(L * 0.72, C * 0.9, H, 0.15);
|
|
376
|
+
|
|
377
|
+
// === Status Colors ===
|
|
378
|
+
const successH = (H + 145) % 360;
|
|
379
|
+
const warningH = (H + 100) % 360;
|
|
380
|
+
const errorH = (H + 210) % 360;
|
|
381
|
+
|
|
382
|
+
const colorError = isDark
|
|
383
|
+
? oklchToHex(0.68, 0.18, errorH)
|
|
384
|
+
: oklchToHex(0.50, 0.16, errorH);
|
|
385
|
+
const colorErrorGlow = oklchToRgba(0.68, 0.18, errorH, isDark ? 0.3 : 0.2);
|
|
386
|
+
const colorErrorBg = oklchToRgba(0.68, 0.18, errorH, isDark ? 0.1 : 0.08);
|
|
387
|
+
const colorSuccess = isDark
|
|
388
|
+
? oklchToHex(0.72, 0.16, successH)
|
|
389
|
+
: oklchToHex(0.48, 0.14, successH);
|
|
390
|
+
const colorSuccessBg = oklchToRgba(0.55, 0.12, successH, isDark ? 0.2 : 0.12);
|
|
391
|
+
const colorWarning = isDark
|
|
392
|
+
? oklchToHex(0.78, 0.15, warningH)
|
|
393
|
+
: oklchToHex(0.55, 0.14, warningH);
|
|
394
|
+
const colorWarningBg = oklchToRgba(0.78, 0.15, warningH, isDark ? 0.15 : 0.12);
|
|
395
|
+
const colorWarningGlow = oklchToRgba(0.78, 0.15, warningH, isDark ? 0.3 : 0.2);
|
|
396
|
+
|
|
397
|
+
// === CLI Preview ===
|
|
398
|
+
const cliBg = isDark
|
|
399
|
+
? oklchToHex(0.10, C * 0.03, H)
|
|
400
|
+
: oklchToHex(0.96, C * 0.02, H);
|
|
401
|
+
const cliBorder = isDark
|
|
402
|
+
? oklchToHex(0.28, C * 0.05, H)
|
|
403
|
+
: borderGlass;
|
|
404
|
+
const cliText = isDark
|
|
405
|
+
? oklchToHex(0.72, 0.14, successH)
|
|
406
|
+
: oklchToHex(0.42, 0.12, successH);
|
|
407
|
+
const cliMuted = isDark
|
|
408
|
+
? oklchToHex(0.40, C * 0.04, H)
|
|
409
|
+
: tMuted;
|
|
410
|
+
const cliHoverBg = isDark
|
|
411
|
+
? oklchToHex(0.15, C * 0.04, H)
|
|
412
|
+
: oklchToHex(0.92, C * 0.03, H);
|
|
413
|
+
|
|
414
|
+
// === Syntax Highlighting (fixed offsets from base hue) ===
|
|
415
|
+
const syntaxComment = isDark
|
|
416
|
+
? oklchToHex(0.50, C * 0.04, H)
|
|
417
|
+
: oklchToHex(0.50, C * 0.04, H);
|
|
418
|
+
const syntaxKeyword = isDark
|
|
419
|
+
? oklchToHex(0.72, 0.14, (H + 30) % 360)
|
|
420
|
+
: oklchToHex(0.45, 0.14, (H + 30) % 360);
|
|
421
|
+
const syntaxString = isDark
|
|
422
|
+
? oklchToHex(0.75, 0.13, successH)
|
|
423
|
+
: oklchToHex(0.45, 0.13, successH);
|
|
424
|
+
const syntaxNumber = isDark
|
|
425
|
+
? oklchToHex(0.72, 0.14, (H + 180) % 360)
|
|
426
|
+
: oklchToHex(0.48, 0.14, (H + 180) % 360);
|
|
427
|
+
const syntaxFunction = isDark
|
|
428
|
+
? oklchToHex(0.75, 0.12, (H + 60) % 360)
|
|
429
|
+
: oklchToHex(0.45, 0.12, (H + 60) % 360);
|
|
430
|
+
const syntaxOperator = isDark
|
|
431
|
+
? oklchToHex(0.70, 0.10, (H + 15) % 360)
|
|
432
|
+
: oklchToHex(0.45, 0.10, (H + 15) % 360);
|
|
433
|
+
const syntaxVariable = isDark
|
|
434
|
+
? oklchToHex(0.80, 0.10, (H - 20 + 360) % 360)
|
|
435
|
+
: oklchToHex(0.40, 0.10, (H - 20 + 360) % 360);
|
|
436
|
+
const syntaxPunctuation = isDark
|
|
437
|
+
? oklchToHex(0.65, C * 0.04, H)
|
|
438
|
+
: oklchToHex(0.40, C * 0.04, H);
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
bgApp, bgGlass, bgGlassStrong, bgPanel,
|
|
442
|
+
tPrimary, tMuted, borderGlass,
|
|
443
|
+
accentPrimary, accentSecondary, glowPrimary,
|
|
444
|
+
colorError, colorErrorGlow, colorErrorBg,
|
|
445
|
+
colorSuccess, colorSuccessBg,
|
|
446
|
+
colorWarning, colorWarningBg, colorWarningGlow,
|
|
447
|
+
colorInfo: accentSecondary,
|
|
448
|
+
cliBg, cliBorder, cliText, cliMuted, cliHoverBg,
|
|
449
|
+
syntaxComment, syntaxKeyword, syntaxString, syntaxNumber,
|
|
450
|
+
syntaxFunction, syntaxOperator, syntaxVariable, syntaxPunctuation,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Convert BeamThemeColors to a CSS custom properties object
|
|
456
|
+
* suitable for applying to :host or document root.
|
|
457
|
+
*/
|
|
458
|
+
export function beamThemeToCSS(colors: BeamThemeColors): Record<string, string> {
|
|
459
|
+
return {
|
|
460
|
+
'--bg-app': colors.bgApp,
|
|
461
|
+
'--bg-glass': colors.bgGlass,
|
|
462
|
+
'--bg-glass-strong': colors.bgGlassStrong,
|
|
463
|
+
'--bg-panel': colors.bgPanel,
|
|
464
|
+
'--t-primary': colors.tPrimary,
|
|
465
|
+
'--t-muted': colors.tMuted,
|
|
466
|
+
'--border-glass': colors.borderGlass,
|
|
467
|
+
'--accent-primary': colors.accentPrimary,
|
|
468
|
+
'--accent-secondary': colors.accentSecondary,
|
|
469
|
+
'--glow-primary': colors.glowPrimary,
|
|
470
|
+
'--color-error': colors.colorError,
|
|
471
|
+
'--color-error-glow': colors.colorErrorGlow,
|
|
472
|
+
'--color-error-bg': colors.colorErrorBg,
|
|
473
|
+
'--color-success': colors.colorSuccess,
|
|
474
|
+
'--color-success-bg': colors.colorSuccessBg,
|
|
475
|
+
'--color-warning': colors.colorWarning,
|
|
476
|
+
'--color-warning-bg': colors.colorWarningBg,
|
|
477
|
+
'--color-warning-glow': colors.colorWarningGlow,
|
|
478
|
+
'--color-info': colors.colorInfo,
|
|
479
|
+
'--cli-bg': colors.cliBg,
|
|
480
|
+
'--cli-border': colors.cliBorder,
|
|
481
|
+
'--cli-text': colors.cliText,
|
|
482
|
+
'--cli-muted': colors.cliMuted,
|
|
483
|
+
'--cli-hover-bg': colors.cliHoverBg,
|
|
484
|
+
'--syntax-comment': colors.syntaxComment,
|
|
485
|
+
'--syntax-keyword': colors.syntaxKeyword,
|
|
486
|
+
'--syntax-string': colors.syntaxString,
|
|
487
|
+
'--syntax-number': colors.syntaxNumber,
|
|
488
|
+
'--syntax-function': colors.syntaxFunction,
|
|
489
|
+
'--syntax-operator': colors.syntaxOperator,
|
|
490
|
+
'--syntax-variable': colors.syntaxVariable,
|
|
491
|
+
'--syntax-punctuation': colors.syntaxPunctuation,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* This is the foundation for all Photon UI - both BEAM interface and auto-generated UIs.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
// Re-export OKLCH engine (browser-safe, no Node.js deps)
|
|
12
|
+
export * from './oklch.js';
|
|
13
|
+
|
|
11
14
|
// =============================================================================
|
|
12
15
|
// SPACING - 8pt Grid System (Apple HIG)
|
|
13
16
|
// =============================================================================
|
|
@@ -122,14 +125,14 @@ const colorPalette = {
|
|
|
122
125
|
100: '#ffffff',
|
|
123
126
|
},
|
|
124
127
|
|
|
125
|
-
// Light-theme neutrals (
|
|
128
|
+
// Light-theme neutrals (warm cream/beige undertone)
|
|
126
129
|
neutralLight: {
|
|
127
|
-
85: '#
|
|
128
|
-
88: '#
|
|
129
|
-
91: '#
|
|
130
|
-
94: '#
|
|
131
|
-
96: '#
|
|
132
|
-
98: '#
|
|
130
|
+
85: '#C8C0B8', // heavy borders / dividers
|
|
131
|
+
88: '#D0C9C1', // surface-container-highest
|
|
132
|
+
91: '#DDD7CF', // surface-container-high
|
|
133
|
+
94: '#EAE4DD', // bg-app — warm cream structure
|
|
134
|
+
96: '#F0EBE5', // surface-container
|
|
135
|
+
98: '#F8F5F1', // surface — warm off-white panels
|
|
133
136
|
},
|
|
134
137
|
|
|
135
138
|
// Primary (blue - trust, action)
|
|
@@ -244,10 +247,10 @@ export const colorsLight = {
|
|
|
244
247
|
surfaceContainerHighest: colorPalette.neutralLight[88], // #C4CDD5
|
|
245
248
|
surfaceBright: colorPalette.neutral[100], // #FFFFFF — overlays/modals
|
|
246
249
|
|
|
247
|
-
// Text on surfaces (
|
|
248
|
-
onSurface: '#
|
|
249
|
-
onSurfaceVariant: '#
|
|
250
|
-
onSurfaceMuted: '#
|
|
250
|
+
// Text on surfaces (warm charcoal, high contrast)
|
|
251
|
+
onSurface: '#2C2420', // warm brown-charcoal — WCAG AAA
|
|
252
|
+
onSurfaceVariant: '#4A3F38', // warm dark brown for readability
|
|
253
|
+
onSurfaceMuted: '#6B5E54', // warm muted brown — WCAG AA
|
|
251
254
|
|
|
252
255
|
// Primary (darker for light theme)
|
|
253
256
|
primary: colorPalette.primary[40],
|
|
@@ -273,9 +276,9 @@ export const colorsLight = {
|
|
|
273
276
|
onError: colorPalette.neutral[100], // white text
|
|
274
277
|
onErrorContainer: colorPalette.error[10],
|
|
275
278
|
|
|
276
|
-
// Outline (visible but not harsh)
|
|
277
|
-
outline: '#
|
|
278
|
-
outlineVariant: '#
|
|
279
|
+
// Outline (warm, visible but not harsh)
|
|
280
|
+
outline: '#9B9088', // warm gray-brown for visibility
|
|
281
|
+
outlineVariant: '#D5CFC8', // warm border, not washed out
|
|
279
282
|
|
|
280
283
|
// Scrim (overlay — lighter than dark theme's 0.5)
|
|
281
284
|
scrim: 'rgba(0, 0, 0, 0.2)' as const,
|