@ohhwells/bridge 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,7 +34,6 @@ __export(index_exports, {
34
34
  LinkEditorPanel: () => LinkEditorPanel,
35
35
  LinkPopover: () => LinkPopover,
36
36
  OhhwellsBridge: () => OhhwellsBridge,
37
- SchedulingWidget: () => SchedulingWidget,
38
37
  Toggle: () => Toggle,
39
38
  ToggleGroup: () => ToggleGroup,
40
39
  ToggleGroupItem: () => ToggleGroupItem,
@@ -49,752 +48,10 @@ __export(index_exports, {
49
48
  module.exports = __toCommonJS(index_exports);
50
49
 
51
50
  // src/OhhwellsBridge.tsx
52
- var import_react5 = __toESM(require("react"), 1);
53
- var import_client = require("react-dom/client");
54
- var import_react_dom = require("react-dom");
55
-
56
- // src/ui/SchedulingWidget.tsx
57
- var import_react2 = require("react");
58
-
59
- // src/ui/EmailCaptureModal.tsx
60
- var import_react = require("react");
61
- var import_radix_ui = require("radix-ui");
62
- var import_jsx_runtime = require("react/jsx-runtime");
63
- function Spinner() {
64
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
65
- "svg",
66
- {
67
- width: "32",
68
- height: "32",
69
- viewBox: "0 0 32 32",
70
- fill: "none",
71
- style: { animation: "ohw-spin 0.75s linear infinite" },
72
- children: [
73
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
74
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
75
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
76
- "path",
77
- {
78
- d: "M16 4a12 12 0 0 1 12 12",
79
- stroke: "#3b82f6",
80
- strokeWidth: "3",
81
- strokeLinecap: "round"
82
- }
83
- )
84
- ]
85
- }
86
- );
87
- }
88
- function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
89
- const [email, setEmail] = (0, import_react.useState)("");
90
- const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
91
- const [loading, setLoading] = (0, import_react.useState)(false);
92
- const [success, setSuccess] = (0, import_react.useState)(false);
93
- const [error, setError] = (0, import_react.useState)(null);
94
- const isBook = title === "Confirm your spot";
95
- const handleSubmit = async () => {
96
- if (!email.trim()) return;
97
- setLoading(true);
98
- setError(null);
99
- try {
100
- await onSubmit(email.trim());
101
- setSubmittedEmail(email.trim());
102
- setSuccess(true);
103
- } catch (e) {
104
- setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
105
- } finally {
106
- setLoading(false);
107
- }
108
- };
109
- const handleKeyDown = (e) => {
110
- if (e.key === "Enter" && !loading) handleSubmit();
111
- };
112
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
113
- if (!open) onClose();
114
- }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
115
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
- import_radix_ui.Dialog.Overlay,
117
- {
118
- className: "fixed inset-0 z-50",
119
- style: { background: "rgba(0,0,0,0.45)" }
120
- }
121
- ),
122
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
123
- import_radix_ui.Dialog.Content,
124
- {
125
- className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
126
- style: { maxWidth: 400 },
127
- children: [
128
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
129
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
130
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
- import_radix_ui.Dialog.Title,
132
- {
133
- className: "font-body text-xl font-bold m-0 leading-tight",
134
- style: { color: "var(--color-dark,#200C02)" },
135
- children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
136
- }
137
- ),
138
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
139
- import_radix_ui.Dialog.Description,
140
- {
141
- className: "font-body text-sm m-0",
142
- style: { color: "#6B7280" },
143
- children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
144
- }
145
- )
146
- ] }),
147
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
148
- import_radix_ui.Dialog.Close,
149
- {
150
- className: "shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-transparent border-none cursor-pointer mt-0.5 absolute top-2.5 right-1.5",
151
- style: { color: "#6B7280" },
152
- "aria-label": "Close",
153
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
156
- ] })
157
- }
158
- )
159
- ] }),
160
- !success && (loading ? (
161
- /* Loading state — full body replaced */
162
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
163
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
165
- ] })
166
- ) : (
167
- /* Email form */
168
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
169
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
170
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
171
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
172
- "input",
173
- {
174
- type: "email",
175
- value: email,
176
- onChange: (e) => setEmail(e.target.value),
177
- onKeyDown: handleKeyDown,
178
- placeholder: "hello@gmail.com",
179
- autoFocus: true,
180
- className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
181
- style: {
182
- borderColor: "#E7E5E4",
183
- background: "#fff",
184
- color: "var(--color-dark,#200C02)"
185
- }
186
- }
187
- ),
188
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
189
- ] }),
190
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
191
- "button",
192
- {
193
- onClick: handleSubmit,
194
- disabled: !email.trim(),
195
- className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
196
- style: {
197
- background: "var(--color-primary,#3D312B)",
198
- color: "var(--color-light,#FEFFF0)"
199
- },
200
- children: "Send"
201
- }
202
- ) })
203
- ] })
204
- ))
205
- ]
206
- }
207
- )
208
- ] }) });
209
- }
210
-
211
- // src/ui/SchedulingWidget.tsx
212
- var import_jsx_runtime2 = require("react/jsx-runtime");
213
- var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
214
- var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
215
- function getApiDomain() {
216
- const h = window.location.hostname;
217
- for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
218
- if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
219
- }
220
- return h;
221
- }
222
- function classRunsOnDate(cls, date, type) {
223
- if (type === "FIXED") {
224
- const d = String(date.getDate()).padStart(2, "0");
225
- const m = String(date.getMonth() + 1).padStart(2, "0");
226
- return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
227
- }
228
- return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
229
- }
230
- function formatClassTime(cls) {
231
- let h = parseInt(cls.startTime.hour, 10);
232
- const m = parseInt(cls.startTime.minutes, 10);
233
- if (cls.startTime.time === "PM" && h !== 12) h += 12;
234
- if (cls.startTime.time === "AM" && h === 12) h = 0;
235
- const endTotal = h * 60 + m + cls.duration;
236
- const eh = Math.floor(endTotal / 60) % 24;
237
- const em = endTotal % 60;
238
- return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
239
- }
240
- function getBookingsOnDate(cls, date) {
241
- if (!cls.bookings?.length) return 0;
242
- const ds = date.toISOString().split("T")[0];
243
- return cls.bookings.filter((b) => {
244
- try {
245
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
246
- } catch {
247
- return false;
248
- }
249
- }).length;
250
- }
251
- function buildTimezoneLabel(tz) {
252
- try {
253
- const now = /* @__PURE__ */ new Date();
254
- const timeStr = new Intl.DateTimeFormat("en-US", {
255
- hour: "2-digit",
256
- minute: "2-digit",
257
- hour12: false,
258
- timeZone: tz
259
- }).format(now);
260
- const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
261
- const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
262
- return `${timeStr} (${offsetPart}) ${city} time`;
263
- } catch {
264
- return tz;
265
- }
266
- }
267
- function EmptyState({ inEditor }) {
268
- const handleAddSchedule = () => {
269
- if (inEditor) {
270
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
271
- }
272
- };
273
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
274
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
275
- "svg",
276
- {
277
- width: "20",
278
- height: "20",
279
- viewBox: "0 0 24 24",
280
- fill: "none",
281
- stroke: "var(--color-accent, #A89B83)",
282
- strokeWidth: "1.5",
283
- strokeLinecap: "round",
284
- strokeLinejoin: "round",
285
- children: [
286
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
287
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
288
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
289
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
290
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
291
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
292
- ]
293
- }
294
- ) }),
295
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
296
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
297
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base font-normal text-center text-(--color-accent,#A89B83) m-0", children: "Add a scheduling widget to get instant bookings." })
298
- ] }),
299
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
300
- "button",
301
- {
302
- onClick: handleAddSchedule,
303
- className: "h-9 px-2 py-1.5 flex items-center justify-center bg-white border border-[#E7E5E4] rounded-md font-body text-sm font-medium text-(--color-dark,#200C02) cursor-pointer",
304
- children: "Add Schedule"
305
- }
306
- )
307
- ] });
308
- }
309
- function LoadingSkeleton() {
310
- const shimmer = {
311
- background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
312
- backgroundSize: "200% 100%",
313
- animation: "ohw-shimmer 1.5s infinite"
314
- };
315
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
316
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
317
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
318
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
319
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
320
- ] }, i)) }),
321
- [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
322
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
323
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
324
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
325
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
326
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
327
- ] }),
328
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
329
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
330
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
331
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
332
- ] })
333
- ] }),
334
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
335
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
336
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
337
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
338
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
339
- ] }),
340
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
341
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
342
- ] }),
343
- i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
344
- ] }, i))
345
- ] });
346
- }
347
- function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
348
- const todayMs = (0, import_react2.useMemo)(() => {
349
- const d = /* @__PURE__ */ new Date();
350
- d.setHours(0, 0, 0, 0);
351
- return d.getTime();
352
- }, []);
353
- const scrollRef = (0, import_react2.useRef)(null);
354
- const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
355
- const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
356
- const updateScrollState = () => {
357
- const el = scrollRef.current;
358
- if (!el) return;
359
- setCanScrollLeft(el.scrollLeft > 0);
360
- setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
361
- };
362
- (0, import_react2.useEffect)(() => {
363
- const el = scrollRef.current;
364
- if (!el) return;
365
- updateScrollState();
366
- el.addEventListener("scroll", updateScrollState);
367
- const ro = new ResizeObserver(updateScrollState);
368
- ro.observe(el);
369
- return () => {
370
- el.removeEventListener("scroll", updateScrollState);
371
- ro.disconnect();
372
- };
373
- }, [dates]);
374
- const scroll = (dir) => {
375
- scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
376
- };
377
- const isDragging = (0, import_react2.useRef)(false);
378
- const dragStartX = (0, import_react2.useRef)(0);
379
- const dragStartScrollLeft = (0, import_react2.useRef)(0);
380
- const onDragStart = (e) => {
381
- if (!scrollRef.current) return;
382
- isDragging.current = true;
383
- dragStartX.current = e.clientX;
384
- dragStartScrollLeft.current = scrollRef.current.scrollLeft;
385
- scrollRef.current.style.cursor = "grabbing";
386
- scrollRef.current.style.userSelect = "none";
387
- };
388
- const onDragMove = (e) => {
389
- if (!isDragging.current || !scrollRef.current) return;
390
- const delta = e.clientX - dragStartX.current;
391
- scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
392
- };
393
- const onDragEnd = () => {
394
- if (!scrollRef.current) return;
395
- isDragging.current = false;
396
- scrollRef.current.style.cursor = "grab";
397
- scrollRef.current.style.userSelect = "";
398
- };
399
- const datesWithClasses = (0, import_react2.useMemo)(
400
- () => new Set(dates.map(
401
- (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
402
- ).filter((i) => i >= 0)),
403
- [dates, schedule]
404
- );
405
- const selectedDate = dates[selectedIdx];
406
- const selectedClasses = (0, import_react2.useMemo)(
407
- () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
408
- [schedule, selectedDate]
409
- );
410
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
411
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
412
- canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
413
- "button",
414
- {
415
- onClick: () => scroll("left"),
416
- className: "hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
417
- style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
418
- "aria-label": "Scroll left",
419
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
420
- }
421
- ),
422
- canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
423
- "button",
424
- {
425
- onClick: () => scroll("right"),
426
- className: "hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
427
- style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
428
- "aria-label": "Scroll right",
429
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
430
- }
431
- ),
432
- canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
- "div",
434
- {
435
- className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
436
- style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
437
- }
438
- ),
439
- canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
440
- "div",
441
- {
442
- className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
443
- style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
444
- }
445
- ),
446
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
447
- "div",
448
- {
449
- ref: scrollRef,
450
- className: "overflow-x-auto w-full",
451
- style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
452
- onMouseDown: onDragStart,
453
- onMouseMove: onDragMove,
454
- onMouseUp: onDragEnd,
455
- onMouseLeave: onDragEnd,
456
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
457
- const isToday = date.getTime() === todayMs;
458
- const isSelected = i === selectedIdx;
459
- const clickable = datesWithClasses.has(i);
460
- const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
461
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
462
- "div",
463
- {
464
- onClick: () => clickable && onSelectDate(i),
465
- className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
466
- children: [
467
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
468
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
469
- "div",
470
- {
471
- className: "w-10 h-10 rounded-full flex items-center justify-center",
472
- style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
473
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
474
- "span",
475
- {
476
- className: "font-body text-xl font-bold text-center tracking-display-tight",
477
- style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
478
- children: date.getDate()
479
- }
480
- )
481
- }
482
- )
483
- ]
484
- },
485
- i
486
- );
487
- }) })
488
- }
489
- )
490
- ] }),
491
- selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
492
- const booked = getBookingsOnDate(cls, selectedDate);
493
- const available = cls.maxParticipants - booked;
494
- const isFull = available <= 0;
495
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
496
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
497
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
498
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
499
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
500
- "span",
501
- {
502
- className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
503
- style: { borderColor: "#0885FE", color: "#0885FE" },
504
- children: "GROUP"
505
- }
506
- ),
507
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
508
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
509
- available,
510
- "/",
511
- cls.maxParticipants
512
- ] }),
513
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
514
- ] }) })
515
- ] }),
516
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
517
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
518
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
519
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
520
- ] }),
521
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
522
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
523
- available,
524
- "/",
525
- cls.maxParticipants
526
- ] }),
527
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
528
- ] }) }),
529
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
530
- "button",
531
- {
532
- className: "w-full sm:w-[200px] sm:shrink-0 px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
533
- style: isFull ? {
534
- background: "transparent",
535
- border: "1px solid var(--color-dark, #200C02)",
536
- color: "var(--color-dark, #200C02)"
537
- } : {
538
- background: "var(--color-primary, #3D312B)",
539
- border: "none",
540
- color: "var(--color-light, #FEFFF0)"
541
- },
542
- onClick: () => {
543
- if (!cls.id) return;
544
- onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
545
- },
546
- children: isFull ? "Join Waitlist" : "Book Now"
547
- }
548
- )
549
- ] })
550
- ] }),
551
- i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
552
- ] }, cls.id ?? i);
553
- }) })
554
- ] });
555
- }
556
- function CalendarFoldIcon() {
557
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
558
- "svg",
559
- {
560
- width: "16",
561
- height: "16",
562
- viewBox: "0 0 24 24",
563
- fill: "none",
564
- stroke: "currentColor",
565
- strokeWidth: "2",
566
- strokeLinecap: "round",
567
- strokeLinejoin: "round",
568
- style: { flexShrink: 0 },
569
- children: [
570
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
571
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
572
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
573
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
574
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
575
- ]
576
- }
577
- );
578
- }
579
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
580
- const [schedule, setSchedule] = (0, import_react2.useState)(null);
581
- const [loading, setLoading] = (0, import_react2.useState)(true);
582
- const [inEditor, setInEditor] = (0, import_react2.useState)(false);
583
- const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
584
- const [modalState, setModalState] = (0, import_react2.useState)(null);
585
- const switchScheduleIdRef = (0, import_react2.useRef)(null);
586
- const dates = (0, import_react2.useMemo)(() => {
587
- const today = /* @__PURE__ */ new Date();
588
- today.setHours(0, 0, 0, 0);
589
- return Array.from({ length: 14 }, (_, i) => {
590
- const d = new Date(today);
591
- d.setDate(today.getDate() + i);
592
- return d;
593
- });
594
- }, []);
595
- const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
596
- (0, import_react2.useEffect)(() => {
597
- if (!schedule?.classes) return;
598
- for (let i = 0; i < dates.length; i++) {
599
- if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
600
- setSelectedIdx(i);
601
- return;
602
- }
603
- }
604
- }, [schedule, dates]);
605
- (0, import_react2.useEffect)(() => {
606
- if (typeof window === "undefined") return;
607
- const isInEditor = window.self !== window.top;
608
- setInEditor(isInEditor);
609
- if (isInEditor) {
610
- const startEmpty = initialScheduleId === null;
611
- if (startEmpty) setLoading(false);
612
- let initialized = startEmpty;
613
- const timer = !startEmpty ? setTimeout(() => {
614
- if (!initialized) {
615
- initialized = true;
616
- setLoading(false);
617
- }
618
- }, 5e3) : null;
619
- const handler = (e) => {
620
- if (e.data?.type === "ow:clear-scheduling-widget") {
621
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
622
- setSchedule(null);
623
- setLoading(false);
624
- return;
625
- }
626
- if (e.data?.type === "ow:switch-schedule") {
627
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
628
- switchScheduleIdRef.current = e.data.scheduleId ?? null;
629
- initialized = false;
630
- setLoading(true);
631
- window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
632
- return;
633
- }
634
- if (e.data?.type !== "ow:user-schedules-response") return;
635
- if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
636
- if (initialized && switchScheduleIdRef.current === null) return;
637
- initialized = true;
638
- if (timer) clearTimeout(timer);
639
- const schedules = e.data.schedules ?? [];
640
- const isSwitching = switchScheduleIdRef.current !== null;
641
- const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
642
- switchScheduleIdRef.current = null;
643
- setSchedule(target);
644
- setLoading(false);
645
- if (notifyOnConnect && target && !isSwitching) {
646
- window.parent.postMessage({
647
- type: "ow:schedule-connected",
648
- schedule: { id: target.id, name: target.name },
649
- insertAfter
650
- }, "*");
651
- window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
652
- }
653
- };
654
- window.addEventListener("message", handler);
655
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
656
- return () => {
657
- window.removeEventListener("message", handler);
658
- if (timer) clearTimeout(timer);
659
- };
660
- }
661
- if (initialScheduleId === null) {
662
- setLoading(false);
663
- return;
664
- }
665
- ;
666
- (async () => {
667
- try {
668
- if (initialScheduleId) {
669
- const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
670
- const data = await res.json();
671
- setSchedule(data?.id ? data : null);
672
- } else {
673
- const domain = getApiDomain();
674
- const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
675
- const slugs = await sitemapRes.json();
676
- if (!Array.isArray(slugs) || !slugs.length) {
677
- setLoading(false);
678
- return;
679
- }
680
- const { slug } = [...slugs].sort(
681
- (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
682
- )[0];
683
- const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
684
- const data = await res.json();
685
- setSchedule(data?.id ? data : null);
686
- }
687
- } catch {
688
- }
689
- setLoading(false);
690
- })();
691
- }, []);
692
- const timezoneLabel = schedule?.timezone ? (() => {
693
- try {
694
- return buildTimezoneLabel(schedule.timezone);
695
- } catch {
696
- return void 0;
697
- }
698
- })() : void 0;
699
- const handleModalSubmit = async (email) => {
700
- if (!modalState) return;
701
- const { mode, classId, classDate } = modalState;
702
- const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
703
- const res = await fetch(endpoint, {
704
- method: "POST",
705
- headers: { "Content-Type": "application/json" },
706
- body: JSON.stringify({ classDate: classDate.toISOString(), email })
707
- });
708
- if (!res.ok) {
709
- const data = await res.json().catch(() => ({}));
710
- throw new Error(data?.message ?? "Something went wrong. Please try again.");
711
- }
712
- };
713
- const handleReplaceSchedule = () => {
714
- setIsHovered(false);
715
- if (schedule) {
716
- window.parent.postMessage({
717
- type: "ow:schedule-connected",
718
- schedule: { id: schedule.id, name: schedule.name },
719
- insertAfter
720
- }, "*");
721
- } else {
722
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
723
- }
724
- };
725
- const sectionId = `scheduling-${insertAfter}`;
726
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
727
- "section",
728
- {
729
- "data-ohw-section": sectionId,
730
- "data-ohw-scheduling-anchor": insertAfter,
731
- className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
732
- onMouseEnter: () => inEditor && setIsHovered(true),
733
- onMouseLeave: () => setIsHovered(false),
734
- children: [
735
- inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
736
- "div",
737
- {
738
- className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
739
- onClick: handleReplaceSchedule,
740
- children: [
741
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
742
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
743
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
744
- "div",
745
- {
746
- className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
747
- style: {
748
- fontSize: 14,
749
- lineHeight: "24px",
750
- fontFamily: "'Figtree', system-ui, sans-serif",
751
- fontWeight: 500,
752
- color: "#0C0A09"
753
- },
754
- children: [
755
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
756
- "Replace schedule"
757
- ]
758
- }
759
- ) })
760
- ]
761
- }
762
- ),
763
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
764
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
765
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
766
- schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
767
- ] }),
768
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
769
- timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
770
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
771
- ScheduleView,
772
- {
773
- schedule,
774
- dates,
775
- selectedIdx,
776
- onSelectDate: setSelectedIdx,
777
- onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
778
- }
779
- ) })
780
- ] })
781
- ] }),
782
- modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
783
- EmailCaptureModal,
784
- {
785
- title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
786
- subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
787
- onSubmit: handleModalSubmit,
788
- onClose: () => setModalState(null)
789
- }
790
- )
791
- ]
792
- }
793
- );
794
- }
51
+ var import_react3 = __toESM(require("react"), 1);
795
52
 
