@ultrst-armada/floks 1.2.4 → 1.2.5

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.
@@ -0,0 +1,1217 @@
1
+ import {
2
+ useFloksFrnlStore
3
+ } from "./chunk-X4B3EBMY.js";
4
+ import {
5
+ BottomStripProvider,
6
+ useBottomStrip
7
+ } from "./chunk-62ITRDJX.js";
8
+
9
+ // src/frnl/frnlButton.tsx
10
+ import { useId, useMemo } from "react";
11
+ import { defineFrnlComponent } from "@ultrst-armada/frnlts";
12
+ import { useFrnlComponent } from "@ultrst-armada/frnlts/react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ var frnlButtonDefinition = defineFrnlComponent({
15
+ name: "frnlButton",
16
+ states: {
17
+ idle: {
18
+ name: "idle",
19
+ events: { click: { functionId: "frnlButton.idle.click" } },
20
+ visualParameters: { opacity: 1 },
21
+ phases: {
22
+ attackPhase: { visualDefinition: { parameters: {} } },
23
+ sustainPhase: { visualDefinition: { parameters: {} } },
24
+ releasePhase: { visualDefinition: { parameters: {} } }
25
+ }
26
+ },
27
+ hover: {
28
+ name: "hover",
29
+ events: { click: { functionId: "frnlButton.hover.click" } },
30
+ visualParameters: { opacity: 1 },
31
+ phases: {
32
+ attackPhase: { visualDefinition: { parameters: {} } },
33
+ sustainPhase: { visualDefinition: { parameters: {} } },
34
+ releasePhase: { visualDefinition: { parameters: {} } }
35
+ }
36
+ },
37
+ active: {
38
+ name: "active",
39
+ events: { click: { functionId: "frnlButton.active.click" } },
40
+ visualParameters: { opacity: 1 },
41
+ phases: {
42
+ attackPhase: { visualDefinition: { parameters: {} } },
43
+ sustainPhase: { visualDefinition: { parameters: {} } },
44
+ releasePhase: { visualDefinition: { parameters: {} } }
45
+ }
46
+ },
47
+ disabled: {
48
+ name: "disabled",
49
+ events: { click: { functionId: "frnlButton.disabled.click" } },
50
+ visualParameters: { opacity: 0.4 },
51
+ phases: {
52
+ attackPhase: { visualDefinition: { parameters: {} } },
53
+ sustainPhase: { visualDefinition: { parameters: {} } },
54
+ releasePhase: { visualDefinition: { parameters: {} } }
55
+ }
56
+ },
57
+ loading: {
58
+ name: "loading",
59
+ events: { click: { functionId: "frnlButton.loading.click" } },
60
+ visualParameters: { opacity: 0.6 },
61
+ phases: {
62
+ attackPhase: { visualDefinition: { parameters: {} } },
63
+ sustainPhase: { visualDefinition: { parameters: {} } },
64
+ releasePhase: { visualDefinition: { parameters: {} } }
65
+ }
66
+ }
67
+ }
68
+ });
69
+ var SIZE_CLASSES = {
70
+ xs: "px-1.5 py-0.5 text-[10px] gap-1 h-6",
71
+ sm: "px-2.5 py-1 text-xs gap-1.5 h-7",
72
+ md: "px-3.5 py-1.5 text-xs gap-2 h-8",
73
+ lg: "px-4 py-2 text-sm gap-2.5 h-10",
74
+ xl: "px-5 py-2.5 text-base gap-3 h-12"
75
+ };
76
+ var VARIANT_STYLES = {
77
+ primary: {
78
+ backgroundColor: "var(--skipo-bg-surface, #1e293b)",
79
+ borderColor: "var(--skipo-border-subtle, #334155)",
80
+ color: "var(--skipo-text-primary, #f8fafc)"
81
+ },
82
+ secondary: {
83
+ backgroundColor: "var(--skipo-bg-panel, #0f172a)",
84
+ borderColor: "var(--skipo-border-subtle, #334155)",
85
+ color: "var(--skipo-text-muted, #94a3b8)"
86
+ },
87
+ accent: {
88
+ backgroundColor: "var(--skipo-accent, #38bdf8)",
89
+ borderColor: "var(--skipo-accent-contrast, #0284c7)",
90
+ color: "#000000",
91
+ fontWeight: 700
92
+ },
93
+ dock: {
94
+ backgroundColor: "var(--skipo-bg-subtle, #1e293b)",
95
+ borderColor: "var(--skipo-border-color, #334155)",
96
+ color: "var(--skipo-text-primary, #f8fafc)"
97
+ },
98
+ danger: {
99
+ backgroundColor: "rgba(239, 68, 68, 0.15)",
100
+ borderColor: "rgba(239, 68, 68, 0.4)",
101
+ color: "#f87171"
102
+ },
103
+ success: {
104
+ backgroundColor: "rgba(16, 185, 129, 0.15)",
105
+ borderColor: "rgba(16, 185, 129, 0.4)",
106
+ color: "#34d399"
107
+ },
108
+ ghost: {
109
+ backgroundColor: "transparent",
110
+ borderColor: "transparent",
111
+ color: "var(--skipo-text-muted, #94a3b8)"
112
+ },
113
+ outline: {
114
+ backgroundColor: "transparent",
115
+ borderColor: "var(--skipo-border-subtle, #334155)",
116
+ color: "var(--skipo-text-primary, #f8fafc)"
117
+ }
118
+ };
119
+ var frnlButton = ({
120
+ id,
121
+ variant = "secondary",
122
+ size = "md",
123
+ label,
124
+ tooltip,
125
+ description,
126
+ isActive = false,
127
+ loading = false,
128
+ icon,
129
+ disabled = false,
130
+ children,
131
+ onClick,
132
+ onMouseEnter,
133
+ onMouseLeave,
134
+ onFocus,
135
+ onBlur,
136
+ className = "",
137
+ style,
138
+ ...rest
139
+ }) => {
140
+ const store = useFloksFrnlStore();
141
+ const { setInspectionText } = useBottomStrip();
142
+ const generatedId = useId().replace(/[:/]/g, "");
143
+ const componentId = id ?? `frnlButton.${label ? label.toLowerCase().replace(/[^a-z0-9]/g, "-") : generatedId}`;
144
+ const resolvedState = disabled ? "disabled" : loading ? "loading" : isActive ? "active" : "idle";
145
+ const handlers = useMemo(
146
+ () => ({
147
+ "frnlButton.idle.click": (payload) => onClick?.(payload),
148
+ "frnlButton.hover.click": (payload) => onClick?.(payload),
149
+ "frnlButton.active.click": (payload) => onClick?.(payload),
150
+ "frnlButton.disabled.click": () => {
151
+ },
152
+ "frnlButton.loading.click": () => {
153
+ }
154
+ }),
155
+ [onClick]
156
+ );
157
+ const frnl = useFrnlComponent({
158
+ id: componentId,
159
+ definition: frnlButtonDefinition,
160
+ store,
161
+ state: resolvedState,
162
+ properties: { variant, size, label, isActive, disabled, loading },
163
+ handlers
164
+ });
165
+ const handleHoverStart = (e) => {
166
+ onMouseEnter?.(e);
167
+ const effLabel = label || tooltip || (typeof children === "string" ? children : void 0);
168
+ if (effLabel || description) {
169
+ setInspectionText(effLabel || null, description || null);
170
+ }
171
+ };
172
+ const handleHoverEnd = (e) => {
173
+ onMouseLeave?.(e);
174
+ setInspectionText(null);
175
+ };
176
+ return /* @__PURE__ */ jsxs(
177
+ "button",
178
+ {
179
+ ...rest,
180
+ id: componentId,
181
+ type: "button",
182
+ disabled: disabled || loading,
183
+ "data-frnl-component": "frnlButton",
184
+ "data-frnl-state": frnl.state,
185
+ onClick: (e) => {
186
+ if (disabled || loading) return;
187
+ frnl.events.click(e);
188
+ },
189
+ onMouseEnter: handleHoverStart,
190
+ onMouseLeave: handleHoverEnd,
191
+ onFocus: (e) => {
192
+ onFocus?.(e);
193
+ const effLabel = label || tooltip || (typeof children === "string" ? children : void 0);
194
+ if (effLabel || description) setInspectionText(effLabel || null, description || null);
195
+ },
196
+ onBlur: (e) => {
197
+ onBlur?.(e);
198
+ setInspectionText(null);
199
+ },
200
+ className: `inline-flex items-center justify-center font-mono font-bold uppercase tracking-wider border rounded transition-all duration-150 select-none cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed active:scale-[0.98] ${SIZE_CLASSES[size]} ${className}`,
201
+ style: {
202
+ ...VARIANT_STYLES[variant],
203
+ ...isActive ? { borderColor: "var(--skipo-accent, #38bdf8)", boxShadow: "0 0 10px rgba(56, 189, 248, 0.25)" } : {},
204
+ ...style
205
+ },
206
+ children: [
207
+ loading ? /* @__PURE__ */ jsx("span", { className: "w-3.5 h-3.5 border-2 border-current border-t-transparent rounded-full animate-spin shrink-0" }) : icon ? /* @__PURE__ */ jsx("span", { className: "shrink-0 flex items-center justify-center", children: icon }) : null,
208
+ children ? /* @__PURE__ */ jsx("span", { children }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
209
+ ]
210
+ }
211
+ );
212
+ };
213
+ frnlButton.displayName = "frnlButton";
214
+
215
+ // src/frnl/frnlTextInput.tsx
216
+ import { useId as useId2, useMemo as useMemo2 } from "react";
217
+ import { defineFrnlComponent as defineFrnlComponent2 } from "@ultrst-armada/frnlts";
218
+ import { useFrnlComponent as useFrnlComponent2 } from "@ultrst-armada/frnlts/react";
219
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
220
+ var frnlTextInputDefinition = defineFrnlComponent2({
221
+ name: "frnlTextInput",
222
+ states: {
223
+ idle: {
224
+ name: "idle",
225
+ events: {
226
+ change: { functionId: "frnlTextInput.idle.change" },
227
+ focus: { functionId: "frnlTextInput.idle.focus" }
228
+ },
229
+ visualParameters: { borderColor: "var(--skipo-border-subtle, #334155)" },
230
+ phases: {
231
+ attackPhase: { visualDefinition: { parameters: {} } },
232
+ sustainPhase: { visualDefinition: { parameters: {} } },
233
+ releasePhase: { visualDefinition: { parameters: {} } }
234
+ }
235
+ },
236
+ focused: {
237
+ name: "focused",
238
+ events: {
239
+ change: { functionId: "frnlTextInput.focused.change" },
240
+ blur: { functionId: "frnlTextInput.focused.blur" }
241
+ },
242
+ visualParameters: { borderColor: "var(--skipo-accent, #38bdf8)" },
243
+ phases: {
244
+ attackPhase: { visualDefinition: { parameters: {} } },
245
+ sustainPhase: { visualDefinition: { parameters: {} } },
246
+ releasePhase: { visualDefinition: { parameters: {} } }
247
+ }
248
+ },
249
+ error: {
250
+ name: "error",
251
+ events: {
252
+ change: { functionId: "frnlTextInput.error.change" }
253
+ },
254
+ visualParameters: { borderColor: "#ef4444" },
255
+ phases: {
256
+ attackPhase: { visualDefinition: { parameters: {} } },
257
+ sustainPhase: { visualDefinition: { parameters: {} } },
258
+ releasePhase: { visualDefinition: { parameters: {} } }
259
+ }
260
+ },
261
+ disabled: {
262
+ name: "disabled",
263
+ events: {},
264
+ visualParameters: { opacity: 0.5 },
265
+ phases: {
266
+ attackPhase: { visualDefinition: { parameters: {} } },
267
+ sustainPhase: { visualDefinition: { parameters: {} } },
268
+ releasePhase: { visualDefinition: { parameters: {} } }
269
+ }
270
+ }
271
+ }
272
+ });
273
+ var frnlTextInput = ({
274
+ id,
275
+ label,
276
+ description,
277
+ error,
278
+ size = "md",
279
+ leadingIcon,
280
+ trailingIcon,
281
+ onClear,
282
+ disabled = false,
283
+ value,
284
+ onChange,
285
+ onFocus,
286
+ onBlur,
287
+ onMouseEnter,
288
+ onMouseLeave,
289
+ className = "",
290
+ style,
291
+ ...rest
292
+ }) => {
293
+ const store = useFloksFrnlStore();
294
+ const { setInspectionText } = useBottomStrip();
295
+ const generatedId = useId2().replace(/[:/]/g, "");
296
+ const componentId = id ?? `frnlTextInput.${label ? label.toLowerCase().replace(/[^a-z0-9]/g, "-") : generatedId}`;
297
+ const resolvedState = disabled ? "disabled" : error ? "error" : "idle";
298
+ const handlers = useMemo2(
299
+ () => ({
300
+ "frnlTextInput.idle.change": (payload) => onChange?.(payload),
301
+ "frnlTextInput.idle.focus": (payload) => onFocus?.(payload),
302
+ "frnlTextInput.focused.change": (payload) => onChange?.(payload),
303
+ "frnlTextInput.focused.blur": (payload) => onBlur?.(payload),
304
+ "frnlTextInput.error.change": (payload) => onChange?.(payload)
305
+ }),
306
+ [onChange, onFocus, onBlur]
307
+ );
308
+ const frnl = useFrnlComponent2({
309
+ id: componentId,
310
+ definition: frnlTextInputDefinition,
311
+ store,
312
+ state: resolvedState,
313
+ properties: { label, error: Boolean(error), disabled },
314
+ handlers
315
+ });
316
+ const handleHover = (e) => {
317
+ onMouseEnter?.(e);
318
+ if (label || description) {
319
+ setInspectionText(label || null, description || null);
320
+ }
321
+ };
322
+ const handleLeave = (e) => {
323
+ onMouseLeave?.(e);
324
+ setInspectionText(null);
325
+ };
326
+ const heightClass = size === "sm" ? "h-7 text-xs" : size === "lg" ? "h-10 text-sm" : "h-8 text-xs";
327
+ return /* @__PURE__ */ jsxs2("div", { className: `flex flex-col gap-1 font-mono select-none ${className}`, children: [
328
+ label && /* @__PURE__ */ jsx2(
329
+ "label",
330
+ {
331
+ htmlFor: componentId,
332
+ className: "text-[10px] font-bold uppercase tracking-wider text-[var(--skipo-text-muted,#94a3b8)]",
333
+ children: label
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsxs2(
337
+ "div",
338
+ {
339
+ className: `relative flex items-center border rounded transition-colors bg-[var(--skipo-bg-panel,#0f172a)] ${error ? "border-rose-500" : "border-[var(--skipo-border-subtle,#334155)] focus-within:border-[var(--skipo-accent,#38bdf8)]"}`,
340
+ style,
341
+ children: [
342
+ leadingIcon && /* @__PURE__ */ jsx2("span", { className: "pl-2.5 text-[var(--skipo-text-muted,#94a3b8)] flex items-center justify-center shrink-0", children: leadingIcon }),
343
+ /* @__PURE__ */ jsx2(
344
+ "input",
345
+ {
346
+ ...rest,
347
+ id: componentId,
348
+ value,
349
+ disabled,
350
+ "data-frnl-component": "frnlTextInput",
351
+ "data-frnl-state": frnl.state,
352
+ onChange: (e) => {
353
+ onChange?.(e);
354
+ frnl.events.change?.(e);
355
+ },
356
+ onFocus: (e) => {
357
+ onFocus?.(e);
358
+ frnl.events.focus?.(e);
359
+ if (label || description) setInspectionText(label || null, description || null);
360
+ },
361
+ onBlur: (e) => {
362
+ onBlur?.(e);
363
+ frnl.events.blur?.(e);
364
+ setInspectionText(null);
365
+ },
366
+ onMouseEnter: handleHover,
367
+ onMouseLeave: handleLeave,
368
+ className: `w-full bg-transparent px-3 py-1 text-[var(--skipo-text-primary,#f8fafc)] focus:outline-none placeholder-[var(--skipo-text-muted,#64748b)] disabled:opacity-40 disabled:cursor-not-allowed ${heightClass}`
369
+ }
370
+ ),
371
+ trailingIcon && /* @__PURE__ */ jsx2("span", { className: "pr-2.5 text-[var(--skipo-text-muted,#94a3b8)] flex items-center justify-center shrink-0", children: trailingIcon }),
372
+ onClear && value && !disabled && /* @__PURE__ */ jsx2(
373
+ "button",
374
+ {
375
+ type: "button",
376
+ onClick: onClear,
377
+ className: "pr-2.5 text-[var(--skipo-text-muted,#94a3b8)] hover:text-white cursor-pointer",
378
+ "aria-label": "Clear Input",
379
+ children: "\xD7"
380
+ }
381
+ )
382
+ ]
383
+ }
384
+ ),
385
+ typeof error === "string" && /* @__PURE__ */ jsx2("span", { className: "text-[10px] text-rose-400 font-semibold", children: error })
386
+ ] });
387
+ };
388
+ frnlTextInput.displayName = "frnlTextInput";
389
+
390
+ // src/frnl/frnlControls.tsx
391
+ import { useId as useId3, useMemo as useMemo3 } from "react";
392
+ import { defineFrnlComponent as defineFrnlComponent3 } from "@ultrst-armada/frnlts";
393
+ import { useFrnlComponent as useFrnlComponent3 } from "@ultrst-armada/frnlts/react";
394
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
395
+ var frnlSelectDefinition = defineFrnlComponent3({
396
+ name: "frnlSelect",
397
+ states: {
398
+ idle: {
399
+ name: "idle",
400
+ events: { change: { functionId: "frnlSelect.idle.change" } },
401
+ visualParameters: {},
402
+ phases: {
403
+ attackPhase: { visualDefinition: { parameters: {} } },
404
+ sustainPhase: { visualDefinition: { parameters: {} } },
405
+ releasePhase: { visualDefinition: { parameters: {} } }
406
+ }
407
+ },
408
+ disabled: {
409
+ name: "disabled",
410
+ events: {},
411
+ visualParameters: { opacity: 0.5 },
412
+ phases: {
413
+ attackPhase: { visualDefinition: { parameters: {} } },
414
+ sustainPhase: { visualDefinition: { parameters: {} } },
415
+ releasePhase: { visualDefinition: { parameters: {} } }
416
+ }
417
+ }
418
+ }
419
+ });
420
+ var frnlSelect = ({
421
+ id,
422
+ label,
423
+ description,
424
+ options,
425
+ size = "md",
426
+ disabled = false,
427
+ value,
428
+ onChange,
429
+ onMouseEnter,
430
+ onMouseLeave,
431
+ className = "",
432
+ style,
433
+ ...rest
434
+ }) => {
435
+ const store = useFloksFrnlStore();
436
+ const { setInspectionText } = useBottomStrip();
437
+ const generatedId = useId3().replace(/[:/]/g, "");
438
+ const componentId = id ?? `frnlSelect.${label ? label.toLowerCase().replace(/[^a-z0-9]/g, "-") : generatedId}`;
439
+ const handlers = useMemo3(
440
+ () => ({
441
+ "frnlSelect.idle.change": (payload) => onChange?.(payload)
442
+ }),
443
+ [onChange]
444
+ );
445
+ const frnl = useFrnlComponent3({
446
+ id: componentId,
447
+ definition: frnlSelectDefinition,
448
+ store,
449
+ state: disabled ? "disabled" : "idle",
450
+ properties: { label, disabled },
451
+ handlers
452
+ });
453
+ const heightClass = size === "sm" ? "h-7 text-xs" : size === "lg" ? "h-10 text-sm" : "h-8 text-xs";
454
+ return /* @__PURE__ */ jsxs3("div", { className: `flex flex-col gap-1 font-mono select-none ${className}`, children: [
455
+ label && /* @__PURE__ */ jsx3(
456
+ "label",
457
+ {
458
+ htmlFor: componentId,
459
+ className: "text-[10px] font-bold uppercase tracking-wider text-[var(--skipo-text-muted,#94a3b8)]",
460
+ children: label
461
+ }
462
+ ),
463
+ /* @__PURE__ */ jsx3(
464
+ "select",
465
+ {
466
+ ...rest,
467
+ id: componentId,
468
+ value,
469
+ disabled,
470
+ "data-frnl-component": "frnlSelect",
471
+ "data-frnl-state": frnl.state,
472
+ onChange: (e) => {
473
+ onChange?.(e);
474
+ frnl.events.change?.(e);
475
+ },
476
+ onMouseEnter: (e) => {
477
+ onMouseEnter?.(e);
478
+ if (label || description) setInspectionText(label || null, description || null);
479
+ },
480
+ onMouseLeave: (e) => {
481
+ onMouseLeave?.(e);
482
+ setInspectionText(null);
483
+ },
484
+ className: `w-full bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-primary,#f8fafc)] border border-[var(--skipo-border-subtle,#334155)] rounded px-2.5 py-1 focus:outline-none focus:border-[var(--skipo-accent,#38bdf8)] disabled:opacity-40 disabled:cursor-not-allowed ${heightClass}`,
485
+ style,
486
+ children: options.map((opt) => /* @__PURE__ */ jsx3("option", { value: opt.value, disabled: opt.disabled, className: "bg-[#0f172a] text-white", children: opt.label }, opt.value))
487
+ }
488
+ )
489
+ ] });
490
+ };
491
+ frnlSelect.displayName = "frnlSelect";
492
+ var frnlToggleDefinition = defineFrnlComponent3({
493
+ name: "frnlToggle",
494
+ states: {
495
+ off: {
496
+ name: "off",
497
+ events: { toggle: { functionId: "frnlToggle.off.toggle" } },
498
+ visualParameters: { checked: false },
499
+ phases: {
500
+ attackPhase: { visualDefinition: { parameters: {} } },
501
+ sustainPhase: { visualDefinition: { parameters: {} } },
502
+ releasePhase: { visualDefinition: { parameters: {} } }
503
+ }
504
+ },
505
+ on: {
506
+ name: "on",
507
+ events: { toggle: { functionId: "frnlToggle.on.toggle" } },
508
+ visualParameters: { checked: true },
509
+ phases: {
510
+ attackPhase: { visualDefinition: { parameters: {} } },
511
+ sustainPhase: { visualDefinition: { parameters: {} } },
512
+ releasePhase: { visualDefinition: { parameters: {} } }
513
+ }
514
+ }
515
+ }
516
+ });
517
+ var frnlToggle = ({
518
+ id,
519
+ checked,
520
+ onChange,
521
+ label,
522
+ description,
523
+ disabled = false,
524
+ className = ""
525
+ }) => {
526
+ const store = useFloksFrnlStore();
527
+ const { setInspectionText } = useBottomStrip();
528
+ const generatedId = useId3().replace(/[:/]/g, "");
529
+ const componentId = id ?? `frnlToggle.${label ? label.toLowerCase().replace(/[^a-z0-9]/g, "-") : generatedId}`;
530
+ const handlers = useMemo3(
531
+ () => ({
532
+ "frnlToggle.off.toggle": () => onChange(true),
533
+ "frnlToggle.on.toggle": () => onChange(false)
534
+ }),
535
+ [onChange]
536
+ );
537
+ const frnl = useFrnlComponent3({
538
+ id: componentId,
539
+ definition: frnlToggleDefinition,
540
+ store,
541
+ state: checked ? "on" : "off",
542
+ properties: { label, checked, disabled },
543
+ handlers
544
+ });
545
+ return /* @__PURE__ */ jsxs3(
546
+ "button",
547
+ {
548
+ type: "button",
549
+ role: "switch",
550
+ id: componentId,
551
+ "aria-checked": checked,
552
+ disabled,
553
+ "data-frnl-component": "frnlToggle",
554
+ "data-frnl-state": frnl.state,
555
+ onClick: () => {
556
+ if (disabled) return;
557
+ frnl.events.toggle?.(void 0);
558
+ },
559
+ onMouseEnter: () => {
560
+ if (label || description) setInspectionText(label || null, description || null);
561
+ },
562
+ onMouseLeave: () => setInspectionText(null),
563
+ className: `inline-flex items-center gap-2 select-none font-mono text-xs cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed ${className}`,
564
+ children: [
565
+ /* @__PURE__ */ jsx3(
566
+ "div",
567
+ {
568
+ className: `w-8 h-4 rounded-full transition-colors relative flex items-center p-0.5 ${checked ? "bg-[var(--skipo-accent,#38bdf8)]" : "bg-[var(--skipo-border-subtle,#334155)]"}`,
569
+ children: /* @__PURE__ */ jsx3(
570
+ "div",
571
+ {
572
+ className: `w-3 h-3 rounded-full bg-black transition-transform ${checked ? "translate-x-4" : "translate-x-0"}`
573
+ }
574
+ )
575
+ }
576
+ ),
577
+ label && /* @__PURE__ */ jsx3("span", { className: "text-[var(--skipo-text-primary,#f8fafc)] uppercase text-[11px] font-semibold", children: label })
578
+ ]
579
+ }
580
+ );
581
+ };
582
+ frnlToggle.displayName = "frnlToggle";
583
+ var frnlSlider = ({
584
+ id,
585
+ value,
586
+ onChange,
587
+ min = 0,
588
+ max = 100,
589
+ step = 1,
590
+ label,
591
+ unit = "",
592
+ description,
593
+ disabled = false,
594
+ className = ""
595
+ }) => {
596
+ const { setInspectionText } = useBottomStrip();
597
+ const generatedId = useId3().replace(/[:/]/g, "");
598
+ const componentId = id ?? `frnlSlider.${label ? label.toLowerCase().replace(/[^a-z0-9]/g, "-") : generatedId}`;
599
+ return /* @__PURE__ */ jsxs3(
600
+ "div",
601
+ {
602
+ id: componentId,
603
+ "data-frnl-component": "frnlSlider",
604
+ onMouseEnter: () => {
605
+ if (label || description) setInspectionText(label || null, description || null);
606
+ },
607
+ onMouseLeave: () => setInspectionText(null),
608
+ className: `flex flex-col gap-1 font-mono select-none ${className}`,
609
+ children: [
610
+ /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center text-[10px] uppercase font-bold tracking-wider", children: [
611
+ label && /* @__PURE__ */ jsx3("span", { className: "text-[var(--skipo-text-muted,#94a3b8)]", children: label }),
612
+ /* @__PURE__ */ jsxs3("span", { className: "text-[var(--skipo-accent,#38bdf8)]", children: [
613
+ value,
614
+ unit
615
+ ] })
616
+ ] }),
617
+ /* @__PURE__ */ jsx3(
618
+ "input",
619
+ {
620
+ type: "range",
621
+ min,
622
+ max,
623
+ step,
624
+ value,
625
+ disabled,
626
+ onChange: (e) => onChange(parseFloat(e.target.value)),
627
+ className: "w-full h-1.5 bg-[var(--skipo-border-subtle,#334155)] accent-[var(--skipo-accent,#38bdf8)] cursor-pointer disabled:opacity-40"
628
+ }
629
+ )
630
+ ]
631
+ }
632
+ );
633
+ };
634
+ frnlSlider.displayName = "frnlSlider";
635
+ var BADGE_STYLES = {
636
+ neutral: "bg-[var(--skipo-bg-subtle,#1e293b)] border-[var(--skipo-border-subtle,#334155)] text-[var(--skipo-text-muted,#94a3b8)]",
637
+ accent: "bg-cyan-500/10 border-cyan-500/30 text-cyan-400",
638
+ success: "bg-emerald-500/10 border-emerald-500/30 text-emerald-400",
639
+ danger: "bg-rose-500/10 border-rose-500/30 text-rose-400",
640
+ warning: "bg-amber-500/10 border-amber-500/30 text-amber-400",
641
+ info: "bg-blue-500/10 border-blue-500/30 text-blue-400"
642
+ };
643
+ var frnlBadge = ({
644
+ variant = "neutral",
645
+ size = "md",
646
+ dot = false,
647
+ children,
648
+ className = "",
649
+ ...rest
650
+ }) => {
651
+ const sizeClasses = size === "sm" ? "px-1.5 py-0.2 text-[9px]" : "px-2 py-0.5 text-[10px]";
652
+ return /* @__PURE__ */ jsxs3(
653
+ "span",
654
+ {
655
+ ...rest,
656
+ "data-frnl-component": "frnlBadge",
657
+ className: `inline-flex items-center gap-1.5 font-mono font-bold uppercase tracking-wider border rounded select-none ${BADGE_STYLES[variant]} ${sizeClasses} ${className}`,
658
+ children: [
659
+ dot && /* @__PURE__ */ jsx3("span", { className: "w-1.5 h-1.5 rounded-full bg-current animate-pulse" }),
660
+ children
661
+ ]
662
+ }
663
+ );
664
+ };
665
+ frnlBadge.displayName = "frnlBadge";
666
+
667
+ // src/frnl/frnlTabWindow.tsx
668
+ import { useState } from "react";
669
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
670
+ var frnlTabWindow = ({
671
+ id = "frnl-tab-window",
672
+ tabs,
673
+ activeTabId,
674
+ onTabChange,
675
+ className = "",
676
+ children
677
+ }) => {
678
+ const [internalActiveTab, setInternalActiveTab] = useState(tabs[0]?.id);
679
+ const activeId = activeTabId ?? internalActiveTab;
680
+ const { setInspectionText } = useBottomStrip();
681
+ const handleSelect = (tab) => {
682
+ if (tab.disabled) return;
683
+ if (onTabChange) onTabChange(tab.id);
684
+ else setInternalActiveTab(tab.id);
685
+ };
686
+ const currentTab = tabs.find((t) => t.id === activeId);
687
+ return /* @__PURE__ */ jsxs4(
688
+ "div",
689
+ {
690
+ id,
691
+ "data-frnl-component": "frnlTabWindow",
692
+ className: `flex flex-col w-full h-full min-h-0 bg-[var(--skipo-bg,#0a0c10)] text-[var(--skipo-text-primary,#f8fafc)] font-mono ${className}`,
693
+ children: [
694
+ /* @__PURE__ */ jsx4("div", { className: "h-10 border-b border-[var(--skipo-border-color,#334155)] bg-[var(--skipo-surface,#14171f)] px-3 flex items-center gap-1 shrink-0 select-none overflow-x-auto", children: tabs.map((tab) => {
695
+ const isActive = tab.id === activeId;
696
+ return /* @__PURE__ */ jsxs4(
697
+ "button",
698
+ {
699
+ type: "button",
700
+ role: "tab",
701
+ "aria-selected": isActive,
702
+ disabled: tab.disabled,
703
+ onClick: () => handleSelect(tab),
704
+ onMouseEnter: () => {
705
+ if (tab.label || tab.description) {
706
+ setInspectionText(
707
+ `TAB: ${tab.label.toUpperCase()}`,
708
+ tab.description || `Switch to ${tab.label} tab view`
709
+ );
710
+ }
711
+ },
712
+ onMouseLeave: () => setInspectionText(null),
713
+ className: `flex items-center gap-2 px-3 py-1.5 text-xs font-bold uppercase tracking-wider rounded transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed ${isActive ? "bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-accent,#38bdf8)] border border-[var(--skipo-border-subtle,#334155)]" : "text-[var(--skipo-text-muted,#94a3b8)] hover:text-white hover:bg-[var(--skipo-bg-subtle,#1e293b)] border border-transparent"}`,
714
+ children: [
715
+ tab.icon && /* @__PURE__ */ jsx4("span", { className: "w-3.5 h-3.5 shrink-0 flex items-center", children: tab.icon }),
716
+ /* @__PURE__ */ jsx4("span", { children: tab.label })
717
+ ]
718
+ },
719
+ tab.id
720
+ );
721
+ }) }),
722
+ /* @__PURE__ */ jsx4("div", { className: "flex-1 overflow-auto p-4 min-h-0 relative", children: currentTab?.content ?? children })
723
+ ]
724
+ }
725
+ );
726
+ };
727
+ frnlTabWindow.displayName = "frnlTabWindow";
728
+
729
+ // src/frnl/frnlTopBar.tsx
730
+ import React5, { useState as useState2, useRef, useEffect } from "react";
731
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
732
+ var frnlTopBar = ({
733
+ id = "frnl-top-bar",
734
+ breadcrumbs = [],
735
+ appLogo,
736
+ appName,
737
+ tabs = [],
738
+ activeTabId,
739
+ onTabChange,
740
+ user,
741
+ onLogout,
742
+ onOpenSettings,
743
+ actions,
744
+ className = ""
745
+ }) => {
746
+ const { setInspectionText } = useBottomStrip();
747
+ const [profileOpen, setProfileOpen] = useState2(false);
748
+ const profileMenuRef = useRef(null);
749
+ useEffect(() => {
750
+ const handleClickOutside = (e) => {
751
+ if (profileMenuRef.current && !profileMenuRef.current.contains(e.target)) {
752
+ setProfileOpen(false);
753
+ }
754
+ };
755
+ if (profileOpen) {
756
+ document.addEventListener("mousedown", handleClickOutside);
757
+ }
758
+ return () => document.removeEventListener("mousedown", handleClickOutside);
759
+ }, [profileOpen]);
760
+ return /* @__PURE__ */ jsxs5(
761
+ "header",
762
+ {
763
+ id,
764
+ "data-frnl-component": "frnlTopBar",
765
+ className: `h-11 border-b border-[var(--skipo-border-color,#334155)] bg-[var(--skipo-surface,#14171f)] px-3 flex items-center justify-between select-none shrink-0 font-mono text-xs z-40 relative ${className}`,
766
+ children: [
767
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 min-w-0", children: [
768
+ appLogo ? /* @__PURE__ */ jsx5("div", { className: "shrink-0 flex items-center justify-center", children: appLogo }) : appName ? /* @__PURE__ */ jsx5(
769
+ "div",
770
+ {
771
+ className: "w-6 h-6 rounded bg-[var(--skipo-accent,#38bdf8)] text-black font-black flex items-center justify-center text-xs shrink-0 cursor-default",
772
+ onMouseEnter: () => setInspectionText(`APPLICATION: ${appName.toUpperCase()}`),
773
+ onMouseLeave: () => setInspectionText(null),
774
+ children: appName.slice(0, 2).toUpperCase()
775
+ }
776
+ ) : null,
777
+ breadcrumbs.length > 0 && /* @__PURE__ */ jsx5("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-[var(--skipo-text-muted,#94a3b8)]", children: breadcrumbs.map((crumb, idx) => /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
778
+ crumb.onClick ? /* @__PURE__ */ jsx5(
779
+ "button",
780
+ {
781
+ type: "button",
782
+ onClick: crumb.onClick,
783
+ onMouseEnter: () => setInspectionText(`NAVIGATE: ${crumb.label.toUpperCase()}`),
784
+ onMouseLeave: () => setInspectionText(null),
785
+ className: "hover:text-white transition-colors cursor-pointer truncate max-w-[140px]",
786
+ children: crumb.label
787
+ }
788
+ ) : crumb.href ? /* @__PURE__ */ jsx5(
789
+ "a",
790
+ {
791
+ href: crumb.href,
792
+ onMouseEnter: () => setInspectionText(`NAVIGATE: ${crumb.label.toUpperCase()}`),
793
+ onMouseLeave: () => setInspectionText(null),
794
+ className: "hover:text-white transition-colors truncate max-w-[140px]",
795
+ children: crumb.label
796
+ }
797
+ ) : /* @__PURE__ */ jsx5("span", { className: "text-[var(--skipo-text-primary,#f8fafc)] font-semibold truncate max-w-[140px]", children: crumb.label }),
798
+ idx < breadcrumbs.length - 1 && /* @__PURE__ */ jsx5("span", { className: "opacity-40", children: "/" })
799
+ ] }, idx)) })
800
+ ] }),
801
+ tabs.length > 0 && /* @__PURE__ */ jsx5(
802
+ "nav",
803
+ {
804
+ role: "tablist",
805
+ className: "flex items-center gap-1 bg-[var(--skipo-bg-panel,#0f172a)] p-0.5 rounded border border-[var(--skipo-border-subtle,#334155)]",
806
+ children: tabs.map((tab) => {
807
+ const isActive = tab.id === activeTabId;
808
+ return /* @__PURE__ */ jsx5(
809
+ "button",
810
+ {
811
+ type: "button",
812
+ role: "tab",
813
+ "aria-selected": isActive,
814
+ disabled: tab.disabled,
815
+ onClick: () => onTabChange?.(tab.id),
816
+ onMouseEnter: () => setInspectionText(
817
+ `TAB: ${tab.label.toUpperCase()}`,
818
+ tab.description || `Switch view to ${tab.label}`
819
+ ),
820
+ onMouseLeave: () => setInspectionText(null),
821
+ className: `p-1.5 rounded transition-all flex items-center justify-center cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed ${isActive ? "bg-[var(--skipo-accent,#38bdf8)] text-black shadow-xs" : "text-[var(--skipo-text-muted,#94a3b8)] hover:text-white hover:bg-[var(--skipo-surface,#14171f)]"}`,
822
+ "aria-label": tab.label,
823
+ children: /* @__PURE__ */ jsx5("span", { className: "w-4 h-4 flex items-center justify-center", children: tab.icon })
824
+ },
825
+ tab.id
826
+ );
827
+ })
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5 shrink-0", children: [
831
+ actions,
832
+ onOpenSettings && /* @__PURE__ */ jsx5(
833
+ "button",
834
+ {
835
+ type: "button",
836
+ onClick: onOpenSettings,
837
+ onMouseEnter: () => setInspectionText("SETTINGS", "Open system preferences, theme and configurations"),
838
+ onMouseLeave: () => setInspectionText(null),
839
+ className: "p-1.5 rounded border border-[var(--skipo-border-subtle,#334155)] bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-muted,#94a3b8)] hover:text-white hover:border-[var(--skipo-accent,#38bdf8)] cursor-pointer transition-colors",
840
+ "aria-label": "Settings",
841
+ children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
842
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "3" }),
843
+ /* @__PURE__ */ jsx5("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
844
+ ] })
845
+ }
846
+ ),
847
+ user && /* @__PURE__ */ jsxs5("div", { className: "relative", ref: profileMenuRef, children: [
848
+ /* @__PURE__ */ jsx5(
849
+ "button",
850
+ {
851
+ type: "button",
852
+ onClick: () => setProfileOpen((prev) => !prev),
853
+ onMouseEnter: () => setInspectionText(
854
+ `USER: ${user.name || user.username || "PROFILE"}`,
855
+ `Active session as ${user.email || user.username || "user"}`
856
+ ),
857
+ onMouseLeave: () => setInspectionText(null),
858
+ className: "w-7 h-7 rounded border border-[var(--skipo-border-subtle,#334155)] bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-primary,#f8fafc)] hover:border-[var(--skipo-accent,#38bdf8)] flex items-center justify-center cursor-pointer transition-colors",
859
+ "aria-label": "User Profile",
860
+ "aria-expanded": profileOpen,
861
+ children: /* @__PURE__ */ jsx5("span", { children: user.avatarGlyph || "\u{1F464}" })
862
+ }
863
+ ),
864
+ profileOpen && /* @__PURE__ */ jsxs5("div", { className: "absolute right-0 top-full mt-1.5 w-56 rounded border border-[var(--skipo-border-color,#334155)] bg-[var(--skipo-surface,#14171f)] p-3 shadow-xl z-50 flex flex-col gap-2.5 font-mono animate-fade-in", children: [
865
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 pb-2 border-b border-[var(--skipo-border-subtle,#334155)]", children: [
866
+ /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded bg-[var(--skipo-bg-panel,#0f172a)] border border-[var(--skipo-border-subtle,#334155)] flex items-center justify-center text-sm", children: user.avatarGlyph || "\u{1F464}" }),
867
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col min-w-0", children: [
868
+ /* @__PURE__ */ jsx5("span", { className: "font-bold text-[var(--skipo-text-primary,#f8fafc)] truncate text-xs", children: user.name || user.username || "User" }),
869
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px] text-[var(--skipo-text-muted,#94a3b8)] truncate", children: user.email || `@${user.username || "active"}` })
870
+ ] })
871
+ ] }),
872
+ user.role && /* @__PURE__ */ jsxs5("div", { className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--skipo-bg-panel,#0f172a)] border border-[var(--skipo-border-subtle,#334155)] text-[var(--skipo-accent,#38bdf8)] uppercase font-bold self-start", children: [
873
+ "ROLE: ",
874
+ user.role
875
+ ] }),
876
+ onLogout && /* @__PURE__ */ jsxs5(
877
+ "button",
878
+ {
879
+ type: "button",
880
+ onClick: () => {
881
+ setProfileOpen(false);
882
+ onLogout();
883
+ },
884
+ className: "w-full flex items-center gap-2 px-2.5 py-1.5 rounded text-rose-400 hover:bg-rose-500/10 border border-transparent hover:border-rose-500/30 text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer",
885
+ children: [
886
+ /* @__PURE__ */ jsxs5("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
887
+ /* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
888
+ /* @__PURE__ */ jsx5("polyline", { points: "16 17 21 12 16 7" }),
889
+ /* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
890
+ ] }),
891
+ /* @__PURE__ */ jsx5("span", { children: "Sign Out" })
892
+ ]
893
+ }
894
+ )
895
+ ] })
896
+ ] })
897
+ ] })
898
+ ]
899
+ }
900
+ );
901
+ };
902
+ frnlTopBar.displayName = "frnlTopBar";
903
+
904
+ // src/frnl/frnlBottomBar.tsx
905
+ import { useState as useState3, useRef as useRef2, useEffect as useEffect2 } from "react";
906
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
907
+ var frnlBottomBar = ({
908
+ id = "frnl-bottom-bar",
909
+ onManualSave,
910
+ isSaving = false,
911
+ recentApiRequests = [],
912
+ className = ""
913
+ }) => {
914
+ const {
915
+ inspectionText,
916
+ inspectionDescription,
917
+ isDKeyPressed,
918
+ autoSave,
919
+ apiActivity,
920
+ setInspectionText
921
+ } = useBottomStrip();
922
+ const [apiPanelOpen, setApiPanelOpen] = useState3(false);
923
+ const apiPanelRef = useRef2(null);
924
+ useEffect2(() => {
925
+ const handleClick = (e) => {
926
+ if (apiPanelRef.current && !apiPanelRef.current.contains(e.target)) {
927
+ setApiPanelOpen(false);
928
+ }
929
+ };
930
+ if (apiPanelOpen) document.addEventListener("mousedown", handleClick);
931
+ return () => document.removeEventListener("mousedown", handleClick);
932
+ }, [apiPanelOpen]);
933
+ const showDescription = isDKeyPressed && Boolean(inspectionDescription);
934
+ const displayText = showDescription ? inspectionDescription : inspectionText || 'HOVER UI ELEMENT // HOLD "D" TO INSPECT';
935
+ return /* @__PURE__ */ jsxs6(
936
+ "footer",
937
+ {
938
+ id,
939
+ "data-frnl-component": "frnlBottomBar",
940
+ className: `h-[30px] min-h-[30px] border-t border-[var(--skipo-border-color,#334155)] bg-[var(--skipo-surface,#14171f)] px-3 flex items-center justify-between select-none shrink-0 font-mono text-[11px] z-40 relative text-[var(--skipo-text-muted,#94a3b8)] ${className}`,
941
+ children: [
942
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 shrink-0", children: [
943
+ /* @__PURE__ */ jsxs6(
944
+ "button",
945
+ {
946
+ type: "button",
947
+ onClick: () => autoSave.toggle(),
948
+ onMouseEnter: () => setInspectionText(
949
+ autoSave.enabled ? "AUTO-SAVE: ACTIVE" : "AUTO-SAVE: PAUSED",
950
+ autoSave.enabled ? "Automatic background persistence active" : "Changes require manual save"
951
+ ),
952
+ onMouseLeave: () => setInspectionText(null),
953
+ className: `flex items-center gap-1.5 px-2 py-0.5 rounded border transition-colors cursor-pointer ${autoSave.enabled ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-400" : "border-[var(--skipo-border-subtle,#334155)] bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-muted,#64748b)]"}`,
954
+ "aria-label": "Toggle Auto Save",
955
+ children: [
956
+ /* @__PURE__ */ jsx6(
957
+ "span",
958
+ {
959
+ className: `w-1.5 h-1.5 rounded-full ${autoSave.enabled ? "bg-emerald-400 animate-pulse" : "bg-slate-500"}`
960
+ }
961
+ ),
962
+ /* @__PURE__ */ jsx6("span", { className: "text-[10px] font-bold uppercase tracking-wider", children: autoSave.enabled ? "AUTO" : "MANUAL" })
963
+ ]
964
+ }
965
+ ),
966
+ onManualSave && /* @__PURE__ */ jsxs6(
967
+ "button",
968
+ {
969
+ type: "button",
970
+ disabled: isSaving,
971
+ onClick: () => onManualSave(),
972
+ onMouseEnter: () => setInspectionText("MANUAL SAVE", "Trigger immediate persistence to disk / database"),
973
+ onMouseLeave: () => setInspectionText(null),
974
+ className: "flex items-center gap-1 px-2 py-0.5 rounded border border-[var(--skipo-border-subtle,#334155)] bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-primary,#f8fafc)] hover:border-[var(--skipo-accent,#38bdf8)] hover:text-[var(--skipo-accent,#38bdf8)] transition-colors cursor-pointer disabled:opacity-40",
975
+ "aria-label": "Save Now",
976
+ children: [
977
+ isSaving ? /* @__PURE__ */ jsx6("span", { className: "w-2.5 h-2.5 border-2 border-current border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsxs6("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
978
+ /* @__PURE__ */ jsx6("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
979
+ /* @__PURE__ */ jsx6("polyline", { points: "17 21 17 13 7 13 7 21" }),
980
+ /* @__PURE__ */ jsx6("polyline", { points: "7 3 7 8 15 8" })
981
+ ] }),
982
+ /* @__PURE__ */ jsx6("span", { className: "text-[10px] font-bold uppercase tracking-wider", children: "Save" })
983
+ ]
984
+ }
985
+ )
986
+ ] }),
987
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 flex items-center justify-center px-4 overflow-hidden", children: /* @__PURE__ */ jsxs6(
988
+ "div",
989
+ {
990
+ "data-testid": "frnl-bottom-strip-center",
991
+ className: "flex items-center gap-1.5 truncate transition-all duration-150 max-w-2xl",
992
+ children: [
993
+ showDescription && /* @__PURE__ */ jsx6("span", { className: "px-1 py-0.2 rounded text-[9px] font-bold uppercase tracking-wider bg-amber-400 text-black shrink-0", children: "D-INSPECT" }),
994
+ /* @__PURE__ */ jsx6(
995
+ "span",
996
+ {
997
+ className: `truncate ${showDescription ? "text-[11px] font-light italic text-[var(--skipo-text-primary,#f8fafc)]" : inspectionText ? "text-xs font-bold text-[var(--skipo-accent,#38bdf8)]" : "text-xs font-medium text-[var(--skipo-text-muted,#64748b)]"}`,
998
+ children: displayText
999
+ }
1000
+ )
1001
+ ]
1002
+ }
1003
+ ) }),
1004
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 shrink-0 relative", ref: apiPanelRef, children: [
1005
+ /* @__PURE__ */ jsxs6(
1006
+ "button",
1007
+ {
1008
+ type: "button",
1009
+ onClick: () => setApiPanelOpen((prev) => !prev),
1010
+ onMouseEnter: () => setInspectionText(
1011
+ `API: ${apiActivity.activeCount} IN-FLIGHT`,
1012
+ "Toggle recent API requests inspector log"
1013
+ ),
1014
+ onMouseLeave: () => setInspectionText(null),
1015
+ className: `flex items-center gap-1.5 px-2 py-0.5 rounded border transition-colors cursor-pointer ${apiActivity.activeCount > 0 ? "border-cyan-500/40 bg-cyan-500/10 text-cyan-400" : "border-[var(--skipo-border-subtle,#334155)] bg-[var(--skipo-bg-panel,#0f172a)] text-[var(--skipo-text-muted,#94a3b8)] hover:text-white"}`,
1016
+ "aria-label": "API Requests",
1017
+ "aria-expanded": apiPanelOpen,
1018
+ children: [
1019
+ /* @__PURE__ */ jsx6(
1020
+ "svg",
1021
+ {
1022
+ width: "12",
1023
+ height: "12",
1024
+ viewBox: "0 0 24 24",
1025
+ fill: "none",
1026
+ stroke: "currentColor",
1027
+ strokeWidth: "2",
1028
+ strokeLinecap: "round",
1029
+ strokeLinejoin: "round",
1030
+ className: apiActivity.activeCount > 0 ? "animate-pulse" : "",
1031
+ children: /* @__PURE__ */ jsx6("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" })
1032
+ }
1033
+ ),
1034
+ /* @__PURE__ */ jsxs6("span", { className: "text-[10px] font-bold uppercase tracking-wider", children: [
1035
+ "API ",
1036
+ apiActivity.activeCount > 0 ? `(${apiActivity.activeCount})` : "LOG"
1037
+ ] })
1038
+ ]
1039
+ }
1040
+ ),
1041
+ apiPanelOpen && /* @__PURE__ */ jsxs6("div", { className: "absolute right-0 bottom-full mb-1.5 w-80 rounded border border-[var(--skipo-border-color,#334155)] bg-[var(--skipo-surface,#14171f)] p-3 shadow-2xl z-50 flex flex-col gap-2 font-mono text-xs animate-fade-in max-h-72 overflow-hidden", children: [
1042
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between pb-1.5 border-b border-[var(--skipo-border-subtle,#334155)]", children: [
1043
+ /* @__PURE__ */ jsx6("span", { className: "font-bold text-[var(--skipo-text-primary,#f8fafc)] uppercase text-[10px] tracking-wider", children: "Recent API Requests" }),
1044
+ /* @__PURE__ */ jsxs6("span", { className: "text-[10px] text-[var(--skipo-accent,#38bdf8)] font-bold", children: [
1045
+ apiActivity.activeCount,
1046
+ " In-Flight"
1047
+ ] })
1048
+ ] }),
1049
+ /* @__PURE__ */ jsx6("div", { className: "overflow-y-auto max-h-52 flex flex-col gap-1.5 pr-1", children: recentApiRequests.length > 0 ? recentApiRequests.map((req) => /* @__PURE__ */ jsxs6(
1050
+ "div",
1051
+ {
1052
+ className: "p-1.5 rounded bg-[var(--skipo-bg-panel,#0f172a)] border border-[var(--skipo-border-subtle,#334155)] flex items-center justify-between text-[10px]",
1053
+ children: [
1054
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 truncate mr-2", children: [
1055
+ /* @__PURE__ */ jsx6("span", { className: "font-bold text-[var(--skipo-accent,#38bdf8)]", children: req.method }),
1056
+ /* @__PURE__ */ jsx6("span", { className: "truncate text-[var(--skipo-text-primary,#f8fafc)]", children: req.endpoint })
1057
+ ] }),
1058
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1 shrink-0", children: [
1059
+ req.status && /* @__PURE__ */ jsx6(
1060
+ "span",
1061
+ {
1062
+ className: `font-bold ${req.status < 400 ? "text-emerald-400" : "text-rose-400"}`,
1063
+ children: req.status
1064
+ }
1065
+ ),
1066
+ req.durationMs !== void 0 && /* @__PURE__ */ jsxs6("span", { className: "text-[var(--skipo-text-muted,#64748b)]", children: [
1067
+ req.durationMs,
1068
+ "ms"
1069
+ ] })
1070
+ ] })
1071
+ ]
1072
+ },
1073
+ req.id
1074
+ )) : /* @__PURE__ */ jsx6("div", { className: "py-4 text-center text-[var(--skipo-text-muted,#64748b)] text-[10px]", children: "No recent backend requests recorded." }) })
1075
+ ] })
1076
+ ] })
1077
+ ]
1078
+ }
1079
+ );
1080
+ };
1081
+ frnlBottomBar.displayName = "frnlBottomBar";
1082
+
1083
+ // src/frnl/frnlPage.tsx
1084
+ import React7, { useEffect as useEffect3, useState as useState4 } from "react";
1085
+ import { mountFrnlOrchestration } from "@ultrst-armada/frnlts";
1086
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1087
+ function FrnlGlobalHoverInspector() {
1088
+ const { setInspectionText } = useBottomStrip();
1089
+ useEffect3(() => {
1090
+ const handleMouseOver = (e) => {
1091
+ const target = e.target;
1092
+ if (!target) return;
1093
+ const el = target.closest(
1094
+ '[data-skipo-label], [data-hover], [title], [aria-label], button, a, input, select, textarea, [role="tab"], [role="button"]'
1095
+ );
1096
+ if (el) {
1097
+ const label = el.getAttribute("data-skipo-label") || el.getAttribute("data-hover") || el.getAttribute("title") || el.getAttribute("aria-label");
1098
+ const desc = el.getAttribute("data-skipo-desc");
1099
+ if (label || desc) {
1100
+ if (el.hasAttribute("title") && !el.hasAttribute("data-frnl-original-title")) {
1101
+ el.setAttribute("data-frnl-original-title", el.getAttribute("title") || "");
1102
+ el.removeAttribute("title");
1103
+ }
1104
+ setInspectionText(label || null, desc || null);
1105
+ }
1106
+ }
1107
+ };
1108
+ const handleMouseOut = (e) => {
1109
+ const target = e.target;
1110
+ if (!target) return;
1111
+ const el = target.closest("[data-frnl-original-title]");
1112
+ if (el) {
1113
+ const orig = el.getAttribute("data-frnl-original-title");
1114
+ if (orig) {
1115
+ el.setAttribute("title", orig);
1116
+ el.removeAttribute("data-frnl-original-title");
1117
+ }
1118
+ }
1119
+ setInspectionText(null);
1120
+ };
1121
+ document.addEventListener("mouseover", handleMouseOver);
1122
+ document.addEventListener("mouseout", handleMouseOut);
1123
+ return () => {
1124
+ document.removeEventListener("mouseover", handleMouseOver);
1125
+ document.removeEventListener("mouseout", handleMouseOut);
1126
+ };
1127
+ }, [setInspectionText]);
1128
+ return null;
1129
+ }
1130
+ function FrnlPageInner({
1131
+ id = "frnl-page",
1132
+ appName = "Skipo App",
1133
+ appLogo,
1134
+ breadcrumbs,
1135
+ tabs = [],
1136
+ activeTabId,
1137
+ onTabChange,
1138
+ showTopBar = true,
1139
+ showBottomBar = true,
1140
+ topBarActions,
1141
+ user,
1142
+ onLogout,
1143
+ onOpenSettings,
1144
+ onSave,
1145
+ isSaving = false,
1146
+ recentApiRequests = [],
1147
+ orchestrations = [],
1148
+ className = "",
1149
+ children
1150
+ }) {
1151
+ const store = useFloksFrnlStore();
1152
+ const [internalActiveTab, setInternalActiveTab] = useState4(tabs[0]?.id);
1153
+ const activeTab = activeTabId ?? internalActiveTab;
1154
+ useEffect3(() => {
1155
+ const cleanups = orchestrations.map(
1156
+ (orchestration) => mountFrnlOrchestration(store, orchestration)
1157
+ );
1158
+ return () => {
1159
+ for (const cleanup of cleanups.reverse()) cleanup();
1160
+ };
1161
+ }, [orchestrations, store]);
1162
+ const handleTabChange = (tabId) => {
1163
+ if (onTabChange) onTabChange(tabId);
1164
+ else setInternalActiveTab(tabId);
1165
+ };
1166
+ const currentTab = tabs.find((t) => t.id === activeTab);
1167
+ return /* @__PURE__ */ jsxs7(
1168
+ "div",
1169
+ {
1170
+ id,
1171
+ "data-frnl-component": "frnlPage",
1172
+ className: `h-screen w-screen flex flex-col bg-[var(--skipo-bg,#0a0c10)] text-[var(--skipo-text-primary,#f8fafc)] font-mono select-none overflow-hidden ${className}`,
1173
+ children: [
1174
+ /* @__PURE__ */ jsx7(FrnlGlobalHoverInspector, {}),
1175
+ showTopBar && React7.createElement(frnlTopBar, {
1176
+ appName,
1177
+ appLogo,
1178
+ breadcrumbs,
1179
+ tabs,
1180
+ activeTabId: activeTab,
1181
+ onTabChange: handleTabChange,
1182
+ user,
1183
+ onLogout,
1184
+ onOpenSettings,
1185
+ actions: topBarActions
1186
+ }),
1187
+ /* @__PURE__ */ jsx7("main", { className: "flex-1 flex flex-col min-h-0 overflow-y-auto relative p-4", children: currentTab?.content ?? children }),
1188
+ showBottomBar && React7.createElement(frnlBottomBar, {
1189
+ onManualSave: onSave,
1190
+ isSaving,
1191
+ recentApiRequests
1192
+ })
1193
+ ]
1194
+ }
1195
+ );
1196
+ }
1197
+ var frnlPage = (props) => {
1198
+ return /* @__PURE__ */ jsx7(BottomStripProvider, { initialAutoSave: props.autoSaveEnabled ?? true, children: /* @__PURE__ */ jsx7(FrnlPageInner, { ...props }) });
1199
+ };
1200
+ frnlPage.displayName = "frnlPage";
1201
+
1202
+ export {
1203
+ frnlButtonDefinition,
1204
+ frnlButton,
1205
+ frnlTextInputDefinition,
1206
+ frnlTextInput,
1207
+ frnlSelectDefinition,
1208
+ frnlSelect,
1209
+ frnlToggleDefinition,
1210
+ frnlToggle,
1211
+ frnlSlider,
1212
+ frnlBadge,
1213
+ frnlTabWindow,
1214
+ frnlTopBar,
1215
+ frnlBottomBar,
1216
+ frnlPage
1217
+ };