claude-artifact-framework 0.2.1 → 0.4.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.
package/dist/index.esm.js CHANGED
@@ -100,10 +100,1095 @@ function createSharedStore(key, initialState, { pollMs = 2e3, debounceMs = 300 }
100
100
  const timer = setInterval(poll, pollMs);
101
101
  return { ...store, ready, stop: () => clearInterval(timer) };
102
102
  }
103
+
104
+ // src/react-runtime.js
105
+ var React = typeof globalThis !== "undefined" ? globalThis.React : void 0;
106
+ function useReact(instance) {
107
+ React = instance;
108
+ return instance;
109
+ }
110
+ function hasReact() {
111
+ return Boolean(React);
112
+ }
113
+ function getReact() {
114
+ return required();
115
+ }
116
+ function required() {
117
+ if (!React) {
118
+ throw new Error(
119
+ 'claude-artifact-framework: React not found. Hand it over once, before createApp:\n\n import React from "react";\n ArtifactKit.useReact(React);\n'
120
+ );
121
+ }
122
+ return React;
123
+ }
124
+ var createElement = (...args) => required().createElement(...args);
125
+ var useState = (...args) => required().useState(...args);
126
+ var useEffect = (...args) => required().useEffect(...args);
127
+ var Component = React && React.Component || class ReactMissing {
128
+ render() {
129
+ return required();
130
+ }
131
+ };
132
+
133
+ // src/appState.js
134
+ var DATA_KEY = "caf:data";
135
+ var VIEW_KEY = "caf:view";
136
+ var EMPTY_VIEW = { screen: null, arg: null, stack: [], step: 0, selected: null };
137
+ function isPlainObject(v) {
138
+ return v !== null && typeof v === "object" && !Array.isArray(v);
139
+ }
140
+ function mergeDefaults(defaults, stored) {
141
+ if (!isPlainObject(defaults) || !isPlainObject(stored)) {
142
+ return stored === void 0 ? defaults : stored;
143
+ }
144
+ const out = { ...defaults };
145
+ for (const key of Object.keys(stored)) {
146
+ out[key] = isPlainObject(defaults[key]) ? mergeDefaults(defaults[key], stored[key]) : stored[key];
147
+ }
148
+ return out;
149
+ }
150
+ function debounce2(fn, ms) {
151
+ let timer;
152
+ return (...args) => {
153
+ clearTimeout(timer);
154
+ timer = setTimeout(() => fn(...args), ms);
155
+ };
156
+ }
157
+ function createAppState(defaults, { debounceMs = 400 } = {}) {
158
+ let data = structuredCloneish(defaults);
159
+ let view = { ...EMPTY_VIEW };
160
+ let hydrated = false;
161
+ const listeners = /* @__PURE__ */ new Set();
162
+ let queued = false;
163
+ function notify() {
164
+ if (queued) return;
165
+ queued = true;
166
+ Promise.resolve().then(() => {
167
+ queued = false;
168
+ for (const fn of listeners) fn();
169
+ });
170
+ }
171
+ const persistData = debounce2(() => storage.set(DATA_KEY, data), debounceMs);
172
+ const persistView = debounce2(() => storage.set(VIEW_KEY, view), debounceMs);
173
+ const ready = (async () => {
174
+ const [storedData, storedView] = await Promise.all([
175
+ storage.get(DATA_KEY, void 0),
176
+ storage.get(VIEW_KEY, void 0)
177
+ ]);
178
+ if (storedData !== void 0) data = mergeDefaults(defaults, storedData);
179
+ if (storedView !== void 0) view = { ...EMPTY_VIEW, ...storedView };
180
+ hydrated = true;
181
+ notify();
182
+ })();
183
+ function update(fn) {
184
+ const result = fn(data);
185
+ if (result !== void 0) data = result;
186
+ data = { ...data };
187
+ persistData();
188
+ notify();
189
+ }
190
+ function setView(patch) {
191
+ view = { ...view, ...patch };
192
+ persistView();
193
+ notify();
194
+ }
195
+ return {
196
+ ready,
197
+ isHydrated: () => hydrated,
198
+ getData: () => data,
199
+ getView: () => view,
200
+ update,
201
+ setView,
202
+ go(screen, arg) {
203
+ setView({ stack: [...view.stack, { screen: view.screen, arg: view.arg }], screen, arg });
204
+ },
205
+ back() {
206
+ const stack = view.stack.slice();
207
+ const previous = stack.pop() || { screen: null, arg: null };
208
+ setView({ stack, screen: previous.screen, arg: previous.arg });
209
+ },
210
+ subscribe(fn) {
211
+ listeners.add(fn);
212
+ return () => listeners.delete(fn);
213
+ }
214
+ };
215
+ }
216
+ function structuredCloneish(value) {
217
+ if (Array.isArray(value)) return value.map(structuredCloneish);
218
+ if (isPlainObject(value)) {
219
+ const out = {};
220
+ for (const k of Object.keys(value)) out[k] = structuredCloneish(value[k]);
221
+ return out;
222
+ }
223
+ return value;
224
+ }
225
+
226
+ // src/theme.js
227
+ function clamp(n, lo, hi) {
228
+ return Math.min(hi, Math.max(lo, n));
229
+ }
230
+ function hexToRgb(hex) {
231
+ let h = String(hex).replace("#", "").trim();
232
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
233
+ const n = parseInt(h, 16);
234
+ if (!Number.isFinite(n)) return [59, 110, 246];
235
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
236
+ }
237
+ function rgbToHex(rgb) {
238
+ return "#" + rgb.map((v) => clamp(Math.round(v), 0, 255).toString(16).padStart(2, "0")).join("");
239
+ }
240
+ function rgbToHsl([r, g, b]) {
241
+ r /= 255;
242
+ g /= 255;
243
+ b /= 255;
244
+ const max = Math.max(r, g, b);
245
+ const min = Math.min(r, g, b);
246
+ const l = (max + min) / 2;
247
+ const d = max - min;
248
+ let h = 0;
249
+ let s = 0;
250
+ if (d) {
251
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
252
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
253
+ else if (max === g) h = (b - r) / d + 2;
254
+ else h = (r - g) / d + 4;
255
+ h /= 6;
256
+ }
257
+ return [h * 360, s * 100, l * 100];
258
+ }
259
+ function hslToRgb(h, s, l) {
260
+ h = (h % 360 + 360) % 360 / 360;
261
+ s = clamp(s, 0, 100) / 100;
262
+ l = clamp(l, 0, 100) / 100;
263
+ if (!s) return [l * 255, l * 255, l * 255];
264
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
265
+ const p = 2 * l - q;
266
+ const channel = (t) => {
267
+ if (t < 0) t += 1;
268
+ if (t > 1) t -= 1;
269
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
270
+ if (t < 1 / 2) return q;
271
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
272
+ return p;
273
+ };
274
+ return [channel(h + 1 / 3) * 255, channel(h) * 255, channel(h - 1 / 3) * 255];
275
+ }
276
+ function hsl(h, s, l) {
277
+ return rgbToHex(hslToRgb(h, s, l));
278
+ }
279
+ function luminance(rgb) {
280
+ const a = rgb.map((v) => {
281
+ v /= 255;
282
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
283
+ });
284
+ return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
285
+ }
286
+ function contrast(a, b) {
287
+ const la = luminance(hexToRgb(a));
288
+ const lb = luminance(hexToRgb(b));
289
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
290
+ }
291
+ function accentAt(h, s, against, min, from, step) {
292
+ for (let l = from; l >= 4 && l <= 96; l += step) {
293
+ const candidate = hsl(h, s, l);
294
+ if (contrast(candidate, against) >= min) return candidate;
295
+ }
296
+ return hsl(h, s, step < 0 ? 24 : 82);
297
+ }
298
+ function palette(seed = "#3b6ef6") {
299
+ const [h, rawSat] = rgbToHsl(hexToRgb(seed));
300
+ const sat = clamp(rawSat, 30, 88);
301
+ const tint = clamp(sat * 0.1, 3, 9);
302
+ const lightSurface = hsl(h, tint * 0.4, 100);
303
+ const darkSurface = hsl(h, tint * 1.4, 13);
304
+ return {
305
+ light: {
306
+ bg: hsl(h, tint, 96.5),
307
+ surface: lightSurface,
308
+ sunken: hsl(h, tint, 93),
309
+ border: hsl(h, tint, 86),
310
+ text: hsl(h, tint * 1.6, 11),
311
+ muted: hsl(h, tint * 1.2, 41),
312
+ accent: accentAt(h, sat, "#ffffff", 4.5, 54, -2),
313
+ accentText: "#ffffff",
314
+ danger: accentAt(2, 68, "#ffffff", 4.5, 54, -2),
315
+ ok: accentAt(148, 62, "#ffffff", 4.5, 44, -2),
316
+ warn: accentAt(38, 88, "#ffffff", 4.5, 44, -2)
317
+ },
318
+ dark: {
319
+ bg: hsl(h, tint * 1.5, 9),
320
+ surface: darkSurface,
321
+ sunken: hsl(h, tint * 1.5, 7),
322
+ border: hsl(h, tint * 1.3, 24),
323
+ text: hsl(h, tint * 0.6, 95),
324
+ muted: hsl(h, tint * 0.8, 66),
325
+ accent: accentAt(h, clamp(sat, 40, 80), darkSurface, 4.5, 62, 2),
326
+ accentText: hsl(h, tint * 2, 8),
327
+ danger: accentAt(2, 70, darkSurface, 4.5, 62, 2),
328
+ ok: accentAt(148, 55, darkSurface, 4.5, 62, 2),
329
+ warn: accentAt(38, 80, darkSurface, 4.5, 62, 2)
330
+ }
331
+ };
332
+ }
333
+ function vars(scheme) {
334
+ return Object.entries(scheme).map(([k, v]) => `--caf-${k}: ${v};`).join("");
335
+ }
336
+ function themeCss(seed) {
337
+ const p = palette(seed);
338
+ return `
339
+ :root { ${vars(p.light)} }
340
+ @media (prefers-color-scheme: dark) { :root { ${vars(p.dark)} } }
341
+ :root[data-theme="dark"] { ${vars(p.dark)} }
342
+ :root[data-theme="light"] { ${vars(p.light)} }
343
+ `;
344
+ }
345
+ function seriesColors(seed, n) {
346
+ const [h0, s0] = rgbToHsl(hexToRgb(seed || "#3b6ef6"));
347
+ const s = clamp(s0, 45, 70);
348
+ return Array.from({ length: n }, (_, i) => hsl((h0 + i * 137.5) % 360, s, 52));
349
+ }
350
+
351
+ // src/blocks.js
352
+ function formatValue(value, format) {
353
+ if (value === null || value === void 0 || value === "") return "\u2014";
354
+ const n = Number(value);
355
+ switch (format) {
356
+ case "money":
357
+ return Number.isFinite(n) ? n.toLocaleString(void 0, { style: "currency", currency: "USD", maximumFractionDigits: 2 }) : String(value);
358
+ case "percent":
359
+ return Number.isFinite(n) ? `${n.toLocaleString(void 0, { maximumFractionDigits: 2 })}%` : String(value);
360
+ case "number":
361
+ return Number.isFinite(n) ? n.toLocaleString(void 0, { maximumFractionDigits: 2 }) : String(value);
362
+ case "date":
363
+ return value instanceof Date ? value.toLocaleDateString() : String(value);
364
+ default:
365
+ return String(value);
366
+ }
367
+ }
368
+ function validateField(field, value) {
369
+ if (field.required && (value === "" || value === null || value === void 0)) {
370
+ return "Required";
371
+ }
372
+ if (value === "" || value === null || value === void 0) return null;
373
+ if (field.type === "number" || field.type === "money" || field.type === "percent") {
374
+ const n = Number(value);
375
+ if (!Number.isFinite(n)) return "Must be a number";
376
+ if (field.min !== void 0 && n < field.min) return `Must be at least ${field.min}`;
377
+ if (field.max !== void 0 && n > field.max) return `Must be at most ${field.max}`;
378
+ }
379
+ return null;
380
+ }
381
+ function Field({ field, value, onChange }) {
382
+ const error = validateField(field, value);
383
+ const id = `caf-f-${field.key}`;
384
+ const numeric = field.type === "number" || field.type === "money" || field.type === "percent";
385
+ if (field.type === "check") {
386
+ return createElement(
387
+ "label",
388
+ { className: "caf-check" },
389
+ createElement("input", {
390
+ id,
391
+ type: "checkbox",
392
+ checked: Boolean(value),
393
+ onChange: (e) => onChange(e.target.checked)
394
+ }),
395
+ createElement("span", null, field.label || field.key)
396
+ );
397
+ }
398
+ const common = {
399
+ id,
400
+ value: value === null || value === void 0 ? "" : value,
401
+ "aria-invalid": error ? "true" : void 0,
402
+ onChange: (e) => onChange(numeric ? toNumber(e.target.value) : e.target.value)
403
+ };
404
+ let control;
405
+ if (field.type === "textarea") {
406
+ control = createElement("textarea", { ...common, rows: field.rows || 3 });
407
+ } else if (field.type === "select") {
408
+ control = createElement(
409
+ "select",
410
+ common,
411
+ createElement("option", { value: "" }, field.placeholder || "Select\u2026"),
412
+ (field.options || []).map((opt) => {
413
+ const val = typeof opt === "string" ? opt : opt.value;
414
+ const label = typeof opt === "string" ? opt : opt.label;
415
+ return createElement("option", { key: val, value: val }, label);
416
+ })
417
+ );
418
+ } else {
419
+ control = createElement("input", {
420
+ ...common,
421
+ type: field.type === "date" ? "date" : numeric ? "number" : "text",
422
+ min: field.min,
423
+ max: field.max,
424
+ step: field.step || (field.type === "money" || field.type === "percent" ? "any" : void 0),
425
+ placeholder: field.placeholder
426
+ });
427
+ }
428
+ return createElement(
429
+ "div",
430
+ { className: "caf-field" },
431
+ createElement("label", { htmlFor: id }, field.label || field.key),
432
+ control,
433
+ field.hint && !error ? createElement("small", { className: "caf-hint" }, field.hint) : null,
434
+ error ? createElement("small", { className: "caf-error" }, error) : null
435
+ );
436
+ }
437
+ function toNumber(raw) {
438
+ if (raw === "") return "";
439
+ const n = Number(raw);
440
+ return Number.isFinite(n) ? n : raw;
441
+ }
442
+ function FieldsBlock({ spec, ctx }) {
443
+ const fields = spec.fields || [];
444
+ return createElement(
445
+ "div",
446
+ { className: "caf-block caf-fields" },
447
+ spec.title ? createElement("h2", { className: "caf-block-title" }, spec.title) : null,
448
+ fields.map(
449
+ (field) => createElement(Field, {
450
+ key: field.key,
451
+ field,
452
+ value: ctx.data[field.key],
453
+ onChange: (v) => ctx.update((d) => {
454
+ d[field.key] = v;
455
+ })
456
+ })
457
+ )
458
+ );
459
+ }
460
+ function OutputBlock({ spec, ctx }) {
461
+ const items = typeof spec.output === "function" ? spec.output(ctx.data) : spec.output || [];
462
+ if (!items.length) {
463
+ return createElement("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing to show yet.");
464
+ }
465
+ return createElement(
466
+ "div",
467
+ { className: "caf-block caf-output" },
468
+ spec.title ? createElement("h2", { className: "caf-block-title" }, spec.title) : null,
469
+ createElement(
470
+ "div",
471
+ { className: "caf-output-grid" },
472
+ items.map(
473
+ (item, i) => createElement(
474
+ "div",
475
+ { key: item.label || i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
476
+ createElement("span", { className: "caf-stat-label" }, item.label),
477
+ createElement("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
478
+ item.hint ? createElement("small", { className: "caf-hint" }, item.hint) : null
479
+ )
480
+ )
481
+ )
482
+ );
483
+ }
484
+ function TextBlock({ spec, ctx }) {
485
+ const body = typeof spec.text === "function" ? spec.text(ctx.data) : spec.text;
486
+ return createElement(
487
+ "div",
488
+ { className: "caf-block caf-text" },
489
+ spec.title ? createElement("h2", { className: "caf-block-title" }, spec.title) : null,
490
+ createElement("p", null, body)
491
+ );
492
+ }
493
+ function ListBlock({ spec, ctx }) {
494
+ const rows = (typeof spec.list === "function" ? spec.list(ctx.data) : spec.list) || [];
495
+ if (!rows.length) {
496
+ return createElement("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing here yet.");
497
+ }
498
+ const title = spec.title || ((r) => r.title ?? r.name ?? r.label ?? String(r));
499
+ return createElement(
500
+ "div",
501
+ { className: "caf-block caf-list" },
502
+ rows.map(
503
+ (row, i) => createElement(
504
+ "button",
505
+ {
506
+ key: row.id ?? i,
507
+ className: "caf-row",
508
+ type: "button",
509
+ onClick: spec.onSelect ? () => spec.onSelect(row, ctx) : void 0
510
+ },
511
+ createElement("span", { className: "caf-row-title" }, title(row)),
512
+ spec.subtitle ? createElement("span", { className: "caf-row-sub" }, spec.subtitle(row)) : null
513
+ )
514
+ )
515
+ );
516
+ }
517
+ var CHART_TYPES = ["bar", "line", "donut"];
518
+ function ChartBlock({ spec, ctx }) {
519
+ const c = typeof spec.chart === "function" ? spec.chart(ctx.data) : spec.chart;
520
+ const items = c && c.items || [];
521
+ if (!items.length) {
522
+ return createElement("div", { className: "caf-block caf-empty" }, spec.empty || "No data to chart yet.");
523
+ }
524
+ const type = c.type || "bar";
525
+ if (!CHART_TYPES.includes(type)) {
526
+ throw new Error(`unknown chart type "${type}". Valid types: ${CHART_TYPES.join(", ")}.`);
527
+ }
528
+ const colors = ctx.colors(items.length);
529
+ const body = type === "bar" ? barChart(items, c, colors) : type === "line" ? lineChart(items, c) : donutChart(items, c, colors);
530
+ return createElement(
531
+ "div",
532
+ { className: "caf-block caf-chart" },
533
+ spec.title ? createElement("h2", { className: "caf-block-title" }, spec.title) : null,
534
+ body
535
+ );
536
+ }
537
+ function barChart(items, c, colors) {
538
+ const max = Math.max(...items.map((it) => Number(it.value) || 0), 0) || 1;
539
+ return createElement(
540
+ "div",
541
+ { className: "caf-bars" },
542
+ items.map(
543
+ (it, i) => createElement(
544
+ "div",
545
+ { key: it.label ?? i, className: "caf-bar-row" },
546
+ createElement("span", { className: "caf-bar-label" }, it.label),
547
+ createElement(
548
+ "div",
549
+ { className: "caf-bar-track" },
550
+ createElement("div", {
551
+ className: "caf-bar-fill",
552
+ style: { width: `${Math.max(2, (Number(it.value) || 0) / max * 100)}%`, background: colors[i] }
553
+ })
554
+ ),
555
+ createElement("span", { className: "caf-bar-value" }, formatValue(it.value, c.format))
556
+ )
557
+ )
558
+ );
559
+ }
560
+ function lineChart(items, c) {
561
+ const values = items.map((it) => Number(it.value) || 0);
562
+ const min = Math.min(...values);
563
+ const max = Math.max(...values);
564
+ const span = max - min || 1;
565
+ const W = 100;
566
+ const H = 36;
567
+ const pts = values.map((v, i) => [
568
+ values.length === 1 ? W / 2 : i / (values.length - 1) * W,
569
+ H - 3 - (v - min) / span * (H - 6)
570
+ ]);
571
+ const last = pts[pts.length - 1];
572
+ return createElement(
573
+ "div",
574
+ { className: "caf-line" },
575
+ createElement(
576
+ "svg",
577
+ { viewBox: `0 0 ${W} ${H}`, className: "caf-line-svg", preserveAspectRatio: "none", "aria-hidden": "true" },
578
+ [0.25, 0.5, 0.75].map(
579
+ (f) => createElement("line", { key: f, x1: 0, x2: W, y1: H * f, y2: H * f, className: "caf-line-grid" })
580
+ ),
581
+ createElement("polyline", { points: pts.map((p) => p.join(",")).join(" "), className: "caf-line-path" }),
582
+ createElement("circle", { cx: last[0], cy: last[1], r: 1.6, className: "caf-line-dot" })
583
+ ),
584
+ createElement(
585
+ "div",
586
+ { className: "caf-line-meta" },
587
+ createElement("span", { className: "caf-hint" }, String(items[0].label ?? "")),
588
+ createElement("span", { className: "caf-line-last" }, formatValue(values[values.length - 1], c.format)),
589
+ createElement("span", { className: "caf-hint" }, String(items[items.length - 1].label ?? ""))
590
+ )
591
+ );
592
+ }
593
+ function donutChart(items, c, colors) {
594
+ const total = items.reduce((s, it) => s + (Number(it.value) || 0), 0) || 1;
595
+ const R = 15.9155;
596
+ let offset = 25;
597
+ const segments = items.map((it, i) => {
598
+ const pct = (Number(it.value) || 0) / total * 100;
599
+ const seg = createElement("circle", {
600
+ key: it.label ?? i,
601
+ cx: 21,
602
+ cy: 21,
603
+ r: R,
604
+ className: "caf-donut-seg",
605
+ stroke: colors[i],
606
+ strokeDasharray: `${pct} ${100 - pct}`,
607
+ strokeDashoffset: offset
608
+ });
609
+ offset -= pct;
610
+ return seg;
611
+ });
612
+ return createElement(
613
+ "div",
614
+ { className: "caf-donut" },
615
+ createElement("svg", { viewBox: "0 0 42 42", className: "caf-donut-svg", "aria-hidden": "true" }, segments),
616
+ createElement(
617
+ "div",
618
+ { className: "caf-donut-legend" },
619
+ items.map(
620
+ (it, i) => createElement(
621
+ "div",
622
+ { key: it.label ?? i, className: "caf-legend-row" },
623
+ createElement("span", { className: "caf-legend-swatch", style: { background: colors[i] } }),
624
+ createElement("span", { className: "caf-legend-label" }, it.label),
625
+ createElement("span", { className: "caf-legend-value" }, formatValue(it.value, c.format))
626
+ )
627
+ )
628
+ )
629
+ );
630
+ }
631
+ function TimerBlock({ spec, ctx }) {
632
+ const t = spec.timer || {};
633
+ const total = Math.max(1, Math.round((t.minutes || 0) * 60 + (t.seconds || 0)) || 300);
634
+ const [left, setLeft] = useState(total);
635
+ const [running, setRunning] = useState(false);
636
+ useEffect(() => {
637
+ if (!running) return;
638
+ const id = setInterval(() => setLeft((prev) => Math.max(0, prev - 1)), 1e3);
639
+ return () => clearInterval(id);
640
+ }, [running]);
641
+ useEffect(() => {
642
+ if (left === 0 && running) {
643
+ setRunning(false);
644
+ if (typeof t.onDone === "function") ctx.update((d) => t.onDone(d));
645
+ }
646
+ }, [left, running]);
647
+ const mm = String(Math.floor(left / 60)).padStart(2, "0");
648
+ const ss = String(left % 60).padStart(2, "0");
649
+ const done = left === 0;
650
+ return createElement(
651
+ "div",
652
+ { className: "caf-block caf-timer" },
653
+ spec.title ? createElement("h2", { className: "caf-block-title" }, spec.title) : null,
654
+ t.label ? createElement("span", { className: "caf-hint" }, t.label) : null,
655
+ createElement("div", { className: done ? "caf-timer-time caf-timer-done" : "caf-timer-time" }, done ? t.doneText || "Done!" : `${mm}:${ss}`),
656
+ createElement("div", { className: "caf-bar-track" }, createElement("div", { className: "caf-bar-fill caf-timer-fill", style: { width: `${left / total * 100}%` } })),
657
+ createElement(
658
+ "div",
659
+ { className: "caf-timer-controls" },
660
+ createElement(
661
+ "button",
662
+ { type: "button", className: "caf-btn caf-btn-primary", disabled: done, onClick: () => setRunning((r) => !r) },
663
+ running ? "Pause" : "Start"
664
+ ),
665
+ createElement("button", { type: "button", className: "caf-btn", onClick: () => {
666
+ setRunning(false);
667
+ setLeft(total);
668
+ } }, "Reset")
669
+ )
670
+ );
671
+ }
672
+ function CustomBlock({ spec, ctx }) {
673
+ const rendered = spec.custom(ctx);
674
+ if (typeof rendered === "string") {
675
+ return createElement("div", { className: "caf-block", dangerouslySetInnerHTML: { __html: rendered } });
676
+ }
677
+ return createElement("div", { className: "caf-block" }, rendered);
678
+ }
679
+ var BLOCKS = {
680
+ fields: FieldsBlock,
681
+ output: OutputBlock,
682
+ text: TextBlock,
683
+ list: ListBlock,
684
+ chart: ChartBlock,
685
+ timer: TimerBlock,
686
+ custom: CustomBlock
687
+ };
688
+ var BLOCK_NAMES = Object.keys(BLOCKS);
689
+
690
+ // src/records.js
691
+ function recordDefaults(fields) {
692
+ const rec = {};
693
+ for (const f of fields) {
694
+ rec[f.key] = f.value !== void 0 ? f.value : f.type === "check" ? false : "";
695
+ }
696
+ return rec;
697
+ }
698
+ function makeId() {
699
+ return "r" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
700
+ }
701
+ function rowTitle(spec, rec) {
702
+ if (spec.title) return spec.title(rec);
703
+ const f = spec.fields.find((f2) => !f2.type || f2.type === "text" || f2.type === "textarea" || f2.type === "select");
704
+ const v = f && rec[f.key];
705
+ return v === "" || v === void 0 || v === null ? "(untitled)" : String(v);
706
+ }
707
+ function Summary({ spec, records }) {
708
+ if (!spec.summary) return null;
709
+ const items = spec.summary(records) || [];
710
+ if (!items.length) return null;
711
+ return createElement(
712
+ "div",
713
+ { className: "caf-block caf-output" },
714
+ createElement(
715
+ "div",
716
+ { className: "caf-output-grid" },
717
+ items.map(
718
+ (item, i) => createElement(
719
+ "div",
720
+ { key: item.label || i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
721
+ createElement("span", { className: "caf-stat-label" }, item.label),
722
+ createElement("span", { className: "caf-stat-value" }, formatValue(item.value, item.format))
723
+ )
724
+ )
725
+ )
726
+ );
727
+ }
728
+ function ListScreen({ spec, ctx }) {
729
+ const label = spec.label || "item";
730
+ let records = ctx.data.records;
731
+ if (spec.sort) records = records.slice().sort(spec.sort);
732
+ function add() {
733
+ const rec = { id: makeId(), ...recordDefaults(spec.fields) };
734
+ ctx.update((d) => {
735
+ d.records = [...d.records, rec];
736
+ });
737
+ ctx.go("record", rec.id);
738
+ }
739
+ return createElement(
740
+ "div",
741
+ { className: "caf-block caf-list" },
742
+ createElement(
743
+ "div",
744
+ { className: "caf-toolbar" },
745
+ createElement("span", { className: "caf-count" }, `${records.length} ${label}${records.length === 1 ? "" : "s"}`),
746
+ createElement("button", { type: "button", className: "caf-btn caf-btn-primary", onClick: add }, `Add ${label}`)
747
+ ),
748
+ records.length === 0 ? createElement("div", { className: "caf-empty" }, spec.empty || `No ${label}s yet. Add the first one.`) : records.map(
749
+ (rec) => createElement(
750
+ "button",
751
+ {
752
+ key: rec.id,
753
+ type: "button",
754
+ className: "caf-row",
755
+ onClick: () => ctx.go("record", rec.id)
756
+ },
757
+ createElement("span", { className: "caf-row-title" }, rowTitle(spec, rec)),
758
+ spec.subtitle ? createElement("span", { className: "caf-row-sub" }, spec.subtitle(rec)) : null
759
+ )
760
+ )
761
+ );
762
+ }
763
+ function DetailScreen({ spec, ctx, record }) {
764
+ const label = spec.label || "item";
765
+ function remove() {
766
+ ctx.update((d) => {
767
+ d.records = d.records.filter((r) => r.id !== record.id);
768
+ });
769
+ ctx.back();
770
+ }
771
+ return createElement(
772
+ "div",
773
+ { className: "caf-block caf-fields" },
774
+ createElement(
775
+ "div",
776
+ { className: "caf-toolbar" },
777
+ createElement("button", { type: "button", className: "caf-btn", onClick: ctx.back }, "\u2039 Back"),
778
+ createElement("button", { type: "button", className: "caf-btn caf-btn-danger", onClick: remove }, `Delete ${label}`)
779
+ ),
780
+ spec.fields.map(
781
+ (field) => createElement(Field, {
782
+ key: field.key,
783
+ field,
784
+ value: record[field.key],
785
+ onChange: (v) => ctx.update((d) => {
786
+ const rec = d.records.find((r) => r.id === record.id);
787
+ if (rec) rec[field.key] = v;
788
+ })
789
+ })
790
+ )
791
+ );
792
+ }
793
+ function RecordsLayout({ spec, ctx }) {
794
+ const rspec = spec.records;
795
+ if (ctx.view.screen === "record") {
796
+ const record = ctx.data.records.find((r) => r.id === ctx.view.arg);
797
+ if (record) {
798
+ return createElement("div", { className: "caf-panel caf-panel-single" }, createElement(DetailScreen, { spec: rspec, ctx, record }));
799
+ }
800
+ }
801
+ return createElement(
802
+ "div",
803
+ { className: "caf-panel caf-panel-single" },
804
+ createElement(Summary, { spec: rspec, records: ctx.data.records }),
805
+ createElement(ListScreen, { spec: rspec, ctx })
806
+ );
807
+ }
808
+
809
+ // src/steps.js
810
+ function stepErrors(step, data) {
811
+ return (step.fields || []).some((f) => validateField(f, data[f.key]) !== null);
812
+ }
813
+ function ResultScreen({ step, ctx }) {
814
+ const items = step.result(ctx.data) || [];
815
+ return createElement(
816
+ "div",
817
+ { className: "caf-block caf-output" },
818
+ step.title ? createElement("h2", { className: "caf-block-title" }, step.title) : null,
819
+ createElement(
820
+ "div",
821
+ { className: "caf-output-grid" },
822
+ items.map(
823
+ (item, i) => createElement(
824
+ "div",
825
+ { key: item.label ?? i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
826
+ createElement("span", { className: "caf-stat-label" }, item.label),
827
+ createElement("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
828
+ item.hint ? createElement("small", { className: "caf-hint" }, item.hint) : null
829
+ )
830
+ )
831
+ ),
832
+ createElement(
833
+ "div",
834
+ { className: "caf-step-nav" },
835
+ createElement("button", { type: "button", className: "caf-btn", onClick: () => ctx.setStep(0) }, "Start over")
836
+ )
837
+ );
838
+ }
839
+ function StepsLayout({ spec, ctx }) {
840
+ const steps = spec.steps;
841
+ const i = Math.min(Math.max(ctx.view.step || 0, 0), steps.length - 1);
842
+ const step = steps[i];
843
+ const isResult = typeof step.result === "function";
844
+ const isLast = i === steps.length - 1;
845
+ return createElement(
846
+ "div",
847
+ { className: "caf-panel caf-panel-single" },
848
+ createElement(
849
+ "div",
850
+ { className: "caf-steps-progress" },
851
+ createElement("span", { className: "caf-hint" }, isResult ? "Result" : `Step ${i + 1} of ${steps.length}`),
852
+ createElement(
853
+ "div",
854
+ { className: "caf-bar-track" },
855
+ createElement("div", { className: "caf-bar-fill caf-steps-fill", style: { width: `${(i + 1) / steps.length * 100}%` } })
856
+ )
857
+ ),
858
+ isResult ? createElement(ResultScreen, { step, ctx }) : createElement(
859
+ "div",
860
+ { className: "caf-block caf-fields" },
861
+ step.title ? createElement("h2", { className: "caf-block-title" }, step.title) : null,
862
+ step.text ? createElement("p", { className: "caf-step-text" }, typeof step.text === "function" ? step.text(ctx.data) : step.text) : null,
863
+ (step.fields || []).map(
864
+ (field) => createElement(Field, {
865
+ key: field.key,
866
+ field,
867
+ value: ctx.data[field.key],
868
+ onChange: (v) => ctx.update((d) => {
869
+ d[field.key] = v;
870
+ })
871
+ })
872
+ ),
873
+ createElement(
874
+ "div",
875
+ { className: "caf-step-nav" },
876
+ i > 0 ? createElement("button", { type: "button", className: "caf-btn", onClick: () => ctx.setStep(i - 1) }, "\u2039 Back") : createElement("span"),
877
+ !isLast ? createElement(
878
+ "button",
879
+ {
880
+ type: "button",
881
+ className: "caf-btn caf-btn-primary",
882
+ disabled: stepErrors(step, ctx.data),
883
+ onClick: () => ctx.setStep(i + 1)
884
+ },
885
+ "Next \u203A"
886
+ ) : null
887
+ )
888
+ )
889
+ );
890
+ }
891
+
892
+ // src/app.js
893
+ var LAYOUTS = ["panel", "records", "steps"];
894
+ var RESERVED = ["title", "empty", "wide", "onSelect", "subtitle"];
895
+ function validate(spec) {
896
+ const layout = spec.layout || "panel";
897
+ if (!LAYOUTS.includes(layout)) {
898
+ throw new Error(
899
+ `claude-artifact-framework: unknown layout "${layout}". Valid layouts: ${LAYOUTS.join(", ")}.`
900
+ );
901
+ }
902
+ if (layout === "steps") {
903
+ const steps = spec.steps;
904
+ if (!Array.isArray(steps) || steps.length === 0) {
905
+ throw new Error('claude-artifact-framework: layout "steps" needs `steps: [...]` with at least one step.');
906
+ }
907
+ const STEP_KEYS = ["title", "text", "fields", "result"];
908
+ const seen = /* @__PURE__ */ new Set();
909
+ steps.forEach((st, i) => {
910
+ if (!st || typeof st !== "object") {
911
+ throw new Error(`claude-artifact-framework: steps[${i}] must be an object.`);
912
+ }
913
+ const unknown = Object.keys(st).filter((k) => !STEP_KEYS.includes(k));
914
+ if (unknown.length) {
915
+ throw new Error(
916
+ `claude-artifact-framework: steps[${i}] has unknown keys (${unknown.join(", ")}). Valid keys: ${STEP_KEYS.join(", ")}.`
917
+ );
918
+ }
919
+ if (!st.fields && !st.text && !st.result) {
920
+ throw new Error(`claude-artifact-framework: steps[${i}] needs \`fields\`, \`text\`, or \`result\`.`);
921
+ }
922
+ for (const f of st.fields || []) {
923
+ if (!f || !f.key) throw new Error(`claude-artifact-framework: every field in steps[${i}] needs a \`key\`.`);
924
+ if (seen.has(f.key)) {
925
+ throw new Error(
926
+ `claude-artifact-framework: field key "${f.key}" appears in more than one step \u2014 keys share one data pool and must be unique.`
927
+ );
928
+ }
929
+ seen.add(f.key);
930
+ }
931
+ });
932
+ return layout;
933
+ }
934
+ if (layout === "records") {
935
+ const r = spec.records;
936
+ if (!r || !Array.isArray(r.fields) || r.fields.length === 0) {
937
+ throw new Error(
938
+ 'claude-artifact-framework: layout "records" needs `records: { fields: [...] }` \u2014 the same field declarations the fields block uses.'
939
+ );
940
+ }
941
+ const seen = /* @__PURE__ */ new Set();
942
+ for (const f of r.fields) {
943
+ if (!f || !f.key) throw new Error("claude-artifact-framework: every records field needs a `key`.");
944
+ if (f.key === "id") throw new Error('claude-artifact-framework: "id" is reserved on records \u2014 the framework assigns it.');
945
+ if (seen.has(f.key)) throw new Error(`claude-artifact-framework: duplicate records field key "${f.key}".`);
946
+ seen.add(f.key);
947
+ }
948
+ return layout;
949
+ }
950
+ const blocks = spec.blocks || [];
951
+ if (!Array.isArray(blocks)) {
952
+ throw new Error("claude-artifact-framework: `blocks` must be an array.");
953
+ }
954
+ blocks.forEach((block, i) => {
955
+ if (!block || typeof block !== "object") {
956
+ throw new Error(`claude-artifact-framework: blocks[${i}] must be an object like { fields: [...] }.`);
957
+ }
958
+ const keys = Object.keys(block).filter((k) => !RESERVED.includes(k));
959
+ const known = keys.filter((k) => BLOCK_NAMES.includes(k));
960
+ if (known.length === 0) {
961
+ throw new Error(
962
+ `claude-artifact-framework: blocks[${i}] has no recognised block type (found: ${keys.join(", ") || "nothing"}). Valid block types: ${BLOCK_NAMES.join(", ")}.`
963
+ );
964
+ }
965
+ if (known.length > 1) {
966
+ throw new Error(
967
+ `claude-artifact-framework: blocks[${i}] declares more than one block type (${known.join(
968
+ ", "
969
+ )}). Split them into separate entries of \`blocks\`.`
970
+ );
971
+ }
972
+ });
973
+ return layout;
974
+ }
975
+ function defaultsFrom(spec) {
976
+ const data = { ...spec.data || {} };
977
+ if ((spec.layout || "panel") === "records" && !Array.isArray(data.records)) {
978
+ data.records = [];
979
+ }
980
+ for (const block of [...spec.blocks || [], ...spec.steps || []]) {
981
+ for (const field of block.fields || []) {
982
+ if (field && field.key !== void 0 && !(field.key in data)) {
983
+ data[field.key] = field.value !== void 0 ? field.value : field.type === "check" ? false : "";
984
+ }
985
+ }
986
+ }
987
+ return data;
988
+ }
989
+ var BlockBoundary = null;
990
+ function boundary(React2) {
991
+ if (!BlockBoundary) {
992
+ BlockBoundary = class extends React2.Component {
993
+ constructor(props) {
994
+ super(props);
995
+ this.state = { error: null };
996
+ }
997
+ static getDerivedStateFromError(error) {
998
+ return { error };
999
+ }
1000
+ render() {
1001
+ if (this.state.error) {
1002
+ return createElement(
1003
+ "div",
1004
+ { className: "caf-block caf-block-error" },
1005
+ createElement("strong", null, "This section failed to render"),
1006
+ createElement("code", null, String(this.state.error.message || this.state.error))
1007
+ );
1008
+ }
1009
+ return this.props.children;
1010
+ }
1011
+ };
1012
+ }
1013
+ return BlockBoundary;
1014
+ }
1015
+ function Block({ block, ctx }) {
1016
+ const name = Object.keys(block).find((k) => BLOCK_NAMES.includes(k));
1017
+ const Component2 = BLOCKS[name];
1018
+ return createElement(boundary(ctx.React), null, createElement(Component2, { spec: block, ctx }));
1019
+ }
1020
+ function Panel({ spec, ctx }) {
1021
+ return createElement(
1022
+ "div",
1023
+ { className: "caf-panel" },
1024
+ (spec.blocks || []).map(
1025
+ (block, i) => createElement(
1026
+ "section",
1027
+ { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
1028
+ createElement(Block, { block, ctx })
1029
+ )
1030
+ )
1031
+ );
1032
+ }
1033
+ var LAYOUT_COMPONENTS = { panel: Panel, records: RecordsLayout, steps: StepsLayout };
1034
+ function createApp(spec = {}) {
1035
+ const layout = validate(spec);
1036
+ const state = createAppState(defaultsFrom(spec));
1037
+ const css = themeCss(spec.theme && spec.theme.seed) + BASE_CSS;
1038
+ return function App() {
1039
+ if (!hasReact()) {
1040
+ throw new Error(
1041
+ 'claude-artifact-framework: React not found. Hand it over once, before rendering:\n\n import React from "react";\n ArtifactKit.useReact(React);\n'
1042
+ );
1043
+ }
1044
+ const [, force] = useState(0);
1045
+ useEffect(() => {
1046
+ const unsubscribe = state.subscribe(() => force((n) => n + 1));
1047
+ if (state.isHydrated()) force((n) => n + 1);
1048
+ return unsubscribe;
1049
+ }, []);
1050
+ const ctx = {
1051
+ React: getReact(),
1052
+ data: state.getData(),
1053
+ view: state.getView(),
1054
+ update: state.update,
1055
+ go: state.go,
1056
+ back: state.back,
1057
+ setStep: (n) => state.setView({ step: n }),
1058
+ colors: (n) => seriesColors(spec.theme && spec.theme.seed, n)
1059
+ };
1060
+ const Layout = LAYOUT_COMPONENTS[layout];
1061
+ return createElement(
1062
+ "div",
1063
+ { className: "caf-root" },
1064
+ createElement("style", { dangerouslySetInnerHTML: { __html: css } }),
1065
+ createElement(
1066
+ "header",
1067
+ { className: "caf-header" },
1068
+ spec.title ? createElement("h1", null, spec.title) : null,
1069
+ spec.subtitle ? createElement("p", null, spec.subtitle) : null
1070
+ ),
1071
+ state.isHydrated() ? createElement(Layout, { spec, ctx }) : createElement("div", { className: "caf-block caf-loading" }, "Loading\u2026")
1072
+ );
1073
+ };
1074
+ }
1075
+ var BASE_CSS = `
1076
+ .caf-root {
1077
+ --caf-radius: 10px;
1078
+ box-sizing: border-box;
1079
+ min-height: 100vh;
1080
+ padding: 2rem 1.25rem 4rem;
1081
+ background: var(--caf-bg);
1082
+ color: var(--caf-text);
1083
+ font-family: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
1084
+ -webkit-font-smoothing: antialiased;
1085
+ }
1086
+ .caf-root *, .caf-root *::before, .caf-root *::after { box-sizing: inherit; }
1087
+ .caf-header { max-width: 760px; margin: 0 auto 1.5rem; }
1088
+ .caf-header h1 { margin: 0; font-size: 1.5rem; font-weight: 650; letter-spacing: -0.01em; text-wrap: balance; }
1089
+ .caf-header p { margin: 0.4rem 0 0; color: var(--caf-muted); font-size: 0.95rem; line-height: 1.5; }
1090
+ .caf-panel { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
1091
+ .caf-slot { grid-column: span 2; min-width: 0; }
1092
+ @media (min-width: 720px) { .caf-slot { grid-column: span 1; } .caf-slot-wide { grid-column: span 2; } }
1093
+ .caf-block {
1094
+ background: var(--caf-surface);
1095
+ border: 1px solid var(--caf-border);
1096
+ border-radius: var(--caf-radius);
1097
+ padding: 1.1rem;
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ gap: 0.8rem;
1101
+ height: 100%;
1102
+ }
1103
+ .caf-block-title { margin: 0; font-size: 0.95rem; font-weight: 650; }
1104
+ .caf-field { display: flex; flex-direction: column; gap: 0.3rem; }
1105
+ .caf-field label { font-size: 0.78rem; font-weight: 600; color: var(--caf-muted); letter-spacing: 0.02em; }
1106
+ .caf-field input, .caf-field select, .caf-field textarea {
1107
+ font: inherit; font-size: 0.95rem; padding: 0.5rem 0.6rem;
1108
+ border-radius: 7px; border: 1px solid var(--caf-border);
1109
+ background: var(--caf-sunken); color: var(--caf-text); width: 100%;
1110
+ }
1111
+ .caf-field input:focus-visible, .caf-field select:focus-visible, .caf-field textarea:focus-visible,
1112
+ .caf-row:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
1113
+ .caf-field input[aria-invalid="true"] { border-color: var(--caf-danger); }
1114
+ .caf-check { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
1115
+ .caf-hint { color: var(--caf-muted); font-size: 0.75rem; }
1116
+ .caf-error { color: var(--caf-danger); font-size: 0.75rem; font-weight: 600; }
1117
+ .caf-output-grid { display: flex; flex-direction: column; gap: 0.7rem; }
1118
+ .caf-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
1119
+ .caf-stat-label { color: var(--caf-muted); font-size: 0.82rem; }
1120
+ .caf-stat-value { font-size: 1rem; font-weight: 600; font-variant-numeric: tabular-nums; }
1121
+ .caf-stat-big { flex-direction: column; align-items: flex-start; gap: 0.15rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--caf-border); }
1122
+ .caf-stat-big .caf-stat-value { font-size: 1.9rem; font-weight: 680; letter-spacing: -0.02em; color: var(--caf-accent); }
1123
+ .caf-list { padding: 0.4rem; gap: 0; }
1124
+ .caf-row {
1125
+ font: inherit; text-align: left; cursor: pointer;
1126
+ display: flex; flex-direction: column; gap: 0.15rem;
1127
+ padding: 0.65rem 0.7rem; border: none; border-radius: 7px;
1128
+ background: transparent; color: inherit; width: 100%;
1129
+ }
1130
+ .caf-row:hover { background: var(--caf-sunken); }
1131
+ .caf-row-title { font-size: 0.92rem; font-weight: 550; }
1132
+ .caf-row-sub { font-size: 0.78rem; color: var(--caf-muted); }
1133
+ .caf-panel-single { grid-template-columns: 1fr; }
1134
+ .caf-panel-single .caf-slot, .caf-panel-single > * { grid-column: span 1; }
1135
+ .caf-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; padding: 0.3rem 0.3rem 0.5rem; }
1136
+ .caf-count { font-size: 0.8rem; color: var(--caf-muted); font-variant-numeric: tabular-nums; }
1137
+ .caf-btn {
1138
+ font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer;
1139
+ padding: 0.45rem 0.8rem; border-radius: 7px;
1140
+ border: 1px solid var(--caf-border); background: var(--caf-sunken); color: var(--caf-text);
1141
+ }
1142
+ .caf-btn:hover { filter: brightness(0.97); }
1143
+ .caf-btn:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
1144
+ .caf-btn-primary { background: var(--caf-accent); border-color: var(--caf-accent); color: var(--caf-accentText); }
1145
+ .caf-btn-danger { background: transparent; border-color: var(--caf-danger); color: var(--caf-danger); }
1146
+ .caf-bars { display: flex; flex-direction: column; gap: 0.55rem; }
1147
+ .caf-bar-row { display: grid; grid-template-columns: minmax(60px, 30%) 1fr auto; align-items: center; gap: 0.6rem; }
1148
+ .caf-bar-label { font-size: 0.82rem; color: var(--caf-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1149
+ .caf-bar-track { height: 8px; border-radius: 4px; background: var(--caf-sunken); overflow: hidden; }
1150
+ .caf-bar-fill { height: 100%; border-radius: 4px; background: var(--caf-accent); }
1151
+ .caf-bar-value { font-size: 0.82rem; font-weight: 600; font-variant-numeric: tabular-nums; }
1152
+ .caf-line-svg { width: 100%; height: 110px; display: block; }
1153
+ .caf-line-grid { stroke: var(--caf-border); stroke-width: 0.3; }
1154
+ .caf-line-path { fill: none; stroke: var(--caf-accent); stroke-width: 1.1; stroke-linejoin: round; stroke-linecap: round; }
1155
+ .caf-line-dot { fill: var(--caf-accent); }
1156
+ .caf-line-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 0.6rem; }
1157
+ .caf-line-last { font-weight: 650; font-variant-numeric: tabular-nums; color: var(--caf-accent); }
1158
+ .caf-donut { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
1159
+ .caf-donut-svg { width: 110px; height: 110px; flex: none; transform: rotate(0.001deg); }
1160
+ .caf-donut-seg { fill: none; stroke-width: 6; }
1161
+ .caf-donut-legend { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; flex: 1; }
1162
+ .caf-legend-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.84rem; }
1163
+ .caf-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
1164
+ .caf-legend-label { color: var(--caf-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1165
+ .caf-legend-value { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
1166
+ .caf-timer { align-items: center; text-align: center; }
1167
+ .caf-timer .caf-bar-track { width: 100%; }
1168
+ .caf-timer-time { font-size: 2.6rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
1169
+ .caf-timer-done { color: var(--caf-accent); }
1170
+ .caf-timer-fill { transition: width 0.9s linear; }
1171
+ .caf-timer-controls { display: flex; gap: 0.6rem; }
1172
+ .caf-btn:disabled { opacity: 0.45; cursor: not-allowed; }
1173
+ .caf-steps-progress { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.2rem; }
1174
+ .caf-steps-fill { transition: width 0.25s ease; }
1175
+ .caf-step-text { margin: 0; color: var(--caf-muted); font-size: 0.92rem; line-height: 1.55; }
1176
+ .caf-step-nav { display: flex; justify-content: space-between; gap: 0.6rem; margin-top: 0.4rem; }
1177
+ .caf-empty, .caf-loading { color: var(--caf-muted); font-size: 0.9rem; text-align: center; padding: 1.6rem 1rem; }
1178
+ .caf-block-error { border-color: var(--caf-danger); gap: 0.4rem; }
1179
+ .caf-block-error strong { font-size: 0.9rem; color: var(--caf-danger); }
1180
+ .caf-block-error code { font-size: 0.78rem; color: var(--caf-muted); word-break: break-word; }
1181
+ `;
103
1182
  export {
1183
+ contrast,
1184
+ createApp,
104
1185
  createPersistedStore,
105
1186
  createSharedStore,
106
1187
  createStore,
107
- storage
1188
+ hasReact,
1189
+ palette,
1190
+ storage,
1191
+ themeCss,
1192
+ useReact
108
1193
  };
109
1194
  //# sourceMappingURL=index.esm.js.map