796
53
  // src/ui/toggle-group.tsx
797
- var React2 = __toESM(require("react"), 1);
54
+ var React = __toESM(require("react"), 1);
798
55
 
799
56
  // node_modules/clsx/dist/clsx.mjs
800
57
  function r(e) {
@@ -4113,8 +3370,8 @@ var cva = (base, config) => (props) => {
4113
3370
  };
4114
3371
 
4115
3372
  // src/ui/toggle.tsx
4116
- var import_radix_ui2 = require("radix-ui");
4117
- var import_jsx_runtime3 = require("react/jsx-runtime");
3373
+ var import_radix_ui = require("radix-ui");
3374
+ var import_jsx_runtime = require("react/jsx-runtime");
4118
3375
  var toggleVariants = cva(
4119
3376
  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
4120
3377
  {
@@ -4141,8 +3398,8 @@ function Toggle({
4141
3398
  size,
4142
3399
  ...props
4143
3400
  }) {
4144
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4145
- import_radix_ui2.Toggle.Root,
3401
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3402
+ import_radix_ui.Toggle.Root,
4146
3403
  {
4147
3404
  "data-slot": "toggle",
4148
3405
  className: cn(toggleVariants({ variant, size, className })),
@@ -4152,8 +3409,8 @@ function Toggle({
4152
3409
  }
4153
3410
 
4154
3411
  // src/ui/toggle-group.tsx
4155
- var import_jsx_runtime4 = require("react/jsx-runtime");
4156
- var ToggleGroupContext = React2.createContext({
3412
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3413
+ var ToggleGroupContext = React.createContext({
4157
3414
  value: "",
4158
3415
  onValueChange: () => {
4159
3416
  }
@@ -4165,13 +3422,13 @@ function ToggleGroup({
4165
3422
  children,
4166
3423
  ...props
4167
3424
  }) {
4168
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3425
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4169
3426
  "div",
4170
3427
  {
4171
3428
  role: "group",
4172
3429
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
4173
3430
  ...props,
4174
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3431
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4175
3432
  }
4176
3433
  );
4177
3434
  }
@@ -4181,8 +3438,8 @@ function ToggleGroupItem({
4181
3438
  children,
4182
3439
  ...props
4183
3440
  }) {
4184
- const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
4185
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3441
+ const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3442
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4186
3443
  Toggle,
4187
3444
  {
4188
3445
  pressed: groupValue === value,
@@ -4202,7 +3459,7 @@ function ToggleGroupItem({
4202
3459
  }
4203
3460
 
4204
3461
  // src/OhhwellsBridge.tsx
4205
- var import_react_dom2 = require("react-dom");
3462
+ var import_react_dom = require("react-dom");
4206
3463
  var import_navigation = require("next/navigation");
4207
3464
 
4208
3465
  // src/lib/session-search.ts
@@ -4311,7 +3568,10 @@ function isValidUrl(urlString) {
4311
3568
  const hostname = url.hostname;
4312
3569
  if (!hostname || hostname.length === 0) return false;
4313
3570
  if (hostname.includes(" ")) return false;
4314
- return hostname.includes(".") || hostname === "localhost";
3571
+ if (hostname === "localhost") return true;
3572
+ if (!hostname.includes(".")) return false;
3573
+ if (!/[a-z]/i.test(hostname)) return false;
3574
+ return true;
4315
3575
  } catch {
4316
3576
  return false;
4317
3577
  }
@@ -4322,12 +3582,19 @@ function normalizeUrl(value) {
4322
3582
  return `https://${trimmed}`;
4323
3583
  }
4324
3584
  function validateUrlInput(value, pages, allPages) {
4325
- if (!value.trim()) return "";
4326
- const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
4327
- if (filtered.length > 0) return "";
4328
- const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
4329
- if (matchingPage) return "";
4330
- if (!isValidUrl(normalizeUrl(value))) {
3585
+ const trimmed = value.trim();
3586
+ if (!trimmed) return "";
3587
+ const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
3588
+ if (exactPage) return "";
3589
+ const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
3590
+ if (hasPrefixMatch) {
3591
+ return "Select a page from the list or enter a valid URL";
3592
+ }
3593
+ const hasScheme = /^https?:\/\//i.test(trimmed);
3594
+ if (!hasScheme && !trimmed.includes(".")) {
3595
+ return "Please enter a valid URL (e.g., https://example.com)";
3596
+ }
3597
+ if (!isValidUrl(normalizeUrl(trimmed))) {
4331
3598
  return "Please enter a valid URL (e.g., https://example.com)";
4332
3599
  }
4333
3600
  return "";
@@ -4486,9 +3753,9 @@ function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4486
3753
  }
4487
3754
 
4488
3755
  // src/ui/button.tsx
4489
- var React3 = __toESM(require("react"), 1);
4490
- var import_radix_ui3 = require("radix-ui");
4491
- var import_jsx_runtime5 = require("react/jsx-runtime");
3756
+ var React2 = __toESM(require("react"), 1);
3757
+ var import_radix_ui2 = require("radix-ui");
3758
+ var import_jsx_runtime3 = require("react/jsx-runtime");
4492
3759
  var buttonVariants = cva(
4493
3760
  "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4494
3761
  {
@@ -4509,10 +3776,10 @@ var buttonVariants = cva(
4509
3776
  }
4510
3777
  }
4511
3778
  );
4512
- var Button = React3.forwardRef(
3779
+ var Button = React2.forwardRef(
4513
3780
  ({ className, variant, size, asChild = false, ...props }, ref) => {
4514
- const Comp = asChild ? import_radix_ui3.Slot.Root : "button";
4515
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3781
+ const Comp = asChild ? import_radix_ui2.Slot.Root : "button";
3782
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4516
3783
  Comp,
4517
3784
  {
4518
3785
  ref,
@@ -4526,75 +3793,75 @@ var Button = React3.forwardRef(
4526
3793
  Button.displayName = "Button";
4527
3794
 
4528
3795
  // src/ui/icons.tsx
4529
- var import_jsx_runtime6 = require("react/jsx-runtime");
3796
+ var import_jsx_runtime4 = require("react/jsx-runtime");
4530
3797
  function IconX({ className, ...props }) {
4531
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4532
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18 6 6 18" }),
4533
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 6 12 12" })
3798
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3799
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 6 6 18" }),
3800
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 6 12 12" })
4534
3801
  ] });
4535
3802
  }
4536
3803
  function IconChevronDown({ className, ...props }) {
4537
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 9 6 6 6-6" }) });
3804
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 9 6 6 6-6" }) });
4538
3805
  }
4539
3806
  function IconFile({ className, ...props }) {
4540
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4541
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4542
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
3807
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
3809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4543
3810
  ] });
4544
3811
  }
4545
3812
  function IconInfo({ className, ...props }) {
4546
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4547
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
4548
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 16v-4" }),
4549
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 8h.01" })
3813
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3814
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
3815
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 16v-4" }),
3816
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 8h.01" })
4550
3817
  ] });
4551
3818
  }
4552
3819
  function IconArrowRight({ className, ...props }) {
4553
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4554
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 12h14" }),
4555
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m12 5 7 7-7 7" })
3820
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3821
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M5 12h14" }),
3822
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m12 5 7 7-7 7" })
4556
3823
  ] });
4557
3824
  }
4558
3825
  function IconSection({ className, ...props }) {
4559
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4560
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4561
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4562
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
3826
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3827
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
3828
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
3829
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4563
3830
  ] });
4564
3831
  }
4565
3832
 
4566
3833
  // src/ui/link-modal/DestinationBreadcrumb.tsx
4567
- var import_jsx_runtime7 = require("react/jsx-runtime");
3834
+ var import_jsx_runtime5 = require("react/jsx-runtime");
4568
3835
  function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4569
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
4570
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4571
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
4572
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
4573
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4574
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
3836
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
3837
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
3838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3", children: [
3839
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
3840
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3841
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4575
3842
  ] }),
