@reopt-ai/opt-ui 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/COMPONENT_CATALOG.md +178 -50
  2. package/README.md +28 -7
  3. package/dist/app.css +170 -0
  4. package/dist/core/index.cjs +164 -324
  5. package/dist/core/index.d.cts +3 -6
  6. package/dist/core/index.d.ts +3 -6
  7. package/dist/core/index.js +3 -323
  8. package/dist/docs/01-getting-started.md +25 -5
  9. package/dist/docs/02-components/01-core.md +36 -28
  10. package/dist/docs/02-components/02-visuals.md +1 -1
  11. package/dist/docs/02-components/03-shells.md +144 -23
  12. package/dist/docs/02-components/04-surfaces.md +1 -1
  13. package/dist/docs/02-components/index.md +4 -4
  14. package/dist/docs/03-recipes/03-layouts.md +16 -12
  15. package/dist/docs/05-migration/01-breaking-changes.md +46 -1
  16. package/dist/field-sidebar-BTZCBviA.d.cts +1031 -0
  17. package/dist/field-sidebar-BTZCBviA.d.cts.map +1 -0
  18. package/dist/field-sidebar-CB8vjvUM.d.ts +1031 -0
  19. package/dist/field-sidebar-CB8vjvUM.d.ts.map +1 -0
  20. package/dist/field-sidebar-Cs-J-5VE.js +4233 -0
  21. package/dist/field-sidebar-Cs-J-5VE.js.map +1 -0
  22. package/dist/field-sidebar-CzIQX70-.cjs +4561 -0
  23. package/dist/id-registry.cjs +1939 -1872
  24. package/dist/id-registry.d.cts +14 -11
  25. package/dist/id-registry.d.cts.map +1 -0
  26. package/dist/id-registry.d.ts +14 -11
  27. package/dist/id-registry.d.ts.map +1 -0
  28. package/dist/id-registry.js +1937 -1871
  29. package/dist/id-registry.js.map +1 -0
  30. package/dist/id-registry.json +113 -28
  31. package/dist/index.cjs +8380 -9790
  32. package/dist/index.d.cts +1555 -1442
  33. package/dist/index.d.cts.map +1 -0
  34. package/dist/index.d.ts +1555 -1442
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +7996 -9778
  37. package/dist/index.js.map +1 -0
  38. package/dist/meta.cjs +7753 -6875
  39. package/dist/meta.d.cts.map +1 -0
  40. package/dist/meta.d.ts.map +1 -0
  41. package/dist/meta.js +7752 -6846
  42. package/dist/meta.js.map +1 -0
  43. package/dist/pagination-DWbvACuN.js +3850 -0
  44. package/dist/pagination-DWbvACuN.js.map +1 -0
  45. package/dist/pagination-DjfxBcjX.cjs +4739 -0
  46. package/dist/query.cjs +386 -0
  47. package/dist/query.d.cts +141 -0
  48. package/dist/query.d.cts.map +1 -0
  49. package/dist/query.d.ts +141 -0
  50. package/dist/query.d.ts.map +1 -0
  51. package/dist/query.js +371 -0
  52. package/dist/query.js.map +1 -0
  53. package/dist/shader-surface-BUPZ0P-n.js +3654 -0
  54. package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
  55. package/dist/shader-surface-BfTP9c1Q.d.ts +1788 -0
  56. package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
  57. package/dist/shader-surface-DRrcLrWf.d.cts +1788 -0
  58. package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
  59. package/dist/shader-surface-Fav5K9UC.cjs +4041 -0
  60. package/dist/shells/index.cjs +58 -65
  61. package/dist/shells/index.d.cts +4 -5
  62. package/dist/shells/index.d.ts +4 -5
  63. package/dist/shells/index.js +4 -64
  64. package/dist/tailwind.css +49 -0
  65. package/dist/theme/presets/default.css +2 -2
  66. package/dist/theme/presets/minimal.css +2 -2
  67. package/dist/theme/presets/mono-dark.css +2 -2
  68. package/dist/theme/presets/natural.css +1 -1
  69. package/dist/theme/presets/pro.css +2 -2
  70. package/dist/theme/server.cjs +257 -228
  71. package/dist/theme/server.d.cts +48 -60
  72. package/dist/theme/server.d.cts.map +1 -0
  73. package/dist/theme/server.d.ts +48 -60
  74. package/dist/theme/server.d.ts.map +1 -0
  75. package/dist/theme/server.js +234 -189
  76. package/dist/theme/server.js.map +1 -0
  77. package/dist/types-D0FlcYnM.d.cts +301 -0
  78. package/dist/types-D0FlcYnM.d.cts.map +1 -0
  79. package/dist/types-D0FlcYnM.d.ts +301 -0
  80. package/dist/types-D0FlcYnM.d.ts.map +1 -0
  81. package/dist/visuals/index.cjs +10 -4
  82. package/dist/visuals/index.d.cts +2 -1
  83. package/dist/visuals/index.d.ts +2 -1
  84. package/dist/visuals/index.js +1 -2
  85. package/package.json +27 -11
  86. package/dist/chunk-3GWWZKX7.js +0 -38
  87. package/dist/chunk-3QFYBBL6.cjs +0 -4759
  88. package/dist/chunk-3XD4HIFL.js +0 -3241
  89. package/dist/chunk-AFF2HPE5.cjs +0 -5008
  90. package/dist/chunk-NXZJIHEZ.cjs +0 -3241
  91. package/dist/chunk-ONE3C5RV.cjs +0 -38
  92. package/dist/chunk-RBM2RNC2.js +0 -5008
  93. package/dist/chunk-VBGPEW45.js +0 -4759
  94. package/dist/index-BZ_lBlO1.d.ts +0 -474
  95. package/dist/index-BuvxoWHf.d.cts +0 -474
  96. package/dist/index-DWyqDIkH.d.cts +0 -1689
  97. package/dist/index-Uyijm14M.d.ts +0 -1689
  98. package/dist/types-D4-0lwaE.d.cts +0 -298
  99. package/dist/types-D4-0lwaE.d.ts +0 -298