4576
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4577
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4578
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4579
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
3843
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
3844
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
3845
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3846
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4580
3847
  ] })
4581
3848
  ] })
4582
3849
  ] });
4583
3850
  }
4584
3851
 
4585
3852
  // src/ui/link-modal/SectionTreeItem.tsx
4586
- var import_jsx_runtime8 = require("react/jsx-runtime");
3853
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4587
3854
  function SectionTreeItem({ section, onSelect, selected }) {
4588
3855
  const interactive = Boolean(onSelect);
4589
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
4590
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3856
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
3857
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4591
3858
  "div",
4592
3859
  {
4593
3860
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4594
3861
  "aria-hidden": true
4595
3862
  }
4596
3863
  ),
4597
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3864
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4598
3865
  "div",
4599
3866
  {
4600
3867
  role: interactive ? "button" : void 0,
@@ -4612,8 +3879,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
4612
3879
  interactive && selected && "border-primary"
4613
3880
  ),
4614
3881
  children: [
4615
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4616
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
3882
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3883
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4617
3884
  ]
4618
3885
  }
4619
3886
  )
@@ -4621,23 +3888,23 @@ function SectionTreeItem({ section, onSelect, selected }) {
4621
3888
  }
4622
3889
  function SectionPickerList({ sections, onSelect }) {
4623
3890
  if (sections.length === 0) {
4624
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
3891
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4625
3892
  }
4626
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
4627
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4628
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionTreeItem, { section, onSelect }, section.id))
3893
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-1", children: [
3894
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
3895
+ sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionTreeItem, { section, onSelect }, section.id))
4629
3896
  ] });
4630
3897
  }
4631
3898
 
4632
3899
  // src/ui/link-modal/UrlOrPageInput.tsx
4633
- var import_react3 = require("react");
3900
+ var import_react = require("react");
4634
3901
 
4635
3902
  // src/ui/input.tsx
4636
- var React4 = __toESM(require("react"), 1);
4637
- var import_jsx_runtime9 = require("react/jsx-runtime");
4638
- var Input = React4.forwardRef(
3903
+ var React3 = __toESM(require("react"), 1);
3904
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3905
+ var Input = React3.forwardRef(
4639
3906
  ({ className, type, ...props }, ref) => {
4640
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3907
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4641
3908
  "input",
4642
3909
  {
4643
3910
  type,
@@ -4655,11 +3922,11 @@ var Input = React4.forwardRef(
4655
3922
  Input.displayName = "Input";
4656
3923
 
4657
3924
  // src/ui/label.tsx
4658
- var import_radix_ui4 = require("radix-ui");
4659
- var import_jsx_runtime10 = require("react/jsx-runtime");
3925
+ var import_radix_ui3 = require("radix-ui");
3926
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4660
3927
  function Label({ className, ...props }) {
4661
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4662
- import_radix_ui4.Label.Root,
3928
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3929
+ import_radix_ui3.Label.Root,
4663
3930
  {
4664
3931
  "data-slot": "label",
4665
3932
  className: cn("text-sm font-medium leading-5 text-foreground", className),
@@ -4669,13 +3936,13 @@ function Label({ className, ...props }) {
4669
3936
  }
4670
3937
 
4671
3938
  // src/ui/popover.tsx
4672
- var import_radix_ui5 = require("radix-ui");
4673
- var import_jsx_runtime11 = require("react/jsx-runtime");
3939
+ var import_radix_ui4 = require("radix-ui");
3940
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4674
3941
  function Popover({ ...props }) {
4675
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Root, { "data-slot": "popover", ...props });
3942
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Root, { "data-slot": "popover", ...props });
4676
3943
  }
4677
3944
  function PopoverTrigger({ ...props }) {
4678
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
3945
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
4679
3946
  }
4680
3947
  function PopoverContent({
4681
3948
  className,
@@ -4683,8 +3950,8 @@ function PopoverContent({
4683
3950
  sideOffset = 6,
4684
3951
  ...props
4685
3952
  }) {
4686
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4687
- import_radix_ui5.Popover.Content,
3953
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3954
+ import_radix_ui4.Popover.Content,
4688
3955
  {
4689
3956
  "data-slot": "popover-content",
4690
3957
  align,
@@ -4699,9 +3966,9 @@ function PopoverContent({
4699
3966
  }
4700
3967
 
4701
3968
  // src/ui/link-modal/UrlOrPageInput.tsx
4702
- var import_jsx_runtime12 = require("react/jsx-runtime");
3969
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4703
3970
  function FieldChevron({ onClick }) {
4704
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3971
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4705
3972
  "button",
4706
3973
  {
4707
3974
  type: "button",
@@ -4709,7 +3976,7 @@ function FieldChevron({ onClick }) {
4709
3976
  onClick,
4710
3977
  "aria-label": "Open page list",
4711
3978
  tabIndex: -1,
4712
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
3979
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconChevronDown, {})
4713
3980
  }
4714
3981
  );
4715
3982
  }
@@ -4725,9 +3992,9 @@ function UrlOrPageInput({
4725
3992
  readOnly = false,
4726
3993
  urlError
4727
3994
  }) {
4728
- const inputId = (0, import_react3.useId)();
4729
- const inputRef = (0, import_react3.useRef)(null);
4730
- const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
3995
+ const inputId = (0, import_react.useId)();
3996
+ const inputRef = (0, import_react.useRef)(null);
3997
+ const [isFocused, setIsFocused] = (0, import_react.useState)(false);
4731
3998
  const openDropdown = () => {
4732
3999
  if (readOnly || filteredPages.length === 0) return;
4733
4000
  onDropdownOpenChange(true);
@@ -4748,12 +4015,12 @@ function UrlOrPageInput({
4748
4015
  "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
4749
4016
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4750
4017
  );
4751
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4752
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4753
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4754
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4755
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4756
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4018
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4019
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4020
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4021
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4022
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4023
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4757
4024
  Input,
4758
4025
  {
4759
4026
  ref: inputRef,
@@ -4772,7 +4039,7 @@ function UrlOrPageInput({
4772
4039
  )
4773
4040
  }
4774
4041
  ),
4775
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4042
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4776
4043
  "button",
4777
4044
  {
4778
4045
  type: "button",
@@ -4780,31 +4047,31 @@ function UrlOrPageInput({
4780
4047
  onMouseDown: clearSelection,
4781
4048
  "aria-label": "Clear selected page",
4782
4049
  tabIndex: -1,
4783
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconX, { "aria-hidden": true })
4050
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconX, { "aria-hidden": true })
4784
4051
  }
4785
4052
  ) : null,
4786
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4053
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4787
4054
  ] }) }),
4788
- filteredPages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4055
+ filteredPages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4789
4056
  "button",
4790
4057
  {
4791
4058
  type: "button",
4792
4059
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
4793
4060
  onClick: () => onPageSelect(page),
4794
4061
  children: [
4795
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4796
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
4062
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4063
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "truncate", children: page.title })
4797
4064
  ]
4798
4065
  },
4799
4066
  page.path
4800
4067
  )) })
4801
4068
  ] }),
4802
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4069
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4803
4070
  ] });
4804
4071
  }
4805
4072
 
4806
4073
  // src/ui/link-modal/useLinkModalState.ts