@@ -0,0 +1,3654 @@
1
+ "use client";
2
+ import { $t as koBreadcrumbLabels, Bn as _objectSpread2, Cn as OPT_FOCUS, En as OPT_INPUT, In as OPT_TEXT_PRIMARY, Ln as OPT_TEXT_SECONDARY, Pn as OPT_TEXTAREA_SIZE, Qt as Button, Rn as OPT_TEXT_TERTIARY, Un as cn, Vn as _objectWithoutProperties, Yt as Spinner, an as RequiredMark, at as MenuPopover$1, cn as OPT_ANIMATE_DRAWER, ct as MenuTrigger$1, hn as OPT_BORDER, in as FieldMessages, kn as OPT_SURFACE, ln as OPT_ANIMATE_ENTER_EXIT, nt as MenuItem$1, on as getDescribedBy, ot as MenuRoot$1, qt as useInputId, rn as useControlledState, st as MenuSeparator$1, wn as OPT_FOCUS_VISIBLE } from "./pagination-DWbvACuN.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import * as React from "react";
5
+ import { createContext, createElement, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
6
+ import { AccordionContent, AccordionItem, AccordionRoot, AccordionTrigger, DialogDismiss, DialogPanel, DialogRoot, Meter, RadioGroupRoot, Separator, Toggle, ToggleGroup, useDialogClose, useRadioGroupContext } from "@reopt-ai/opt-ui-primitives";
7
+ import { ChevronDown, GripVertical } from "lucide-react";
8
+ import { Group, Panel, Separator as Separator$1 } from "react-resizable-panels";
9
+ import { Mesh, Program, Renderer, Triangle } from "ogl";
10
+ import { hexToSrgb } from "@reopt-ai/opt-palette";
11
+ //#region src/core/text.tsx
12
+ const _excluded$17 = [
13
+ "as",
14
+ "variant",
15
+ "tone",
16
+ "truncate",
17
+ "className",
18
+ "children"
19
+ ];
20
+ const toneStyles = {
21
+ primary: "text-text-primary",
22
+ secondary: "text-text-secondary",
23
+ tertiary: "text-text-tertiary",
24
+ inherit: "text-inherit"
25
+ };
26
+ /** Semantic body text whose visual role is controlled by foundation tokens. */
27
+ function Text(_ref) {
28
+ let { as, variant = "body", tone = "primary", truncate = false, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$17);
29
+ const Component = as !== null && as !== void 0 ? as : "span";
30
+ const elementProps = _objectSpread2(_objectSpread2({}, props), {}, {
31
+ className: cn(`opt-type-${variant}`, toneStyles[tone], truncate && "truncate", className),
32
+ "data-opt-component": "text",
33
+ "data-opt-part": "root",
34
+ "data-opt-id": "NFKP2",
35
+ "data-opt-slug": "text.Text",
36
+ "data-role": variant,
37
+ "data-tone": tone
38
+ });
39
+ return createElement(Component, elementProps, children);
40
+ }
41
+ //#endregion
42
+ //#region src/core/heading.tsx
43
+ const _excluded$16 = [
44
+ "level",
45
+ "variant",
46
+ "className",
47
+ "children"
48
+ ];
49
+ /** Semantic heading with independently selectable visual hierarchy. */
50
+ function Heading(_ref) {
51
+ let { level = 2, variant = `heading-${level}`, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$16);
52
+ const Component = {
53
+ 1: "h1",
54
+ 2: "h2",
55
+ 3: "h3",
56
+ 4: "h4",
57
+ 5: "h5",
58
+ 6: "h6"
59
+ }[level];
60
+ return createElement(Component, _objectSpread2(_objectSpread2({ className: cn(`opt-type-${variant}`, "text-text-primary", className) }, props), {}, {
61
+ "data-opt-component": "heading",
62
+ "data-opt-part": "root",
63
+ "data-opt-id": "NF0BT",
64
+ "data-opt-slug": "heading.Heading",
65
+ "data-level": level,
66
+ "data-role": variant
67
+ }), children);
68
+ }
69
+ //#endregion
70
+ //#region src/core/separator.tsx
71
+ /**
72
+ * Visual divider between content. Wraps the headless primitive with design
73
+ * tokens. With `children`, renders a centered label flanked by rules.
74
+ */
75
+ function Separator$2({ orientation = "horizontal", decorative = false, children, className }) {
76
+ if (orientation === "horizontal" && children != null) return /* @__PURE__ */ jsxs("div", {
77
+ className: cn("gap-element flex items-center", className),
78
+ "data-opt-id": "7PWJS",
79
+ "data-opt-slug": "separator.Separator",
80
+ children: [
81
+ /* @__PURE__ */ jsx(Separator, {
82
+ decorative: true,
83
+ orientation: "horizontal",
84
+ className: "bg-border h-px flex-1"
85
+ }),
86
+ /* @__PURE__ */ jsx("span", {
87
+ className: "text-text-tertiary shrink-0 text-xs",
88
+ children
89
+ }),
90
+ /* @__PURE__ */ jsx(Separator, {
91
+ decorative: true,
92
+ orientation: "horizontal",
93
+ className: "bg-border h-px flex-1"
94
+ })
95
+ ]
96
+ });
97
+ return /* @__PURE__ */ jsx(Separator, {
98
+ orientation,
99
+ decorative,
100
+ className: cn("bg-border shrink-0", orientation === "horizontal" ? "h-px w-full" : "h-full w-px self-stretch", className),
101
+ "data-opt-id": "7PWJS",
102
+ "data-opt-slug": "separator.Separator"
103
+ });
104
+ }
105
+ Separator$2.displayName = "Separator";
106
+ //#endregion
107
+ //#region src/core/otp-field.tsx
108
+ const PATTERNS = {
109
+ numeric: /[0-9]/,
110
+ alphanumeric: /[a-zA-Z0-9]/
111
+ };
112
+ /**
113
+ * One-time-code / PIN entry split into per-character cells, with paste
114
+ * distribution, auto-advancing focus, and arrow/backspace navigation.
115
+ */
116
+ function OtpField({ length = 6, value: controlledValue, defaultValue = "", onChange, onComplete, type = "numeric", mask = false, disabled = false, autoFocus = false, name, label, required, error, hint, className, id, "aria-label": ariaLabel }) {
117
+ const reactId = React.useId();
118
+ const fieldId = id !== null && id !== void 0 ? id : `otp-${reactId}`;
119
+ const hasError = Boolean(error);
120
+ const pattern = PATTERNS[type];
121
+ const inputsRef = React.useRef([]);
122
+ const labelId = label ? `${fieldId}-label` : void 0;
123
+ const groupLabelledBy = ariaLabel ? void 0 : labelId;
124
+ const groupAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : groupLabelledBy ? void 0 : "One-time code";
125
+ const [value, setRawValue] = useControlledState(defaultValue, controlledValue, onChange);
126
+ const completedRef = React.useRef(false);
127
+ const setValue = React.useCallback((next) => {
128
+ const clipped = next.slice(0, length);
129
+ setRawValue(clipped);
130
+ if (clipped.length === length) {
131
+ if (!completedRef.current) {
132
+ completedRef.current = true;
133
+ onComplete === null || onComplete === void 0 || onComplete(clipped);
134
+ }
135
+ } else completedRef.current = false;
136
+ }, [
137
+ length,
138
+ setRawValue,
139
+ onComplete
140
+ ]);
141
+ const focusCell = React.useCallback((index) => {
142
+ var _inputsRef$current$ta, _inputsRef$current$ta2;
143
+ const target = Math.max(0, Math.min(index, inputsRef.current.length - 1));
144
+ (_inputsRef$current$ta = inputsRef.current[target]) === null || _inputsRef$current$ta === void 0 || _inputsRef$current$ta.focus();
145
+ (_inputsRef$current$ta2 = inputsRef.current[target]) === null || _inputsRef$current$ta2 === void 0 || _inputsRef$current$ta2.select();
146
+ }, []);
147
+ const cells = React.useMemo(() => Array.from({ length }, (_, i) => {
148
+ var _value$i;
149
+ return (_value$i = value[i]) !== null && _value$i !== void 0 ? _value$i : "";
150
+ }), [length, value]);
151
+ const setCell = React.useCallback((index, char) => {
152
+ const arr = Array.from({ length }, (_, i) => {
153
+ var _value$i2;
154
+ return (_value$i2 = value[i]) !== null && _value$i2 !== void 0 ? _value$i2 : "";
155
+ });
156
+ arr[index] = char;
157
+ setValue(arr.join(""));
158
+ }, [
159
+ length,
160
+ value,
161
+ setValue
162
+ ]);
163
+ const handleChange = React.useCallback((index) => (e) => {
164
+ var _chars;
165
+ const raw = e.target.value;
166
+ const chars = raw.split("").filter((c) => pattern.test(c));
167
+ const char = (_chars = chars[chars.length - 1]) !== null && _chars !== void 0 ? _chars : "";
168
+ if (char === "" && raw !== "") return;
169
+ setCell(index, char);
170
+ if (char) focusCell(index + 1);
171
+ }, [
172
+ pattern,
173
+ setCell,
174
+ focusCell
175
+ ]);
176
+ const handleKeyDown = React.useCallback((index) => (e) => {
177
+ switch (e.key) {
178
+ case "Backspace":
179
+ e.preventDefault();
180
+ if (cells[index]) setCell(index, "");
181
+ else if (index > 0) {
182
+ setCell(index - 1, "");
183
+ focusCell(index - 1);
184
+ }
185
+ break;
186
+ case "ArrowLeft":
187
+ e.preventDefault();
188
+ focusCell(index - 1);
189
+ break;
190
+ case "ArrowRight":
191
+ e.preventDefault();
192
+ focusCell(index + 1);
193
+ }
194
+ }, [
195
+ cells,
196
+ setCell,
197
+ focusCell
198
+ ]);
199
+ const handlePaste = React.useCallback((index) => (e) => {
200
+ e.preventDefault();
201
+ const pasted = e.clipboardData.getData("text").split("").filter((c) => pattern.test(c)).join("");
202
+ if (!pasted) return;
203
+ const arr = Array.from({ length }, (_, i) => {
204
+ var _value$i3;
205
+ return (_value$i3 = value[i]) !== null && _value$i3 !== void 0 ? _value$i3 : "";
206
+ });
207
+ for (let i = 0; i < pasted.length && index + i < length; i++) arr[index + i] = pasted[i];
208
+ setValue(arr.join(""));
209
+ focusCell(index + pasted.length);
210
+ }, [
211
+ pattern,
212
+ length,
213
+ value,
214
+ setValue,
215
+ focusCell
216
+ ]);
217
+ return /* @__PURE__ */ jsxs("div", {
218
+ className: cn("flex flex-col gap-1.5", className),
219
+ "data-opt-id": "0083Q",
220
+ "data-opt-slug": "otp-field.OtpField",
221
+ children: [
222
+ label && /* @__PURE__ */ jsxs("span", {
223
+ className: "text-sm font-medium text-text-primary",
224
+ id: labelId,
225
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
226
+ }),
227
+ /* @__PURE__ */ jsx("div", {
228
+ role: "group",
229
+ "aria-label": groupAriaLabel,
230
+ "aria-labelledby": groupLabelledBy,
231
+ "aria-describedby": getDescribedBy(fieldId, error, hint),
232
+ className: "gap-element flex items-center",
233
+ children: cells.map((cell, i) => /* @__PURE__ */ jsx("input", {
234
+ ref: (node) => {
235
+ inputsRef.current[i] = node;
236
+ },
237
+ type: mask ? "password" : "text",
238
+ inputMode: type === "numeric" ? "numeric" : "text",
239
+ autoComplete: i === 0 ? "one-time-code" : "off",
240
+ autoFocus: autoFocus && i === 0,
241
+ maxLength: 1,
242
+ disabled,
243
+ "aria-label": `${i + 1}번째 자리`,
244
+ "aria-invalid": hasError || void 0,
245
+ value: cell,
246
+ onChange: handleChange(i),
247
+ onKeyDown: handleKeyDown(i),
248
+ onPaste: handlePaste(i),
249
+ onFocus: (e) => e.target.select(),
250
+ className: cn(OPT_INPUT, "h-12 w-10 text-center text-lg font-medium", hasError && "border-danger focus-visible:outline-danger")
251
+ }, i))
252
+ }),
253
+ name && /* @__PURE__ */ jsx("input", {
254
+ type: "hidden",
255
+ name,
256
+ value,
257
+ disabled
258
+ }),
259
+ /* @__PURE__ */ jsx(FieldMessages, {
260
+ id: fieldId,
261
+ error,
262
+ hint
263
+ })
264
+ ]
265
+ });
266
+ }
267
+ OtpField.displayName = "OtpField";
268
+ //#endregion
269
+ //#region src/core/toggle.tsx
270
+ const _excluded$15 = [
271
+ "size",
272
+ "className",
273
+ "children"
274
+ ];
275
+ const _excluded2$8 = [
276
+ "className",
277
+ "children",
278
+ "orientation"
279
+ ];
280
+ const sizeStyles$5 = {
281
+ sm: "h-8 min-w-8 px-2 text-xs gap-1.5",
282
+ md: "h-10 min-w-10 px-3 text-sm gap-2"
283
+ };
284
+ const toggleBase = cn("inline-flex cursor-pointer items-center justify-center rounded-md border border-border bg-surface font-medium text-text-secondary", "hover:bg-bg-subtle hover:text-text-primary", "data-[pressed]:border-accent data-[pressed]:bg-accent-subtle data-[pressed]:text-accent", "disabled:pointer-events-none disabled:opacity-40", OPT_FOCUS_VISIBLE);
285
+ /** A two-state (pressed) button. Use standalone or inside `ToggleGroup`. */
286
+ function Toggle$1(_ref) {
287
+ let { size = "md", className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
288
+ return /* @__PURE__ */ jsx(Toggle, _objectSpread2(_objectSpread2({ className: cn(toggleBase, sizeStyles$5[size], className) }, props), {}, {
289
+ "data-opt-id": "GFCMY",
290
+ "data-opt-slug": "toggle.Toggle",
291
+ children
292
+ }));
293
+ }
294
+ Toggle$1.displayName = "Toggle";
295
+ /** A set of related toggles with shared selection and keyboard navigation. */
296
+ function ToggleGroup$1(_ref2) {
297
+ let { className, children, orientation = "horizontal" } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$8);
298
+ return /* @__PURE__ */ jsx(ToggleGroup, _objectSpread2(_objectSpread2({
299
+ orientation,
300
+ "aria-orientation": orientation,
301
+ className: cn("inline-flex gap-1", orientation === "vertical" ? "flex-col" : "flex-row", className)
302
+ }, props), {}, {
303
+ "data-opt-id": "47J37",
304
+ "data-opt-slug": "toggle.ToggleGroup",
305
+ children
306
+ }));
307
+ }
308
+ ToggleGroup$1.displayName = "ToggleGroup";
309
+ //#endregion
310
+ //#region src/core/accordion.tsx
311
+ const _excluded$14 = ["className"];
312
+ const _excluded2$7 = ["className"];
313
+ const _excluded3$4 = ["className", "children"];
314
+ const _excluded4$3 = ["className", "children"];
315
+ const triggerClass = `group/acc-trigger flex w-full cursor-pointer items-center justify-between gap-2 py-4 text-left text-sm font-medium text-text-primary transition-colors hover:text-text-secondary ${OPT_FOCUS} disabled:cursor-not-allowed disabled:opacity-50`;
316
+ const contentClass = `overflow-hidden text-sm text-text-secondary ${OPT_ANIMATE_ENTER_EXIT}`;
317
+ /** A vertically stacked set of expandable panels (single or multiple open). */
318
+ function AccordionRoot$1(_ref) {
319
+ let { className } = _ref, props = _objectWithoutProperties(_ref, _excluded$14);
320
+ return /* @__PURE__ */ jsx("div", {
321
+ className: cn("border-border border-y", className),
322
+ "data-opt-id": "CSBA9",
323
+ "data-opt-slug": "accordion.AccordionRoot",
324
+ children: /* @__PURE__ */ jsx(AccordionRoot, _objectSpread2({}, props))
325
+ });
326
+ }
327
+ AccordionRoot$1.displayName = "AccordionRoot";
328
+ /** A single accordion entry, identified by its `value`. */
329
+ function AccordionItem$1(_ref2) {
330
+ let { className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$7);
331
+ return /* @__PURE__ */ jsx(AccordionItem, _objectSpread2(_objectSpread2({ className: cn("border-border border-b last:border-b-0", className) }, props), {}, {
332
+ "data-opt-id": "JTY9M",
333
+ "data-opt-slug": "accordion.AccordionItem"
334
+ }));
335
+ }
336
+ AccordionItem$1.displayName = "AccordionItem";
337
+ /** The clickable header that expands/collapses its panel. */
338
+ function AccordionTrigger$1(_ref3) {
339
+ let { className, children } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$4);
340
+ return /* @__PURE__ */ jsxs(AccordionTrigger, _objectSpread2(_objectSpread2({ className: cn(triggerClass, className) }, props), {}, {
341
+ "data-opt-id": "CWJZZ",
342
+ "data-opt-slug": "accordion.AccordionTrigger",
343
+ children: [/* @__PURE__ */ jsx("span", {
344
+ className: "flex-1",
345
+ children
346
+ }), /* @__PURE__ */ jsx(ChevronDown, {
347
+ "aria-hidden": "true",
348
+ className: "text-text-tertiary h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]/acc-trigger:rotate-180"
349
+ })]
350
+ }));
351
+ }
352
+ AccordionTrigger$1.displayName = "AccordionTrigger";
353
+ /** The collapsible panel body associated with a trigger. */
354
+ function AccordionContent$1(_ref4) {
355
+ let { className, children } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4$3);
356
+ return /* @__PURE__ */ jsx(AccordionContent, _objectSpread2(_objectSpread2({ className: cn(contentClass, className) }, props), {}, {
357
+ "data-opt-id": "J98AG",
358
+ "data-opt-slug": "accordion.AccordionContent",
359
+ children: /* @__PURE__ */ jsx("div", {
360
+ className: "pb-4",
361
+ children
362
+ })
363
+ }));
364
+ }
365
+ AccordionContent$1.displayName = "AccordionContent";
366
+ //#endregion
367
+ //#region src/core/breadcrumb.tsx
368
+ function interpolate(template, values) {
369
+ return template.replace(/\{(\w+)\}/g, (_, key) => key in values ? String(values[key]) : `{${key}}`);
370
+ }
371
+ /** Renders the breadcrumb component. */
372
+ function Breadcrumb({ items, separator = "/", className, onNavigate, "aria-label": ariaLabel = "Breadcrumb" }) {
373
+ return /* @__PURE__ */ jsx("nav", {
374
+ "aria-label": ariaLabel,
375
+ className: cn("flex items-center gap-1 px-1.5 text-sm", className),
376
+ "data-opt-id": "3FR0Y",
377
+ "data-opt-slug": "breadcrumb.Breadcrumb",
378
+ children: /* @__PURE__ */ jsx("ol", {
379
+ className: "flex items-center gap-1",
380
+ children: items.map((item, index) => {
381
+ const isLast = index === items.length - 1;
382
+ const linkClassName = cn(`flex items-center gap-1.5 rounded-sm ${OPT_FOCUS} transition-colors`, item.disabled ? `${OPT_TEXT_TERTIARY} cursor-not-allowed` : `${OPT_TEXT_SECONDARY} hover:text-text-primary cursor-pointer`);
383
+ return /* @__PURE__ */ jsx("li", {
384
+ className: "flex items-center gap-1",
385
+ children: isLast ? /* @__PURE__ */ jsxs("span", {
386
+ "aria-current": "page",
387
+ className: `flex items-center gap-1.5 font-medium ${OPT_TEXT_PRIMARY}`,
388
+ children: [item.icon, item.label]
389
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [item.href ? /* @__PURE__ */ jsxs("a", {
390
+ href: item.disabled ? void 0 : item.href,
391
+ "aria-disabled": item.disabled || void 0,
392
+ className: linkClassName,
393
+ onClick: () => !item.disabled && (onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(item)),
394
+ children: [item.icon, item.label]
395
+ }) : /* @__PURE__ */ jsxs("button", {
396
+ type: "button",
397
+ disabled: item.disabled,
398
+ "aria-label": interpolate(koBreadcrumbLabels.navAriaLabel, { label: item.label }),
399
+ className: linkClassName,
400
+ onClick: () => !item.disabled && (onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(item)),
401
+ children: [item.icon, item.label]
402
+ }), /* @__PURE__ */ jsx("span", {
403
+ "aria-hidden": "true",
404
+ className: OPT_TEXT_TERTIARY,
405
+ children: separator
406
+ })] })
407
+ }, item.id);
408
+ })
409
+ })
410
+ });
411
+ }
412
+ //#endregion
413
+ //#region src/core/card.tsx
414
+ const _excluded$13 = [
415
+ "variant",
416
+ "padding",
417
+ "shadow",
418
+ "selectable",
419
+ "selected",
420
+ "onSelectedChange",
421
+ "className",
422
+ "children",
423
+ "onClick",
424
+ "onKeyDown",
425
+ "role",
426
+ "tabIndex"
427
+ ];
428
+ const _excluded2$6 = [
429
+ "gap",
430
+ "className",
431
+ "children"
432
+ ];
433
+ const _excluded3$3 = [
434
+ "as",
435
+ "className",
436
+ "children"
437
+ ];
438
+ const _excluded4$2 = ["className", "children"];
439
+ const _excluded5 = [
440
+ "spacing",
441
+ "className",
442
+ "children"
443
+ ];
444
+ const _excluded6 = [
445
+ "divider",
446
+ "className",
447
+ "children"
448
+ ];
449
+ /** Renders the card component. */
450
+ function Card(_ref) {
451
+ let { variant = "default", padding = "md", shadow = true, selectable = false, selected = false, onSelectedChange, className, children, onClick, onKeyDown, role, tabIndex } = _ref, props = _objectWithoutProperties(_ref, _excluded$13);
452
+ const isInteractive = selectable || !!onClick;
453
+ const handleClick = selectable ? (e) => {
454
+ onSelectedChange === null || onSelectedChange === void 0 || onSelectedChange(!selected);
455
+ onClick === null || onClick === void 0 || onClick(e);
456
+ } : onClick;
457
+ const handleKeyDown = selectable ? (e) => {
458
+ if (e.key === "Enter" || e.key === " ") {
459
+ e.preventDefault();
460
+ onSelectedChange === null || onSelectedChange === void 0 || onSelectedChange(!selected);
461
+ }
462
+ onKeyDown === null || onKeyDown === void 0 || onKeyDown(e);
463
+ } : isInteractive ? (e) => {
464
+ if (e.key === "Enter" || e.key === " ") {
465
+ e.preventDefault();
466
+ e.currentTarget.click();
467
+ }
468
+ onKeyDown === null || onKeyDown === void 0 || onKeyDown(e);
469
+ } : onKeyDown;
470
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2(_objectSpread2({}, props), {}, { className: cn("rounded-xl", variant === "default" && cn(`border border-border bg-surface`, shadow && "shadow-sm"), variant === "outline" && "border-border border-2 bg-transparent", variant === "ghost" && "bg-bg-subtle", padding === "none" && "p-0", padding === "sm" && "p-3", padding === "md" && "p-5", padding === "lg" && "p-8", selectable && "cursor-pointer transition-colors", selectable && selected && "ring-accent border-accent ring-2 ring-offset-1", selectable && !selected && "hover:border-accent/50", className) }, isInteractive ? {
471
+ role: selectable ? "option" : "button",
472
+ tabIndex: 0,
473
+ "aria-selected": selectable ? selected : void 0,
474
+ onClick: handleClick,
475
+ onKeyDown: handleKeyDown
476
+ } : {
477
+ role,
478
+ tabIndex,
479
+ "aria-selected": selectable ? selected : void 0,
480
+ onKeyDown
481
+ }), {}, {
482
+ "data-opt-id": "0A18R",
483
+ "data-opt-slug": "card.Card",
484
+ children
485
+ }));
486
+ }
487
+ const headerGapClasses = {
488
+ sm: "gap-1",
489
+ md: "gap-1.5",
490
+ lg: "gap-2"
491
+ };
492
+ /** Renders the card header component. */
493
+ function CardHeader(_ref2) {
494
+ let { gap = "sm", className, children } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$6);
495
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({ className: cn("flex flex-col", headerGapClasses[gap], className) }, props), {}, {
496
+ "data-opt-id": "JST3J",
497
+ "data-opt-slug": "card.CardHeader",
498
+ children
499
+ }));
500
+ }
501
+ /** Renders the card title component. */
502
+ function CardTitle(_ref3) {
503
+ let { as: Tag = "h3", className, children } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$3);
504
+ return /* @__PURE__ */ jsx(Tag, _objectSpread2(_objectSpread2({ className: cn(`text-lg font-semibold ${OPT_TEXT_PRIMARY}`, className) }, props), {}, {
505
+ "data-opt-id": "V15TX",
506
+ "data-opt-slug": "card.CardTitle",
507
+ children
508
+ }));
509
+ }
510
+ /** Renders the card description component. */
511
+ function CardDescription(_ref4) {
512
+ let { className, children } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4$2);
513
+ return /* @__PURE__ */ jsx("p", _objectSpread2(_objectSpread2({ className: cn(`text-sm ${OPT_TEXT_SECONDARY}`, className) }, props), {}, {
514
+ "data-opt-id": "YB1RN",
515
+ "data-opt-slug": "card.CardDescription",
516
+ children
517
+ }));
518
+ }
519
+ const contentSpacingClasses = {
520
+ none: "",
521
+ sm: "mt-2",
522
+ md: "mt-4",
523
+ lg: "mt-6"
524
+ };
525
+ /** Renders the card content component. */
526
+ function CardContent(_ref5) {
527
+ let { spacing = "md", className, children } = _ref5, props = _objectWithoutProperties(_ref5, _excluded5);
528
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({ className: cn(contentSpacingClasses[spacing], className) }, props), {}, {
529
+ "data-opt-id": "9Y68R",
530
+ "data-opt-slug": "card.CardContent",
531
+ children
532
+ }));
533
+ }
534
+ /** Renders the card footer component. */
535
+ function CardFooter(_ref6) {
536
+ let { divider = true, className, children } = _ref6, props = _objectWithoutProperties(_ref6, _excluded6);
537
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({ className: cn("mt-4 flex items-center justify-end gap-2 pt-4", divider && `border-t border border-border`, className) }, props), {}, {
538
+ "data-opt-id": "GRNTR",
539
+ "data-opt-slug": "card.CardFooter",
540
+ children
541
+ }));
542
+ }
543
+ //#endregion
544
+ //#region src/core/switch.tsx
545
+ const _excluded$12 = [
546
+ "label",
547
+ "required",
548
+ "error",
549
+ "hint",
550
+ "description",
551
+ "checked",
552
+ "defaultChecked",
553
+ "onChange",
554
+ "size",
555
+ "disabled",
556
+ "className",
557
+ "id",
558
+ "onClick",
559
+ "aria-label",
560
+ "aria-labelledby"
561
+ ];
562
+ const sizeStyles$4 = {
563
+ sm: {
564
+ track: "h-5 w-9",
565
+ thumb: "h-4 w-4",
566
+ translate: "translate-x-4"
567
+ },
568
+ md: {
569
+ track: "h-6 w-11",
570
+ thumb: "h-5 w-5",
571
+ translate: "translate-x-5"
572
+ },
573
+ lg: {
574
+ track: "h-7 w-14",
575
+ thumb: "h-6 w-6",
576
+ translate: "translate-x-7"
577
+ }
578
+ };
579
+ const Switch = forwardRef((_ref, ref) => {
580
+ var _useInputId;
581
+ let { label, required, error, hint, description, checked: controlledChecked, defaultChecked = false, onChange, size = "md", disabled, className, id, onClick, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy } = _ref, props = _objectWithoutProperties(_ref, _excluded$12);
582
+ const fallbackId = useId();
583
+ const switchId = (_useInputId = useInputId(id, label, "switch")) !== null && _useInputId !== void 0 ? _useInputId : `switch-${fallbackId}`;
584
+ const hintText = hint !== null && hint !== void 0 ? hint : description;
585
+ const hasError = Boolean(error);
586
+ const styles = sizeStyles$4[size];
587
+ const labelId = label ? `${switchId}-label` : void 0;
588
+ const switchLabelledBy = ariaLabelledBy !== null && ariaLabelledBy !== void 0 ? ariaLabelledBy : ariaLabel ? void 0 : labelId;
589
+ const switchAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : switchLabelledBy ? void 0 : "Switch";
590
+ const isControlled = controlledChecked !== void 0;
591
+ const [internalChecked, setInternalChecked] = useState(defaultChecked);
592
+ const checked = isControlled ? controlledChecked : internalChecked;
593
+ const handleClick = useCallback((e) => {
594
+ const next = !checked;
595
+ if (!isControlled) setInternalChecked(next);
596
+ onChange === null || onChange === void 0 || onChange(next);
597
+ onClick === null || onClick === void 0 || onClick(e);
598
+ }, [
599
+ checked,
600
+ isControlled,
601
+ onChange,
602
+ onClick
603
+ ]);
604
+ return /* @__PURE__ */ jsxs("div", {
605
+ className: cn("flex flex-col gap-1.5", className),
606
+ "data-opt-id": "51Z9R",
607
+ "data-opt-slug": "switch.Switch",
608
+ children: [/* @__PURE__ */ jsxs("div", {
609
+ className: "flex items-start gap-3",
610
+ children: [/* @__PURE__ */ jsx("button", _objectSpread2(_objectSpread2({
611
+ ref,
612
+ type: "button",
613
+ role: "switch",
614
+ id: switchId,
615
+ "aria-checked": checked,
616
+ "aria-label": switchAriaLabel,
617
+ "aria-labelledby": switchLabelledBy,
618
+ disabled,
619
+ className: cn("group relative inline-flex shrink-0 cursor-pointer rounded-full p-0.5 transition-colors", styles.track, "bg-bg-muted", "aria-[checked=true]:bg-accent", "disabled:cursor-not-allowed disabled:opacity-50", OPT_FOCUS_VISIBLE),
620
+ "aria-invalid": hasError,
621
+ "aria-required": required,
622
+ "aria-describedby": getDescribedBy(switchId, error, hintText),
623
+ onClick: handleClick
624
+ }, props), {}, { children: /* @__PURE__ */ jsx("span", { className: cn("pointer-events-none inline-block transform rounded-full bg-white shadow-sm ring-0 transition-transform", styles.thumb, "translate-x-0", size === "sm" && "group-aria-[checked=true]:translate-x-4", size === "md" && "group-aria-[checked=true]:translate-x-5", size === "lg" && "group-aria-[checked=true]:translate-x-7") }) })), (label || hintText) && /* @__PURE__ */ jsxs("div", {
625
+ className: "flex flex-col",
626
+ children: [label && /* @__PURE__ */ jsxs("span", {
627
+ id: labelId,
628
+ className: cn("text-text-primary text-sm font-medium", disabled && "opacity-50"),
629
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
630
+ }), hintText && /* @__PURE__ */ jsx("p", {
631
+ id: `${switchId}-hint`,
632
+ className: cn("text-text-tertiary text-sm", disabled && "opacity-50"),
633
+ children: hintText
634
+ })]
635
+ })]
636
+ }), /* @__PURE__ */ jsx(FieldMessages, {
637
+ id: switchId,
638
+ error
639
+ })]
640
+ });
641
+ });
642
+ Switch.displayName = "Switch";
643
+ //#endregion
644
+ //#region src/core/skeleton.tsx
645
+ const _excluded$11 = [
646
+ "variant",
647
+ "width",
648
+ "height",
649
+ "lines",
650
+ "animated",
651
+ "className",
652
+ "style"
653
+ ];
654
+ const _excluded2$5 = ["lines", "className"];
655
+ const _excluded3$2 = ["size", "className"];
656
+ const _excluded4$1 = ["className"];
657
+ const variantStyles$2 = {
658
+ text: "rounded h-4",
659
+ circular: "rounded-full",
660
+ rectangular: "rounded-lg"
661
+ };
662
+ /** Renders the skeleton component. */
663
+ function Skeleton(_ref) {
664
+ let { variant = "text", width, height, lines = 1, animated = true, className, style } = _ref, props = _objectWithoutProperties(_ref, _excluded$11);
665
+ const baseClass = cn("bg-bg-subtle", animated && "animate-pulse", variantStyles$2[variant], className);
666
+ const computedStyle = _objectSpread2({
667
+ width: typeof width === "number" ? `${width}px` : width,
668
+ height: typeof height === "number" ? `${height}px` : height
669
+ }, style);
670
+ if (variant === "text" && lines > 1) return /* @__PURE__ */ jsxs("div", {
671
+ className: "gap-element flex flex-col",
672
+ role: "status",
673
+ "aria-label": "Loading",
674
+ "data-opt-id": "EJT87",
675
+ "data-opt-slug": "skeleton.Skeleton",
676
+ children: [Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx("div", _objectSpread2({
677
+ className: cn(baseClass, i === lines - 1 && "w-4/5"),
678
+ style: i === lines - 1 ? void 0 : computedStyle
679
+ }, props), i)), /* @__PURE__ */ jsx("span", {
680
+ className: "sr-only",
681
+ children: "Loading..."
682
+ })]
683
+ });
684
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({
685
+ className: baseClass,
686
+ style: computedStyle,
687
+ role: "status",
688
+ "aria-label": "Loading"
689
+ }, props), {}, {
690
+ "data-opt-id": "EJT87",
691
+ "data-opt-slug": "skeleton.Skeleton",
692
+ children: /* @__PURE__ */ jsx("span", {
693
+ className: "sr-only",
694
+ children: "Loading..."
695
+ })
696
+ }));
697
+ }
698
+ /** Renders the skeleton text component. */
699
+ function SkeletonText(_ref2) {
700
+ let { lines = 3, className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$5);
701
+ return /* @__PURE__ */ jsx(Skeleton, _objectSpread2(_objectSpread2({
702
+ variant: "text",
703
+ lines,
704
+ className
705
+ }, props), {}, {
706
+ "data-opt-id": "MX3D5",
707
+ "data-opt-slug": "skeleton.SkeletonText"
708
+ }));
709
+ }
710
+ /** Renders the skeleton avatar component. */
711
+ function SkeletonAvatar(_ref3) {
712
+ let { size = 40, className } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$2);
713
+ return /* @__PURE__ */ jsx(Skeleton, _objectSpread2(_objectSpread2({
714
+ variant: "circular",
715
+ width: size,
716
+ height: size,
717
+ className
718
+ }, props), {}, {
719
+ "data-opt-id": "MP0HZ",
720
+ "data-opt-slug": "skeleton.SkeletonAvatar"
721
+ }));
722
+ }
723
+ /** Renders the skeleton card component. */
724
+ function SkeletonCard(_ref4) {
725
+ let { className } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4$1);
726
+ return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("gap-group flex flex-col", className) }, props), {}, {
727
+ "data-opt-id": "34V5D",
728
+ "data-opt-slug": "skeleton.SkeletonCard",
729
+ children: [
730
+ /* @__PURE__ */ jsx(Skeleton, {
731
+ variant: "rectangular",
732
+ height: 120
733
+ }),
734
+ /* @__PURE__ */ jsx(Skeleton, {
735
+ variant: "text",
736
+ width: "60%"
737
+ }),
738
+ /* @__PURE__ */ jsx(Skeleton, {
739
+ variant: "text",
740
+ lines: 2
741
+ })
742
+ ]
743
+ }));
744
+ }
745
+ /** Renders the skeleton table component. */
746
+ function SkeletonTable({ rows = 5, columns = 4, className }) {
747
+ return /* @__PURE__ */ jsxs("div", {
748
+ className: cn("gap-element flex flex-col", className),
749
+ role: "status",
750
+ "aria-label": "Loading table",
751
+ "data-opt-id": "3KEDN",
752
+ "data-opt-slug": "skeleton.SkeletonTable",
753
+ children: [
754
+ /* @__PURE__ */ jsx("div", {
755
+ className: "border-border-subtle gap-group flex border-b pb-2",
756
+ children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, {
757
+ variant: "text",
758
+ className: "flex-1",
759
+ height: 16
760
+ }, i))
761
+ }),
762
+ Array.from({ length: rows }).map((_, rowIndex) => /* @__PURE__ */ jsx("div", {
763
+ className: "gap-group flex py-2",
764
+ children: Array.from({ length: columns }).map((_, colIndex) => /* @__PURE__ */ jsx(Skeleton, {
765
+ variant: "text",
766
+ className: "flex-1",
767
+ height: 14
768
+ }, colIndex))
769
+ }, rowIndex)),
770
+ /* @__PURE__ */ jsx("span", {
771
+ className: "sr-only",
772
+ children: "Loading..."
773
+ })
774
+ ]
775
+ });
776
+ }
777
+ //#endregion
778
+ //#region src/core/textarea.tsx
779
+ const _excluded$10 = [
780
+ "label",
781
+ "error",
782
+ "hint",
783
+ "fullWidth",
784
+ "required",
785
+ "resize",
786
+ "size",
787
+ "className",
788
+ "id",
789
+ "rows"
790
+ ];
791
+ const textareaSizeStyles = OPT_TEXTAREA_SIZE;
792
+ const resizeStyles = {
793
+ none: "resize-none",
794
+ vertical: "resize-y",
795
+ horizontal: "resize-x",
796
+ both: "resize"
797
+ };
798
+ const Textarea = forwardRef((_ref, ref) => {
799
+ var _useInputId;
800
+ let { label, error, hint, fullWidth = false, required, resize = "vertical", size = "md", className, id, rows = 4 } = _ref, props = _objectWithoutProperties(_ref, _excluded$10);
801
+ const fallbackId = useId().replace(/:/g, "");
802
+ const textareaId = (_useInputId = useInputId(id, label, "textarea")) !== null && _useInputId !== void 0 ? _useInputId : `textarea-${fallbackId}`;
803
+ const hasError = Boolean(error);
804
+ return /* @__PURE__ */ jsxs("div", {
805
+ className: cn("flex flex-col gap-1.5", fullWidth && "w-full"),
806
+ "data-opt-id": "6T58H",
807
+ "data-opt-slug": "textarea.Textarea",
808
+ children: [
809
+ label && /* @__PURE__ */ jsxs("label", {
810
+ htmlFor: textareaId,
811
+ className: "text-text-primary text-sm font-medium",
812
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
813
+ }),
814
+ /* @__PURE__ */ jsx("textarea", _objectSpread2({
815
+ ref,
816
+ id: textareaId,
817
+ rows,
818
+ className: cn("border-border bg-surface text-text-primary placeholder:text-text-tertiary w-full rounded-lg border px-4 py-3 text-sm outline-transparent", OPT_FOCUS_VISIBLE, textareaSizeStyles[size], resizeStyles[resize], hasError && "border-danger focus-visible:outline-danger", !fullWidth && "w-auto", className),
819
+ "aria-invalid": hasError,
820
+ "aria-describedby": getDescribedBy(textareaId, error, hint)
821
+ }, props)),
822
+ /* @__PURE__ */ jsx(FieldMessages, {
823
+ id: textareaId,
824
+ error,
825
+ hint
826
+ })
827
+ ]
828
+ });
829
+ });
830
+ Textarea.displayName = "Textarea";
831
+ //#endregion
832
+ //#region src/core/avatar.tsx
833
+ const _excluded$9 = [
834
+ "src",
835
+ "srcSet",
836
+ "sizes",
837
+ "crossOrigin",
838
+ "referrerPolicy",
839
+ "alt",
840
+ "name",
841
+ "size",
842
+ "fallback",
843
+ "status",
844
+ "className",
845
+ "onLoadingStatusChange"
846
+ ];
847
+ const _excluded2$4 = [
848
+ "max",
849
+ "size",
850
+ "children",
851
+ "className"
852
+ ];
853
+ const useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
854
+ /**
855
+ * Tracks an image's loading status by preloading it off-DOM, so the avatar can
856
+ * show its fallback until the image is actually decoded (no broken-image flash)
857
+ * and recover from late `error`/`load` events. Supports responsive `srcSet`/`sizes`.
858
+ */
859
+ function useImageLoadingStatus(src, { srcSet, sizes, crossOrigin, referrerPolicy }) {
860
+ const [status, setStatus] = useState("idle");
861
+ useIsoLayoutEffect(() => {
862
+ if (!src && !srcSet) {
863
+ setStatus("error");
864
+ return;
865
+ }
866
+ let isMounted = true;
867
+ const image = new window.Image();
868
+ const update = (next) => () => {
869
+ if (isMounted) setStatus(next);
870
+ };
871
+ setStatus("loading");
872
+ image.onload = update("loaded");
873
+ image.onerror = update("error");
874
+ if (referrerPolicy) image.referrerPolicy = referrerPolicy;
875
+ image.crossOrigin = crossOrigin !== null && crossOrigin !== void 0 ? crossOrigin : null;
876
+ if (sizes) image.sizes = sizes;
877
+ if (srcSet) image.srcset = srcSet;
878
+ if (src) image.src = src;
879
+ if (image.complete) setStatus(image.naturalWidth > 0 ? "loaded" : "error");
880
+ return () => {
881
+ isMounted = false;
882
+ };
883
+ }, [
884
+ src,
885
+ srcSet,
886
+ sizes,
887
+ crossOrigin,
888
+ referrerPolicy
889
+ ]);
890
+ return status;
891
+ }
892
+ const sizeStyles$3 = {
893
+ xs: "h-6 w-6 text-[10px]",
894
+ sm: "h-8 w-8 text-xs",
895
+ md: "h-10 w-10 text-sm",
896
+ lg: "h-12 w-12 text-base",
897
+ xl: "h-16 w-16 text-lg"
898
+ };
899
+ const statusStyles = {
900
+ online: "bg-success",
901
+ offline: "bg-bg-muted",
902
+ busy: "bg-danger",
903
+ away: "bg-warning"
904
+ };
905
+ const statusSizeStyles = {
906
+ xs: "h-1.5 w-1.5 border",
907
+ sm: "h-2 w-2 border",
908
+ md: "h-2.5 w-2.5 border-2",
909
+ lg: "h-3 w-3 border-2",
910
+ xl: "h-4 w-4 border-2"
911
+ };
912
+ function getInitials(name) {
913
+ const parts = name.trim().split(/\s+/);
914
+ if (parts.length === 1) return parts[0].charAt(0).toUpperCase();
915
+ return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
916
+ }
917
+ const bgColors = [
918
+ "bg-blue-500",
919
+ "bg-emerald-500",
920
+ "bg-amber-500",
921
+ "bg-rose-500",
922
+ "bg-purple-500",
923
+ "bg-cyan-500",
924
+ "bg-orange-500",
925
+ "bg-indigo-500"
926
+ ];
927
+ function getColorFromName(name) {
928
+ let hash = 0;
929
+ for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
930
+ return bgColors[Math.abs(hash) % bgColors.length];
931
+ }
932
+ /** Renders the avatar component. */
933
+ function Avatar(_ref) {
934
+ var _ref2, _ref3;
935
+ let { src, srcSet, sizes, crossOrigin, referrerPolicy, alt, name, size = "md", fallback, status, className, onLoadingStatusChange } = _ref, props = _objectWithoutProperties(_ref, _excluded$9);
936
+ const loadingStatus = useImageLoadingStatus(src, {
937
+ srcSet,
938
+ sizes,
939
+ crossOrigin,
940
+ referrerPolicy
941
+ });
942
+ useEffect(() => {
943
+ onLoadingStatusChange === null || onLoadingStatusChange === void 0 || onLoadingStatusChange(loadingStatus);
944
+ }, [loadingStatus, onLoadingStatusChange]);
945
+ const showImage = (Boolean(src) || Boolean(srcSet)) && loadingStatus === "loaded";
946
+ const initials = name ? getInitials(name) : null;
947
+ const bgColor = name ? getColorFromName(name) : "bg-bg-subtle";
948
+ const avatarLabel = status ? `${(_ref2 = name !== null && name !== void 0 ? name : alt) !== null && _ref2 !== void 0 ? _ref2 : "Avatar"} (${status})` : (_ref3 = name !== null && name !== void 0 ? name : alt) !== null && _ref3 !== void 0 ? _ref3 : "Avatar";
949
+ let avatarContent;
950
+ if (showImage) {
951
+ var _ref4;
952
+ avatarContent = /* @__PURE__ */ jsx("img", {
953
+ src,
954
+ srcSet,
955
+ sizes,
956
+ crossOrigin,
957
+ referrerPolicy,
958
+ alt: (_ref4 = alt !== null && alt !== void 0 ? alt : name) !== null && _ref4 !== void 0 ? _ref4 : "Avatar",
959
+ className: "h-full w-full object-cover"
960
+ });
961
+ } else if (fallback) avatarContent = fallback;
962
+ else if (initials) avatarContent = /* @__PURE__ */ jsx("span", {
963
+ className: "font-medium text-white",
964
+ children: initials
965
+ });
966
+ else avatarContent = /* @__PURE__ */ jsx("svg", {
967
+ className: "text-text-tertiary h-3/5 w-3/5",
968
+ fill: "currentColor",
969
+ viewBox: "0 0 24 24",
970
+ "aria-hidden": "true",
971
+ children: /* @__PURE__ */ jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" })
972
+ });
973
+ return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({
974
+ role: "img",
975
+ "aria-label": avatarLabel,
976
+ className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full", sizeStyles$3[size], !showImage && bgColor, className)
977
+ }, props), {}, {
978
+ "data-opt-id": "SCMM0",
979
+ "data-opt-slug": "avatar.Avatar",
980
+ children: [avatarContent, status && /* @__PURE__ */ jsx("span", {
981
+ className: cn("border-surface absolute right-0 bottom-0 rounded-full", statusStyles[status], statusSizeStyles[size]),
982
+ "aria-hidden": "true"
983
+ })]
984
+ }));
985
+ }
986
+ /** Renders the avatar group component. */
987
+ function AvatarGroup(_ref5) {
988
+ let { max = 4, size = "md", children, className } = _ref5, props = _objectWithoutProperties(_ref5, _excluded2$4);
989
+ const avatars = Array.isArray(children) ? children : [children];
990
+ const visibleAvatars = avatars.slice(0, max);
991
+ const remainingCount = avatars.length - max;
992
+ return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("flex", className) }, props), {}, {
993
+ "data-opt-id": "MVB53",
994
+ "data-opt-slug": "avatar.AvatarGroup",
995
+ children: [visibleAvatars.map((child, index) => /* @__PURE__ */ jsx("span", {
996
+ className: "ring-surface relative -ml-2 inline-block rounded-full ring-2 first:ml-0",
997
+ children: child
998
+ }, index)), remainingCount > 0 && /* @__PURE__ */ jsxs("span", {
999
+ className: cn("bg-bg-subtle text-text-secondary ring-surface relative inline-flex items-center justify-center rounded-full font-medium ring-2", sizeStyles$3[size]),
1000
+ children: ["+", remainingCount]
1001
+ })]
1002
+ }));
1003
+ }
1004
+ //#endregion
1005
+ //#region src/core/progress.tsx
1006
+ const _excluded$8 = [
1007
+ "value",
1008
+ "max",
1009
+ "size",
1010
+ "variant",
1011
+ "showLabel",
1012
+ "label",
1013
+ "animated",
1014
+ "indeterminate",
1015
+ "className"
1016
+ ];
1017
+ const _excluded2$3 = [
1018
+ "value",
1019
+ "max",
1020
+ "size",
1021
+ "strokeWidth",
1022
+ "variant",
1023
+ "showLabel",
1024
+ "label",
1025
+ "indeterminate",
1026
+ "className"
1027
+ ];
1028
+ const sizeStyles$2 = {
1029
+ sm: "h-1",
1030
+ md: "h-2",
1031
+ lg: "h-3"
1032
+ };
1033
+ const variantStyles$1 = {
1034
+ default: "bg-accent",
1035
+ success: "bg-success",
1036
+ warning: "bg-warning",
1037
+ error: "bg-danger"
1038
+ };
1039
+ function getProgressValue(value, max) {
1040
+ const safeMax = Number.isFinite(max) ? Math.max(0, max) : 100;
1041
+ const clampedValue = safeMax > 0 ? Math.min(Math.max(Number.isFinite(value) ? value : 0, 0), safeMax) : 0;
1042
+ const percentage = safeMax > 0 ? clampedValue / safeMax * 100 : 0;
1043
+ return {
1044
+ clampedValue,
1045
+ max: safeMax,
1046
+ percentage,
1047
+ valueText: `${Math.round(percentage)}%`
1048
+ };
1049
+ }
1050
+ /** Renders the progress component. */
1051
+ function Progress(_ref) {
1052
+ let { value, max = 100, size = "md", variant = "default", showLabel = false, label, animated = true, indeterminate = false, className } = _ref, props = _objectWithoutProperties(_ref, _excluded$8);
1053
+ const progress = indeterminate || value === void 0 ? null : getProgressValue(value, max);
1054
+ const shouldRenderHeader = Boolean(label) || showLabel && progress;
1055
+ return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("w-full", className) }, props), {}, {
1056
+ "data-opt-id": "Y5N8R",
1057
+ "data-opt-slug": "progress.Progress",
1058
+ children: [shouldRenderHeader && /* @__PURE__ */ jsxs("div", {
1059
+ className: "mb-1.5 flex items-center justify-between text-sm",
1060
+ children: [label && /* @__PURE__ */ jsx("span", {
1061
+ className: "text-text-primary font-medium",
1062
+ children: label
1063
+ }), showLabel && progress && /* @__PURE__ */ jsx("span", {
1064
+ className: "text-text-tertiary",
1065
+ children: progress.valueText
1066
+ })]
1067
+ }), /* @__PURE__ */ jsx("div", {
1068
+ className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$2[size]),
1069
+ role: "progressbar",
1070
+ "aria-valuenow": progress === null || progress === void 0 ? void 0 : progress.clampedValue,
1071
+ "aria-valuemin": progress ? 0 : void 0,
1072
+ "aria-valuemax": progress === null || progress === void 0 ? void 0 : progress.max,
1073
+ "aria-valuetext": progress === null || progress === void 0 ? void 0 : progress.valueText,
1074
+ "aria-label": label !== null && label !== void 0 ? label : progress ? `${progress.valueText} complete` : "Loading",
1075
+ children: /* @__PURE__ */ jsx("div", {
1076
+ className: cn("h-full rounded-full", variantStyles$1[variant], !progress && "w-1/3 animate-pulse", animated && (progress ? "transition-all duration-300 ease-out" : "transition-opacity duration-700 ease-in-out")),
1077
+ style: progress ? { width: `${progress.percentage}%` } : void 0
1078
+ })
1079
+ })]
1080
+ }));
1081
+ }
1082
+ const circularVariantStyles = {
1083
+ default: "text-accent",
1084
+ success: "text-success",
1085
+ warning: "text-warning",
1086
+ error: "text-danger"
1087
+ };
1088
+ /** Renders the circular progress component. */
1089
+ function CircularProgress(_ref2) {
1090
+ var _progress$percentage;
1091
+ let { value, max = 100, size = 48, strokeWidth = 4, variant = "default", showLabel = false, label, indeterminate = false, className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$3);
1092
+ const labelId = useId();
1093
+ const isIndeterminate = indeterminate || value === void 0;
1094
+ const progress = isIndeterminate ? null : getProgressValue(value, max);
1095
+ const accessibleName = label !== null && label !== void 0 ? label : progress ? `${progress.valueText} complete` : "Loading";
1096
+ const radius = (size - strokeWidth) / 2;
1097
+ const circumference = radius * 2 * Math.PI;
1098
+ const strokeDashoffset = circumference - ((_progress$percentage = progress === null || progress === void 0 ? void 0 : progress.percentage) !== null && _progress$percentage !== void 0 ? _progress$percentage : 0) / 100 * circumference;
1099
+ return /* @__PURE__ */ jsxs("div", {
1100
+ className: "relative inline-flex items-center justify-center",
1101
+ "data-opt-id": "Q69KP",
1102
+ "data-opt-slug": "progress.CircularProgress",
1103
+ children: [
1104
+ label && /* @__PURE__ */ jsx("span", {
1105
+ id: labelId,
1106
+ className: "sr-only",
1107
+ children: label
1108
+ }),
1109
+ /* @__PURE__ */ jsxs("svg", _objectSpread2(_objectSpread2({
1110
+ width: size,
1111
+ height: size,
1112
+ className: cn(isIndeterminate ? "animate-spin" : "-rotate-90", className),
1113
+ role: isIndeterminate ? "progressbar" : "meter",
1114
+ "aria-labelledby": label ? labelId : void 0,
1115
+ "aria-valuenow": progress === null || progress === void 0 ? void 0 : progress.clampedValue,
1116
+ "aria-valuemin": progress ? 0 : void 0,
1117
+ "aria-valuemax": progress === null || progress === void 0 ? void 0 : progress.max,
1118
+ "aria-valuetext": progress === null || progress === void 0 ? void 0 : progress.valueText,
1119
+ "aria-label": label ? void 0 : accessibleName
1120
+ }, props), {}, { children: [/* @__PURE__ */ jsx("circle", {
1121
+ cx: size / 2,
1122
+ cy: size / 2,
1123
+ r: radius,
1124
+ fill: "none",
1125
+ strokeWidth,
1126
+ className: "stroke-border-subtle"
1127
+ }), /* @__PURE__ */ jsx("circle", {
1128
+ cx: size / 2,
1129
+ cy: size / 2,
1130
+ r: radius,
1131
+ fill: "none",
1132
+ strokeWidth,
1133
+ strokeLinecap: "round",
1134
+ className: cn("transition-all duration-300 ease-out", circularVariantStyles[variant]),
1135
+ style: {
1136
+ strokeDasharray: isIndeterminate ? `${circumference * .28} ${circumference}` : circumference,
1137
+ strokeDashoffset: isIndeterminate ? 0 : strokeDashoffset,
1138
+ stroke: "currentColor"
1139
+ }
1140
+ })] })),
1141
+ showLabel && progress && /* @__PURE__ */ jsx("span", {
1142
+ className: "text-text-primary absolute text-xs font-medium",
1143
+ children: progress.valueText
1144
+ })
1145
+ ]
1146
+ });
1147
+ }
1148
+ //#endregion
1149
+ //#region src/core/meter.tsx
1150
+ const _excluded$7 = [
1151
+ "value",
1152
+ "min",
1153
+ "max",
1154
+ "size",
1155
+ "variant",
1156
+ "showValue",
1157
+ "label",
1158
+ "getValueLabel",
1159
+ "className"
1160
+ ];
1161
+ const sizeStyles$1 = {
1162
+ sm: "h-1",
1163
+ md: "h-2",
1164
+ lg: "h-3"
1165
+ };
1166
+ const variantStyles = {
1167
+ default: "bg-accent",
1168
+ success: "bg-success",
1169
+ warning: "bg-warning",
1170
+ error: "bg-danger"
1171
+ };
1172
+ /**
1173
+ * A scalar measurement within a known range (disk usage, score, capacity) —
1174
+ * `role="meter"`, distinct from Progress (task completion). Built on the
1175
+ * opt-ui-primitives Meter primitive, which owns the role/aria/valuetext.
1176
+ */
1177
+ function Meter$1(_ref) {
1178
+ let { value, min = 0, max = 100, size = "md", variant = "default", showValue = false, label, getValueLabel, className } = _ref, props = _objectWithoutProperties(_ref, _excluded$7);
1179
+ const clamped = Math.min(Math.max(value, min), max);
1180
+ const percentage = max === min ? 0 : (clamped - min) / (max - min) * 100;
1181
+ return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({ className: cn("w-full", className) }, props), {}, {
1182
+ "data-opt-id": "G01TE",
1183
+ "data-opt-slug": "meter.Meter",
1184
+ children: [(showValue || label) && /* @__PURE__ */ jsxs("div", {
1185
+ className: "mb-1.5 flex items-center justify-between text-sm",
1186
+ children: [label && /* @__PURE__ */ jsx("span", {
1187
+ className: "text-text-primary font-medium",
1188
+ children: label
1189
+ }), showValue && /* @__PURE__ */ jsxs("span", {
1190
+ className: "text-text-tertiary",
1191
+ children: [Math.round(percentage), "%"]
1192
+ })]
1193
+ }), /* @__PURE__ */ jsx(Meter, {
1194
+ value: clamped,
1195
+ min,
1196
+ max,
1197
+ getValueLabel,
1198
+ "aria-label": label !== null && label !== void 0 ? label : `${Math.round(percentage)}%`,
1199
+ className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$1[size]),
1200
+ children: /* @__PURE__ */ jsx("div", {
1201
+ className: cn("h-full rounded-full transition-all duration-300 ease-out", variantStyles[variant]),
1202
+ style: { width: `${percentage}%` }
1203
+ })
1204
+ })]
1205
+ }));
1206
+ }
1207
+ //#endregion
1208
+ //#region src/core/step-builder.tsx
1209
+ /** Renders the step builder component. */
1210
+ function StepBuilder({ steps, onChange, label, addLabel = "Add step", maxSteps, className, "aria-label": ariaLabel }) {
1211
+ var _ref;
1212
+ const [editingId, setEditingId] = useState(null);
1213
+ const nextStepIdRef = useRef(1);
1214
+ const createStepId = useCallback(() => {
1215
+ const existingIds = new Set(steps.map((step) => step.id));
1216
+ let id = "";
1217
+ do
1218
+ id = `step-${nextStepIdRef.current++}`;
1219
+ while (existingIds.has(id));
1220
+ return id;
1221
+ }, [steps]);
1222
+ const addStep = useCallback(() => {
1223
+ if (maxSteps && steps.length >= maxSteps) return;
1224
+ const newStep = {
1225
+ id: createStepId(),
1226
+ label: `Step ${steps.length + 1}`
1227
+ };
1228
+ onChange([...steps, newStep]);
1229
+ setEditingId(newStep.id);
1230
+ }, [
1231
+ steps,
1232
+ onChange,
1233
+ maxSteps,
1234
+ createStepId
1235
+ ]);
1236
+ const removeStep = useCallback((id) => {
1237
+ onChange(steps.filter((s) => s.id !== id));
1238
+ if (editingId === id) setEditingId(null);
1239
+ }, [
1240
+ steps,
1241
+ onChange,
1242
+ editingId
1243
+ ]);
1244
+ const updateStep = useCallback((id, updates) => {
1245
+ onChange(steps.map((s) => s.id === id ? _objectSpread2(_objectSpread2({}, s), updates) : s));
1246
+ }, [steps, onChange]);
1247
+ const moveStep = useCallback((index, direction) => {
1248
+ const target = index + direction;
1249
+ if (target < 0 || target >= steps.length) return;
1250
+ const newSteps = [...steps];
1251
+ [newSteps[index], newSteps[target]] = [newSteps[target], newSteps[index]];
1252
+ onChange(newSteps);
1253
+ }, [steps, onChange]);
1254
+ const atLimit = Boolean(maxSteps && steps.length >= maxSteps);
1255
+ return /* @__PURE__ */ jsxs("div", {
1256
+ className: cn("flex flex-col gap-3", className),
1257
+ role: "list",
1258
+ "aria-label": (_ref = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : label) !== null && _ref !== void 0 ? _ref : "Step builder",
1259
+ "data-opt-id": "7BQDT",
1260
+ "data-opt-slug": "step-builder.StepBuilder",
1261
+ children: [
1262
+ label && /* @__PURE__ */ jsx("span", {
1263
+ className: "text-sm font-medium text-text-primary",
1264
+ children: label
1265
+ }),
1266
+ steps.map((step, index) => {
1267
+ var _step$description;
1268
+ return /* @__PURE__ */ jsxs("div", {
1269
+ className: cn(OPT_SURFACE, OPT_BORDER, "flex items-start gap-3 p-3"),
1270
+ role: "listitem",
1271
+ children: [
1272
+ /* @__PURE__ */ jsx("span", {
1273
+ className: "bg-accent text-accent-fg flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full text-xs font-bold",
1274
+ "aria-hidden": "true",
1275
+ children: index + 1
1276
+ }),
1277
+ /* @__PURE__ */ jsx("div", {
1278
+ className: "min-w-0 flex-1",
1279
+ children: editingId === step.id ? /* @__PURE__ */ jsxs("div", {
1280
+ className: "flex flex-col gap-2",
1281
+ children: [
1282
+ /* @__PURE__ */ jsx("input", {
1283
+ type: "text",
1284
+ value: step.label,
1285
+ onChange: (e) => updateStep(step.id, { label: e.target.value }),
1286
+ onKeyDown: (e) => {
1287
+ if (e.key === "Enter") setEditingId(null);
1288
+ },
1289
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset w-full rounded-md border px-2 py-1 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-1", OPT_TEXT_PRIMARY, OPT_FOCUS_VISIBLE),
1290
+ "aria-label": "Step label",
1291
+ autoFocus: true
1292
+ }),
1293
+ /* @__PURE__ */ jsx("input", {
1294
+ type: "text",
1295
+ value: (_step$description = step.description) !== null && _step$description !== void 0 ? _step$description : "",
1296
+ onChange: (e) => updateStep(step.id, { description: e.target.value }),
1297
+ placeholder: "Description (optional)",
1298
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset w-full rounded-md border px-2 py-1 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-1", OPT_TEXT_PRIMARY, "placeholder:text-text-tertiary", OPT_FOCUS_VISIBLE),
1299
+ "aria-label": "Step description"
1300
+ }),
1301
+ /* @__PURE__ */ jsx(Button, {
1302
+ variant: "ghost",
1303
+ size: "sm",
1304
+ onClick: () => setEditingId(null),
1305
+ children: "Done"
1306
+ })
1307
+ ]
1308
+ }) : /* @__PURE__ */ jsxs("button", {
1309
+ type: "button",
1310
+ className: "w-full text-left",
1311
+ onClick: () => setEditingId(step.id),
1312
+ "aria-label": `Edit ${step.label}`,
1313
+ children: [/* @__PURE__ */ jsx("span", {
1314
+ className: cn(OPT_TEXT_PRIMARY, "text-sm font-medium"),
1315
+ children: step.label
1316
+ }), step.description && /* @__PURE__ */ jsx("span", {
1317
+ className: cn("text-text-secondary", "mt-0.5 block text-xs"),
1318
+ children: step.description
1319
+ })]
1320
+ })
1321
+ }),
1322
+ /* @__PURE__ */ jsxs("div", {
1323
+ className: "flex flex-shrink-0 items-center gap-1",
1324
+ children: [
1325
+ /* @__PURE__ */ jsx("button", {
1326
+ type: "button",
1327
+ onClick: () => moveStep(index, -1),
1328
+ disabled: index === 0,
1329
+ className: cn("rounded p-1 text-sm", OPT_TEXT_TERTIARY, "hover:bg-bg-subtle", "disabled:opacity-30", OPT_FOCUS_VISIBLE),
1330
+ "aria-label": "Move up",
1331
+ children: "▲"
1332
+ }),
1333
+ /* @__PURE__ */ jsx("button", {
1334
+ type: "button",
1335
+ onClick: () => moveStep(index, 1),
1336
+ disabled: index === steps.length - 1,
1337
+ className: cn("rounded p-1 text-sm", OPT_TEXT_TERTIARY, "hover:bg-bg-subtle", "disabled:opacity-30", OPT_FOCUS_VISIBLE),
1338
+ "aria-label": "Move down",
1339
+ children: "▼"
1340
+ }),
1341
+ /* @__PURE__ */ jsx("button", {
1342
+ type: "button",
1343
+ onClick: () => removeStep(step.id),
1344
+ className: cn("text-danger-fg hover:bg-danger-subtle rounded p-1 text-sm", OPT_FOCUS_VISIBLE),
1345
+ "aria-label": `Remove ${step.label}`,
1346
+ children: "✕"
1347
+ })
1348
+ ]
1349
+ })
1350
+ ]
1351
+ }, step.id);
1352
+ }),
1353
+ /* @__PURE__ */ jsxs(Button, {
1354
+ variant: "secondary",
1355
+ size: "sm",
1356
+ onClick: addStep,
1357
+ disabled: atLimit,
1358
+ className: "self-start",
1359
+ children: ["+ ", addLabel]
1360
+ })
1361
+ ]
1362
+ });
1363
+ }
1364
+ StepBuilder.displayName = "StepBuilder";
1365
+ //#endregion
1366
+ //#region src/lib/formatters.ts
1367
+ const OPT_UI_DEFAULT_LOCALE = "ko-KR";
1368
+ const KST_OFFSET_MS = 324e5;
1369
+ function getKstDateParts(input) {
1370
+ const date = input instanceof Date ? input : new Date(input);
1371
+ if (Number.isNaN(date.getTime())) return null;
1372
+ const kstDate = new Date(date.getTime() + KST_OFFSET_MS);
1373
+ return {
1374
+ year: kstDate.getUTCFullYear(),
1375
+ month: kstDate.getUTCMonth() + 1,
1376
+ day: kstDate.getUTCDate(),
1377
+ hour: kstDate.getUTCHours(),
1378
+ minute: kstDate.getUTCMinutes(),
1379
+ second: kstDate.getUTCSeconds()
1380
+ };
1381
+ }
1382
+ function pad2(value) {
1383
+ return String(value).padStart(2, "0");
1384
+ }
1385
+ function meridiem(hour) {
1386
+ return hour < 12 ? "오전" : "오후";
1387
+ }
1388
+ function hour12(hour) {
1389
+ const normalized = hour % 12;
1390
+ return normalized === 0 ? 12 : normalized;
1391
+ }
1392
+ /** Formats a date as Korean short date text in the opt-ui default timezone. */
1393
+ function formatOptShortDate(input) {
1394
+ const parts = getKstDateParts(input);
1395
+ if (!parts) return "-";
1396
+ return `${parts.month}월 ${parts.day}일`;
1397
+ }
1398
+ /** Formats a date as Korean clock time in the opt-ui default timezone. */
1399
+ function formatOptTime(input) {
1400
+ const parts = getKstDateParts(input);
1401
+ if (!parts) return "-";
1402
+ return `${meridiem(parts.hour)} ${pad2(hour12(parts.hour))}:${pad2(parts.minute)}`;
1403
+ }
1404
+ /** Formats a date as Korean date-time text in the opt-ui default timezone. */
1405
+ function formatOptDateTime(input) {
1406
+ const parts = getKstDateParts(input);
1407
+ if (!parts) return "-";
1408
+ return `${parts.year}. ${parts.month}. ${parts.day}. ${meridiem(parts.hour)} ${hour12(parts.hour)}:${pad2(parts.minute)}:${pad2(parts.second)}`;
1409
+ }
1410
+ /** Formats numbers without depending on the runtime default locale. */
1411
+ function formatOptNumber(value) {
1412
+ return new Intl.NumberFormat(OPT_UI_DEFAULT_LOCALE).format(value);
1413
+ }
1414
+ //#endregion
1415
+ //#region src/core/condition-builder.tsx
1416
+ const OPERATOR_LABELS = {
1417
+ equals: "equals",
1418
+ not_equals: "does not equal",
1419
+ contains: "contains",
1420
+ not_contains: "does not contain",
1421
+ gt: ">",
1422
+ lt: "<",
1423
+ gte: ">=",
1424
+ lte: "<=",
1425
+ is_set: "is set",
1426
+ is_not_set: "is not set"
1427
+ };
1428
+ const OPERATORS = /* @__PURE__ */ new Set([
1429
+ "equals",
1430
+ "not_equals",
1431
+ "contains",
1432
+ "not_contains",
1433
+ "gt",
1434
+ "lt",
1435
+ "gte",
1436
+ "lte",
1437
+ "is_set",
1438
+ "is_not_set"
1439
+ ]);
1440
+ function isConditionOperator(v) {
1441
+ return OPERATORS.has(v);
1442
+ }
1443
+ const NO_VALUE_OPS = ["is_set", "is_not_set"];
1444
+ const DEFAULT_PROPERTIES = [
1445
+ "event_name",
1446
+ "user_id",
1447
+ "email",
1448
+ "country",
1449
+ "browser",
1450
+ "os",
1451
+ "device",
1452
+ "page_url",
1453
+ "referrer",
1454
+ "amount"
1455
+ ];
1456
+ const ConditionBuilderIdScopeContext = React.createContext(null);
1457
+ function collectConditionBuilderIds(groups) {
1458
+ const groupIds = /* @__PURE__ */ new Set();
1459
+ const conditionIds = /* @__PURE__ */ new Set();
1460
+ const visit = (items) => {
1461
+ for (const group of items) {
1462
+ groupIds.add(group.id);
1463
+ for (const condition of group.conditions) conditionIds.add(condition.id);
1464
+ if (group.children) visit(group.children);
1465
+ }
1466
+ };
1467
+ visit(groups);
1468
+ return {
1469
+ groupIds,
1470
+ conditionIds
1471
+ };
1472
+ }
1473
+ /** Renders the condition builder component. */
1474
+ function ConditionBuilder({ groups, onChange, properties = DEFAULT_PROPERTIES, label, addConditionLabel = "Add condition", addGroupLabel = "Add group", addNestedGroupLabel = "Add nested group", nested = false, maxDepth = 3, conditionTypes, onPreview, previewCount, className, "aria-label": ariaLabel }) {
1475
+ var _ref;
1476
+ const inheritedIdScope = React.useContext(ConditionBuilderIdScopeContext);
1477
+ const nextGroupIdRef = React.useRef(1);
1478
+ const nextConditionIdRef = React.useRef(1);
1479
+ const getLocalReservedIds = useCallback(() => collectConditionBuilderIds(groups), [groups]);
1480
+ const localIdScope = React.useMemo(() => ({
1481
+ nextGroupIdRef,
1482
+ nextConditionIdRef,
1483
+ getReservedIds: getLocalReservedIds
1484
+ }), [getLocalReservedIds]);
1485
+ const idScope = inheritedIdScope !== null && inheritedIdScope !== void 0 ? inheritedIdScope : localIdScope;
1486
+ const createGroupId = useCallback((suffix = "") => {
1487
+ const { groupIds } = idScope.getReservedIds();
1488
+ let id = "";
1489
+ do
1490
+ id = `group-${idScope.nextGroupIdRef.current++}${suffix}`;
1491
+ while (groupIds.has(id));
1492
+ return id;
1493
+ }, [idScope]);
1494
+ const createConditionId = useCallback((suffix = "") => {
1495
+ const { conditionIds } = idScope.getReservedIds();
1496
+ let id = "";
1497
+ do
1498
+ id = `cond-${idScope.nextConditionIdRef.current++}${suffix}`;
1499
+ while (conditionIds.has(id));
1500
+ return id;
1501
+ }, [idScope]);
1502
+ useEffect(() => {
1503
+ onPreview === null || onPreview === void 0 || onPreview(groups);
1504
+ }, [groups, onPreview]);
1505
+ const addGroup = useCallback(() => {
1506
+ var _properties$;
1507
+ const newGroup = {
1508
+ id: createGroupId(),
1509
+ logic: "and",
1510
+ conditions: [{
1511
+ id: createConditionId(),
1512
+ property: (_properties$ = properties[0]) !== null && _properties$ !== void 0 ? _properties$ : "",
1513
+ operator: "equals",
1514
+ value: ""
1515
+ }]
1516
+ };
1517
+ onChange([...groups, newGroup]);
1518
+ }, [
1519
+ groups,
1520
+ onChange,
1521
+ properties,
1522
+ createGroupId,
1523
+ createConditionId
1524
+ ]);
1525
+ const removeGroup = useCallback((groupId) => {
1526
+ onChange(groups.filter((g) => g.id !== groupId));
1527
+ }, [groups, onChange]);
1528
+ const updateGroupLogic = useCallback((groupId, logic) => {
1529
+ onChange(groups.map((g) => g.id === groupId ? _objectSpread2(_objectSpread2({}, g), {}, { logic }) : g));
1530
+ }, [groups, onChange]);
1531
+ const addCondition = useCallback((groupId) => {
1532
+ onChange(groups.map((g) => {
1533
+ var _properties$2;
1534
+ return g.id === groupId ? _objectSpread2(_objectSpread2({}, g), {}, { conditions: [...g.conditions, {
1535
+ id: createConditionId(),
1536
+ property: (_properties$2 = properties[0]) !== null && _properties$2 !== void 0 ? _properties$2 : "",
1537
+ operator: "equals",
1538
+ value: ""
1539
+ }] }) : g;
1540
+ }));
1541
+ }, [
1542
+ groups,
1543
+ onChange,
1544
+ properties,
1545
+ createConditionId
1546
+ ]);
1547
+ const removeCondition = useCallback((groupId, condId) => {
1548
+ onChange(groups.map((g) => g.id === groupId ? _objectSpread2(_objectSpread2({}, g), {}, { conditions: g.conditions.filter((c) => c.id !== condId) }) : g));
1549
+ }, [groups, onChange]);
1550
+ const updateCondition = useCallback((groupId, condId, updates) => {
1551
+ onChange(groups.map((g) => g.id === groupId ? _objectSpread2(_objectSpread2({}, g), {}, { conditions: g.conditions.map((c) => c.id === condId ? _objectSpread2(_objectSpread2({}, c), updates) : c) }) : g));
1552
+ }, [groups, onChange]);
1553
+ return /* @__PURE__ */ jsx(ConditionBuilderIdScopeContext.Provider, {
1554
+ value: idScope,
1555
+ children: /* @__PURE__ */ jsxs("div", {
1556
+ className: cn("flex flex-col gap-3", className),
1557
+ "aria-label": (_ref = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : label) !== null && _ref !== void 0 ? _ref : "Condition builder",
1558
+ "data-opt-id": "SXXKJ",
1559
+ "data-opt-slug": "condition-builder.ConditionBuilder",
1560
+ children: [
1561
+ label && /* @__PURE__ */ jsx("span", {
1562
+ className: "text-sm font-medium text-text-primary",
1563
+ children: label
1564
+ }),
1565
+ groups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { children: [groupIndex > 0 && /* @__PURE__ */ jsxs("div", {
1566
+ className: "my-2 flex items-center gap-2",
1567
+ children: [
1568
+ /* @__PURE__ */ jsx("div", { className: "bg-border-subtle h-px flex-1" }),
1569
+ /* @__PURE__ */ jsx("span", {
1570
+ className: "text-warning-fg text-xs font-semibold uppercase",
1571
+ children: "OR"
1572
+ }),
1573
+ /* @__PURE__ */ jsx("div", { className: "bg-border-subtle h-px flex-1" })
1574
+ ]
1575
+ }), /* @__PURE__ */ jsxs("div", {
1576
+ className: cn(OPT_SURFACE, OPT_BORDER, "p-3"),
1577
+ role: "group",
1578
+ "aria-label": `Condition group ${groupIndex + 1}`,
1579
+ children: [
1580
+ /* @__PURE__ */ jsxs("div", {
1581
+ className: "mb-2 flex items-center justify-between",
1582
+ children: [/* @__PURE__ */ jsxs("div", {
1583
+ className: "flex items-center gap-2",
1584
+ children: [
1585
+ /* @__PURE__ */ jsx("span", {
1586
+ className: cn(OPT_TEXT_SECONDARY, "text-xs font-medium"),
1587
+ children: "Match"
1588
+ }),
1589
+ /* @__PURE__ */ jsxs("select", {
1590
+ value: group.logic,
1591
+ onChange: (e) => {
1592
+ const v = e.target.value;
1593
+ if (v === "and" || v === "or") updateGroupLogic(group.id, v);
1594
+ },
1595
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset rounded border px-2 py-0.5 text-xs font-semibold outline-none focus-visible:ring-2 focus-visible:ring-offset-1", OPT_TEXT_PRIMARY, OPT_FOCUS_VISIBLE),
1596
+ "aria-label": "Group logic",
1597
+ children: [/* @__PURE__ */ jsx("option", {
1598
+ value: "and",
1599
+ children: "ALL"
1600
+ }), /* @__PURE__ */ jsx("option", {
1601
+ value: "or",
1602
+ children: "ANY"
1603
+ })]
1604
+ }),
1605
+ /* @__PURE__ */ jsx("span", {
1606
+ className: cn(OPT_TEXT_SECONDARY, "text-xs"),
1607
+ children: "of the following"
1608
+ })
1609
+ ]
1610
+ }), groups.length > 1 && /* @__PURE__ */ jsx("button", {
1611
+ type: "button",
1612
+ onClick: () => removeGroup(group.id),
1613
+ className: cn("text-danger-fg hover:bg-danger-subtle rounded p-1 text-xs", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
1614
+ "aria-label": "Remove group",
1615
+ children: "✕"
1616
+ })]
1617
+ }),
1618
+ /* @__PURE__ */ jsx("div", {
1619
+ className: "flex flex-col gap-2",
1620
+ children: group.conditions.map((cond, condIndex) => {
1621
+ const customRenderer = cond.type != null ? conditionTypes === null || conditionTypes === void 0 ? void 0 : conditionTypes[cond.type] : void 0;
1622
+ return /* @__PURE__ */ jsxs("div", {
1623
+ className: "flex items-center gap-2",
1624
+ children: [
1625
+ condIndex > 0 && /* @__PURE__ */ jsx("span", {
1626
+ className: cn("w-8 text-center text-[10px] font-semibold uppercase", group.logic === "and" ? "text-info-fg" : "text-warning-fg"),
1627
+ children: group.logic
1628
+ }),
1629
+ condIndex === 0 && /* @__PURE__ */ jsx("span", { className: "w-8" }),
1630
+ customRenderer ? /* @__PURE__ */ jsx("div", {
1631
+ className: "min-w-[100px] flex-1",
1632
+ role: "group",
1633
+ "aria-label": customRenderer.label,
1634
+ children: customRenderer.render(cond, (updates) => updateCondition(group.id, cond.id, updates))
1635
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1636
+ /* @__PURE__ */ jsx("select", {
1637
+ value: cond.property,
1638
+ onChange: (e) => updateCondition(group.id, cond.id, { property: e.target.value }),
1639
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset min-w-[120px] rounded border px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-1", OPT_TEXT_PRIMARY, OPT_FOCUS_VISIBLE),
1640
+ "aria-label": "Property",
1641
+ children: properties.map((p) => /* @__PURE__ */ jsx("option", {
1642
+ value: p,
1643
+ children: p
1644
+ }, p))
1645
+ }),
1646
+ /* @__PURE__ */ jsx("select", {
1647
+ value: cond.operator,
1648
+ onChange: (e) => {
1649
+ const v = e.target.value;
1650
+ if (isConditionOperator(v)) updateCondition(group.id, cond.id, { operator: v });
1651
+ },
1652
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset min-w-[130px] rounded border px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-1", OPT_TEXT_PRIMARY, OPT_FOCUS_VISIBLE),
1653
+ "aria-label": "Operator",
1654
+ children: Object.entries(OPERATOR_LABELS).map(([key, lbl]) => /* @__PURE__ */ jsx("option", {
1655
+ value: key,
1656
+ children: lbl
1657
+ }, key))
1658
+ }),
1659
+ !NO_VALUE_OPS.includes(cond.operator) && /* @__PURE__ */ jsx("input", {
1660
+ type: "text",
1661
+ value: cond.value,
1662
+ onChange: (e) => updateCondition(group.id, cond.id, { value: e.target.value }),
1663
+ placeholder: "Value",
1664
+ className: cn("border-border bg-surface focus-visible:ring-ring focus-visible:ring-offset-ring-offset min-w-[100px] flex-1 rounded border px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-1", "text-text-primary", "placeholder:text-text-tertiary", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
1665
+ "aria-label": "Value"
1666
+ })
1667
+ ] }),
1668
+ group.conditions.length > 1 && /* @__PURE__ */ jsx("button", {
1669
+ type: "button",
1670
+ onClick: () => removeCondition(group.id, cond.id),
1671
+ className: cn("text-danger-fg hover:bg-danger-subtle rounded p-1 text-sm", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
1672
+ "aria-label": "Remove condition",
1673
+ children: "✕"
1674
+ })
1675
+ ]
1676
+ }, cond.id);
1677
+ })
1678
+ }),
1679
+ nested && group.children && group.children.length > 0 && /* @__PURE__ */ jsx("div", {
1680
+ className: "border-accent/30 mt-2 ml-4 border-l-2 pl-3",
1681
+ children: /* @__PURE__ */ jsx(ConditionBuilder, {
1682
+ groups: group.children,
1683
+ onChange: (newChildren) => {
1684
+ onChange(groups.map((g) => g.id === group.id ? _objectSpread2(_objectSpread2({}, g), {}, { children: newChildren }) : g));
1685
+ },
1686
+ properties,
1687
+ addConditionLabel,
1688
+ addGroupLabel,
1689
+ addNestedGroupLabel,
1690
+ nested,
1691
+ maxDepth: maxDepth - 1,
1692
+ conditionTypes
1693
+ })
1694
+ }),
1695
+ /* @__PURE__ */ jsxs("div", {
1696
+ className: "mt-2 flex items-center gap-2",
1697
+ children: [/* @__PURE__ */ jsxs(Button, {
1698
+ variant: "ghost",
1699
+ size: "sm",
1700
+ onClick: () => addCondition(group.id),
1701
+ children: ["+ ", addConditionLabel]
1702
+ }), nested && maxDepth > 1 && /* @__PURE__ */ jsxs(Button, {
1703
+ variant: "ghost",
1704
+ size: "sm",
1705
+ onClick: () => {
1706
+ var _properties$3;
1707
+ const nestedGroup = {
1708
+ id: createGroupId("-nested"),
1709
+ logic: group.logic === "and" ? "or" : "and",
1710
+ conditions: [{
1711
+ id: createConditionId("-nested"),
1712
+ property: (_properties$3 = properties[0]) !== null && _properties$3 !== void 0 ? _properties$3 : "",
1713
+ operator: "equals",
1714
+ value: ""
1715
+ }]
1716
+ };
1717
+ onChange(groups.map((g) => {
1718
+ var _g$children;
1719
+ return g.id === group.id ? _objectSpread2(_objectSpread2({}, g), {}, { children: [...(_g$children = g.children) !== null && _g$children !== void 0 ? _g$children : [], nestedGroup] }) : g;
1720
+ }));
1721
+ },
1722
+ children: ["+ ", addNestedGroupLabel]
1723
+ })]
1724
+ })
1725
+ ]
1726
+ })] }, group.id)),
1727
+ /* @__PURE__ */ jsxs("div", {
1728
+ className: "flex items-center gap-3",
1729
+ children: [/* @__PURE__ */ jsxs(Button, {
1730
+ variant: "secondary",
1731
+ size: "sm",
1732
+ onClick: addGroup,
1733
+ className: "self-start",
1734
+ children: ["+ ", addGroupLabel]
1735
+ }), previewCount != null && /* @__PURE__ */ jsxs("span", {
1736
+ className: cn("text-text-secondary", "text-sm"),
1737
+ children: [formatOptNumber(previewCount), " matches"]
1738
+ })]
1739
+ })
1740
+ ]
1741
+ })
1742
+ });
1743
+ }
1744
+ ConditionBuilder.displayName = "ConditionBuilder";
1745
+ //#endregion
1746
+ //#region src/core/dropdown-menu.tsx
1747
+ const _excluded$6 = ["className"];
1748
+ const _excluded2$2 = ["className"];
1749
+ const _excluded3$1 = ["className", "variant"];
1750
+ const _excluded4 = ["className"];
1751
+ const DropdownMenu = MenuRoot$1;
1752
+ /** Renders the dropdown trigger component. */
1753
+ function DropdownTrigger(_ref) {
1754
+ let { className } = _ref, props = _objectWithoutProperties(_ref, _excluded$6);
1755
+ return /* @__PURE__ */ jsx(MenuTrigger$1, _objectSpread2(_objectSpread2({ className: cn("cursor-pointer gap-2", className) }, props), {}, {
1756
+ "data-opt-id": "C4BSS",
1757
+ "data-opt-slug": "dropdown-menu.DropdownTrigger"
1758
+ }));
1759
+ }
1760
+ /** Renders the dropdown content component. */
1761
+ function DropdownContent(_ref2) {
1762
+ let { className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$2);
1763
+ return /* @__PURE__ */ jsx(MenuPopover$1, _objectSpread2(_objectSpread2({ className: cn(className) }, props), {}, {
1764
+ "data-opt-id": "J6NF7",
1765
+ "data-opt-slug": "dropdown-menu.DropdownContent"
1766
+ }));
1767
+ }
1768
+ /** Renders the dropdown item component. */
1769
+ function DropdownItem(_ref3) {
1770
+ let { className, variant = "default" } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3$1);
1771
+ return /* @__PURE__ */ jsx(MenuItem$1, _objectSpread2(_objectSpread2({ className: cn("cursor-pointer data-[disabled]:cursor-not-allowed", variant === "destructive" && "text-danger-fg data-[active-item]:bg-danger-subtle data-[active-item]:text-danger-fg", className) }, props), {}, {
1772
+ "data-opt-id": "5M6GA",
1773
+ "data-opt-slug": "dropdown-menu.DropdownItem"
1774
+ }));
1775
+ }
1776
+ /** Renders the dropdown separator component. */
1777
+ function DropdownSeparator(_ref4) {
1778
+ let { className } = _ref4, props = _objectWithoutProperties(_ref4, _excluded4);
1779
+ return /* @__PURE__ */ jsx(MenuSeparator$1, _objectSpread2(_objectSpread2({ className: cn(className) }, props), {}, {
1780
+ "data-opt-id": "K9Y54",
1781
+ "data-opt-slug": "dropdown-menu.DropdownSeparator"
1782
+ }));
1783
+ }
1784
+ //#endregion
1785
+ //#region src/core/radio-group.tsx
1786
+ const _excluded$5 = [
1787
+ "label",
1788
+ "hint",
1789
+ "description",
1790
+ "value",
1791
+ "name",
1792
+ "size",
1793
+ "disabled",
1794
+ "className"
1795
+ ];
1796
+ const sizeStyles = {
1797
+ sm: {
1798
+ radio: "h-4 w-4",
1799
+ dot: "h-2 w-2",
1800
+ text: "text-sm"
1801
+ },
1802
+ md: {
1803
+ radio: "h-5 w-5",
1804
+ dot: "h-2.5 w-2.5",
1805
+ text: "text-sm"
1806
+ },
1807
+ lg: {
1808
+ radio: "h-6 w-6",
1809
+ dot: "h-3 w-3",
1810
+ text: "text-base"
1811
+ }
1812
+ };
1813
+ const RadioGroupContext = createContext({ size: "md" });
1814
+ /** Renders the radio group component. */
1815
+ function RadioGroup({ children, value, defaultValue = "", onChange, disabled, size = "md", error, required, className, orientation = "vertical", label, name }) {
1816
+ const ctxValue = useMemo(() => ({
1817
+ size,
1818
+ disabled
1819
+ }), [size, disabled]);
1820
+ const hasError = Boolean(error);
1821
+ const fallbackGroupId = useId().replace(/:/g, "");
1822
+ const groupId = label ? `radio-group-${label.replace(/\s+/g, "-").toLowerCase()}` : `radio-group-${fallbackGroupId}`;
1823
+ const labelId = label ? `${groupId}-label` : void 0;
1824
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, {
1825
+ value: ctxValue,
1826
+ children: /* @__PURE__ */ jsxs("div", {
1827
+ className: cn("flex flex-col gap-1.5", className),
1828
+ "data-opt-id": "2F20H",
1829
+ "data-opt-slug": "radio-group.RadioGroup",
1830
+ children: [/* @__PURE__ */ jsxs(RadioGroupRoot, {
1831
+ value,
1832
+ defaultValue,
1833
+ onChange,
1834
+ disabled,
1835
+ orientation,
1836
+ name,
1837
+ id: groupId,
1838
+ "aria-label": label ? void 0 : "Radio group",
1839
+ "aria-labelledby": labelId,
1840
+ "aria-required": required,
1841
+ "aria-invalid": hasError,
1842
+ "aria-describedby": getDescribedBy(groupId, error, void 0),
1843
+ className: cn("flex", orientation === "vertical" ? "flex-col gap-3" : "flex-row gap-6"),
1844
+ children: [label && /* @__PURE__ */ jsxs("div", {
1845
+ id: labelId,
1846
+ className: "text-text-secondary mb-1 text-sm font-medium",
1847
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
1848
+ }), children]
1849
+ }), /* @__PURE__ */ jsx(FieldMessages, {
1850
+ id: groupId,
1851
+ error
1852
+ })]
1853
+ })
1854
+ });
1855
+ }
1856
+ const Radio = forwardRef((_ref, ref) => {
1857
+ var _ref2;
1858
+ let { label, hint, description, value, name: _name, size: sizeProp, disabled: disabledProp, className } = _ref, props = _objectWithoutProperties(_ref, _excluded$5);
1859
+ const groupContext = useContext(RadioGroupContext);
1860
+ const primContext = useRadioGroupContext();
1861
+ const size = sizeProp !== null && sizeProp !== void 0 ? sizeProp : groupContext.size;
1862
+ const disabled = (_ref2 = disabledProp !== null && disabledProp !== void 0 ? disabledProp : groupContext.disabled) !== null && _ref2 !== void 0 ? _ref2 : primContext === null || primContext === void 0 ? void 0 : primContext.disabled;
1863
+ const hintText = hint !== null && hint !== void 0 ? hint : description;
1864
+ const styles = sizeStyles[size];
1865
+ const isChecked = (primContext === null || primContext === void 0 ? void 0 : primContext.value) === value;
1866
+ const radioId = `radio-${useId().replace(/:/g, "")}`;
1867
+ const labelId = label ? `${radioId}-label` : void 0;
1868
+ const hintId = hintText ? `${radioId}-hint` : void 0;
1869
+ const handleChange = useCallback(() => {
1870
+ if (!disabled && value != null) primContext === null || primContext === void 0 || primContext.setValue(value);
1871
+ }, [
1872
+ disabled,
1873
+ value,
1874
+ primContext
1875
+ ]);
1876
+ return /* @__PURE__ */ jsxs("label", _objectSpread2(_objectSpread2({
1877
+ ref,
1878
+ "aria-checked": isChecked,
1879
+ className: cn("group flex items-start gap-3", disabled && "cursor-not-allowed opacity-50", !disabled && "cursor-pointer", className)
1880
+ }, props), {}, {
1881
+ "data-opt-id": "HY7YG",
1882
+ "data-opt-slug": "radio-group.Radio",
1883
+ children: [
1884
+ /* @__PURE__ */ jsx("input", {
1885
+ id: radioId,
1886
+ type: "radio",
1887
+ name: primContext === null || primContext === void 0 ? void 0 : primContext.name,
1888
+ value,
1889
+ checked: isChecked,
1890
+ disabled,
1891
+ onChange: handleChange,
1892
+ "aria-label": label ? void 0 : value !== null && value !== void 0 ? value : "Radio option",
1893
+ "aria-labelledby": labelId,
1894
+ "aria-describedby": hintId,
1895
+ className: "sr-only"
1896
+ }),
1897
+ /* @__PURE__ */ jsx("span", {
1898
+ className: cn("relative mt-0.5 inline-flex shrink-0 items-center justify-center rounded-full", styles.radio, "border-border border-2", "bg-surface", "group-aria-checked:border-accent", "transition-colors", "group-has-[:focus-visible]:outline-ring outline-transparent group-has-[:focus-visible]:outline-2 group-has-[:focus-visible]:-outline-offset-1"),
1899
+ "aria-hidden": "true",
1900
+ children: /* @__PURE__ */ jsx("span", { className: cn(styles.dot, "bg-accent rounded-full", "scale-0 opacity-0 transition-all", "group-aria-checked:scale-100 group-aria-checked:opacity-100") })
1901
+ }),
1902
+ (label || hintText) && /* @__PURE__ */ jsxs("div", {
1903
+ className: "flex flex-col",
1904
+ children: [label && /* @__PURE__ */ jsx("span", {
1905
+ id: labelId,
1906
+ className: cn("text-text-primary font-medium", styles.text),
1907
+ children: label
1908
+ }), hintText && /* @__PURE__ */ jsx("span", {
1909
+ id: hintId,
1910
+ className: "text-text-tertiary text-sm",
1911
+ children: hintText
1912
+ })]
1913
+ })
1914
+ ]
1915
+ }));
1916
+ });
1917
+ Radio.displayName = "Radio";
1918
+ //#endregion
1919
+ //#region src/core/loading-overlay.tsx
1920
+ const _excluded$4 = [
1921
+ "loading",
1922
+ "children",
1923
+ "label",
1924
+ "className"
1925
+ ];
1926
+ /** Renders the loading overlay component. */
1927
+ function LoadingOverlay(_ref) {
1928
+ let { loading, children, label = "Loading…", className } = _ref, props = _objectWithoutProperties(_ref, _excluded$4);
1929
+ return /* @__PURE__ */ jsxs("div", _objectSpread2(_objectSpread2({
1930
+ "data-opt-id": "380ZR",
1931
+ "data-opt-slug": "loading-overlay.LoadingOverlay"
1932
+ }, props), {}, {
1933
+ className: cn("relative", className),
1934
+ children: [children, loading && /* @__PURE__ */ jsx("div", {
1935
+ className: "bg-surface/60 absolute inset-0 z-10 flex items-center justify-center rounded-lg backdrop-blur-[1px]",
1936
+ role: "status",
1937
+ "aria-live": "polite",
1938
+ "aria-label": label,
1939
+ children: /* @__PURE__ */ jsxs("div", {
1940
+ className: "flex flex-col items-center gap-2",
1941
+ children: [/* @__PURE__ */ jsx(Spinner, {
1942
+ size: "md",
1943
+ "aria-hidden": "true"
1944
+ }), /* @__PURE__ */ jsx("span", {
1945
+ className: "text-text-tertiary text-sm",
1946
+ children: label
1947
+ })]
1948
+ })
1949
+ })]
1950
+ }));
1951
+ }
1952
+ LoadingOverlay.displayName = "LoadingOverlay";
1953
+ //#endregion
1954
+ //#region src/core/block-layout.tsx
1955
+ const _excluded$3 = [
1956
+ "children",
1957
+ "className",
1958
+ "spacing",
1959
+ "inset",
1960
+ "loading",
1961
+ "loadingLabel"
1962
+ ];
1963
+ const spacingClass = {
1964
+ section: "gap-section",
1965
+ group: "gap-group",
1966
+ element: "gap-element",
1967
+ none: "gap-0"
1968
+ };
1969
+ const insetClass = {
1970
+ section: "p-section",
1971
+ group: "p-group",
1972
+ element: "p-element",
1973
+ none: "p-0"
1974
+ };
1975
+ /** Renders the block layout component. */
1976
+ function BlockLayout(_ref) {
1977
+ let { children, className, spacing = "section", inset, loading, loadingLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$3);
1978
+ const layoutClassName = cn("flex flex-col", spacingClass[spacing], inset ? insetClass[inset] : void 0, className);
1979
+ if (loading) return /* @__PURE__ */ jsx(LoadingOverlay, _objectSpread2(_objectSpread2({
1980
+ "data-opt-id": "9D8QR",
1981
+ "data-opt-slug": "block-layout.BlockLayout"
1982
+ }, props), {}, {
1983
+ className: layoutClassName,
1984
+ loading,
1985
+ label: loadingLabel,
1986
+ children
1987
+ }));
1988
+ return /* @__PURE__ */ jsx("div", _objectSpread2(_objectSpread2({
1989
+ "data-opt-id": "9D8QR",
1990
+ "data-opt-slug": "block-layout.BlockLayout"
1991
+ }, props), {}, {
1992
+ className: layoutClassName,
1993
+ children
1994
+ }));
1995
+ }
1996
+ BlockLayout.displayName = "BlockLayout";
1997
+ //#endregion
1998
+ //#region src/core/create-block.tsx
1999
+ const _excluded$2 = ["loading", "className"];
2000
+ /**
2001
+ * Block 팩토리. BlockLayout 래핑을 구조적으로 강제합니다.
2002
+ *
2003
+ * @param name - displayName으로 사용할 컴포넌트 이름
2004
+ * @param render - 나머지 props를 받아 콘텐츠를 반환하는 렌더 함수
2005
+ * @param options - layoutClassName: BlockLayout에 전달할 추가 className
2006
+ */
2007
+ function createBlock(name, render, options) {
2008
+ const Component = (props) => {
2009
+ const { loading, className } = props, rest = _objectWithoutProperties(props, _excluded$2);
2010
+ const layoutCn = (options === null || options === void 0 ? void 0 : options.layoutClassName) ? `${options.layoutClassName}${className ? ` ${className}` : ""}` : className;
2011
+ return /* @__PURE__ */ jsx(BlockLayout, {
2012
+ loading,
2013
+ className: layoutCn,
2014
+ children: render(rest)
2015
+ });
2016
+ };
2017
+ Component.displayName = name;
2018
+ return Component;
2019
+ }
2020
+ //#endregion
2021
+ //#region src/core/page-header.tsx
2022
+ /** Renders the page header component. */
2023
+ function PageHeader({ title, description, actions, className }) {
2024
+ return /* @__PURE__ */ jsxs("div", {
2025
+ className: cn("gap-group flex flex-col items-stretch sm:flex-row sm:items-center sm:justify-between", className),
2026
+ "data-opt-id": "7AK8Z",
2027
+ "data-opt-slug": "page-header.PageHeader",
2028
+ children: [/* @__PURE__ */ jsxs("div", {
2029
+ className: "gap-element flex min-w-0 flex-col",
2030
+ children: [/* @__PURE__ */ jsx("h2", {
2031
+ className: "text-text-primary text-lg font-semibold",
2032
+ children: title
2033
+ }), description && /* @__PURE__ */ jsx("p", {
2034
+ className: "text-text-tertiary text-sm",
2035
+ children: description
2036
+ })]
2037
+ }), actions && /* @__PURE__ */ jsx("div", {
2038
+ className: "gap-element flex w-full shrink-0 items-center sm:w-auto",
2039
+ children: actions
2040
+ })]
2041
+ });
2042
+ }
2043
+ PageHeader.displayName = "PageHeader";
2044
+ //#endregion
2045
+ //#region src/core/scroll-area.tsx
2046
+ const overflowMap = {
2047
+ vertical: "overflow-y-auto overflow-x-hidden",
2048
+ horizontal: "overflow-x-auto overflow-y-hidden",
2049
+ both: "overflow-auto"
2050
+ };
2051
+ /** Renders the scroll area component. */
2052
+ function ScrollArea({ children, className, orientation = "vertical" }) {
2053
+ return /* @__PURE__ */ jsx("div", {
2054
+ className: cn(overflowMap[orientation], "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-border hover:scrollbar-thumb-border-hover", className),
2055
+ "data-opt-id": "A9X4K",
2056
+ "data-opt-slug": "scroll-area.ScrollArea",
2057
+ children
2058
+ });
2059
+ }
2060
+ ScrollArea.displayName = "ScrollArea";
2061
+ //#endregion
2062
+ //#region src/core/container.tsx
2063
+ const maxWidthMap = {
2064
+ sm: "max-w-screen-sm",
2065
+ md: "max-w-screen-md",
2066
+ lg: "max-w-screen-lg",
2067
+ xl: "max-w-screen-xl",
2068
+ "2xl": "max-w-screen-2xl",
2069
+ full: "max-w-full"
2070
+ };
2071
+ /** Renders the container component. */
2072
+ function Container({ children, maxWidth = "xl", center = true, className }) {
2073
+ return /* @__PURE__ */ jsx("div", {
2074
+ className: cn(maxWidthMap[maxWidth], center && "mx-auto", "px-4", className),
2075
+ "data-opt-id": "H3G96",
2076
+ "data-opt-slug": "container.Container",
2077
+ children
2078
+ });
2079
+ }
2080
+ Container.displayName = "Container";
2081
+ //#endregion
2082
+ //#region src/core/section.tsx
2083
+ /** Renders the section component. */
2084
+ function Section({ children, title, description, className }) {
2085
+ const titleId = `${useId()}-title`;
2086
+ return /* @__PURE__ */ jsxs("section", {
2087
+ className: cn("gap-group flex flex-col", className),
2088
+ "aria-labelledby": title ? titleId : void 0,
2089
+ "data-opt-id": "M3QXW",
2090
+ "data-opt-slug": "section.Section",
2091
+ children: [(title || description) && /* @__PURE__ */ jsxs("div", {
2092
+ className: "gap-element flex flex-col",
2093
+ children: [title && /* @__PURE__ */ jsx("h3", {
2094
+ id: titleId,
2095
+ className: "text-text-primary text-base font-semibold",
2096
+ children: title
2097
+ }), description && /* @__PURE__ */ jsx("p", {
2098
+ className: "text-text-tertiary text-sm",
2099
+ children: description
2100
+ })]
2101
+ }), children]
2102
+ });
2103
+ }
2104
+ Section.displayName = "Section";
2105
+ //#endregion
2106
+ //#region src/core/resizable.tsx
2107
+ const _excluded$1 = [
2108
+ "direction",
2109
+ "children",
2110
+ "className",
2111
+ "id",
2112
+ "autoSaveId"
2113
+ ];
2114
+ const _excluded2$1 = [
2115
+ "collapsedSize",
2116
+ "defaultSize",
2117
+ "minSize",
2118
+ "maxSize",
2119
+ "children",
2120
+ "className"
2121
+ ];
2122
+ const _excluded3 = ["className", "withHandle"];
2123
+ function toPanelSize(size) {
2124
+ return typeof size === "number" ? `${size}%` : size;
2125
+ }
2126
+ /** Renders the resizable panel group component. */
2127
+ function ResizablePanelGroup(_ref) {
2128
+ let { direction, children, className, id, autoSaveId } = _ref, props = _objectWithoutProperties(_ref, _excluded$1);
2129
+ return /* @__PURE__ */ jsx(Group, _objectSpread2(_objectSpread2({
2130
+ orientation: direction,
2131
+ id: id !== null && id !== void 0 ? id : autoSaveId,
2132
+ className: cn("flex h-full w-full", className)
2133
+ }, props), {}, {
2134
+ "data-opt-id": "BM5RR",
2135
+ "data-opt-slug": "resizable.ResizablePanelGroup",
2136
+ children
2137
+ }));
2138
+ }
2139
+ ResizablePanelGroup.displayName = "ResizablePanelGroup";
2140
+ /** Renders the resizable panel component. */
2141
+ function ResizablePanel(_ref2) {
2142
+ let { collapsedSize, defaultSize, minSize, maxSize, children, className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2$1);
2143
+ return /* @__PURE__ */ jsx(Panel, _objectSpread2(_objectSpread2({
2144
+ collapsedSize: toPanelSize(collapsedSize),
2145
+ defaultSize: toPanelSize(defaultSize),
2146
+ minSize: toPanelSize(minSize),
2147
+ maxSize: toPanelSize(maxSize),
2148
+ className: cn("overflow-auto", className)
2149
+ }, props), {}, {
2150
+ "data-opt-id": "88X4V",
2151
+ "data-opt-slug": "resizable.ResizablePanel",
2152
+ children
2153
+ }));
2154
+ }
2155
+ ResizablePanel.displayName = "ResizablePanel";
2156
+ /** Renders the resizable handle component. */
2157
+ function ResizableHandle(_ref3) {
2158
+ let { className, withHandle = false } = _ref3, props = _objectWithoutProperties(_ref3, _excluded3);
2159
+ return /* @__PURE__ */ jsx(Separator$1, _objectSpread2(_objectSpread2({ className: cn("bg-border relative flex items-center justify-center", "after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2", "data-[resize-handle-active]:bg-accent", "[&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full", "[&[data-orientation=horizontal]]:h-full [&[data-orientation=horizontal]]:w-px", className) }, props), {}, {
2160
+ "data-opt-id": "6Z22W",
2161
+ "data-opt-slug": "resizable.ResizableHandle",
2162
+ children: withHandle && /* @__PURE__ */ jsx("div", {
2163
+ className: "border-border bg-surface z-10 flex h-4 w-3 items-center justify-center rounded-sm border",
2164
+ children: /* @__PURE__ */ jsx(GripVertical, {
2165
+ "aria-hidden": "true",
2166
+ className: "text-text-tertiary h-3 w-3"
2167
+ })
2168
+ })
2169
+ }));
2170
+ }
2171
+ ResizableHandle.displayName = "ResizableHandle";
2172
+ //#endregion
2173
+ //#region src/core/logo.tsx
2174
+ const markSizeStyles = {
2175
+ sm: "h-6 w-6",
2176
+ md: "h-8 w-8",
2177
+ lg: "h-10 w-10"
2178
+ };
2179
+ const textSizeStyles = {
2180
+ sm: "text-sm",
2181
+ md: "text-base",
2182
+ lg: "text-lg"
2183
+ };
2184
+ /** Renders the logo component. */
2185
+ function Logo({ src, icon, text, alt = "Logo", size = "md", href, onClick, className }) {
2186
+ const [imageError, setImageError] = useState(false);
2187
+ const showImage = src && !imageError;
2188
+ const fallbackLetter = text ? text.charAt(0).toUpperCase() : null;
2189
+ let mark = null;
2190
+ if (showImage) mark = /* @__PURE__ */ jsx("img", {
2191
+ src,
2192
+ alt,
2193
+ className: cn("object-contain", markSizeStyles[size]),
2194
+ onError: () => setImageError(true)
2195
+ });
2196
+ else if (icon) mark = /* @__PURE__ */ jsx("span", {
2197
+ className: cn("flex shrink-0 items-center justify-center", markSizeStyles[size]),
2198
+ "aria-hidden": "true",
2199
+ children: icon
2200
+ });
2201
+ else if (fallbackLetter) mark = /* @__PURE__ */ jsx("span", {
2202
+ className: cn("bg-accent text-on-accent flex shrink-0 items-center justify-center rounded-lg font-bold", markSizeStyles[size], textSizeStyles[size]),
2203
+ "aria-hidden": "true",
2204
+ children: fallbackLetter
2205
+ });
2206
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [mark, text && /* @__PURE__ */ jsx("span", {
2207
+ className: cn("text-text-primary font-semibold", textSizeStyles[size]),
2208
+ children: text
2209
+ })] });
2210
+ const sharedClassName = cn("inline-flex items-center gap-element", className);
2211
+ if (href) return /* @__PURE__ */ jsx("a", {
2212
+ href,
2213
+ onClick,
2214
+ className: cn(sharedClassName, "no-underline"),
2215
+ "aria-label": text !== null && text !== void 0 ? text : alt,
2216
+ "data-opt-id": "R906A",
2217
+ "data-opt-slug": "logo.Logo",
2218
+ children: content
2219
+ });
2220
+ if (onClick) return /* @__PURE__ */ jsx("button", {
2221
+ type: "button",
2222
+ onClick,
2223
+ className: sharedClassName,
2224
+ "aria-label": text !== null && text !== void 0 ? text : alt,
2225
+ "data-opt-id": "R906A",
2226
+ "data-opt-slug": "logo.Logo",
2227
+ children: content
2228
+ });
2229
+ return /* @__PURE__ */ jsx("div", {
2230
+ className: sharedClassName,
2231
+ "data-opt-id": "R906A",
2232
+ "data-opt-slug": "logo.Logo",
2233
+ children: content
2234
+ });
2235
+ }
2236
+ //#endregion
2237
+ //#region src/core/drawer.tsx
2238
+ const _excluded = [
2239
+ "className",
2240
+ "side",
2241
+ "size",
2242
+ "swipeToDismiss",
2243
+ "swipeDismissThreshold",
2244
+ "style",
2245
+ "onPointerDown"
2246
+ ];
2247
+ const _excluded2 = ["className"];
2248
+ const sizeMap = {
2249
+ sm: "max-w-xs",
2250
+ md: "max-w-md",
2251
+ lg: "max-w-xl",
2252
+ full: "max-w-full"
2253
+ };
2254
+ const heightMap = {
2255
+ sm: "max-h-[320px]",
2256
+ md: "max-h-[480px]",
2257
+ lg: "max-h-[640px]",
2258
+ full: "max-h-full"
2259
+ };
2260
+ const positionClass = {
2261
+ right: "fixed inset-y-0 right-0",
2262
+ left: "fixed inset-y-0 left-0",
2263
+ bottom: "fixed inset-x-0 bottom-0"
2264
+ };
2265
+ /** Renders the drawer root component. */
2266
+ function DrawerRoot(props) {
2267
+ return /* @__PURE__ */ jsx(DialogRoot, _objectSpread2(_objectSpread2({ animated: true }, props), {}, {
2268
+ "data-opt-id": "YGJ0M",
2269
+ "data-opt-slug": "drawer.DrawerRoot"
2270
+ }));
2271
+ }
2272
+ /** Maps a drag delta to the dismiss offset for a given side (0 = no progress). */
2273
+ function dismissOffset(side, dx, dy) {
2274
+ switch (side) {
2275
+ case "right": return Math.max(0, dx);
2276
+ case "left": return Math.min(0, dx);
2277
+ case "bottom": return Math.max(0, dy);
2278
+ default: return 0;
2279
+ }
2280
+ }
2281
+ /** Renders the drawer panel component. */
2282
+ function DrawerPanel(_ref) {
2283
+ let { className, side = "right", size = "md", swipeToDismiss = true, swipeDismissThreshold = 80, style, onPointerDown } = _ref, props = _objectWithoutProperties(_ref, _excluded);
2284
+ const isHorizontal = side === "left" || side === "right";
2285
+ const close = useDialogClose();
2286
+ const dragStart = useRef(null);
2287
+ const [offset, setOffset] = useState(0);
2288
+ const [dragging, setDragging] = useState(false);
2289
+ const axis = side === "bottom" ? "Y" : "X";
2290
+ const DRAG_ACTIVATE = 6;
2291
+ const handlePointerDown = useCallback((e) => {
2292
+ onPointerDown === null || onPointerDown === void 0 || onPointerDown(e);
2293
+ if (!swipeToDismiss || e.button !== 0) return;
2294
+ dragStart.current = {
2295
+ x: e.clientX,
2296
+ y: e.clientY
2297
+ };
2298
+ }, [swipeToDismiss, onPointerDown]);
2299
+ const handlePointerMove = useCallback((e) => {
2300
+ const start = dragStart.current;
2301
+ if (!start) return;
2302
+ const next = dismissOffset(side, e.clientX - start.x, e.clientY - start.y);
2303
+ if (!dragging) {
2304
+ if (Math.abs(next) < DRAG_ACTIVATE) return;
2305
+ setDragging(true);
2306
+ try {
2307
+ e.currentTarget.setPointerCapture(e.pointerId);
2308
+ } catch (_unused) {}
2309
+ }
2310
+ setOffset(next);
2311
+ }, [side, dragging]);
2312
+ const endDrag = useCallback((e) => {
2313
+ const wasDragging = dragging;
2314
+ dragStart.current = null;
2315
+ setDragging(false);
2316
+ try {
2317
+ e.currentTarget.releasePointerCapture(e.pointerId);
2318
+ } catch (_unused2) {}
2319
+ if (wasDragging && Math.abs(offset) >= swipeDismissThreshold) close();
2320
+ setOffset(0);
2321
+ }, [
2322
+ dragging,
2323
+ offset,
2324
+ swipeDismissThreshold,
2325
+ close
2326
+ ]);
2327
+ const [keyboardInset, setKeyboardInset] = useState(0);
2328
+ useEffect(() => {
2329
+ if (side !== "bottom" || typeof window === "undefined") return;
2330
+ const vv = window.visualViewport;
2331
+ if (!vv) return;
2332
+ const onResize = () => {
2333
+ const inset = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
2334
+ setKeyboardInset(inset);
2335
+ };
2336
+ onResize();
2337
+ vv.addEventListener("resize", onResize);
2338
+ vv.addEventListener("scroll", onResize);
2339
+ return () => {
2340
+ vv.removeEventListener("resize", onResize);
2341
+ vv.removeEventListener("scroll", onResize);
2342
+ };
2343
+ }, [side]);
2344
+ const dragStyle = offset !== 0 ? { transform: `translate${axis}(${offset}px)` } : void 0;
2345
+ return /* @__PURE__ */ jsx(DialogPanel, _objectSpread2(_objectSpread2({
2346
+ className: cn("border-border bg-surface z-50 m-0 flex flex-col border p-0 shadow-2xl", "data-[enter]:fade-in data-[leave]:fade-out", dragging ? "touch-none" : OPT_ANIMATE_DRAWER[side], positionClass[side], isHorizontal ? ["h-full w-full", sizeMap[size]] : ["w-full", heightMap[size]], className),
2347
+ style: _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, style), dragStyle), keyboardInset > 0 ? { marginBottom: keyboardInset } : null), dragging ? { transition: "none" } : null),
2348
+ onPointerDown: handlePointerDown,
2349
+ onPointerMove: swipeToDismiss ? handlePointerMove : void 0,
2350
+ onPointerUp: swipeToDismiss ? endDrag : void 0,
2351
+ onPointerCancel: swipeToDismiss ? endDrag : void 0
2352
+ }, props), {}, {
2353
+ "data-opt-id": "RQERV",
2354
+ "data-opt-slug": "drawer.DrawerPanel"
2355
+ }));
2356
+ }
2357
+ /** Renders the drawer close component. */
2358
+ function DrawerClose(_ref2) {
2359
+ let { className } = _ref2, props = _objectWithoutProperties(_ref2, _excluded2);
2360
+ return /* @__PURE__ */ jsx(DialogDismiss, _objectSpread2(_objectSpread2({ className: cn("hover:bg-bg-subtle rounded-md p-1.5 transition-colors", "cursor-pointer data-[disabled]:cursor-not-allowed", OPT_FOCUS, className) }, props), {}, {
2361
+ "data-opt-id": "M6D6D",
2362
+ "data-opt-slug": "drawer.DrawerClose"
2363
+ }));
2364
+ }
2365
+ //#endregion
2366
+ //#region src/core/virtual-list.tsx
2367
+ /** Renders the virtual list component. */
2368
+ function VirtualList({ items, renderItem, itemHeight, overscan = 3, className, style }) {
2369
+ const containerRef = useRef(null);
2370
+ const scrollTopRef = useRef(0);
2371
+ const containerHeightRef = useRef(0);
2372
+ const rafIdRef = useRef(0);
2373
+ const [visibleRange, setVisibleRange] = useState({
2374
+ start: 0,
2375
+ end: 0
2376
+ });
2377
+ const totalHeight = items.length * itemHeight;
2378
+ const computeRange = useCallback((scrollTop, height) => {
2379
+ const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
2380
+ const visibleCount = Math.ceil(height / itemHeight);
2381
+ return {
2382
+ start,
2383
+ end: Math.min(items.length - 1, start + visibleCount + overscan * 2)
2384
+ };
2385
+ }, [
2386
+ itemHeight,
2387
+ overscan,
2388
+ items.length
2389
+ ]);
2390
+ const scheduleUpdate = useCallback(() => {
2391
+ if (rafIdRef.current) return;
2392
+ rafIdRef.current = requestAnimationFrame(() => {
2393
+ rafIdRef.current = 0;
2394
+ const newRange = computeRange(scrollTopRef.current, containerHeightRef.current);
2395
+ setVisibleRange((prev) => prev.start === newRange.start && prev.end === newRange.end ? prev : newRange);
2396
+ });
2397
+ }, [computeRange]);
2398
+ const handleScroll = useCallback(() => {
2399
+ const el = containerRef.current;
2400
+ if (el) {
2401
+ scrollTopRef.current = el.scrollTop;
2402
+ if (el.clientHeight !== containerHeightRef.current) containerHeightRef.current = el.clientHeight;
2403
+ scheduleUpdate();
2404
+ }
2405
+ }, [scheduleUpdate]);
2406
+ const handleRef = useCallback((el) => {
2407
+ containerRef.current = el;
2408
+ if (el) {
2409
+ containerHeightRef.current = el.clientHeight;
2410
+ const range = computeRange(0, el.clientHeight);
2411
+ setVisibleRange(range);
2412
+ }
2413
+ }, [computeRange]);
2414
+ useEffect(() => {
2415
+ const el = containerRef.current;
2416
+ if (!el || typeof ResizeObserver === "undefined") return;
2417
+ const observer = new ResizeObserver(() => {
2418
+ const nextHeight = el.clientHeight;
2419
+ if (nextHeight !== containerHeightRef.current) {
2420
+ containerHeightRef.current = nextHeight;
2421
+ scheduleUpdate();
2422
+ }
2423
+ });
2424
+ observer.observe(el);
2425
+ return () => {
2426
+ observer.disconnect();
2427
+ };
2428
+ }, [scheduleUpdate]);
2429
+ useEffect(() => {
2430
+ scheduleUpdate();
2431
+ return () => {
2432
+ if (rafIdRef.current) {
2433
+ cancelAnimationFrame(rafIdRef.current);
2434
+ rafIdRef.current = 0;
2435
+ }
2436
+ };
2437
+ }, [
2438
+ scheduleUpdate,
2439
+ items.length,
2440
+ itemHeight,
2441
+ overscan
2442
+ ]);
2443
+ const visibleItems = useMemo(() => {
2444
+ const result = [];
2445
+ for (let i = visibleRange.start; i <= visibleRange.end && i < items.length; i++) result.push(/* @__PURE__ */ jsx("div", {
2446
+ role: "listitem",
2447
+ style: {
2448
+ position: "absolute",
2449
+ top: i * itemHeight,
2450
+ left: 0,
2451
+ right: 0,
2452
+ height: itemHeight
2453
+ },
2454
+ children: renderItem(items[i], i)
2455
+ }, i));
2456
+ return result;
2457
+ }, [
2458
+ visibleRange.start,
2459
+ visibleRange.end,
2460
+ items,
2461
+ itemHeight,
2462
+ renderItem
2463
+ ]);
2464
+ return /* @__PURE__ */ jsx("div", {
2465
+ ref: handleRef,
2466
+ onScroll: handleScroll,
2467
+ className: cn("overflow-auto", className),
2468
+ style: _objectSpread2({ position: "relative" }, style),
2469
+ role: "list",
2470
+ "data-opt-id": "VT2NP",
2471
+ "data-opt-slug": "virtual-list.VirtualList",
2472
+ children: /* @__PURE__ */ jsx("div", {
2473
+ style: {
2474
+ height: totalHeight,
2475
+ position: "relative"
2476
+ },
2477
+ children: visibleItems
2478
+ })
2479
+ });
2480
+ }
2481
+ //#endregion
2482
+ //#region src/core/shader-presets.ts
2483
+ /** 모든 프리셋이 공유하는 풀스크린 정점 셰이더. */
2484
+ const SHADER_VERTEX = `
2485
+ attribute vec2 position;
2486
+ attribute vec2 uv;
2487
+ varying vec2 vUv;
2488
+ void main() {
2489
+ vUv = uv;
2490
+ gl_Position = vec4(position, 0.0, 1.0);
2491
+ }
2492
+ `;
2493
+ /**
2494
+ * 모든 프래그먼트 본문 앞에 붙는 공유 프렐류드.
2495
+ * uniform 선언 + 공통 helper(rotate/value noise/fbm/grain/lux/coords).
2496
+ */
2497
+ const SHADER_PRELUDE = `
2498
+ precision highp float;
2499
+ varying vec2 vUv;
2500
+ uniform float u_time;
2501
+ uniform vec2 u_resolution;
2502
+ uniform float u_scale;
2503
+ uniform float u_speed;
2504
+ uniform float u_distortion;
2505
+ uniform float u_grain;
2506
+ uniform float u_contrast;
2507
+ uniform float u_angle;
2508
+ uniform float u_dim;
2509
+ uniform float u_vignette;
2510
+ uniform float u_scrim;
2511
+ uniform vec2 u_mouse;
2512
+ uniform float u_interactive;
2513
+ uniform float u_bars;
2514
+ uniform float u_blur;
2515
+ uniform float u_peak;
2516
+ uniform float u_valley;
2517
+ uniform vec3 u_color1;
2518
+ uniform vec3 u_color2;
2519
+ uniform vec3 u_color3;
2520
+
2521
+ const float PI = 3.14159265359;
2522
+
2523
+ mat2 rot(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }
2524
+
2525
+ float hash21(vec2 p){
2526
+ p = fract(p * vec2(123.34, 345.45));
2527
+ p += dot(p, p + 34.345);
2528
+ return fract(p.x * p.y);
2529
+ }
2530
+
2531
+ float vnoise(vec2 p){
2532
+ vec2 i = floor(p);
2533
+ vec2 f = fract(p);
2534
+ vec2 u = f * f * (3.0 - 2.0 * f);
2535
+ float a = hash21(i);
2536
+ float b = hash21(i + vec2(1.0, 0.0));
2537
+ float c = hash21(i + vec2(0.0, 1.0));
2538
+ float d = hash21(i + vec2(1.0, 1.0));
2539
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
2540
+ }
2541
+
2542
+ float fbm(vec2 p){
2543
+ float v = 0.0;
2544
+ float amp = 0.5;
2545
+ for (int i = 0; i < 5; i++){
2546
+ v += amp * vnoise(p);
2547
+ p = rot(0.5) * p * 2.0;
2548
+ amp *= 0.5;
2549
+ }
2550
+ return v;
2551
+ }
2552
+
2553
+ // fine film grain — subtle, premium texture
2554
+ vec3 grain(vec3 col, vec2 uv){
2555
+ float g = hash21(uv * u_resolution.xy + fract(u_time));
2556
+ return col + (g - 0.5) * u_grain;
2557
+ }
2558
+
2559
+ // premium finish: gentle desaturation for an understated, refined look.
2560
+ vec3 lux(vec3 col){
2561
+ float l = dot(col, vec3(0.299, 0.587, 0.114));
2562
+ return clamp(mix(vec3(l), col, 0.82), 0.0, 1.0);
2563
+ }
2564
+
2565
+ // shared output finish: refine + legibility overlays (dim / vignette / bottom
2566
+ // scrim) + film grain. Every preset ends with finish(col, vUv).
2567
+ vec3 finish(vec3 col, vec2 uv){
2568
+ col = lux(col);
2569
+ col *= 1.0 - u_dim * 0.85;
2570
+ float edge = length(uv - 0.5);
2571
+ col *= 1.0 - u_vignette * smoothstep(0.35, 0.85, edge);
2572
+ col *= 1.0 - u_scrim * smoothstep(0.2, 1.0, uv.y);
2573
+ col = grain(col, uv);
2574
+ return col;
2575
+ }
2576
+
2577
+ // aspect-correct, angle-rotated, centered coords + pointer parallax
2578
+ vec2 coords(){
2579
+ vec2 uv = vUv - 0.5;
2580
+ uv.x *= u_resolution.x / max(u_resolution.y, 1.0);
2581
+ uv += (u_mouse - 0.5) * u_interactive * 0.3;
2582
+ uv = rot(radians(u_angle)) * uv;
2583
+ return uv;
2584
+ }
2585
+ `;
2586
+ const PARAM_RANGE = {
2587
+ scale: {
2588
+ min: .4,
2589
+ max: 3,
2590
+ step: .1,
2591
+ default: 1
2592
+ },
2593
+ speed: {
2594
+ min: 0,
2595
+ max: 3,
2596
+ step: .1,
2597
+ default: .5
2598
+ },
2599
+ distortion: {
2600
+ min: 0,
2601
+ max: 1,
2602
+ step: .01,
2603
+ default: .4
2604
+ },
2605
+ grain: {
2606
+ min: 0,
2607
+ max: .5,
2608
+ step: .01,
2609
+ default: .04
2610
+ },
2611
+ contrast: {
2612
+ min: 0,
2613
+ max: 1,
2614
+ step: .01,
2615
+ default: .4
2616
+ },
2617
+ angle: {
2618
+ min: 0,
2619
+ max: 360,
2620
+ step: 1,
2621
+ default: 0
2622
+ },
2623
+ dim: {
2624
+ min: 0,
2625
+ max: 1,
2626
+ step: .01,
2627
+ default: 0
2628
+ },
2629
+ vignette: {
2630
+ min: 0,
2631
+ max: 1,
2632
+ step: .01,
2633
+ default: 0
2634
+ },
2635
+ scrim: {
2636
+ min: 0,
2637
+ max: 1,
2638
+ step: .01,
2639
+ default: 0
2640
+ },
2641
+ interactive: {
2642
+ min: 0,
2643
+ max: 1,
2644
+ step: .01,
2645
+ default: 0
2646
+ },
2647
+ bars: {
2648
+ min: 2,
2649
+ max: 40,
2650
+ step: 1,
2651
+ default: 19
2652
+ },
2653
+ blur: {
2654
+ min: 0,
2655
+ max: 1,
2656
+ step: .01,
2657
+ default: .29
2658
+ },
2659
+ peak: {
2660
+ min: 0,
2661
+ max: 1,
2662
+ step: .01,
2663
+ default: .98
2664
+ },
2665
+ valley: {
2666
+ min: 0,
2667
+ max: 1,
2668
+ step: .01,
2669
+ default: .55
2670
+ }
2671
+ };
2672
+ /** 프리셋이 선언하지 않은 파라미터의 전역 기본값 (uniform 폴백). */
2673
+ const SHADER_PARAM_FALLBACK = {
2674
+ scale: PARAM_RANGE.scale.default,
2675
+ speed: PARAM_RANGE.speed.default,
2676
+ distortion: PARAM_RANGE.distortion.default,
2677
+ grain: PARAM_RANGE.grain.default,
2678
+ contrast: PARAM_RANGE.contrast.default,
2679
+ angle: PARAM_RANGE.angle.default,
2680
+ dim: PARAM_RANGE.dim.default,
2681
+ vignette: PARAM_RANGE.vignette.default,
2682
+ scrim: PARAM_RANGE.scrim.default,
2683
+ interactive: PARAM_RANGE.interactive.default,
2684
+ bars: PARAM_RANGE.bars.default,
2685
+ blur: PARAM_RANGE.blur.default,
2686
+ peak: PARAM_RANGE.peak.default,
2687
+ valley: PARAM_RANGE.valley.default
2688
+ };
2689
+ function param(key, defaultValue) {
2690
+ const range = PARAM_RANGE[key];
2691
+ return {
2692
+ key,
2693
+ min: range.min,
2694
+ max: range.max,
2695
+ step: range.step,
2696
+ default: defaultValue !== null && defaultValue !== void 0 ? defaultValue : range.default
2697
+ };
2698
+ }
2699
+ const SHADER_PRESETS = [
2700
+ {
2701
+ id: "felix-metallic",
2702
+ category: "luxury",
2703
+ colorSlots: 3,
2704
+ defaultColors: [
2705
+ "#0a1424",
2706
+ "#3a5a8c",
2707
+ "#c4d4e8"
2708
+ ],
2709
+ params: [
2710
+ param("scale"),
2711
+ param("speed", .4),
2712
+ param("distortion", .45),
2713
+ param("contrast", .4),
2714
+ param("grain", .04)
2715
+ ],
2716
+ body: `
2717
+ void main(){
2718
+ vec2 uv = coords() * u_scale;
2719
+ float t = u_time * u_speed * 0.3;
2720
+ vec2 q = vec2(fbm(uv + t), fbm(uv - t + 5.2));
2721
+ vec2 r = vec2(
2722
+ fbm(uv + q * 2.0 + vec2(1.7, 9.2) + t),
2723
+ fbm(uv + q * 2.0 + vec2(8.3, 2.8) - t)
2724
+ );
2725
+ float f = fbm(uv + r * (1.0 + u_distortion * 2.0));
2726
+ float band = sin((f + r.x * 0.5) * 6.2831 * 2.0);
2727
+ float spec = pow(abs(band), mix(2.0, 5.0, u_contrast));
2728
+ vec3 col = mix(u_color1, u_color2, smoothstep(0.0, 1.0, f + r.y * 0.4));
2729
+ col = mix(col, u_color3, spec * 0.5);
2730
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2731
+ }
2732
+ `
2733
+ },
2734
+ {
2735
+ id: "liquid-silk",
2736
+ category: "luxury",
2737
+ colorSlots: 3,
2738
+ defaultColors: [
2739
+ "#1c1428",
2740
+ "#6a5a8c",
2741
+ "#d8cfe4"
2742
+ ],
2743
+ params: [
2744
+ param("scale"),
2745
+ param("speed", .4),
2746
+ param("distortion", .4),
2747
+ param("grain", .04),
2748
+ param("angle", 30)
2749
+ ],
2750
+ body: `
2751
+ void main(){
2752
+ vec2 uv = coords() * u_scale;
2753
+ float t = u_time * u_speed * 0.25;
2754
+ float warp = fbm(uv * 1.5 + t);
2755
+ float folds = sin((uv.x * 3.0 + uv.y * 1.5 + warp * (2.0 + u_distortion * 4.0)) * PI);
2756
+ float sheen = 0.5 + 0.5 * folds;
2757
+ vec3 col = mix(u_color1, u_color2, sheen);
2758
+ col = mix(col, u_color3, pow(sheen, 5.0) * 0.6);
2759
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2760
+ }
2761
+ `
2762
+ },
2763
+ {
2764
+ id: "iridescent-pearl",
2765
+ category: "luxury",
2766
+ colorSlots: 3,
2767
+ defaultColors: [
2768
+ "#c9b8d6",
2769
+ "#a8c4d4",
2770
+ "#e4d4dc"
2771
+ ],
2772
+ params: [
2773
+ param("scale"),
2774
+ param("speed", .35),
2775
+ param("distortion", .4),
2776
+ param("grain", .04)
2777
+ ],
2778
+ body: `
2779
+ vec3 palette3(float t){
2780
+ t = fract(t);
2781
+ float s = t * 3.0;
2782
+ if (s < 1.0) return mix(u_color1, u_color2, smoothstep(0.0, 1.0, s));
2783
+ if (s < 2.0) return mix(u_color2, u_color3, smoothstep(0.0, 1.0, s - 1.0));
2784
+ return mix(u_color3, u_color1, smoothstep(0.0, 1.0, s - 2.0));
2785
+ }
2786
+ void main(){
2787
+ vec2 uv = coords() * u_scale;
2788
+ float t = u_time * u_speed * 0.2;
2789
+ float n = fbm(uv * 1.2 + vec2(t, -t));
2790
+ float thin = fbm(uv * 2.5 - n * (1.0 + u_distortion * 3.0) + t * 0.5);
2791
+ vec3 col = palette3(thin * 1.5 + n * 0.5 + t * 0.1);
2792
+ col += pow(thin, 3.0) * 0.12;
2793
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2794
+ }
2795
+ `
2796
+ },
2797
+ {
2798
+ id: "molten-gold",
2799
+ category: "luxury",
2800
+ colorSlots: 3,
2801
+ defaultColors: [
2802
+ "#1c1208",
2803
+ "#b08848",
2804
+ "#e8d4a8"
2805
+ ],
2806
+ params: [
2807
+ param("scale"),
2808
+ param("speed", .4),
2809
+ param("contrast", .4),
2810
+ param("grain", .05)
2811
+ ],
2812
+ body: `
2813
+ void main(){
2814
+ vec2 uv = coords() * u_scale;
2815
+ float t = u_time * u_speed * 0.3;
2816
+ vec2 q = vec2(fbm(uv + t), fbm(uv * 1.3 - t));
2817
+ float f = fbm(uv * 1.5 + q * 2.5);
2818
+ float heat = smoothstep(0.25, 0.95, f);
2819
+ vec3 col = mix(u_color1, u_color2, heat);
2820
+ float hot = pow(smoothstep(0.6, 1.0, f + q.x * 0.3), mix(3.0, 6.0, u_contrast));
2821
+ col = mix(col, u_color3, hot * 0.7);
2822
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2823
+ }
2824
+ `
2825
+ },
2826
+ {
2827
+ id: "liquid-glass",
2828
+ category: "luxury",
2829
+ colorSlots: 3,
2830
+ defaultColors: [
2831
+ "#0c1c2c",
2832
+ "#3a6a88",
2833
+ "#bcdce4"
2834
+ ],
2835
+ params: [
2836
+ param("scale"),
2837
+ param("speed", .4),
2838
+ param("distortion", .4),
2839
+ param("grain", .03)
2840
+ ],
2841
+ body: `
2842
+ void main(){
2843
+ vec2 uv = coords() * u_scale;
2844
+ float t = u_time * u_speed * 0.2;
2845
+ vec2 p = uv;
2846
+ for (int i = 1; i < 5; i++){
2847
+ float fi = float(i);
2848
+ p += (1.0 + u_distortion) * vec2(
2849
+ 0.3 / fi * sin(fi * p.y + t + 0.3 * fi),
2850
+ 0.3 / fi * cos(fi * p.x + t + 0.3 * fi)
2851
+ );
2852
+ }
2853
+ float caustic = 0.5 + 0.5 * sin(p.x * 3.0) * cos(p.y * 3.0);
2854
+ vec3 col = mix(u_color1, u_color2, caustic);
2855
+ col = mix(col, u_color3, pow(caustic, 6.0) * 0.6);
2856
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2857
+ }
2858
+ `
2859
+ },
2860
+ {
2861
+ id: "aurora-veil",
2862
+ category: "luxury",
2863
+ colorSlots: 3,
2864
+ defaultColors: [
2865
+ "#050d18",
2866
+ "#2a8a8c",
2867
+ "#6a5a9c"
2868
+ ],
2869
+ params: [
2870
+ param("scale"),
2871
+ param("speed", .4),
2872
+ param("distortion", .45),
2873
+ param("grain", .03),
2874
+ param("angle", 0)
2875
+ ],
2876
+ body: `
2877
+ void main(){
2878
+ vec2 uv = coords() * u_scale;
2879
+ float t = u_time * u_speed * 0.3;
2880
+ vec3 col = u_color1;
2881
+ for (int i = 0; i < 3; i++){
2882
+ float fi = float(i);
2883
+ float band = uv.y * 1.5
2884
+ + sin(uv.x * 2.0 + t + fi * 1.7) * (0.3 + u_distortion * 0.5)
2885
+ + fbm(uv * 1.5 + t * 0.5 + fi) * 0.6;
2886
+ float glow = exp(-abs(band) * 3.0);
2887
+ vec3 rib = mix(u_color2, u_color3, fi * 0.5);
2888
+ col += rib * glow * 0.5;
2889
+ }
2890
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2891
+ }
2892
+ `
2893
+ },
2894
+ {
2895
+ id: "holographic",
2896
+ category: "luxury",
2897
+ colorSlots: 3,
2898
+ defaultColors: [
2899
+ "#14101e",
2900
+ "#8a6a9c",
2901
+ "#7a9caa"
2902
+ ],
2903
+ params: [
2904
+ param("scale"),
2905
+ param("speed", .35),
2906
+ param("distortion", .4),
2907
+ param("grain", .03)
2908
+ ],
2909
+ body: `
2910
+ vec3 hsv2rgb(vec3 c){
2911
+ vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0);
2912
+ return c.z * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y);
2913
+ }
2914
+ void main(){
2915
+ vec2 uv = coords() * u_scale;
2916
+ float t = u_time * u_speed * 0.2;
2917
+ float n = fbm(uv * 1.5 + t);
2918
+ float ripple = fbm(uv * 3.0 - n * (1.0 + u_distortion * 2.0) + t);
2919
+ float hue = fract(ripple * 1.2 + n * 0.5 + length(uv) * 0.3 + t * 0.1);
2920
+ vec3 holo = hsv2rgb(vec3(hue, 0.3, 0.92));
2921
+ vec3 base = mix(u_color1, u_color2, n);
2922
+ vec3 col = mix(base, holo, 0.35);
2923
+ col = mix(col, u_color3, pow(ripple, 4.0) * 0.5);
2924
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2925
+ }
2926
+ `
2927
+ },
2928
+ {
2929
+ id: "chrome-ripple",
2930
+ category: "luxury",
2931
+ colorSlots: 3,
2932
+ defaultColors: [
2933
+ "#0e1420",
2934
+ "#6a7a92",
2935
+ "#d4dce4"
2936
+ ],
2937
+ params: [
2938
+ param("scale"),
2939
+ param("speed", .4),
2940
+ param("contrast", .4),
2941
+ param("grain", .04)
2942
+ ],
2943
+ body: `
2944
+ void main(){
2945
+ vec2 uv = coords() * u_scale;
2946
+ float t = u_time * u_speed * 0.3;
2947
+ vec2 p = uv;
2948
+ for (int i = 1; i < 6; i++){
2949
+ float fi = float(i);
2950
+ p.x += (0.4 / fi) * sin(fi * 1.5 * p.y + t + fi);
2951
+ p.y += (0.4 / fi) * cos(fi * 1.5 * p.x + t + fi);
2952
+ }
2953
+ float sheen = 0.5 + 0.5 * sin(p.x * 3.0 + p.y * 2.0);
2954
+ float spec = pow(sheen, mix(3.0, 6.0, u_contrast));
2955
+ vec3 col = mix(u_color1, u_color2, sheen);
2956
+ col = mix(col, u_color3, spec * 0.5);
2957
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2958
+ }
2959
+ `
2960
+ },
2961
+ {
2962
+ id: "mesh-flow",
2963
+ category: "flow",
2964
+ colorSlots: 3,
2965
+ defaultColors: [
2966
+ "#3a4a6a",
2967
+ "#6a5a7c",
2968
+ "#4a7a82"
2969
+ ],
2970
+ params: [
2971
+ param("scale"),
2972
+ param("speed", .5),
2973
+ param("grain", .04)
2974
+ ],
2975
+ body: `
2976
+ void main(){
2977
+ vec2 uv = coords() * u_scale;
2978
+ float t = u_time * u_speed * 0.4;
2979
+ vec2 p1 = vec2(sin(t * 0.7), cos(t * 0.9)) * 0.5;
2980
+ vec2 p2 = vec2(cos(t * 0.8 + 1.0), sin(t * 0.6 + 2.0)) * 0.5;
2981
+ vec2 p3 = vec2(sin(t * 0.5 + 3.0), cos(t * 0.7 + 4.0)) * 0.5;
2982
+ float d1 = 1.0 / (0.15 + dot(uv - p1, uv - p1));
2983
+ float d2 = 1.0 / (0.15 + dot(uv - p2, uv - p2));
2984
+ float d3 = 1.0 / (0.15 + dot(uv - p3, uv - p3));
2985
+ float sum = d1 + d2 + d3;
2986
+ vec3 col = (u_color1 * d1 + u_color2 * d2 + u_color3 * d3) / sum;
2987
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
2988
+ }
2989
+ `
2990
+ },
2991
+ {
2992
+ id: "plasma-wave",
2993
+ category: "flow",
2994
+ colorSlots: 3,
2995
+ defaultColors: [
2996
+ "#2a2440",
2997
+ "#4a5a7c",
2998
+ "#8a6a7c"
2999
+ ],
3000
+ params: [
3001
+ param("scale"),
3002
+ param("speed", .4),
3003
+ param("distortion", .4),
3004
+ param("grain", .03)
3005
+ ],
3006
+ body: `
3007
+ void main(){
3008
+ vec2 uv = coords() * u_scale * 3.0;
3009
+ float t = u_time * u_speed * 0.5;
3010
+ float v = sin(uv.x + t)
3011
+ + sin(uv.y + t * 1.3)
3012
+ + sin((uv.x + uv.y) * 0.7 + t)
3013
+ + sin(length(uv) * (1.0 + u_distortion * 2.0) - t * 1.5);
3014
+ v *= 0.25;
3015
+ float m = 0.5 + 0.5 * sin(v * PI);
3016
+ vec3 col = mix(u_color1, u_color2, m);
3017
+ col = mix(col, u_color3, (0.5 + 0.5 * sin(v * 6.2831)) * 0.5);
3018
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3019
+ }
3020
+ `
3021
+ },
3022
+ {
3023
+ id: "swirl-vortex",
3024
+ category: "flow",
3025
+ colorSlots: 3,
3026
+ defaultColors: [
3027
+ "#0a0e16",
3028
+ "#7c4a5c",
3029
+ "#3a7a72"
3030
+ ],
3031
+ params: [
3032
+ param("scale"),
3033
+ param("speed", .4),
3034
+ param("distortion", .45),
3035
+ param("grain", .03)
3036
+ ],
3037
+ body: `
3038
+ void main(){
3039
+ vec2 uv = coords() * u_scale;
3040
+ float t = u_time * u_speed * 0.3;
3041
+ float r = length(uv);
3042
+ float a = atan(uv.y, uv.x);
3043
+ a += (1.0 + u_distortion * 3.0) * (0.5 / (r + 0.2)) + t;
3044
+ vec2 p = vec2(cos(a), sin(a)) * r;
3045
+ float n = fbm(p * 2.0 + t);
3046
+ float bands = sin((a * 3.0 + r * 6.0 - t * 2.0) + n * 4.0);
3047
+ float m = 0.5 + 0.5 * bands;
3048
+ vec3 col = mix(u_color1, u_color2, m);
3049
+ col = mix(col, u_color3, pow(m, 3.0) * 0.6);
3050
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3051
+ }
3052
+ `
3053
+ },
3054
+ {
3055
+ id: "caustics",
3056
+ category: "flow",
3057
+ colorSlots: 3,
3058
+ defaultColors: [
3059
+ "#04161f",
3060
+ "#2a6a7c",
3061
+ "#a8d4d0"
3062
+ ],
3063
+ params: [
3064
+ param("scale"),
3065
+ param("speed", .4),
3066
+ param("distortion", .4),
3067
+ param("grain", .03)
3068
+ ],
3069
+ body: `
3070
+ void main(){
3071
+ vec2 uv = coords() * u_scale * 3.0;
3072
+ float t = u_time * u_speed * 0.4;
3073
+ vec2 p = uv;
3074
+ float c = 0.0;
3075
+ for (int i = 0; i < 4; i++){
3076
+ float fi = float(i);
3077
+ p += vec2(sin(p.y * 1.5 + t + fi), cos(p.x * 1.5 + t * 1.1 + fi))
3078
+ * (0.4 + u_distortion * 0.5);
3079
+ c += 1.0 / (length(fract(p) - 0.5) + 0.1);
3080
+ }
3081
+ c = pow(c * 0.06, 2.0);
3082
+ vec3 col = mix(u_color1, u_color2, clamp(c, 0.0, 1.0));
3083
+ col += u_color3 * pow(clamp(c - 0.6, 0.0, 1.0), 2.0) * 0.6;
3084
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3085
+ }
3086
+ `
3087
+ },
3088
+ {
3089
+ id: "nebula",
3090
+ category: "flow",
3091
+ colorSlots: 3,
3092
+ defaultColors: [
3093
+ "#06040e",
3094
+ "#4a3a6c",
3095
+ "#8a5a7c"
3096
+ ],
3097
+ params: [
3098
+ param("scale"),
3099
+ param("speed", .35),
3100
+ param("distortion", .5),
3101
+ param("grain", .03)
3102
+ ],
3103
+ body: `
3104
+ void main(){
3105
+ vec2 uv = coords() * u_scale;
3106
+ float t = u_time * u_speed * 0.15;
3107
+ vec2 q = vec2(fbm(uv + t), fbm(uv * 1.2 - t + 3.0));
3108
+ float density = fbm(uv * 1.5 + q * (2.0 + u_distortion * 2.0));
3109
+ density = pow(density, 2.0);
3110
+ vec3 col = mix(u_color1, u_color2, density);
3111
+ col = mix(col, u_color3, pow(density, 3.0) * 0.8);
3112
+ float stars = step(0.99, hash21(floor(vUv * u_resolution.xy / 2.5)));
3113
+ col += vec3(stars) * 0.35;
3114
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3115
+ }
3116
+ `
3117
+ },
3118
+ {
3119
+ id: "voronoi-crystal",
3120
+ category: "geometric",
3121
+ colorSlots: 3,
3122
+ defaultColors: [
3123
+ "#121a30",
3124
+ "#3a5a82",
3125
+ "#c4d0e0"
3126
+ ],
3127
+ params: [
3128
+ param("scale"),
3129
+ param("speed", .35),
3130
+ param("contrast", .4),
3131
+ param("grain", .03)
3132
+ ],
3133
+ body: `
3134
+ vec2 hash22(vec2 p){
3135
+ p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
3136
+ return fract(sin(p) * 43758.5453);
3137
+ }
3138
+ void main(){
3139
+ vec2 uv = coords() * u_scale * 3.0;
3140
+ float t = u_time * u_speed * 0.3;
3141
+ vec2 ip = floor(uv);
3142
+ vec2 fp = fract(uv);
3143
+ float f1 = 8.0;
3144
+ float f2 = 8.0;
3145
+ vec2 cellId = vec2(0.0);
3146
+ for (int j = -1; j <= 1; j++){
3147
+ for (int i = -1; i <= 1; i++){
3148
+ vec2 n = vec2(float(i), float(j));
3149
+ vec2 pnt = n + 0.5 + 0.4 * sin(t + 6.2831 * hash22(ip + n));
3150
+ float d = length(pnt - fp);
3151
+ if (d < f1){ f2 = f1; f1 = d; cellId = ip + n; }
3152
+ else if (d < f2){ f2 = d; }
3153
+ }
3154
+ }
3155
+ float facet = f2 - f1;
3156
+ float cell = hash22(cellId).x;
3157
+ vec3 col = mix(u_color1, u_color2, cell);
3158
+ col = mix(col, u_color3, smoothstep(0.05, 0.0, facet) * mix(0.4, 0.7, u_contrast));
3159
+ col += pow(max(0.0, 1.0 - f1), 4.0) * 0.1;
3160
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3161
+ }
3162
+ `
3163
+ },
3164
+ {
3165
+ id: "kaleidoscope",
3166
+ category: "geometric",
3167
+ colorSlots: 3,
3168
+ defaultColors: [
3169
+ "#1a1428",
3170
+ "#7c5a6c",
3171
+ "#b09a6a"
3172
+ ],
3173
+ params: [
3174
+ param("scale"),
3175
+ param("speed", .35),
3176
+ param("distortion", .4),
3177
+ param("grain", .03)
3178
+ ],
3179
+ body: `
3180
+ void main(){
3181
+ vec2 uv = coords() * u_scale;
3182
+ float t = u_time * u_speed * 0.3;
3183
+ float r = length(uv);
3184
+ float a = atan(uv.y, uv.x);
3185
+ float seg = 6.0 + floor(u_distortion * 6.0);
3186
+ a = mod(a, 6.2831 / seg);
3187
+ a = abs(a - 3.14159 / seg);
3188
+ vec2 p = vec2(cos(a), sin(a)) * r;
3189
+ float pattern = fbm(p * 3.0 + t);
3190
+ float m = 0.5 + 0.5 * sin(pattern * 6.2831 + r * 4.0 - t);
3191
+ vec3 col = mix(u_color1, u_color2, m);
3192
+ col = mix(col, u_color3, pow(m, 3.0) * 0.6);
3193
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3194
+ }
3195
+ `
3196
+ },
3197
+ {
3198
+ id: "dither-gradient",
3199
+ category: "retro",
3200
+ colorSlots: 3,
3201
+ defaultColors: [
3202
+ "#14202e",
3203
+ "#7c4a54",
3204
+ "#d4d0c8"
3205
+ ],
3206
+ params: [
3207
+ param("scale"),
3208
+ param("speed", .35),
3209
+ param("distortion", .35)
3210
+ ],
3211
+ body: `
3212
+ float bayer2(vec2 a){ a = floor(a); return fract(dot(a, vec2(0.5, a.y * 0.75))); }
3213
+ float bayer4(vec2 a){ return bayer2(0.5 * a) * 0.25 + bayer2(a); }
3214
+ void main(){
3215
+ vec2 uv = coords() * u_scale;
3216
+ float t = u_time * u_speed * 0.2;
3217
+ float g = fbm(uv * 1.5 + vec2(t, -t) * (0.5 + u_distortion));
3218
+ float levels = 5.0;
3219
+ float d = bayer4(gl_FragCoord.xy);
3220
+ float q = floor(g * levels + d) / levels;
3221
+ vec3 col = mix(u_color1, u_color2, clamp(q, 0.0, 1.0));
3222
+ col = mix(col, u_color3, smoothstep(0.7, 1.0, q));
3223
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3224
+ }
3225
+ `
3226
+ },
3227
+ {
3228
+ id: "halftone",
3229
+ category: "retro",
3230
+ colorSlots: 3,
3231
+ defaultColors: [
3232
+ "#161616",
3233
+ "#8a5a3a",
3234
+ "#c4a06a"
3235
+ ],
3236
+ params: [
3237
+ param("scale"),
3238
+ param("speed", .35),
3239
+ param("distortion", .45)
3240
+ ],
3241
+ body: `
3242
+ void main(){
3243
+ vec2 uv = coords() * u_scale;
3244
+ float t = u_time * u_speed * 0.2;
3245
+ float g = fbm(uv * 1.5 + t);
3246
+ vec2 gp = gl_FragCoord.xy / mix(22.0, 9.0, clamp(u_distortion, 0.0, 1.0));
3247
+ vec2 cell = fract(gp) - 0.5;
3248
+ float radius = g * 0.7;
3249
+ float dot = smoothstep(radius, radius - 0.12, length(cell));
3250
+ vec3 col = mix(u_color1, u_color2, g);
3251
+ col = mix(col, u_color3, dot * 0.85);
3252
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3253
+ }
3254
+ `
3255
+ },
3256
+ {
3257
+ id: "dia-gradient",
3258
+ category: "gradient",
3259
+ colorSlots: 3,
3260
+ defaultColors: [
3261
+ "#FF9ECF",
3262
+ "#A9C9FF",
3263
+ "#FFF3A8"
3264
+ ],
3265
+ params: [
3266
+ param("bars"),
3267
+ param("blur"),
3268
+ param("peak"),
3269
+ param("valley"),
3270
+ param("speed", .3)
3271
+ ],
3272
+ body: `
3273
+ void main(){
3274
+ vec2 uv = vUv;
3275
+ float t = u_time * u_speed * 0.15;
3276
+ float cx = 0.5 + sin(t) * 0.04;
3277
+ float peakShape = clamp(1.0 - abs(uv.x - cx) * 2.0, 0.0, 1.0);
3278
+ peakShape = pow(peakShape, 0.85);
3279
+ float profile = mix(u_valley, u_peak, peakShape);
3280
+ float heightFromBottom = 1.0 - uv.y;
3281
+ float h = heightFromBottom / max(profile, 0.02);
3282
+ float bars = max(1.0, floor(u_bars));
3283
+ float hb = h * bars;
3284
+ float band = floor(hb);
3285
+ float frac = hb - band;
3286
+ float soft = mix(0.04, 0.5, clamp(u_blur, 0.0, 1.0));
3287
+ float sm = smoothstep(0.5 - soft, 0.5 + soft, frac);
3288
+ float hv = clamp((band + sm) / bars, 0.0, 1.0);
3289
+ vec3 col;
3290
+ if (hv < 0.33) col = mix(u_color1, u_color2, smoothstep(0.0, 0.33, hv));
3291
+ else if (hv < 0.66) col = mix(u_color2, u_color3, smoothstep(0.33, 0.66, hv));
3292
+ else col = mix(u_color3, vec3(1.0), smoothstep(0.66, 1.0, hv));
3293
+ float above = smoothstep(1.0, 1.0 + soft * 0.5, h);
3294
+ col = mix(col, vec3(1.0), above);
3295
+ gl_FragColor = vec4(finish(col, vUv), 1.0);
3296
+ }
3297
+ `
3298
+ }
3299
+ ];
3300
+ const SHADER_PRESET_IDS = SHADER_PRESETS.map((preset) => preset.id);
3301
+ const SHADER_PARAM_KEYS = [
3302
+ "scale",
3303
+ "speed",
3304
+ "distortion",
3305
+ "grain",
3306
+ "contrast",
3307
+ "angle",
3308
+ "dim",
3309
+ "vignette",
3310
+ "scrim",
3311
+ "interactive",
3312
+ "bars",
3313
+ "blur",
3314
+ "peak",
3315
+ "valley"
3316
+ ];
3317
+ /** 패턴과 무관하게 모든 프리셋에 공통 적용되는 마감(가독성) 파라미터 키. */
3318
+ const SHADER_OVERLAY_PARAM_KEYS = [
3319
+ "dim",
3320
+ "vignette",
3321
+ "scrim"
3322
+ ];
3323
+ const PRESET_BY_ID = new Map(SHADER_PRESETS.map((preset) => [preset.id, preset]));
3324
+ /** id로 프리셋 조회 (없으면 첫 프리셋 폴백). */
3325
+ function getShaderPreset(id) {
3326
+ var _PRESET_BY_ID$get;
3327
+ return (_PRESET_BY_ID$get = PRESET_BY_ID.get(id)) !== null && _PRESET_BY_ID$get !== void 0 ? _PRESET_BY_ID$get : SHADER_PRESETS[0];
3328
+ }
3329
+ /** 공유 프렐류드 + 프리셋 본문을 합친 완성 프래그먼트 셰이더 소스. */
3330
+ function getShaderFragment(preset) {
3331
+ return `${SHADER_PRELUDE}\n${preset.body}`;
3332
+ }
3333
+ //#endregion
3334
+ //#region src/core/shader-surface.tsx
3335
+ /** 정지(`paused`) 프레임에 사용하는 고정 시간 — t=0의 밋밋함 회피. */
3336
+ const STATIC_TIME = 8;
3337
+ /** hex → 0..1 RGB. 잘못된 hex는 검정으로 폴백. */
3338
+ function toRgbFloat(hex) {
3339
+ try {
3340
+ const [r, g, b] = hexToSrgb(hex);
3341
+ return [
3342
+ r / 255,
3343
+ g / 255,
3344
+ b / 255
3345
+ ];
3346
+ } catch (_unused) {
3347
+ return [
3348
+ 0,
3349
+ 0,
3350
+ 0
3351
+ ];
3352
+ }
3353
+ }
3354
+ function resolveColors(presetId, colors) {
3355
+ var _source$, _ref, _source$2, _ref2, _ref3, _source$3;
3356
+ const preset = getShaderPreset(presetId);
3357
+ const source = colors && colors.length > 0 ? colors : preset.defaultColors;
3358
+ return [
3359
+ (_source$ = source[0]) !== null && _source$ !== void 0 ? _source$ : preset.defaultColors[0],
3360
+ (_ref = (_source$2 = source[1]) !== null && _source$2 !== void 0 ? _source$2 : source[0]) !== null && _ref !== void 0 ? _ref : preset.defaultColors[1],
3361
+ (_ref2 = (_ref3 = (_source$3 = source[2]) !== null && _source$3 !== void 0 ? _source$3 : source[1]) !== null && _ref3 !== void 0 ? _ref3 : source[0]) !== null && _ref2 !== void 0 ? _ref2 : preset.defaultColors[2]
3362
+ ];
3363
+ }
3364
+ function resolveParam(presetId, key, override) {
3365
+ var _spec$default;
3366
+ if (typeof override === "number" && Number.isFinite(override)) return override;
3367
+ const spec = getShaderPreset(presetId).params.find((p) => p.key === key);
3368
+ return (_spec$default = spec === null || spec === void 0 ? void 0 : spec.default) !== null && _spec$default !== void 0 ? _spec$default : SHADER_PARAM_FALLBACK[key];
3369
+ }
3370
+ function prefersReducedMotion() {
3371
+ return typeof window !== "undefined" && window.matchMedia ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
3372
+ }
3373
+ const ShaderSurfaceComponent = forwardRef(function ShaderSurface({ preset, colors, scale, speed, distortion, grain, contrast, angle, dim, vignette, scrim, interactive, params, time, paused = false, dpr, className, style, "aria-hidden": ariaHidden = true }, ref) {
3374
+ const containerRef = useRef(null);
3375
+ const canvasRef = useRef(null);
3376
+ const stateRef = useRef({
3377
+ preset,
3378
+ colors,
3379
+ scale,
3380
+ speed,
3381
+ distortion,
3382
+ grain,
3383
+ contrast,
3384
+ angle,
3385
+ dim,
3386
+ vignette,
3387
+ scrim,
3388
+ interactive,
3389
+ params,
3390
+ time,
3391
+ paused
3392
+ });
3393
+ stateRef.current = {
3394
+ preset,
3395
+ colors,
3396
+ scale,
3397
+ speed,
3398
+ distortion,
3399
+ grain,
3400
+ contrast,
3401
+ angle,
3402
+ dim,
3403
+ vignette,
3404
+ scrim,
3405
+ interactive,
3406
+ params,
3407
+ time,
3408
+ paused
3409
+ };
3410
+ const rendererRef = useRef(null);
3411
+ const programRef = useRef(null);
3412
+ const renderAtRef = useRef(null);
3413
+ const loopRef = useRef(null);
3414
+ const mouseRef = useRef([.5, .5]);
3415
+ const fallbackBackground = useMemo(() => {
3416
+ const [c1, c2, c3] = resolveColors(preset, colors);
3417
+ return `linear-gradient(${resolveParam(preset, "angle", angle) + 120}deg, ${c1}, ${c2}, ${c3})`;
3418
+ }, [
3419
+ preset,
3420
+ colors,
3421
+ angle
3422
+ ]);
3423
+ useImperativeHandle(ref, () => ({ capture: () => {
3424
+ var _stateRef$current$tim;
3425
+ const canvas = canvasRef.current;
3426
+ if (!canvas || !renderAtRef.current || !programRef.current) return null;
3427
+ const now = stateRef.current.paused ? (_stateRef$current$tim = stateRef.current.time) !== null && _stateRef$current$tim !== void 0 ? _stateRef$current$tim : STATIC_TIME : performance.now() / 1e3;
3428
+ renderAtRef.current(now);
3429
+ try {
3430
+ return canvas.toDataURL("image/png");
3431
+ } catch (_unused2) {
3432
+ return null;
3433
+ }
3434
+ } }), []);
3435
+ useEffect(() => {
3436
+ const container = containerRef.current;
3437
+ const canvas = canvasRef.current;
3438
+ if (!container || !canvas) return;
3439
+ let renderer;
3440
+ try {
3441
+ renderer = new Renderer({
3442
+ canvas,
3443
+ dpr: dpr !== null && dpr !== void 0 ? dpr : typeof window !== "undefined" ? Math.min(window.devicePixelRatio || 1, 2) : 1,
3444
+ alpha: true,
3445
+ premultipliedAlpha: false
3446
+ });
3447
+ } catch (_unused3) {
3448
+ return;
3449
+ }
3450
+ const gl = renderer.gl;
3451
+ rendererRef.current = {
3452
+ renderer,
3453
+ gl
3454
+ };
3455
+ const resize = () => {
3456
+ const rect = container.getBoundingClientRect();
3457
+ renderer.setSize(Math.max(1, rect.width), Math.max(1, rect.height));
3458
+ };
3459
+ resize();
3460
+ const renderAt = (timeSeconds) => {
3461
+ const current = programRef.current;
3462
+ if (!current) return;
3463
+ const s = stateRef.current;
3464
+ const [c1, c2, c3] = resolveColors(s.preset, s.colors);
3465
+ const u = current.program.uniforms;
3466
+ const val = (key, override) => {
3467
+ var _s$params;
3468
+ return resolveParam(s.preset, key, override !== null && override !== void 0 ? override : (_s$params = s.params) === null || _s$params === void 0 ? void 0 : _s$params[key]);
3469
+ };
3470
+ u.u_time.value = timeSeconds;
3471
+ u.u_resolution.value = [gl.canvas.width, gl.canvas.height];
3472
+ u.u_scale.value = val("scale", s.scale);
3473
+ u.u_speed.value = val("speed", s.speed);
3474
+ u.u_distortion.value = val("distortion", s.distortion);
3475
+ u.u_grain.value = val("grain", s.grain);
3476
+ u.u_contrast.value = val("contrast", s.contrast);
3477
+ u.u_angle.value = val("angle", s.angle);
3478
+ u.u_dim.value = val("dim", s.dim);
3479
+ u.u_vignette.value = val("vignette", s.vignette);
3480
+ u.u_scrim.value = val("scrim", s.scrim);
3481
+ u.u_interactive.value = val("interactive", s.interactive);
3482
+ u.u_bars.value = val("bars", void 0);
3483
+ u.u_blur.value = val("blur", void 0);
3484
+ u.u_peak.value = val("peak", void 0);
3485
+ u.u_valley.value = val("valley", void 0);
3486
+ u.u_mouse.value = mouseRef.current;
3487
+ u.u_color1.value = toRgbFloat(c1);
3488
+ u.u_color2.value = toRgbFloat(c2);
3489
+ u.u_color3.value = toRgbFloat(c3);
3490
+ renderer.render({ scene: current.mesh });
3491
+ };
3492
+ renderAtRef.current = renderAt;
3493
+ const staticTime = () => {
3494
+ var _stateRef$current$tim2;
3495
+ return (_stateRef$current$tim2 = stateRef.current.time) !== null && _stateRef$current$tim2 !== void 0 ? _stateRef$current$tim2 : STATIC_TIME;
3496
+ };
3497
+ const reduced = prefersReducedMotion();
3498
+ let inView = true;
3499
+ const isAnimating = () => !stateRef.current.paused && !reduced && inView;
3500
+ let raf = 0;
3501
+ let startTime = performance.now();
3502
+ const loop = (nowMs) => {
3503
+ renderAt((nowMs - startTime) / 1e3);
3504
+ raf = requestAnimationFrame(loop);
3505
+ };
3506
+ const start = () => {
3507
+ if (raf) return;
3508
+ startTime = performance.now();
3509
+ raf = requestAnimationFrame(loop);
3510
+ };
3511
+ const stop = () => {
3512
+ if (raf) cancelAnimationFrame(raf);
3513
+ raf = 0;
3514
+ };
3515
+ loopRef.current = {
3516
+ start,
3517
+ stop,
3518
+ isAnimating
3519
+ };
3520
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
3521
+ resize();
3522
+ if (!isAnimating()) renderAt(staticTime());
3523
+ }) : null;
3524
+ resizeObserver === null || resizeObserver === void 0 || resizeObserver.observe(container);
3525
+ const intersectionObserver = typeof IntersectionObserver !== "undefined" ? new IntersectionObserver((entries) => {
3526
+ var _entries$0$isIntersec, _entries$;
3527
+ inView = (_entries$0$isIntersec = (_entries$ = entries[0]) === null || _entries$ === void 0 ? void 0 : _entries$.isIntersecting) !== null && _entries$0$isIntersec !== void 0 ? _entries$0$isIntersec : true;
3528
+ if (inView && isAnimating()) start();
3529
+ else stop();
3530
+ }, { threshold: 0 }) : null;
3531
+ intersectionObserver === null || intersectionObserver === void 0 || intersectionObserver.observe(container);
3532
+ const handlePointer = (event) => {
3533
+ const rect = container.getBoundingClientRect();
3534
+ mouseRef.current = [(event.clientX - rect.left) / Math.max(1, rect.width), (event.clientY - rect.top) / Math.max(1, rect.height)];
3535
+ const s = stateRef.current;
3536
+ if (!isAnimating() && resolveParam(s.preset, "interactive", s.interactive) > 0) renderAt(staticTime());
3537
+ };
3538
+ container.addEventListener("pointermove", handlePointer);
3539
+ if (isAnimating()) start();
3540
+ return () => {
3541
+ stop();
3542
+ resizeObserver === null || resizeObserver === void 0 || resizeObserver.disconnect();
3543
+ intersectionObserver === null || intersectionObserver === void 0 || intersectionObserver.disconnect();
3544
+ container.removeEventListener("pointermove", handlePointer);
3545
+ loopRef.current = null;
3546
+ renderAtRef.current = null;
3547
+ rendererRef.current = null;
3548
+ };
3549
+ }, [dpr]);
3550
+ useEffect(() => {
3551
+ var _renderAtRef$current, _stateRef$current$tim3;
3552
+ const engine = rendererRef.current;
3553
+ if (!engine) return;
3554
+ const { gl } = engine;
3555
+ const presetDef = getShaderPreset(preset);
3556
+ const geometry = new Triangle(gl);
3557
+ const program = new Program(gl, {
3558
+ vertex: SHADER_VERTEX,
3559
+ fragment: getShaderFragment(presetDef),
3560
+ uniforms: {
3561
+ u_time: { value: 0 },
3562
+ u_resolution: { value: [1, 1] },
3563
+ u_scale: { value: 1 },
3564
+ u_speed: { value: 1 },
3565
+ u_distortion: { value: .5 },
3566
+ u_grain: { value: 0 },
3567
+ u_contrast: { value: .5 },
3568
+ u_angle: { value: 0 },
3569
+ u_dim: { value: 0 },
3570
+ u_vignette: { value: 0 },
3571
+ u_scrim: { value: 0 },
3572
+ u_mouse: { value: [.5, .5] },
3573
+ u_interactive: { value: 0 },
3574
+ u_bars: { value: 19 },
3575
+ u_blur: { value: .29 },
3576
+ u_peak: { value: .98 },
3577
+ u_valley: { value: .55 },
3578
+ u_color1: { value: [
3579
+ 0,
3580
+ 0,
3581
+ 0
3582
+ ] },
3583
+ u_color2: { value: [
3584
+ 0,
3585
+ 0,
3586
+ 0
3587
+ ] },
3588
+ u_color3: { value: [
3589
+ 0,
3590
+ 0,
3591
+ 0
3592
+ ] }
3593
+ }
3594
+ });
3595
+ const mesh = new Mesh(gl, {
3596
+ geometry,
3597
+ program
3598
+ });
3599
+ programRef.current = {
3600
+ program,
3601
+ mesh,
3602
+ geometry
3603
+ };
3604
+ const loop = loopRef.current;
3605
+ if (loop === null || loop === void 0 ? void 0 : loop.isAnimating()) loop.start();
3606
+ else (_renderAtRef$current = renderAtRef.current) === null || _renderAtRef$current === void 0 || _renderAtRef$current.call(renderAtRef, (_stateRef$current$tim3 = stateRef.current.time) !== null && _stateRef$current$tim3 !== void 0 ? _stateRef$current$tim3 : STATIC_TIME);
3607
+ return () => {
3608
+ programRef.current = null;
3609
+ program.remove();
3610
+ geometry.remove();
3611
+ };
3612
+ }, [preset]);
3613
+ useEffect(() => {
3614
+ const loop = loopRef.current;
3615
+ if (!loop) return;
3616
+ if (loop.isAnimating()) loop.start();
3617
+ else {
3618
+ var _renderAtRef$current2, _stateRef$current$tim4;
3619
+ loop.stop();
3620
+ (_renderAtRef$current2 = renderAtRef.current) === null || _renderAtRef$current2 === void 0 || _renderAtRef$current2.call(renderAtRef, (_stateRef$current$tim4 = stateRef.current.time) !== null && _stateRef$current$tim4 !== void 0 ? _stateRef$current$tim4 : STATIC_TIME);
3621
+ }
3622
+ }, [
3623
+ paused,
3624
+ colors,
3625
+ scale,
3626
+ speed,
3627
+ distortion,
3628
+ grain,
3629
+ contrast,
3630
+ angle,
3631
+ dim,
3632
+ vignette,
3633
+ scrim,
3634
+ interactive,
3635
+ params,
3636
+ time
3637
+ ]);
3638
+ return /* @__PURE__ */ jsx("div", {
3639
+ ref: containerRef,
3640
+ "aria-hidden": ariaHidden,
3641
+ className: cn("relative overflow-hidden", className),
3642
+ style: _objectSpread2({ background: fallbackBackground }, style),
3643
+ children: /* @__PURE__ */ jsx("canvas", {
3644
+ ref: canvasRef,
3645
+ className: "absolute inset-0 block h-full w-full"
3646
+ })
3647
+ });
3648
+ });
3649
+ ShaderSurfaceComponent.displayName = "ShaderSurface";
3650
+ const ShaderSurface = ShaderSurfaceComponent;
3651
+ //#endregion
3652
+ export { CardHeader as $, DropdownTrigger as A, Avatar as B, LoadingOverlay as C, DropdownItem as D, DropdownContent as E, formatOptTime as F, SkeletonCard as G, Textarea as H, StepBuilder as I, Switch as J, SkeletonTable as K, Meter$1 as L, formatOptDateTime as M, formatOptNumber as N, DropdownMenu as O, formatOptShortDate as P, CardFooter as Q, CircularProgress as R, BlockLayout as S, RadioGroup as T, Skeleton as U, AvatarGroup as V, SkeletonAvatar as W, CardContent as X, Card as Y, CardDescription as Z, Section as _, SHADER_PRESETS as a, AccordionTrigger$1 as at, PageHeader as b, getShaderPreset as c, OtpField as ct, DrawerPanel as d, Text as dt, CardTitle as et, DrawerRoot as f, ResizablePanelGroup as g, ResizablePanel as h, SHADER_PARAM_KEYS as i, AccordionRoot$1 as it, ConditionBuilder as j, DropdownSeparator as k, VirtualList as l, Separator$2 as lt, ResizableHandle as m, SHADER_OVERLAY_PARAM_KEYS as n, AccordionContent$1 as nt, SHADER_PRESET_IDS as o, Toggle$1 as ot, Logo as p, SkeletonText as q, SHADER_PARAM_FALLBACK as r, AccordionItem$1 as rt, getShaderFragment as s, ToggleGroup$1 as st, ShaderSurface as t, Breadcrumb as tt, DrawerClose as u, Heading as ut, Container as v, Radio as w, createBlock as x, ScrollArea as y, Progress as z };
3653
+
3654
+ //# sourceMappingURL=shader-surface-BUPZ0P-n.js.map