4807
- var import_react4 = require("react");
4074
+ var import_react2 = require("react");
4808
4075
  function useLinkModalState({
4809
4076
  open,
4810
4077
  mode,
@@ -4816,17 +4083,17 @@ function useLinkModalState({
4816
4083
  onClose,
4817
4084
  onSubmit
4818
4085
  }) {
4819
- const availablePages = (0, import_react4.useMemo)(
4086
+ const availablePages = (0, import_react2.useMemo)(
4820
4087
  () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4821
4088
  [mode, pages, existingTargets]
4822
4089
  );
4823
- const [searchValue, setSearchValue] = (0, import_react4.useState)("");
4824
- const [selectedPage, setSelectedPage] = (0, import_react4.useState)(null);
4825
- const [selectedSection, setSelectedSection] = (0, import_react4.useState)(null);
4826
- const [step, setStep] = (0, import_react4.useState)("input");
4827
- const [dropdownOpen, setDropdownOpen] = (0, import_react4.useState)(false);
4828
- const [urlError, setUrlError] = (0, import_react4.useState)("");
4829
- const reset = (0, import_react4.useCallback)(() => {
4090
+ const [searchValue, setSearchValue] = (0, import_react2.useState)("");
4091
+ const [selectedPage, setSelectedPage] = (0, import_react2.useState)(null);
4092
+ const [selectedSection, setSelectedSection] = (0, import_react2.useState)(null);
4093
+ const [step, setStep] = (0, import_react2.useState)("input");
4094
+ const [dropdownOpen, setDropdownOpen] = (0, import_react2.useState)(false);
4095
+ const [urlError, setUrlError] = (0, import_react2.useState)("");
4096
+ const reset = (0, import_react2.useCallback)(() => {
4830
4097
  setSearchValue("");
4831
4098
  setSelectedPage(null);
4832
4099
  setSelectedSection(null);
@@ -4834,7 +4101,7 @@ function useLinkModalState({
4834
4101
  setDropdownOpen(false);
4835
4102
  setUrlError("");
4836
4103
  }, []);
4837
- (0, import_react4.useEffect)(() => {
4104
+ (0, import_react2.useEffect)(() => {
4838
4105
  if (!open) return;
4839
4106
  if (mode === "edit" && initialTarget) {
4840
4107
  const { pageRoute } = parseTarget(initialTarget);
@@ -4850,11 +4117,11 @@ function useLinkModalState({
4850
4117
  setDropdownOpen(false);
4851
4118
  setUrlError("");
4852
4119
  }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
4853
- const filteredPages = (0, import_react4.useMemo)(() => {
4120
+ const filteredPages = (0, import_react2.useMemo)(() => {
4854
4121
  if (!searchValue.trim()) return availablePages;
4855
4122
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4856
4123
  }, [availablePages, searchValue]);
4857
- const activeSections = (0, import_react4.useMemo)(() => {
4124
+ const activeSections = (0, import_react2.useMemo)(() => {
4858
4125
  if (!selectedPage) return [];
4859
4126
  return getSectionsForPath(sectionsByPath, selectedPage.path);
4860
4127
  }, [selectedPage, sectionsByPath]);
@@ -4870,9 +4137,8 @@ function useLinkModalState({
4870
4137
  setUrlError("");
4871
4138
  const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
4872
4139
  setDropdownOpen(filtered.length > 0);
4873
- if (value.trim() && filtered.length === 0) {
4874
- const error = validateUrlInput(value, availablePages, pages);
4875
- if (error) setUrlError(error);
4140
+ if (value.trim()) {
4141
+ setUrlError(validateUrlInput(value, availablePages, pages));
4876
4142
  }
4877
4143
  };
4878
4144
  const handlePageSelect = (page) => {
@@ -4901,7 +4167,7 @@ function useLinkModalState({
4901
4167
  reset();
4902
4168
  onClose();
4903
4169
  };
4904
- const isValid = (0, import_react4.useMemo)(() => {
4170
+ const isValid = (0, import_react2.useMemo)(() => {
4905
4171
  if (urlError) return false;
4906
4172
  if (showBreadcrumb) return true;
4907
4173
  if (selectedPage) return true;
@@ -4968,7 +4234,7 @@ function useLinkModalState({
4968
4234
  }
4969
4235
 
4970
4236
  // src/ui/link-modal/LinkEditorPanel.tsx
4971
- var import_jsx_runtime13 = require("react/jsx-runtime");
4237
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4972
4238
  function LinkEditorPanel({
4973
4239
  open = true,
4974
4240
  mode = "create",
@@ -4992,26 +4258,26 @@ function LinkEditorPanel({
4992
4258
  onSubmit
4993
4259
  });
4994
4260
  const isCancel = state.secondaryLabel === "Cancel";
4995
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4996
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4261
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4262
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4997
4263
  "button",
4998
4264
  {
4999
4265
  type: "button",
5000
4266
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5001
4267
  onClick: state.handleClose,
5002
4268
  "aria-label": "Close",
5003
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
4269
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconX, { "aria-hidden": true })
5004
4270
  }
5005
4271
  ),
5006
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
5007
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5008
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4272
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4273
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4274
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5009
4275
  DestinationBreadcrumb,
5010
4276
  {
5011
4277
  pageTitle: state.selectedPage.title,
5012
4278
  sectionLabel: state.selectedSection.label
5013
4279
  }
5014
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4280
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5015
4281
  UrlOrPageInput,
5016
4282
  {
5017
4283
  value: state.searchValue,
@@ -5024,18 +4290,18 @@ function LinkEditorPanel({
5024
4290
  urlError: state.urlError
5025
4291
  }
5026
4292
  ),
5027
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5028
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5029
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5030
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5031
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
4293
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
4294
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4295
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4296
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4297
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Pick a section this link should scroll to." })
5032
4298
  ] })
5033
4299
  ] }) : null,
5034
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5035
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4300
+ state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4301
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5036
4302
  ] }),
5037
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
5038
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4303
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4304
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5039
4305
  Button,
5040
4306
  {
5041
4307
  type: "button",
@@ -5050,7 +4316,7 @@ function LinkEditorPanel({
5050
4316
  children: state.secondaryLabel
5051
4317
  }
5052
4318
  ),
5053
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4319
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5054
4320
  Button,
5055
4321
  {
5056
4322
  type: "button",
@@ -5069,7 +4335,7 @@ function LinkEditorPanel({
5069
4335
  }
5070
4336
 
5071
4337
  // src/ui/link-modal/LinkPopover.tsx
5072
- var import_jsx_runtime14 = require("react/jsx-runtime");
4338
+ var import_jsx_runtime12 = require("react/jsx-runtime");
5073
4339
  function LinkPopover({
5074
4340
  rect,
5075
4341
  parentScroll,
@@ -5077,7 +4343,7 @@ function LinkPopover({
5077
4343
  ...editorProps
5078
4344
  }) {
5079
4345
  const { top, left, transform } = calcPopoverPos(rect, parentScroll);
5080
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4346
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5081
4347
  "div",
5082
4348
  {
5083
4349
  ref: panelRef,
@@ -5090,7 +4356,7 @@ function LinkPopover({
5090
4356
  ),
5091
4357
  style: { top, left, transform },
5092
4358
  onMouseDown: (e) => e.stopPropagation(),
5093
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open: true })
4359
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LinkEditorPanel, { ...editorProps, open: true })
5094
4360
  }
5095
4361
  );
5096
4362
  }
@@ -5153,30 +4419,8 @@ function shouldUseDevFixtures() {
5153
4419
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5154
4420
  }
5155
4421
 
5156
- // src/ui/badge.tsx
5157
- var import_jsx_runtime15 = require("react/jsx-runtime");
5158
- var badgeVariants = cva(
5159
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
5160
- {
5161
- variants: {
5162
- variant: {
5163
- default: "border-transparent bg-primary text-primary-foreground",
5164
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
5165
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
5166
- outline: "text-foreground"
5167
- }
5168
- },
5169
- defaultVariants: {
5170
- variant: "default"
5171
- }
5172
- }
5173
- );
5174
- function Badge({ className, variant, ...props }) {
5175
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5176
- }
5177
-
5178
4422
  // src/OhhwellsBridge.tsx
5179
- var import_jsx_runtime16 = require("react/jsx-runtime");
4423
+ var import_jsx_runtime13 = require("react/jsx-runtime");
5180
4424
  var PRIMARY = "#0885FE";
5181
4425
  var IMAGE_FADE_MS = 300;
5182
4426
  function runOpacityFade(el, onDone) {
@@ -5227,163 +4471,6 @@ function fadeInBgImage(el, url, onReady) {
5227
4471
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
5228
4472
  });
5229
4473
  }
5230
- function getSectionsTracker() {
5231
- let el = document.querySelector("[data-ohw-sections-tracker]");
5232
- if (!el) {
5233
- el = document.createElement("div");
5234
- el.setAttribute("data-ohw-sections-tracker", "");
5235
- el.style.display = "none";
5236
- document.body.appendChild(el);
5237
- }
5238
- return el;
5239
- }
5240
- function updateSectionScheduleId(insertAfter, scheduleId) {
5241
- const tracker = getSectionsTracker();
5242
- let sections = [];
5243
- try {
5244
- sections = JSON.parse(tracker.textContent || "[]");
5245
- } catch {
5246
- }
5247
- const currentPath = window.location.pathname;
5248
- const updated = sections.map((s) => {
5249
- if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
5250
- if (s.pagePath && s.pagePath !== currentPath) return s;
5251
- return { ...s, scheduleId };
5252
- });
5253
- tracker.textContent = JSON.stringify(updated);
5254
- return tracker.textContent ?? "[]";
5255
- }
5256
- function schedulingSectionId(insertAfter) {
5257
- return `scheduling-${insertAfter}`;
5258
- }
5259
- var INSERT_BEFORE_MARKER = ":before:";
5260
- function parseSchedulingInsertAfter(insertAfter) {
5261
- const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
5262
- if (idx < 0) return { anchor: insertAfter, insertBefore: null };
5263
- return {
5264
- anchor: insertAfter.slice(0, idx),
5265
- insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
5266
- };
5267
- }
5268
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
5269
- const parsed = parseSchedulingInsertAfter(insertAfter);
5270
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
5271
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
5272
- return { effectiveInsertAfter, insertBefore };
5273
- }
5274
- function getSchedulingMountPoint(insertAfter) {
5275
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
5276
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
5277
- if (!anchorEl && anchor === "scheduling") {
5278
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
5279
- anchorEl = widgets.at(-1) ?? null;
5280
- }
5281
- if (!anchorEl) return null;
5282
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
5283
- }
5284
- function schedulingMountDepth(insertAfter) {
5285
- if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
5286
- return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
5287
- }
5288
- function getPageSchedulingEntries(raw) {
5289
- if (!raw) return [];
5290
- try {
5291
- const entries = JSON.parse(raw);
5292
- const currentPath = window.location.pathname;
5293
- return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
5294
- } catch {
5295
- return [];
5296
- }
5297
- }
5298
- function isSchedulingWidgetMissing(entry) {
5299
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
5300
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
5301
- }
5302
- function hasMissingSchedulingWidgets(entries) {
5303
- return entries.some(isSchedulingWidgetMissing);
5304
- }
5305
- function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
5306
- if (!hasMissingSchedulingWidgets(entries)) return;
5307
- mountSchedulingEntries(entries, notifyOnConnect);
5308
- }
5309
- function initSectionsFromContent(content, removeExisting = false) {
5310
- const raw = content["__ohw_sections"];
5311
- if (!raw) return;
5312
- try {
5313
- if (removeExisting) {
5314
- document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
5315
- }
5316
- const inEditor = typeof window !== "undefined" && window.self !== window.top;
5317
- if (inEditor) getSectionsTracker().textContent = raw;
5318
- const pageEntries = getPageSchedulingEntries(raw);
5319
- mountSchedulingEntries(pageEntries, false);
5320
- requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
5321
- setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
5322
- } catch {
5323
- }
5324
- }
5325
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
5326
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
5327
- const sectionId = schedulingSectionId(effectiveInsertAfter);
5328
- if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
5329
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
5330
- if (!mountPoint) return false;
5331
- const container = document.createElement("div");
5332
- container.dataset.ohwSectionContainer = "scheduling";
5333
- if (insertBefore) {
5334
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
5335
- const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
5336
- if (!beforePoint) return false;
5337
- beforePoint.insertAdjacentElement("beforebegin", container);
5338
- } else {
5339
- let tail = mountPoint;
5340
- while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
5341
- tail = tail.nextElementSibling;
5342
- }
5343
- tail.insertAdjacentElement("afterend", container);
5344
- }
5345
- const root = (0, import_client.createRoot)(container);
5346
- (0, import_react_dom.flushSync)(() => {
5347
- root.render(
5348
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5349
- SchedulingWidget,
5350
- {
5351
- notifyOnConnect,
5352
- initialScheduleId: scheduleId,
5353
- insertAfter: effectiveInsertAfter
5354
- }
5355
- )
5356
- );
5357
- });
5358
- const tracker = getSectionsTracker();
5359
- let sections = [];
5360
- try {
5361
- sections = JSON.parse(tracker.textContent || "[]");
5362
- } catch {
5363
- }
5364
- const inEditor = typeof window !== "undefined" && window.self !== window.top;
5365
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
5366
- sections.push({
5367
- type: "scheduling",
5368
- insertAfter: effectiveInsertAfter,
5369
- pagePath: window.location.pathname,
5370
- ...scheduleId ? { scheduleId } : {}
5371
- });
5372
- tracker.textContent = JSON.stringify(sections);
5373
- }
5374
- return true;
5375
- }
5376
- function mountSchedulingEntries(entries, notifyOnConnect = false) {
5377
- const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
5378
- for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
5379
- for (let i = pending.length - 1; i >= 0; i--) {
5380
- const { insertAfter, scheduleId } = pending[i];
5381
- if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
5382
- pending.splice(i, 1);
5383
- }
5384
- }
5385
- }
5386
- }
5387
4474
  function getLinkHref(el) {
5388
4475
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5389
4476
  return anchor?.getAttribute("href") ?? "";
@@ -5583,7 +4670,7 @@ var TOOLBAR_GROUPS = [
5583
4670
  ];
5584
4671
  function GlowFrame({ rect, elRef }) {
5585
4672
  const GAP = 6;
5586
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4673
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5587
4674
  "div",
5588
4675
  {
5589
4676
  ref: elRef,
@@ -5636,7 +4723,7 @@ function FloatingToolbar({
5636
4723
  onEditLink
5637
4724
  }) {
5638
4725
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
5639
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4726
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5640
4727
  "div",
5641
4728
  {
5642
4729
  ref: elRef,
@@ -5661,11 +4748,11 @@ function FloatingToolbar({
5661
4748
  },
5662
4749
  onMouseDown: (e) => e.stopPropagation(),
5663
4750
  children: [
5664
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
5665
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4751
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react3.default.Fragment, { children: [
4752
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5666
4753
  btns.map((btn) => {
5667
4754
  const isActive = activeCommands.has(btn.cmd);
5668
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4755
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5669
4756
  "button",
5670
4757
  {
5671
4758
  title: btn.title,
@@ -5691,7 +4778,7 @@ function FloatingToolbar({
5691
4778
  flexShrink: 0,
5692
4779
  padding: 6
5693
4780
  },
5694
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4781
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5695
4782
  "svg",
5696
4783
  {
5697
4784
  width: "16",
@@ -5711,7 +4798,7 @@ function FloatingToolbar({
5711
4798
  );
5712
4799
  })
5713
4800
  ] }, gi)),
5714
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4801
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5715
4802
  "button",
5716
4803
  {
5717
4804
  type: "button",
@@ -5743,9 +4830,9 @@ function FloatingToolbar({
5743
4830
  flexShrink: 0,
5744
4831
  padding: 6
5745
4832
  },
5746
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5747
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5748
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
4833
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
4834
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
4835
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5749
4836
  ] })
5750
4837
  }
5751
4838
  ) : null
@@ -5763,7 +4850,7 @@ function StateToggle({
5763
4850
  states,
5764
4851
  onStateChange
5765
4852
  }) {
5766
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4853
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5767
4854
  ToggleGroup,
5768
4855
  {
5769
4856
  "data-ohw-state-toggle": "",
@@ -5777,35 +4864,18 @@ function StateToggle({
5777
4864
  left: rect.right - 8,
5778
4865
  transform: "translateX(-100%)"
5779
4866
  },
5780
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4867
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5781
4868
  }
5782
4869
  );
5783
4870
  }
5784
4871
  var contentCache = /* @__PURE__ */ new Map();
5785
- function resolveSubdomain(subdomainFromQuery) {
5786
- if (subdomainFromQuery) return subdomainFromQuery;
5787
- if (typeof window !== "undefined") {
5788
- const parts = window.location.hostname.split(".");
5789
- if (parts.length >= 3 && parts[0] !== "www") return parts[0];
5790
- }
5791
- const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
5792
- if (siteUrl) {
5793
- try {
5794
- const host = new URL(siteUrl).hostname;
5795
- const siteParts = host.split(".");
5796
- if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
5797
- } catch {
5798
- }
5799
- }
5800
- return "";
5801
- }
5802
4872
  function OhhwellsBridge() {
5803
4873
  const pathname = (0, import_navigation.usePathname)();
5804
4874
  const router = (0, import_navigation.useRouter)();
5805
4875
  const searchParams = (0, import_navigation.useSearchParams)();
5806
4876
  const isEditMode = isEditSessionActive();
5807
- const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
5808
- (0, import_react5.useEffect)(() => {
4877
+ const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
4878
+ (0, import_react3.useEffect)(() => {
5809
4879
  const figtreeFontId = "ohw-figtree-font";
5810
4880
  if (!document.getElementById(figtreeFontId)) {
5811
4881
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -5828,52 +4898,55 @@ function OhhwellsBridge() {
5828
4898
  };
5829
4899
  }, []);
5830
4900
  const subdomainFromQuery = searchParams.get("subdomain");
5831
- const subdomain = resolveSubdomain(subdomainFromQuery);
5832
- const postToParent = (0, import_react5.useCallback)((data) => {
4901
+ const subdomain = subdomainFromQuery ?? (() => {
4902
+ if (typeof window === "undefined") return "";
4903
+ const parts = window.location.hostname.split(".");
4904
+ return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4905
+ })();
4906
+ const postToParent = (0, import_react3.useCallback)((data) => {
5833
4907
  if (typeof window !== "undefined" && window.parent !== window) {
5834
4908
  window.parent.postMessage(data, "*");
5835
4909
  }
5836
4910
  }, []);
5837
- const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
5838
- const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
5839
- const activeElRef = (0, import_react5.useRef)(null);
5840
- const originalContentRef = (0, import_react5.useRef)(null);
5841
- const activeStateElRef = (0, import_react5.useRef)(null);
5842
- const parentScrollRef = (0, import_react5.useRef)(null);
5843
- const toolbarElRef = (0, import_react5.useRef)(null);
5844
- const glowElRef = (0, import_react5.useRef)(null);
5845
- const hoveredImageRef = (0, import_react5.useRef)(null);
5846
- const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
5847
- const hoveredGapRef = (0, import_react5.useRef)(null);
5848
- const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
5849
- const imageShowTimerRef = (0, import_react5.useRef)(null);
5850
- const editStylesRef = (0, import_react5.useRef)(null);
5851
- const activateRef = (0, import_react5.useRef)(() => {
4911
+ const [fetchState, setFetchState] = (0, import_react3.useState)("idle");
4912
+ const autoSaveTimers = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
4913
+ const activeElRef = (0, import_react3.useRef)(null);
4914
+ const originalContentRef = (0, import_react3.useRef)(null);
4915
+ const activeStateElRef = (0, import_react3.useRef)(null);
4916
+ const parentScrollRef = (0, import_react3.useRef)(null);
4917
+ const toolbarElRef = (0, import_react3.useRef)(null);
4918
+ const glowElRef = (0, import_react3.useRef)(null);
4919
+ const hoveredImageRef = (0, import_react3.useRef)(null);
4920
+ const hoveredImageHasTextOverlapRef = (0, import_react3.useRef)(false);
4921
+ const imageUnhoverTimerRef = (0, import_react3.useRef)(null);
4922
+ const imageShowTimerRef = (0, import_react3.useRef)(null);
4923
+ const editStylesRef = (0, import_react3.useRef)(null);
4924
+ const activateRef = (0, import_react3.useRef)(() => {
5852
4925
  });
5853
- const deactivateRef = (0, import_react5.useRef)(() => {
4926
+ const deactivateRef = (0, import_react3.useRef)(() => {
5854
4927
  });
5855
- const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
4928
+ const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
5856
4929
  });
5857
- const postToParentRef = (0, import_react5.useRef)(postToParent);
4930
+ const postToParentRef = (0, import_react3.useRef)(postToParent);
5858
4931
  postToParentRef.current = postToParent;
5859
- const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
5860
- const [toggleState, setToggleState] = (0, import_react5.useState)(null);
5861
- const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
5862
- const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
5863
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
5864
- const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
5865
- const [sitePages, setSitePages] = (0, import_react5.useState)([]);
5866
- const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
5867
- const sectionsPrefetchGenRef = (0, import_react5.useRef)(0);
5868
- const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
5869
- const linkPopoverPanelRef = (0, import_react5.useRef)(null);
5870
- const linkPopoverOpenRef = (0, import_react5.useRef)(false);
5871
- const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
4932
+ const [toolbarRect, setToolbarRect] = (0, import_react3.useState)(null);
4933
+ const [toggleState, setToggleState] = (0, import_react3.useState)(null);
4934
+ const [maxBadge, setMaxBadge] = (0, import_react3.useState)(null);
4935
+ const [activeCommands, setActiveCommands] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
4936
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react3.useState)(false);
4937
+ const [linkPopover, setLinkPopover] = (0, import_react3.useState)(null);
4938
+ const [sitePages, setSitePages] = (0, import_react3.useState)([]);
4939
+ const [sectionsByPath, setSectionsByPath] = (0, import_react3.useState)({});
4940
+ const sectionsPrefetchGenRef = (0, import_react3.useRef)(0);
4941
+ const setLinkPopoverRef = (0, import_react3.useRef)(setLinkPopover);
4942
+ const linkPopoverPanelRef = (0, import_react3.useRef)(null);
4943
+ const linkPopoverOpenRef = (0, import_react3.useRef)(false);
4944
+ const linkPopoverGraceUntilRef = (0, import_react3.useRef)(0);
5872
4945
  setLinkPopoverRef.current = setLinkPopover;
5873
4946
  const bumpLinkPopoverGrace = () => {
5874
4947
  linkPopoverGraceUntilRef.current = Date.now() + 350;
5875
4948
  };
5876
- const runSectionsPrefetch = (0, import_react5.useCallback)((pages) => {
4949
+ const runSectionsPrefetch = (0, import_react3.useCallback)((pages) => {
5877
4950
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
5878
4951
  const gen = ++sectionsPrefetchGenRef.current;
5879
4952
  const paths = pages.map((p) => p.path);
@@ -5892,9 +4965,9 @@ function OhhwellsBridge() {
5892
4965
  );
5893
4966
  });
5894
4967
  }, [isEditMode, pathname]);
5895
- const runSectionsPrefetchRef = (0, import_react5.useRef)(runSectionsPrefetch);
4968
+ const runSectionsPrefetchRef = (0, import_react3.useRef)(runSectionsPrefetch);
5896
4969
  runSectionsPrefetchRef.current = runSectionsPrefetch;
5897
- (0, import_react5.useEffect)(() => {
4970
+ (0, import_react3.useEffect)(() => {
5898
4971
  if (!linkPopover) return;
5899
4972
  if (hoveredImageRef.current) {
5900
4973
  hoveredImageRef.current = null;
@@ -5902,7 +4975,7 @@ function OhhwellsBridge() {
5902
4975
  }
5903
4976
  postToParent({ type: "ow:image-unhover" });
5904
4977
  }, [linkPopover, postToParent]);
5905
- (0, import_react5.useEffect)(() => {
4978
+ (0, import_react3.useEffect)(() => {
5906
4979
  if (!isEditMode) return;
5907
4980
  const useFixtures = shouldUseDevFixtures();
5908
4981
  if (useFixtures) {
@@ -5926,15 +4999,14 @@ function OhhwellsBridge() {
5926
4999
  if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5927
5000
  return () => window.removeEventListener("message", onSitePages);
5928
5001
  }, [isEditMode, postToParent]);
5929
- const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
5930
- (0, import_react5.useEffect)(() => {
5002
+ (0, import_react3.useEffect)(() => {
5931
5003
  if (!isEditMode || shouldUseDevFixtures()) return;
5932
5004
  void loadAllSectionsManifest().then((manifest) => {
5933
5005
  if (Object.keys(manifest).length === 0) return;
5934
5006
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
5935
5007
  });
5936
5008
  }, [isEditMode]);
5937
- (0, import_react5.useEffect)(() => {
5009
+ (0, import_react3.useEffect)(() => {
5938
5010
  const update = () => {
5939
5011
  const el = activeElRef.current;
5940
5012
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -5942,12 +5014,6 @@ function OhhwellsBridge() {
5942
5014
  if (!prev || !activeStateElRef.current) return prev;
5943
5015
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
5944
5016
  });
5945
- setSectionGap((prev) => {
5946
- if (!prev) return prev;
5947
- const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
5948
- if (!anchor) return prev;
5949
- return { ...prev, y: anchor.getBoundingClientRect().bottom };
5950
- });
5951
5017
  };
5952
5018
  const vvp = window.visualViewport;
5953
5019
  if (!vvp) return;
@@ -5958,10 +5024,10 @@ function OhhwellsBridge() {
5958
5024
  vvp.removeEventListener("resize", update);
5959
5025
  };
5960
5026
  }, []);
5961
- const refreshStateRules = (0, import_react5.useCallback)(() => {
5027
+ const refreshStateRules = (0, import_react3.useCallback)(() => {
5962
5028
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
5963
5029
  }, []);
5964
- const deactivate = (0, import_react5.useCallback)(() => {
5030
+ const deactivate = (0, import_react3.useCallback)(() => {
5965
5031
  const el = activeElRef.current;
5966
5032
  if (!el) return;
5967
5033
  const key = el.dataset.ohwKey;
@@ -5987,7 +5053,7 @@ function OhhwellsBridge() {
5987
5053
  setToolbarShowEditLink(false);
5988
5054
  postToParent({ type: "ow:exit-edit" });
5989
5055
  }, [postToParent]);
5990
- const activate = (0, import_react5.useCallback)((el) => {
5056
+ const activate = (0, import_react3.useCallback)((el) => {
5991
5057
  if (activeElRef.current === el) return;
5992
5058
  deactivate();
5993
5059
  if (hoveredImageRef.current) {
@@ -6006,7 +5072,7 @@ function OhhwellsBridge() {
6006
5072
  }, [deactivate, postToParent]);
6007
5073
  activateRef.current = activate;
6008
5074
  deactivateRef.current = deactivate;
6009
- (0, import_react5.useLayoutEffect)(() => {
5075
+ (0, import_react3.useLayoutEffect)(() => {
6010
5076
  if (!subdomain || isEditMode) {
6011
5077
  setFetchState("done");
6012
5078
  return;
@@ -6014,7 +5080,6 @@ function OhhwellsBridge() {
6014
5080
  const applyContent = (content) => {
6015
5081
  const imageLoads = [];
6016
5082
  for (const [key, val] of Object.entries(content)) {
6017
- if (key === "__ohw_sections") continue;
6018
5083
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6019
5084
  if (el.dataset.ohwEditable === "image") {
6020
5085
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6036,7 +5101,6 @@ function OhhwellsBridge() {
6036
5101
  });
6037
5102
  applyLinkByKey(key, val);
6038
5103
  }
6039
- initSectionsFromContent(content, true);
6040
5104
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
6041
5105
  }) : Promise.resolve();
6042
5106
  };
@@ -6061,15 +5125,12 @@ function OhhwellsBridge() {
6061
5125
  cancelled = true;
6062
5126
  };
6063
5127
  }, [subdomain, isEditMode, pathname]);
6064
- (0, import_react5.useEffect)(() => {
5128
+ (0, import_react3.useEffect)(() => {
6065
5129
  if (!subdomain || isEditMode) return;
6066
- let debounceTimer = null;
6067
5130
  const applyFromCache = () => {
6068
5131
  const content = contentCache.get(subdomain);
6069
5132
  if (!content) return;
6070
- retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
6071
5133
  for (const [key, val] of Object.entries(content)) {
6072
- if (key === "__ohw_sections") continue;
6073
5134
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6074
5135
  if (el.dataset.ohwEditable === "image") {
6075
5136
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6086,28 +5147,21 @@ function OhhwellsBridge() {
6086
5147
  applyLinkByKey(key, val);
6087
5148
  }
6088
5149
  };
6089
- const scheduleApply = () => {
6090
- if (debounceTimer) clearTimeout(debounceTimer);
6091
- debounceTimer = setTimeout(applyFromCache, 150);
6092
- };
6093
- scheduleApply();
6094
- const observer = new MutationObserver(scheduleApply);
5150
+ applyFromCache();
5151
+ const observer = new MutationObserver(applyFromCache);
6095
5152
  observer.observe(document.body, { childList: true, subtree: true });
6096
- return () => {
6097
- observer.disconnect();
6098
- if (debounceTimer) clearTimeout(debounceTimer);
6099
- };
6100
- }, [subdomain, isEditMode, pathname]);
6101
- (0, import_react5.useLayoutEffect)(() => {
5153
+ return () => observer.disconnect();
5154
+ }, [subdomain, isEditMode]);
5155
+ (0, import_react3.useLayoutEffect)(() => {
6102
5156
  const el = document.getElementById("ohw-loader");
6103
5157
  if (!el) return;
6104
5158
  const visible = Boolean(subdomain) && fetchState !== "done";
6105
5159
  el.style.display = visible ? "flex" : "none";
6106
5160
  }, [subdomain, fetchState]);
6107
- (0, import_react5.useEffect)(() => {
5161
+ (0, import_react3.useEffect)(() => {
6108
5162
  postToParent({ type: "ow:navigation", path: pathname });
6109
5163
  }, [pathname, postToParent]);
6110
- (0, import_react5.useEffect)(() => {
5164
+ (0, import_react3.useEffect)(() => {
6111
5165
  if (!isEditMode) return;
6112
5166
  const measure = () => {
6113
5167
  const h = document.body.scrollHeight;
@@ -6131,7 +5185,7 @@ function OhhwellsBridge() {
6131
5185
  window.removeEventListener("resize", handleResize);
6132
5186
  };
6133
5187
  }, [pathname, isEditMode, postToParent]);
6134
- (0, import_react5.useEffect)(() => {
5188
+ (0, import_react3.useEffect)(() => {
6135
5189
  if (!subdomainFromQuery || isEditMode) return;
6136
5190
  const handleClick = (e) => {
6137
5191
  const anchor = e.target.closest("a");
@@ -6147,7 +5201,7 @@ function OhhwellsBridge() {
6147
5201
  document.addEventListener("click", handleClick, true);
6148
5202
  return () => document.removeEventListener("click", handleClick, true);
6149
5203
  }, [subdomainFromQuery, isEditMode, router]);
6150
- (0, import_react5.useEffect)(() => {
5204
+ (0, import_react3.useEffect)(() => {
6151
5205
  if (!isEditMode) {
6152
5206
  editStylesRef.current?.base.remove();
6153
5207
  editStylesRef.current?.forceHover.remove();
@@ -6448,15 +5502,6 @@ function OhhwellsBridge() {
6448
5502
  textEditable.setAttribute("data-ohw-hovered", "");
6449
5503
  return;
6450
5504
  }
6451
- if (hoveredGapRef.current) {
6452
- if (hoveredImageRef.current) {
6453
- hoveredImageRef.current = null;
6454
- resumeAnimTracks();
6455
- postToParentRef.current({ type: "ow:image-unhover" });
6456
- }
6457
- document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6458
- return;
6459
- }
6460
5505
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6461
5506
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
6462
5507
  hoveredImageRef.current = imgEl;
@@ -6522,30 +5567,8 @@ function OhhwellsBridge() {
6522
5567
  }
6523
5568
  }
6524
5569
  };
6525
- const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6526
- const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6527
- const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6528
- const ZONE = 20;
6529
- for (let i = 0; i < sections.length; i++) {
6530
- const a = sections[i];
6531
- const b = sections[i + 1] ?? null;
6532
- const boundaryY = a.getBoundingClientRect().bottom;
6533
- if (Math.abs(y - boundaryY) <= ZONE) {
6534
- const insertAfter = a.dataset.ohwSection ?? "";
6535
- const insertBefore = b?.dataset.ohwSection ?? null;
6536
- hoveredGapRef.current = { insertAfter, insertBefore };
6537
- setSectionGap({ insertAfter, insertBefore, y: boundaryY });
6538
- return;
6539
- }
6540
- }
6541
- if (hoveredGapRef.current) {
6542
- hoveredGapRef.current = null;
6543
- setSectionGap(null);
6544
- }
6545
- };
6546
5570
  const handleMouseMove = (e) => {
6547
5571
  const { clientX, clientY } = e;
6548
- probeSectionGapAt(clientX, clientY);
6549
5572
  probeImageAt(clientX, clientY);
6550
5573
  probeHoverCardsAt(clientX, clientY);
6551
5574
  };
@@ -6553,7 +5576,6 @@ function OhhwellsBridge() {
6553
5576
  if (e.data?.type !== "ow:pointer-sync") return;
6554
5577
  const { clientX, clientY } = e.data;
6555
5578
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
6556
- probeSectionGapAt(clientX, clientY);
6557
5579
  probeImageAt(clientX, clientY);
6558
5580
  probeHoverCardsAt(clientX, clientY);
6559
5581
  };
@@ -6711,12 +5733,7 @@ function OhhwellsBridge() {
6711
5733
  if (e.data?.type !== "ow:hydrate") return;
6712
5734
  const content = e.data.content;
6713
5735
  if (!content) return;
6714
- let sectionsJson = null;
6715
5736
  for (const [key, val] of Object.entries(content)) {
6716
- if (key === "__ohw_sections") {
6717
- sectionsJson = val;
6718
- continue;
6719
- }
6720
5737
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6721
5738
  if (el.dataset.ohwEditable === "image") {
6722
5739
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6731,9 +5748,6 @@ function OhhwellsBridge() {
6731
5748
  });
6732
5749
  applyLinkByKey(key, val);
6733
5750
  }
6734
- if (sectionsJson) {
6735
- initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
6736
- }
6737
5751
  postToParentRef.current({ type: "ow:hydrate-done" });
6738
5752
  };
6739
5753
  window.addEventListener("message", handleHydrate);
@@ -6786,63 +5800,7 @@ function OhhwellsBridge() {
6786
5800
  };
6787
5801
  const handleSave = (e) => {
6788
5802
  if (e.data?.type !== "ow:save") return;
6789
- const nodes = collectEditableNodes();
6790
- const tracker = document.querySelector("[data-ohw-sections-tracker]");
6791
- if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
6792
- postToParentRef.current({ type: "ow:save-result", nodes });
6793
- };
6794
- const handleInsertSection = (e) => {
6795
- if (e.data?.type !== "ow:insert-section") return;
6796
- const { widgetType, insertAfter, insertBefore } = e.data;
6797
- if (widgetType !== "scheduling") return;
6798
- const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
6799
- if (inserted) {
6800
- const tracker = getSectionsTracker();
6801
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
6802
- const h = document.documentElement.scrollHeight;
6803
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
6804
- }
6805
- };
6806
- const handleSwitchSchedule = (e) => {
6807
- if (e.data?.type !== "ow:switch-schedule") return;
6808
- const scheduleId = e.data.scheduleId;
6809
- const insertAfter = e.data.insertAfter;
6810
- if (!scheduleId || !insertAfter) return;
6811
- const text = updateSectionScheduleId(insertAfter, scheduleId);
6812
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6813
- };
6814
- const handleScheduleLinked = (e) => {
6815
- if (e.data?.type !== "ow:schedule-linked") return;
6816
- const scheduleId = e.data.scheduleId;
6817
- const insertAfter = e.data.insertAfter;
6818
- if (!scheduleId || !insertAfter) return;
6819
- const text = updateSectionScheduleId(insertAfter, scheduleId);
6820
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6821
- };
6822
- const handleClearSchedulingWidget = (e) => {
6823
- if (e.data?.type !== "ow:clear-scheduling-widget") return;
6824
- const insertAfter = e.data.insertAfter;
6825
- if (!insertAfter) return;
6826
- const text = updateSectionScheduleId(insertAfter, null);
6827
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6828
- };
6829
- const handleRemoveSchedulingSection = (e) => {
6830
- if (e.data?.type !== "ow:remove-scheduling-section") return;
6831
- document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
6832
- el.remove();
6833
- });
6834
- const tracker = getSectionsTracker();
6835
- let sections = [];
6836
- try {
6837
- sections = JSON.parse(tracker.textContent || "[]");
6838
- } catch {
6839
- }
6840
- const currentPath = window.location.pathname;
6841
- const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
6842
- tracker.textContent = JSON.stringify(updated);
6843
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6844
- const h = document.documentElement.scrollHeight;
6845
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5803
+ postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
6846
5804
  };
6847
5805
  const handleSelectionChange = () => {
6848
5806
  if (!activeElRef.current) return;
@@ -6947,11 +5905,6 @@ function OhhwellsBridge() {
6947
5905
  deactivateRef.current();
6948
5906
  };
6949
5907
  window.addEventListener("message", handleSave);
6950
- window.addEventListener("message", handleInsertSection);
6951
- window.addEventListener("message", handleSwitchSchedule);
6952
- window.addEventListener("message", handleScheduleLinked);
6953
- window.addEventListener("message", handleClearSchedulingWidget);
6954
- window.addEventListener("message", handleRemoveSchedulingSection);
6955
5908
  window.addEventListener("message", handleImageUrl);
6956
5909
  window.addEventListener("message", handleAnimLock);
6957
5910
  window.addEventListener("message", handleCanvasHeight);
@@ -6986,11 +5939,6 @@ function OhhwellsBridge() {
6986
5939
  document.removeEventListener("selectionchange", handleSelectionChange);
6987
5940
  window.removeEventListener("scroll", handleScroll, true);
6988
5941
  window.removeEventListener("message", handleSave);
6989
- window.removeEventListener("message", handleInsertSection);
6990
- window.removeEventListener("message", handleSwitchSchedule);
6991
- window.removeEventListener("message", handleScheduleLinked);
6992
- window.removeEventListener("message", handleClearSchedulingWidget);
6993
- window.removeEventListener("message", handleRemoveSchedulingSection);
6994
5942
  window.removeEventListener("message", handleImageUrl);
6995
5943
  window.removeEventListener("message", handleAnimLock);
6996
5944
  window.removeEventListener("message", handleCanvasHeight);
@@ -7005,7 +5953,7 @@ function OhhwellsBridge() {
7005
5953
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7006
5954
  };
7007
5955
  }, [isEditMode, refreshStateRules]);
7008
- (0, import_react5.useEffect)(() => {
5956
+ (0, import_react3.useEffect)(() => {
7009
5957
  if (!isEditMode) return;
7010
5958
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7011
5959
  el.removeAttribute("data-ohw-active-state");
@@ -7034,19 +5982,19 @@ function OhhwellsBridge() {
7034
5982
  clearTimeout(timer);
7035
5983
  };
7036
5984
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
7037
- (0, import_react5.useEffect)(() => {
5985
+ (0, import_react3.useEffect)(() => {
7038
5986
  scrollToHashSectionWhenReady();
7039
5987
  const onHashChange = () => scrollToHashSectionWhenReady();
7040
5988
  window.addEventListener("hashchange", onHashChange);
7041
5989
  return () => window.removeEventListener("hashchange", onHashChange);
7042
5990
  }, [pathname]);
7043
- const handleCommand = (0, import_react5.useCallback)((cmd) => {
5991
+ const handleCommand = (0, import_react3.useCallback)((cmd) => {
7044
5992
  document.execCommand(cmd, false);
7045
5993
  activeElRef.current?.focus();
7046
5994
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
7047
5995
  refreshActiveCommandsRef.current();
7048
5996
  }, []);
7049
- const handleStateChange = (0, import_react5.useCallback)((state) => {
5997
+ const handleStateChange = (0, import_react3.useCallback)((state) => {
7050
5998
  if (!activeStateElRef.current) return;
7051
5999
  const el = activeStateElRef.current;
7052
6000
  if (state === "Default") {
@@ -7059,8 +6007,8 @@ function OhhwellsBridge() {
7059
6007
  }
7060
6008
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
7061
6009
  }, [deactivate]);
7062
- const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
7063
- const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
6010
+ const closeLinkPopover = (0, import_react3.useCallback)(() => setLinkPopover(null), []);
6011
+ const openLinkPopoverForActive = (0, import_react3.useCallback)(() => {
7064
6012
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
7065
6013
  if (!hrefCtx) return;
7066
6014
  bumpLinkPopoverGrace();
@@ -7070,7 +6018,7 @@ function OhhwellsBridge() {
7070
6018
  rect: hrefCtx.anchor.getBoundingClientRect()
7071
6019
  });
7072
6020
  }, []);
7073
- const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
6021
+ const handleLinkPopoverSubmit = (0, import_react3.useCallback)(
7074
6022
  (target) => {
7075
6023
  if (!linkPopover) return;
7076
6024
  const { key } = linkPopover;
@@ -7083,11 +6031,11 @@ function OhhwellsBridge() {
7083
6031
  const showEditLink = toolbarShowEditLink;
7084
6032
  const currentSections = sectionsByPath[pathname] ?? [];
7085
6033
  linkPopoverOpenRef.current = linkPopover !== null;
7086
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
7087
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7088
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7089
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7090
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6034
+ return bridgeRoot ? (0, import_react_dom.createPortal)(
6035
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6036
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6037
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
6038
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7091
6039
  FloatingToolbar,
7092
6040
  {
7093
6041
  rect: toolbarRect,
@@ -7100,7 +6048,7 @@ function OhhwellsBridge() {
7100
6048
  }
7101
6049
  )
7102
6050
  ] }),
7103
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
6051
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7104
6052
  "div",
7105
6053
  {
7106
6054
  "data-ohw-max-badge": "",
@@ -7126,7 +6074,7 @@ function OhhwellsBridge() {
7126
6074
  ]
7127
6075
  }
7128
6076
  ),
7129
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6077
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7130
6078
  StateToggle,
7131
6079
  {
7132
6080
  rect: toggleState.rect,
@@ -7135,7 +6083,7 @@ function OhhwellsBridge() {
7135
6083
  onStateChange: handleStateChange
7136
6084
  }
7137
6085
  ),
7138
- linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6086
+ linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7139
6087
  LinkPopover,
7140
6088
  {
7141
6089
  rect: linkPopover.rect,
@@ -7150,32 +6098,7 @@ function OhhwellsBridge() {
7150
6098
  onClose: closeLinkPopover,
7151
6099
  onSubmit: handleLinkPopoverSubmit
7152
6100
  }
7153
- ) : null,
7154
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7155
- "div",
7156
- {
7157
- "data-ohw-section-insert-line": "",
7158
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7159
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
7160
- children: [
7161
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7162
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7163
- Badge,
7164
- {
7165
- className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
7166
- onClick: () => {
7167
- window.parent.postMessage(
7168
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7169
- "*"
7170
- );
7171
- },
7172
- children: "Add Section"
7173
- }
7174
- ),
7175
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7176
- ]
7177
- }
7178
- )
6101
+ ) : null
7179
6102
  ] }),
7180
6103
  bridgeRoot
7181
6104
  ) : null;
@@ -7185,7 +6108,6 @@ function OhhwellsBridge() {
7185
6108
  LinkEditorPanel,
7186
6109
  LinkPopover,
7187
6110
  OhhwellsBridge,
7188
- SchedulingWidget,
7189
6111
  Toggle,
7190
6112
  ToggleGroup,
7191
6113
  ToggleGroupItem,