@ohhwells/bridge 0.1.14 → 0.1.16-next.12

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.js CHANGED
@@ -1,10 +1,752 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React2, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
5
+ import { createRoot } from "react-dom/client";
6
+ import { flushSync } from "react-dom";
7
+
8
+ // src/ui/SchedulingWidget.tsx
9
+ import { useEffect, useMemo, useRef, useState as useState2 } from "react";
10
+
11
+ // src/ui/EmailCaptureModal.tsx
12
+ import { useState } from "react";
13
+ import { Dialog } from "radix-ui";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ function Spinner() {
16
+ return /* @__PURE__ */ jsxs(
17
+ "svg",
18
+ {
19
+ width: "32",
20
+ height: "32",
21
+ viewBox: "0 0 32 32",
22
+ fill: "none",
23
+ style: { animation: "ohw-spin 0.75s linear infinite" },
24
+ children: [
25
+ /* @__PURE__ */ jsx("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
26
+ /* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
27
+ /* @__PURE__ */ jsx(
28
+ "path",
29
+ {
30
+ d: "M16 4a12 12 0 0 1 12 12",
31
+ stroke: "#3b82f6",
32
+ strokeWidth: "3",
33
+ strokeLinecap: "round"
34
+ }
35
+ )
36
+ ]
37
+ }
38
+ );
39
+ }
40
+ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
41
+ const [email, setEmail] = useState("");
42
+ const [submittedEmail, setSubmittedEmail] = useState("");
43
+ const [loading, setLoading] = useState(false);
44
+ const [success, setSuccess] = useState(false);
45
+ const [error, setError] = useState(null);
46
+ const isBook = title === "Confirm your spot";
47
+ const handleSubmit = async () => {
48
+ if (!email.trim()) return;
49
+ setLoading(true);
50
+ setError(null);
51
+ try {
52
+ await onSubmit(email.trim());
53
+ setSubmittedEmail(email.trim());
54
+ setSuccess(true);
55
+ } catch (e) {
56
+ setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+ const handleKeyDown = (e) => {
62
+ if (e.key === "Enter" && !loading) handleSubmit();
63
+ };
64
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: true, onOpenChange: (open) => {
65
+ if (!open) onClose();
66
+ }, children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
67
+ /* @__PURE__ */ jsx(
68
+ Dialog.Overlay,
69
+ {
70
+ className: "fixed inset-0 z-50",
71
+ style: { background: "rgba(0,0,0,0.45)" }
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxs(
75
+ Dialog.Content,
76
+ {
77
+ 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",
78
+ style: { maxWidth: 400 },
79
+ children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 p-6", children: [
81
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
82
+ /* @__PURE__ */ jsx(
83
+ Dialog.Title,
84
+ {
85
+ className: "font-body text-xl font-bold m-0 leading-tight",
86
+ style: { color: "var(--color-dark,#200C02)" },
87
+ children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx(
91
+ Dialog.Description,
92
+ {
93
+ className: "font-body text-sm m-0",
94
+ style: { color: "#6B7280" },
95
+ children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
96
+ }
97
+ )
98
+ ] }),
99
+ /* @__PURE__ */ jsx(
100
+ Dialog.Close,
101
+ {
102
+ 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",
103
+ style: { color: "#6B7280" },
104
+ "aria-label": "Close",
105
+ children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
106
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
107
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
108
+ ] })
109
+ }
110
+ )
111
+ ] }),
112
+ !success && (loading ? (
113
+ /* Loading state — full body replaced */
114
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
115
+ /* @__PURE__ */ jsx(Spinner, {}),
116
+ /* @__PURE__ */ jsx("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
117
+ ] })
118
+ ) : (
119
+ /* Email form */
120
+ /* @__PURE__ */ jsxs("div", { className: "px-7 pt-2 pb-6", children: [
121
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 mb-8", children: [
122
+ /* @__PURE__ */ jsx("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
123
+ /* @__PURE__ */ jsx(
124
+ "input",
125
+ {
126
+ type: "email",
127
+ value: email,
128
+ onChange: (e) => setEmail(e.target.value),
129
+ onKeyDown: handleKeyDown,
130
+ placeholder: "hello@gmail.com",
131
+ autoFocus: true,
132
+ className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
133
+ style: {
134
+ borderColor: "#E7E5E4",
135
+ background: "#fff",
136
+ color: "var(--color-dark,#200C02)"
137
+ }
138
+ }
139
+ ),
140
+ error && /* @__PURE__ */ jsx("p", { className: "font-body text-xs text-red-500 m-0", children: error })
141
+ ] }),
142
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
143
+ "button",
144
+ {
145
+ onClick: handleSubmit,
146
+ disabled: !email.trim(),
147
+ className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
148
+ style: {
149
+ background: "var(--color-primary,#3D312B)",
150
+ color: "var(--color-light,#FEFFF0)"
151
+ },
152
+ children: "Send"
153
+ }
154
+ ) })
155
+ ] })
156
+ ))
157
+ ]
158
+ }
159
+ )
160
+ ] }) });
161
+ }
162
+
163
+ // src/ui/SchedulingWidget.tsx
164
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
165
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
166
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
167
+ function getApiDomain() {
168
+ const h = window.location.hostname;
169
+ for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
170
+ if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
171
+ }
172
+ return h;
173
+ }
174
+ function classRunsOnDate(cls, date, type) {
175
+ if (type === "FIXED") {
176
+ const d = String(date.getDate()).padStart(2, "0");
177
+ const m = String(date.getMonth() + 1).padStart(2, "0");
178
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
179
+ }
180
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
181
+ }
182
+ function formatClassTime(cls) {
183
+ let h = parseInt(cls.startTime.hour, 10);
184
+ const m = parseInt(cls.startTime.minutes, 10);
185
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
186
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
187
+ const endTotal = h * 60 + m + cls.duration;
188
+ const eh = Math.floor(endTotal / 60) % 24;
189
+ const em = endTotal % 60;
190
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
191
+ }
192
+ function getBookingsOnDate(cls, date) {
193
+ if (!cls.bookings?.length) return 0;
194
+ const ds = date.toISOString().split("T")[0];
195
+ return cls.bookings.filter((b) => {
196
+ try {
197
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
198
+ } catch {
199
+ return false;
200
+ }
201
+ }).length;
202
+ }
203
+ function buildTimezoneLabel(tz) {
204
+ try {
205
+ const now = /* @__PURE__ */ new Date();
206
+ const timeStr = new Intl.DateTimeFormat("en-US", {
207
+ hour: "2-digit",
208
+ minute: "2-digit",
209
+ hour12: false,
210
+ timeZone: tz
211
+ }).format(now);
212
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
213
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
214
+ return `${timeStr} (${offsetPart}) ${city} time`;
215
+ } catch {
216
+ return tz;
217
+ }
218
+ }
219
+ function EmptyState({ inEditor }) {
220
+ const handleAddSchedule = () => {
221
+ if (inEditor) {
222
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
223
+ }
224
+ };
225
+ return /* @__PURE__ */ jsxs2("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: [
226
+ /* @__PURE__ */ jsx2("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs2(
227
+ "svg",
228
+ {
229
+ width: "20",
230
+ height: "20",
231
+ viewBox: "0 0 24 24",
232
+ fill: "none",
233
+ stroke: "var(--color-accent, #A89B83)",
234
+ strokeWidth: "1.5",
235
+ strokeLinecap: "round",
236
+ strokeLinejoin: "round",
237
+ children: [
238
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
239
+ /* @__PURE__ */ jsx2("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
240
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
241
+ /* @__PURE__ */ jsx2("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
242
+ /* @__PURE__ */ jsx2("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
243
+ /* @__PURE__ */ jsx2("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
244
+ ]
245
+ }
246
+ ) }),
247
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-2", children: [
248
+ /* @__PURE__ */ jsx2("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
249
+ /* @__PURE__ */ jsx2("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." })
250
+ ] }),
251
+ /* @__PURE__ */ jsx2(
252
+ "button",
253
+ {
254
+ onClick: handleAddSchedule,
255
+ 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",
256
+ children: "Add Schedule"
257
+ }
258
+ )
259
+ ] });
260
+ }
261
+ function LoadingSkeleton() {
262
+ const shimmer = {
263
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
264
+ backgroundSize: "200% 100%",
265
+ animation: "ohw-shimmer 1.5s infinite"
266
+ };
267
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
268
+ /* @__PURE__ */ jsx2("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
269
+ /* @__PURE__ */ jsx2("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
270
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
271
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
272
+ ] }, i)) }),
273
+ [0, 1, 2].map((i) => /* @__PURE__ */ jsxs2("div", { children: [
274
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:hidden", children: [
275
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0", children: [
276
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
277
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
278
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
279
+ ] }),
280
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2", children: [
281
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
282
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
283
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
284
+ ] })
285
+ ] }),
286
+ /* @__PURE__ */ jsxs2("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
287
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
288
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2", children: [
289
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
290
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
291
+ ] }),
292
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
293
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
294
+ ] }),
295
+ i < 2 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
296
+ ] }, i))
297
+ ] });
298
+ }
299
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
300
+ const todayMs = useMemo(() => {
301
+ const d = /* @__PURE__ */ new Date();
302
+ d.setHours(0, 0, 0, 0);
303
+ return d.getTime();
304
+ }, []);
305
+ const scrollRef = useRef(null);
306
+ const [canScrollLeft, setCanScrollLeft] = useState2(false);
307
+ const [canScrollRight, setCanScrollRight] = useState2(false);
308
+ const updateScrollState = () => {
309
+ const el = scrollRef.current;
310
+ if (!el) return;
311
+ setCanScrollLeft(el.scrollLeft > 0);
312
+ setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
313
+ };
314
+ useEffect(() => {
315
+ const el = scrollRef.current;
316
+ if (!el) return;
317
+ updateScrollState();
318
+ el.addEventListener("scroll", updateScrollState);
319
+ const ro = new ResizeObserver(updateScrollState);
320
+ ro.observe(el);
321
+ return () => {
322
+ el.removeEventListener("scroll", updateScrollState);
323
+ ro.disconnect();
324
+ };
325
+ }, [dates]);
326
+ const scroll = (dir) => {
327
+ scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
328
+ };
329
+ const isDragging = useRef(false);
330
+ const dragStartX = useRef(0);
331
+ const dragStartScrollLeft = useRef(0);
332
+ const onDragStart = (e) => {
333
+ if (!scrollRef.current) return;
334
+ isDragging.current = true;
335
+ dragStartX.current = e.clientX;
336
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
337
+ scrollRef.current.style.cursor = "grabbing";
338
+ scrollRef.current.style.userSelect = "none";
339
+ };
340
+ const onDragMove = (e) => {
341
+ if (!isDragging.current || !scrollRef.current) return;
342
+ const delta = e.clientX - dragStartX.current;
343
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
344
+ };
345
+ const onDragEnd = () => {
346
+ if (!scrollRef.current) return;
347
+ isDragging.current = false;
348
+ scrollRef.current.style.cursor = "grab";
349
+ scrollRef.current.style.userSelect = "";
350
+ };
351
+ const datesWithClasses = useMemo(
352
+ () => new Set(dates.map(
353
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
354
+ ).filter((i) => i >= 0)),
355
+ [dates, schedule]
356
+ );
357
+ const selectedDate = dates[selectedIdx];
358
+ const selectedClasses = useMemo(
359
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
360
+ [schedule, selectedDate]
361
+ );
362
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
363
+ /* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
364
+ canScrollLeft && /* @__PURE__ */ jsx2(
365
+ "button",
366
+ {
367
+ onClick: () => scroll("left"),
368
+ 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",
369
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
370
+ "aria-label": "Scroll left",
371
+ children: /* @__PURE__ */ jsx2("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__ */ jsx2("polyline", { points: "15 18 9 12 15 6" }) })
372
+ }
373
+ ),
374
+ canScrollRight && /* @__PURE__ */ jsx2(
375
+ "button",
376
+ {
377
+ onClick: () => scroll("right"),
378
+ 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",
379
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
380
+ "aria-label": "Scroll right",
381
+ children: /* @__PURE__ */ jsx2("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__ */ jsx2("polyline", { points: "9 18 15 12 9 6" }) })
382
+ }
383
+ ),
384
+ canScrollLeft && /* @__PURE__ */ jsx2(
385
+ "div",
386
+ {
387
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
388
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
389
+ }
390
+ ),
391
+ canScrollRight && /* @__PURE__ */ jsx2(
392
+ "div",
393
+ {
394
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
395
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsx2(
399
+ "div",
400
+ {
401
+ ref: scrollRef,
402
+ className: "overflow-x-auto w-full",
403
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
404
+ onMouseDown: onDragStart,
405
+ onMouseMove: onDragMove,
406
+ onMouseUp: onDragEnd,
407
+ onMouseLeave: onDragEnd,
408
+ children: /* @__PURE__ */ jsx2("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
409
+ const isToday = date.getTime() === todayMs;
410
+ const isSelected = i === selectedIdx;
411
+ const clickable = datesWithClasses.has(i);
412
+ const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
413
+ return /* @__PURE__ */ jsxs2(
414
+ "div",
415
+ {
416
+ onClick: () => clickable && onSelectDate(i),
417
+ className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
418
+ children: [
419
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
420
+ /* @__PURE__ */ jsx2(
421
+ "div",
422
+ {
423
+ className: "w-10 h-10 rounded-full flex items-center justify-center",
424
+ style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
425
+ children: /* @__PURE__ */ jsx2(
426
+ "span",
427
+ {
428
+ className: "font-body text-xl font-bold text-center tracking-display-tight",
429
+ style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
430
+ children: date.getDate()
431
+ }
432
+ )
433
+ }
434
+ )
435
+ ]
436
+ },
437
+ i
438
+ );
439
+ }) })
440
+ }
441
+ )
442
+ ] }),
443
+ selectedClasses.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
444
+ const booked = getBookingsOnDate(cls, selectedDate);
445
+ const available = cls.maxParticipants - booked;
446
+ const isFull = available <= 0;
447
+ return /* @__PURE__ */ jsxs2("div", { children: [
448
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
449
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
450
+ /* @__PURE__ */ jsx2("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
451
+ /* @__PURE__ */ jsx2(
452
+ "span",
453
+ {
454
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
455
+ style: { borderColor: "#0885FE", color: "#0885FE" },
456
+ children: "GROUP"
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
460
+ /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
461
+ available,
462
+ "/",
463
+ cls.maxParticipants
464
+ ] }),
465
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
466
+ ] }) })
467
+ ] }),
468
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
469
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
470
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
471
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
472
+ ] }),
473
+ /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
474
+ /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
475
+ available,
476
+ "/",
477
+ cls.maxParticipants
478
+ ] }),
479
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
480
+ ] }) }),
481
+ /* @__PURE__ */ jsx2(
482
+ "button",
483
+ {
484
+ 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",
485
+ style: isFull ? {
486
+ background: "transparent",
487
+ border: "1px solid var(--color-dark, #200C02)",
488
+ color: "var(--color-dark, #200C02)"
489
+ } : {
490
+ background: "var(--color-primary, #3D312B)",
491
+ border: "none",
492
+ color: "var(--color-light, #FEFFF0)"
493
+ },
494
+ onClick: () => {
495
+ if (!cls.id) return;
496
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
497
+ },
498
+ children: isFull ? "Join Waitlist" : "Book Now"
499
+ }
500
+ )
501
+ ] })
502
+ ] }),
503
+ i < selectedClasses.length - 1 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
504
+ ] }, cls.id ?? i);
505
+ }) })
506
+ ] });
507
+ }
508
+ function CalendarFoldIcon() {
509
+ return /* @__PURE__ */ jsxs2(
510
+ "svg",
511
+ {
512
+ width: "16",
513
+ height: "16",
514
+ viewBox: "0 0 24 24",
515
+ fill: "none",
516
+ stroke: "currentColor",
517
+ strokeWidth: "2",
518
+ strokeLinecap: "round",
519
+ strokeLinejoin: "round",
520
+ style: { flexShrink: 0 },
521
+ children: [
522
+ /* @__PURE__ */ jsx2("path", { d: "M8 2v4" }),
523
+ /* @__PURE__ */ jsx2("path", { d: "M16 2v4" }),
524
+ /* @__PURE__ */ jsx2("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
525
+ /* @__PURE__ */ jsx2("path", { d: "M3 10h18" }),
526
+ /* @__PURE__ */ jsx2("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
527
+ ]
528
+ }
529
+ );
530
+ }
531
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
532
+ const [schedule, setSchedule] = useState2(null);
533
+ const [loading, setLoading] = useState2(true);
534
+ const [inEditor, setInEditor] = useState2(false);
535
+ const [isHovered, setIsHovered] = useState2(false);
536
+ const [modalState, setModalState] = useState2(null);
537
+ const switchScheduleIdRef = useRef(null);
538
+ const dates = useMemo(() => {
539
+ const today = /* @__PURE__ */ new Date();
540
+ today.setHours(0, 0, 0, 0);
541
+ return Array.from({ length: 14 }, (_, i) => {
542
+ const d = new Date(today);
543
+ d.setDate(today.getDate() + i);
544
+ return d;
545
+ });
546
+ }, []);
547
+ const [selectedIdx, setSelectedIdx] = useState2(0);
548
+ useEffect(() => {
549
+ if (!schedule?.classes) return;
550
+ for (let i = 0; i < dates.length; i++) {
551
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
552
+ setSelectedIdx(i);
553
+ return;
554
+ }
555
+ }
556
+ }, [schedule, dates]);
557
+ useEffect(() => {
558
+ if (typeof window === "undefined") return;
559
+ const isInEditor = window.self !== window.top;
560
+ setInEditor(isInEditor);
561
+ if (isInEditor) {
562
+ const startEmpty = initialScheduleId === null;
563
+ if (startEmpty) setLoading(false);
564
+ let initialized = startEmpty;
565
+ const timer = !startEmpty ? setTimeout(() => {
566
+ if (!initialized) {
567
+ initialized = true;
568
+ setLoading(false);
569
+ }
570
+ }, 5e3) : null;
571
+ const handler = (e) => {
572
+ if (e.data?.type === "ow:clear-scheduling-widget") {
573
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
574
+ setSchedule(null);
575
+ setLoading(false);
576
+ return;
577
+ }
578
+ if (e.data?.type === "ow:switch-schedule") {
579
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
580
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
581
+ initialized = false;
582
+ setLoading(true);
583
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
584
+ return;
585
+ }
586
+ if (e.data?.type !== "ow:user-schedules-response") return;
587
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
588
+ if (initialized && switchScheduleIdRef.current === null) return;
589
+ initialized = true;
590
+ if (timer) clearTimeout(timer);
591
+ const schedules = e.data.schedules ?? [];
592
+ const isSwitching = switchScheduleIdRef.current !== null;
593
+ 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;
594
+ switchScheduleIdRef.current = null;
595
+ setSchedule(target);
596
+ setLoading(false);
597
+ if (notifyOnConnect && target && !isSwitching) {
598
+ window.parent.postMessage({
599
+ type: "ow:schedule-connected",
600
+ schedule: { id: target.id, name: target.name },
601
+ insertAfter
602
+ }, "*");
603
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
604
+ }
605
+ };
606
+ window.addEventListener("message", handler);
607
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
608
+ return () => {
609
+ window.removeEventListener("message", handler);
610
+ if (timer) clearTimeout(timer);
611
+ };
612
+ }
613
+ if (initialScheduleId === null) {
614
+ setLoading(false);
615
+ return;
616
+ }
617
+ ;
618
+ (async () => {
619
+ try {
620
+ if (initialScheduleId) {
621
+ const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
622
+ const data = await res.json();
623
+ setSchedule(data?.id ? data : null);
624
+ } else {
625
+ const domain = getApiDomain();
626
+ const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
627
+ const slugs = await sitemapRes.json();
628
+ if (!Array.isArray(slugs) || !slugs.length) {
629
+ setLoading(false);
630
+ return;
631
+ }
632
+ const { slug } = [...slugs].sort(
633
+ (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
634
+ )[0];
635
+ const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
636
+ const data = await res.json();
637
+ setSchedule(data?.id ? data : null);
638
+ }
639
+ } catch {
640
+ }
641
+ setLoading(false);
642
+ })();
643
+ }, []);
644
+ const timezoneLabel = schedule?.timezone ? (() => {
645
+ try {
646
+ return buildTimezoneLabel(schedule.timezone);
647
+ } catch {
648
+ return void 0;
649
+ }
650
+ })() : void 0;
651
+ const handleModalSubmit = async (email) => {
652
+ if (!modalState) return;
653
+ const { mode, classId, classDate } = modalState;
654
+ const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
655
+ const res = await fetch(endpoint, {
656
+ method: "POST",
657
+ headers: { "Content-Type": "application/json" },
658
+ body: JSON.stringify({ classDate: classDate.toISOString(), email })
659
+ });
660
+ if (!res.ok) {
661
+ const data = await res.json().catch(() => ({}));
662
+ throw new Error(data?.message ?? "Something went wrong. Please try again.");
663
+ }
664
+ };
665
+ const handleReplaceSchedule = () => {
666
+ setIsHovered(false);
667
+ if (schedule) {
668
+ window.parent.postMessage({
669
+ type: "ow:schedule-connected",
670
+ schedule: { id: schedule.id, name: schedule.name },
671
+ insertAfter
672
+ }, "*");
673
+ } else {
674
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
675
+ }
676
+ };
677
+ const sectionId = `scheduling-${insertAfter}`;
678
+ return /* @__PURE__ */ jsxs2(
679
+ "section",
680
+ {
681
+ "data-ohw-section": sectionId,
682
+ "data-ohw-scheduling-anchor": insertAfter,
683
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
684
+ onMouseEnter: () => inEditor && setIsHovered(true),
685
+ onMouseLeave: () => setIsHovered(false),
686
+ children: [
687
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs2(
688
+ "div",
689
+ {
690
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
691
+ onClick: handleReplaceSchedule,
692
+ children: [
693
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
694
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
695
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs2(
696
+ "div",
697
+ {
698
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
699
+ style: {
700
+ fontSize: 14,
701
+ lineHeight: "24px",
702
+ fontFamily: "'Figtree', system-ui, sans-serif",
703
+ fontWeight: 500,
704
+ color: "#0C0A09"
705
+ },
706
+ children: [
707
+ /* @__PURE__ */ jsx2(CalendarFoldIcon, {}),
708
+ "Replace schedule"
709
+ ]
710
+ }
711
+ ) })
712
+ ]
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
716
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
717
+ /* @__PURE__ */ jsx2("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" }),
718
+ schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
719
+ ] }),
720
+ /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
721
+ timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
722
+ /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor }) : /* @__PURE__ */ jsx2(
723
+ ScheduleView,
724
+ {
725
+ schedule,
726
+ dates,
727
+ selectedIdx,
728
+ onSelectDate: setSelectedIdx,
729
+ onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
730
+ }
731
+ ) })
732
+ ] })
733
+ ] }),
734
+ modalState && /* @__PURE__ */ jsx2(
735
+ EmailCaptureModal,
736
+ {
737
+ title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
738
+ subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
739
+ onSubmit: handleModalSubmit,
740
+ onClose: () => setModalState(null)
741
+ }
742
+ )
743
+ ]
744
+ }
745
+ );
746
+ }
5
747
 
6
748
  // src/ui/toggle-group.tsx
7
- import * as React from "react";
749
+ import * as React2 from "react";
8
750
 
9
751
  // node_modules/clsx/dist/clsx.mjs
10
752
  function r(e) {
@@ -3324,7 +4066,7 @@ var cva = (base, config) => (props) => {
3324
4066
 
3325
4067
  // src/ui/toggle.tsx
3326
4068
  import { Toggle as TogglePrimitive } from "radix-ui";
3327
- import { jsx } from "react/jsx-runtime";
4069
+ import { jsx as jsx3 } from "react/jsx-runtime";
3328
4070
  var toggleVariants = cva(
3329
4071
  "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",
3330
4072
  {
@@ -3351,7 +4093,7 @@ function Toggle({
3351
4093
  size,
3352
4094
  ...props
3353
4095
  }) {
3354
- return /* @__PURE__ */ jsx(
4096
+ return /* @__PURE__ */ jsx3(
3355
4097
  TogglePrimitive.Root,
3356
4098
  {
3357
4099
  "data-slot": "toggle",
@@ -3362,8 +4104,8 @@ function Toggle({
3362
4104
  }
3363
4105
 
3364
4106
  // src/ui/toggle-group.tsx
3365
- import { jsx as jsx2 } from "react/jsx-runtime";
3366
- var ToggleGroupContext = React.createContext({
4107
+ import { jsx as jsx4 } from "react/jsx-runtime";
4108
+ var ToggleGroupContext = React2.createContext({
3367
4109
  value: "",
3368
4110
  onValueChange: () => {
3369
4111
  }
@@ -3375,13 +4117,13 @@ function ToggleGroup({
3375
4117
  children,
3376
4118
  ...props
3377
4119
  }) {
3378
- return /* @__PURE__ */ jsx2(
4120
+ return /* @__PURE__ */ jsx4(
3379
4121
  "div",
3380
4122
  {
3381
4123
  role: "group",
3382
4124
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3383
4125
  ...props,
3384
- children: /* @__PURE__ */ jsx2(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4126
+ children: /* @__PURE__ */ jsx4(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3385
4127
  }
3386
4128
  );
3387
4129
  }
@@ -3391,12 +4133,14 @@ function ToggleGroupItem({
3391
4133
  children,
3392
4134
  ...props
3393
4135
  }) {
3394
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3395
- return /* @__PURE__ */ jsx2(
4136
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
4137
+ return /* @__PURE__ */ jsx4(
3396
4138
  Toggle,
3397
4139
  {
3398
4140
  pressed: groupValue === value,
3399
- onPressedChange: () => onValueChange(value),
4141
+ onPressedChange: (pressed) => {
4142
+ if (pressed) onValueChange(value);
4143
+ },
3400
4144
  className: cn(
3401
4145
  "text-foreground cursor-pointer hover:bg-transparent hover:text-foreground",
3402
4146
  "data-[state=on]:bg-background data-[state=on]:shadow-sm",
@@ -3431,8 +4175,30 @@ function isEditSessionActive() {
3431
4175
  return new URLSearchParams(q).get("mode") === "edit";
3432
4176
  }
3433
4177
 
4178
+ // src/ui/badge.tsx
4179
+ import { jsx as jsx5 } from "react/jsx-runtime";
4180
+ var badgeVariants = cva(
4181
+ "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",
4182
+ {
4183
+ variants: {
4184
+ variant: {
4185
+ default: "border-transparent bg-primary text-primary-foreground",
4186
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
4187
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
4188
+ outline: "text-foreground"
4189
+ }
4190
+ },
4191
+ defaultVariants: {
4192
+ variant: "default"
4193
+ }
4194
+ }
4195
+ );
4196
+ function Badge({ className, variant, ...props }) {
4197
+ return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
4198
+ }
4199
+
3434
4200
  // src/OhhwellsBridge.tsx
3435
- import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
4201
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
3436
4202
  var PRIMARY = "#0885FE";
3437
4203
  var IMAGE_FADE_MS = 300;
3438
4204
  function runOpacityFade(el, onDone) {
@@ -3483,6 +4249,163 @@ function fadeInBgImage(el, url, onReady) {
3483
4249
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
3484
4250
  });
3485
4251
  }
4252
+ function getSectionsTracker() {
4253
+ let el = document.querySelector("[data-ohw-sections-tracker]");
4254
+ if (!el) {
4255
+ el = document.createElement("div");
4256
+ el.setAttribute("data-ohw-sections-tracker", "");
4257
+ el.style.display = "none";
4258
+ document.body.appendChild(el);
4259
+ }
4260
+ return el;
4261
+ }
4262
+ function updateSectionScheduleId(insertAfter, scheduleId) {
4263
+ const tracker = getSectionsTracker();
4264
+ let sections = [];
4265
+ try {
4266
+ sections = JSON.parse(tracker.textContent || "[]");
4267
+ } catch {
4268
+ }
4269
+ const currentPath = window.location.pathname;
4270
+ const updated = sections.map((s) => {
4271
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
4272
+ if (s.pagePath && s.pagePath !== currentPath) return s;
4273
+ return { ...s, scheduleId };
4274
+ });
4275
+ tracker.textContent = JSON.stringify(updated);
4276
+ return tracker.textContent ?? "[]";
4277
+ }
4278
+ function schedulingSectionId(insertAfter) {
4279
+ return `scheduling-${insertAfter}`;
4280
+ }
4281
+ var INSERT_BEFORE_MARKER = ":before:";
4282
+ function parseSchedulingInsertAfter(insertAfter) {
4283
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
4284
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
4285
+ return {
4286
+ anchor: insertAfter.slice(0, idx),
4287
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
4288
+ };
4289
+ }
4290
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
4291
+ const parsed = parseSchedulingInsertAfter(insertAfter);
4292
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
4293
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
4294
+ return { effectiveInsertAfter, insertBefore };
4295
+ }
4296
+ function getSchedulingMountPoint(insertAfter) {
4297
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
4298
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
4299
+ if (!anchorEl && anchor === "scheduling") {
4300
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
4301
+ anchorEl = widgets.at(-1) ?? null;
4302
+ }
4303
+ if (!anchorEl) return null;
4304
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
4305
+ }
4306
+ function schedulingMountDepth(insertAfter) {
4307
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
4308
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
4309
+ }
4310
+ function getPageSchedulingEntries(raw) {
4311
+ if (!raw) return [];
4312
+ try {
4313
+ const entries = JSON.parse(raw);
4314
+ const currentPath = window.location.pathname;
4315
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
4316
+ } catch {
4317
+ return [];
4318
+ }
4319
+ }
4320
+ function isSchedulingWidgetMissing(entry) {
4321
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
4322
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
4323
+ }
4324
+ function hasMissingSchedulingWidgets(entries) {
4325
+ return entries.some(isSchedulingWidgetMissing);
4326
+ }
4327
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
4328
+ if (!hasMissingSchedulingWidgets(entries)) return;
4329
+ mountSchedulingEntries(entries, notifyOnConnect);
4330
+ }
4331
+ function initSectionsFromContent(content, removeExisting = false) {
4332
+ const raw = content["__ohw_sections"];
4333
+ if (!raw) return;
4334
+ try {
4335
+ if (removeExisting) {
4336
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
4337
+ }
4338
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4339
+ if (inEditor) getSectionsTracker().textContent = raw;
4340
+ const pageEntries = getPageSchedulingEntries(raw);
4341
+ mountSchedulingEntries(pageEntries, false);
4342
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
4343
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
4344
+ } catch {
4345
+ }
4346
+ }
4347
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
4348
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
4349
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
4350
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
4351
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
4352
+ if (!mountPoint) return false;
4353
+ const container = document.createElement("div");
4354
+ container.dataset.ohwSectionContainer = "scheduling";
4355
+ if (insertBefore) {
4356
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
4357
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
4358
+ if (!beforePoint) return false;
4359
+ beforePoint.insertAdjacentElement("beforebegin", container);
4360
+ } else {
4361
+ let tail = mountPoint;
4362
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
4363
+ tail = tail.nextElementSibling;
4364
+ }
4365
+ tail.insertAdjacentElement("afterend", container);
4366
+ }
4367
+ const root = createRoot(container);
4368
+ flushSync(() => {
4369
+ root.render(
4370
+ /* @__PURE__ */ jsx6(
4371
+ SchedulingWidget,
4372
+ {
4373
+ notifyOnConnect,
4374
+ initialScheduleId: scheduleId,
4375
+ insertAfter: effectiveInsertAfter
4376
+ }
4377
+ )
4378
+ );
4379
+ });
4380
+ const tracker = getSectionsTracker();
4381
+ let sections = [];
4382
+ try {
4383
+ sections = JSON.parse(tracker.textContent || "[]");
4384
+ } catch {
4385
+ }
4386
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4387
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
4388
+ sections.push({
4389
+ type: "scheduling",
4390
+ insertAfter: effectiveInsertAfter,
4391
+ pagePath: window.location.pathname,
4392
+ ...scheduleId ? { scheduleId } : {}
4393
+ });
4394
+ tracker.textContent = JSON.stringify(sections);
4395
+ }
4396
+ return true;
4397
+ }
4398
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
4399
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
4400
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
4401
+ for (let i = pending.length - 1; i >= 0; i--) {
4402
+ const { insertAfter, scheduleId } = pending[i];
4403
+ if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
4404
+ pending.splice(i, 1);
4405
+ }
4406
+ }
4407
+ }
4408
+ }
3486
4409
  function collectEditableNodes() {
3487
4410
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
3488
4411
  if (el.dataset.ohwEditable === "image") {
@@ -3640,7 +4563,7 @@ var TOOLBAR_GROUPS = [
3640
4563
  ];
3641
4564
  function GlowFrame({ rect, elRef }) {
3642
4565
  const GAP = 6;
3643
- return /* @__PURE__ */ jsx3(
4566
+ return /* @__PURE__ */ jsx6(
3644
4567
  "div",
3645
4568
  {
3646
4569
  ref: elRef,
@@ -3691,7 +4614,7 @@ function FloatingToolbar({
3691
4614
  activeCommands
3692
4615
  }) {
3693
4616
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3694
- return /* @__PURE__ */ jsx3(
4617
+ return /* @__PURE__ */ jsx6(
3695
4618
  "div",
3696
4619
  {
3697
4620
  ref: elRef,
@@ -3714,11 +4637,11 @@ function FloatingToolbar({
3714
4637
  pointerEvents: "auto",
3715
4638
  whiteSpace: "nowrap"
3716
4639
  },
3717
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
3718
- gi > 0 && /* @__PURE__ */ jsx3("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4640
+ children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
4641
+ gi > 0 && /* @__PURE__ */ jsx6("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3719
4642
  btns.map((btn) => {
3720
4643
  const isActive = activeCommands.has(btn.cmd);
3721
- return /* @__PURE__ */ jsx3(
4644
+ return /* @__PURE__ */ jsx6(
3722
4645
  "button",
3723
4646
  {
3724
4647
  title: btn.title,
@@ -3744,7 +4667,7 @@ function FloatingToolbar({
3744
4667
  flexShrink: 0,
3745
4668
  padding: 6
3746
4669
  },
3747
- children: /* @__PURE__ */ jsx3(
4670
+ children: /* @__PURE__ */ jsx6(
3748
4671
  "svg",
3749
4672
  {
3750
4673
  width: "16",
@@ -3777,7 +4700,7 @@ function StateToggle({
3777
4700
  states,
3778
4701
  onStateChange
3779
4702
  }) {
3780
- return /* @__PURE__ */ jsx3(
4703
+ return /* @__PURE__ */ jsx6(
3781
4704
  ToggleGroup,
3782
4705
  {
3783
4706
  "data-ohw-state-toggle": "",
@@ -3791,30 +4714,46 @@ function StateToggle({
3791
4714
  left: rect.right - 8,
3792
4715
  transform: "translateX(-100%)"
3793
4716
  },
3794
- children: states.map((state) => /* @__PURE__ */ jsx3(ToggleGroupItem, { value: state, children: state }, state))
4717
+ children: states.map((state) => /* @__PURE__ */ jsx6(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3795
4718
  }
3796
4719
  );
3797
4720
  }
3798
4721
  var contentCache = /* @__PURE__ */ new Map();
4722
+ function resolveSubdomain(subdomainFromQuery) {
4723
+ if (subdomainFromQuery) return subdomainFromQuery;
4724
+ if (typeof window !== "undefined") {
4725
+ const parts = window.location.hostname.split(".");
4726
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
4727
+ }
4728
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
4729
+ if (siteUrl) {
4730
+ try {
4731
+ const host = new URL(siteUrl).hostname;
4732
+ const siteParts = host.split(".");
4733
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
4734
+ } catch {
4735
+ }
4736
+ }
4737
+ return "";
4738
+ }
3799
4739
  function OhhwellsBridge() {
3800
4740
  const pathname = usePathname();
3801
4741
  const router = useRouter();
3802
4742
  const searchParams = useSearchParams();
3803
4743
  const isEditMode = isEditSessionActive();
3804
- useEffect(() => {
3805
- if (!isEditMode) return;
3806
- const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
3807
- const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
3808
- const stylesheet = Object.assign(document.createElement("link"), { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap" });
3809
- document.head.append(preconnect1, preconnect2, stylesheet);
3810
- return () => {
3811
- preconnect1.remove();
3812
- preconnect2.remove();
3813
- stylesheet.remove();
3814
- };
3815
- }, [isEditMode]);
3816
- const [bridgeRoot, setBridgeRoot] = useState(null);
3817
- useEffect(() => {
4744
+ const [bridgeRoot, setBridgeRoot] = useState3(null);
4745
+ useEffect2(() => {
4746
+ const figtreeFontId = "ohw-figtree-font";
4747
+ if (!document.getElementById(figtreeFontId)) {
4748
+ const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
4749
+ const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
4750
+ const stylesheet = Object.assign(document.createElement("link"), {
4751
+ id: figtreeFontId,
4752
+ rel: "stylesheet",
4753
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
4754
+ });
4755
+ document.head.append(preconnect1, preconnect2, stylesheet);
4756
+ }
3818
4757
  const el = document.createElement("div");
3819
4758
  el.setAttribute("data-ohw-bridge-root", "");
3820
4759
  el.setAttribute("data-ohw-bridge", "");
@@ -3826,39 +4765,40 @@ function OhhwellsBridge() {
3826
4765
  };
3827
4766
  }, []);
3828
4767
  const subdomainFromQuery = searchParams.get("subdomain");
3829
- const subdomain = subdomainFromQuery ?? (() => {
3830
- if (typeof window === "undefined") return "";
3831
- const parts = window.location.hostname.split(".");
3832
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
3833
- })();
4768
+ const subdomain = resolveSubdomain(subdomainFromQuery);
3834
4769
  const postToParent = useCallback((data) => {
3835
4770
  if (typeof window !== "undefined" && window.parent !== window) {
3836
4771
  window.parent.postMessage(data, "*");
3837
4772
  }
3838
4773
  }, []);
3839
- const [fetchState, setFetchState] = useState("idle");
3840
- const autoSaveTimers = useRef(/* @__PURE__ */ new Map());
3841
- const activeElRef = useRef(null);
3842
- const originalContentRef = useRef(null);
3843
- const activeStateElRef = useRef(null);
3844
- const parentScrollRef = useRef(null);
3845
- const toolbarElRef = useRef(null);
3846
- const glowElRef = useRef(null);
3847
- const hoveredImageRef = useRef(null);
3848
- const editStylesRef = useRef(null);
3849
- const activateRef = useRef(() => {
4774
+ const [fetchState, setFetchState] = useState3("idle");
4775
+ const autoSaveTimers = useRef2(/* @__PURE__ */ new Map());
4776
+ const activeElRef = useRef2(null);
4777
+ const originalContentRef = useRef2(null);
4778
+ const activeStateElRef = useRef2(null);
4779
+ const parentScrollRef = useRef2(null);
4780
+ const toolbarElRef = useRef2(null);
4781
+ const glowElRef = useRef2(null);
4782
+ const hoveredImageRef = useRef2(null);
4783
+ const hoveredImageHasTextOverlapRef = useRef2(false);
4784
+ const hoveredGapRef = useRef2(null);
4785
+ const imageUnhoverTimerRef = useRef2(null);
4786
+ const imageShowTimerRef = useRef2(null);
4787
+ const editStylesRef = useRef2(null);
4788
+ const activateRef = useRef2(() => {
3850
4789
  });
3851
- const deactivateRef = useRef(() => {
4790
+ const deactivateRef = useRef2(() => {
3852
4791
  });
3853
- const refreshActiveCommandsRef = useRef(() => {
4792
+ const refreshActiveCommandsRef = useRef2(() => {
3854
4793
  });
3855
- const postToParentRef = useRef(postToParent);
4794
+ const postToParentRef = useRef2(postToParent);
3856
4795
  postToParentRef.current = postToParent;
3857
- const [toolbarRect, setToolbarRect] = useState(null);
3858
- const [toggleState, setToggleState] = useState(null);
3859
- const [maxBadge, setMaxBadge] = useState(null);
3860
- const [activeCommands, setActiveCommands] = useState(/* @__PURE__ */ new Set());
3861
- useEffect(() => {
4796
+ const [toolbarRect, setToolbarRect] = useState3(null);
4797
+ const [toggleState, setToggleState] = useState3(null);
4798
+ const [maxBadge, setMaxBadge] = useState3(null);
4799
+ const [activeCommands, setActiveCommands] = useState3(/* @__PURE__ */ new Set());
4800
+ const [sectionGap, setSectionGap] = useState3(null);
4801
+ useEffect2(() => {
3862
4802
  const update = () => {
3863
4803
  const el = activeElRef.current;
3864
4804
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -3866,6 +4806,12 @@ function OhhwellsBridge() {
3866
4806
  if (!prev || !activeStateElRef.current) return prev;
3867
4807
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3868
4808
  });
4809
+ setSectionGap((prev) => {
4810
+ if (!prev) return prev;
4811
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
4812
+ if (!anchor) return prev;
4813
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
4814
+ });
3869
4815
  };
3870
4816
  const vvp = window.visualViewport;
3871
4817
  if (!vvp) return;
@@ -3907,6 +4853,10 @@ function OhhwellsBridge() {
3907
4853
  const activate = useCallback((el) => {
3908
4854
  if (activeElRef.current === el) return;
3909
4855
  deactivate();
4856
+ if (hoveredImageRef.current) {
4857
+ hoveredImageRef.current = null;
4858
+ postToParentRef.current({ type: "ow:image-unhover" });
4859
+ }
3910
4860
  el.setAttribute("contenteditable", "true");
3911
4861
  el.removeAttribute("data-ohw-hovered");
3912
4862
  activeElRef.current = el;
@@ -3926,6 +4876,7 @@ function OhhwellsBridge() {
3926
4876
  const applyContent = (content) => {
3927
4877
  const imageLoads = [];
3928
4878
  for (const [key, val] of Object.entries(content)) {
4879
+ if (key === "__ohw_sections") continue;
3929
4880
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
3930
4881
  if (el.dataset.ohwEditable === "image") {
3931
4882
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -3944,6 +4895,7 @@ function OhhwellsBridge() {
3944
4895
  }
3945
4896
  });
3946
4897
  }
4898
+ initSectionsFromContent(content, true);
3947
4899
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
3948
4900
  }) : Promise.resolve();
3949
4901
  };
@@ -3954,7 +4906,7 @@ function OhhwellsBridge() {
3954
4906
  }
3955
4907
  let cancelled = false;
3956
4908
  setFetchState("loading");
3957
- const apiUrl = globalThis.process?.env?.NEXT_PUBLIC_FLOWOPS_API_URL ?? "http://localhost:4005";
4909
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
3958
4910
  fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
3959
4911
  if (cancelled) return;
3960
4912
  const content = data?.content ?? {};
@@ -3968,12 +4920,15 @@ function OhhwellsBridge() {
3968
4920
  cancelled = true;
3969
4921
  };
3970
4922
  }, [subdomain, isEditMode, pathname]);
3971
- useEffect(() => {
4923
+ useEffect2(() => {
3972
4924
  if (!subdomain || isEditMode) return;
4925
+ let debounceTimer = null;
3973
4926
  const applyFromCache = () => {
3974
4927
  const content = contentCache.get(subdomain);
3975
4928
  if (!content) return;
4929
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
3976
4930
  for (const [key, val] of Object.entries(content)) {
4931
+ if (key === "__ohw_sections") continue;
3977
4932
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
3978
4933
  if (el.dataset.ohwEditable === "image") {
3979
4934
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -3987,21 +4942,28 @@ function OhhwellsBridge() {
3987
4942
  });
3988
4943
  }
3989
4944
  };
3990
- applyFromCache();
3991
- const observer = new MutationObserver(applyFromCache);
4945
+ const scheduleApply = () => {
4946
+ if (debounceTimer) clearTimeout(debounceTimer);
4947
+ debounceTimer = setTimeout(applyFromCache, 150);
4948
+ };
4949
+ scheduleApply();
4950
+ const observer = new MutationObserver(scheduleApply);
3992
4951
  observer.observe(document.body, { childList: true, subtree: true });
3993
- return () => observer.disconnect();
3994
- }, [subdomain, isEditMode]);
4952
+ return () => {
4953
+ observer.disconnect();
4954
+ if (debounceTimer) clearTimeout(debounceTimer);
4955
+ };
4956
+ }, [subdomain, isEditMode, pathname]);
3995
4957
  useLayoutEffect(() => {
3996
4958
  const el = document.getElementById("ohw-loader");
3997
4959
  if (!el) return;
3998
4960
  const visible = Boolean(subdomain) && fetchState !== "done";
3999
4961
  el.style.display = visible ? "flex" : "none";
4000
4962
  }, [subdomain, fetchState]);
4001
- useEffect(() => {
4963
+ useEffect2(() => {
4002
4964
  postToParent({ type: "ow:navigation", path: pathname });
4003
4965
  }, [pathname, postToParent]);
4004
- useEffect(() => {
4966
+ useEffect2(() => {
4005
4967
  if (!isEditMode) return;
4006
4968
  const measure = () => {
4007
4969
  const h = document.body.scrollHeight;
@@ -4025,7 +4987,7 @@ function OhhwellsBridge() {
4025
4987
  window.removeEventListener("resize", handleResize);
4026
4988
  };
4027
4989
  }, [pathname, isEditMode, postToParent]);
4028
- useEffect(() => {
4990
+ useEffect2(() => {
4029
4991
  if (!subdomainFromQuery || isEditMode) return;
4030
4992
  const handleClick = (e) => {
4031
4993
  const anchor = e.target.closest("a");
@@ -4041,7 +5003,7 @@ function OhhwellsBridge() {
4041
5003
  document.addEventListener("click", handleClick, true);
4042
5004
  return () => document.removeEventListener("click", handleClick, true);
4043
5005
  }, [subdomainFromQuery, isEditMode, router]);
4044
- useEffect(() => {
5006
+ useEffect2(() => {
4045
5007
  if (!isEditMode) {
4046
5008
  editStylesRef.current?.base.remove();
4047
5009
  editStylesRef.current?.forceHover.remove();
@@ -4081,6 +5043,7 @@ function OhhwellsBridge() {
4081
5043
  [data-ohw-editable][contenteditable] *::selection { background: ${PRIMARY}59 !important; }
4082
5044
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
4083
5045
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
5046
+ [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
4084
5047
  `;
4085
5048
  const forceHover = document.createElement("style");
4086
5049
  forceHover.setAttribute("data-ohw-active-state-style", "");
@@ -4107,10 +5070,12 @@ function OhhwellsBridge() {
4107
5070
  if (editable) {
4108
5071
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
4109
5072
  e.preventDefault();
5073
+ e.stopPropagation();
4110
5074
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
4111
5075
  return;
4112
5076
  }
4113
5077
  e.preventDefault();
5078
+ e.stopPropagation();
4114
5079
  activateRef.current(editable);
4115
5080
  return;
4116
5081
  }
@@ -4173,12 +5138,15 @@ function OhhwellsBridge() {
4173
5138
  }
4174
5139
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
4175
5140
  };
4176
- const postImageHover = (imgEl, isDragOver = false) => {
5141
+ const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
4177
5142
  const r2 = getVisibleRect(imgEl);
5143
+ const hasTextOverlap = forceTextOverlap ?? false;
5144
+ hoveredImageHasTextOverlapRef.current = hasTextOverlap;
4178
5145
  postToParentRef.current({
4179
5146
  type: "ow:image-hover",
4180
5147
  key: imgEl.dataset.ohwKey ?? "",
4181
5148
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
5149
+ hasTextOverlap,
4182
5150
  ...isDragOver ? { isDragOver: true } : {}
4183
5151
  });
4184
5152
  const track = imgEl.closest("[data-ohw-hover-pause]");
@@ -4225,7 +5193,7 @@ function OhhwellsBridge() {
4225
5193
  }
4226
5194
  return smallest;
4227
5195
  };
4228
- const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false, fromOverlay = false) => {
5196
+ const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
4229
5197
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
4230
5198
  if (toggleEl) {
4231
5199
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
@@ -4251,33 +5219,61 @@ function OhhwellsBridge() {
4251
5219
  }
4252
5220
  return;
4253
5221
  }
4254
- const topEditable = topEl?.closest("[data-ohw-editable]");
4255
- if (topEditable && topEditable.dataset.ohwEditable !== "image" && topEditable.dataset.ohwEditable !== "bg-image") {
4256
- if (topEditable.hasAttribute("contenteditable") && fromOverlay) return;
5222
+ const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
5223
+ const textEditable = Array.from(
5224
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
5225
+ ).find((el) => {
5226
+ const er = el.getBoundingClientRect();
5227
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
5228
+ });
5229
+ if (imageUnhoverTimerRef.current) {
5230
+ clearTimeout(imageUnhoverTimerRef.current);
5231
+ imageUnhoverTimerRef.current = null;
5232
+ }
5233
+ if (imageShowTimerRef.current) {
5234
+ clearTimeout(imageShowTimerRef.current);
5235
+ imageShowTimerRef.current = null;
5236
+ }
5237
+ if (textEditable) {
5238
+ if (textEditable.hasAttribute("contenteditable")) {
5239
+ if (hoveredImageRef.current) {
5240
+ hoveredImageRef.current = null;
5241
+ hoveredImageHasTextOverlapRef.current = false;
5242
+ resumeAnimTracks();
5243
+ postToParentRef.current({ type: "ow:image-unhover" });
5244
+ }
5245
+ return;
5246
+ }
5247
+ if (isStateCardImage) {
5248
+ if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
5249
+ hoveredImageRef.current = imgEl;
5250
+ postImageHover(imgEl, isDragOver, true);
5251
+ }
5252
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5253
+ textEditable.setAttribute("data-ohw-hovered", "");
5254
+ return;
5255
+ }
4257
5256
  if (hoveredImageRef.current) {
4258
5257
  hoveredImageRef.current = null;
5258
+ hoveredImageHasTextOverlapRef.current = false;
4259
5259
  resumeAnimTracks();
4260
5260
  postToParentRef.current({ type: "ow:image-unhover" });
4261
5261
  }
4262
5262
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4263
- topEditable.setAttribute("data-ohw-hovered", "");
5263
+ textEditable.setAttribute("data-ohw-hovered", "");
4264
5264
  return;
4265
5265
  }
4266
- const activeHovered = document.querySelector("[data-ohw-hovered]");
4267
- if (activeHovered) {
4268
- const outlinePad = 8;
4269
- const hr = activeHovered.getBoundingClientRect();
4270
- if (x >= hr.left - outlinePad && x <= hr.right + outlinePad && y >= hr.top - outlinePad && y <= hr.bottom + outlinePad) {
4271
- if (hoveredImageRef.current) {
4272
- hoveredImageRef.current = null;
4273
- resumeAnimTracks();
4274
- postToParentRef.current({ type: "ow:image-unhover" });
4275
- }
4276
- return;
5266
+ if (hoveredGapRef.current) {
5267
+ if (hoveredImageRef.current) {
5268
+ hoveredImageRef.current = null;
5269
+ resumeAnimTracks();
5270
+ postToParentRef.current({ type: "ow:image-unhover" });
4277
5271
  }
5272
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5273
+ return;
4278
5274
  }
4279
5275
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4280
- if (imgEl !== hoveredImageRef.current) {
5276
+ if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4281
5277
  hoveredImageRef.current = imgEl;
4282
5278
  postImageHover(imgEl, isDragOver);
4283
5279
  } else if (isDragOver) {
@@ -4286,9 +5282,29 @@ function OhhwellsBridge() {
4286
5282
  } else {
4287
5283
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
4288
5284
  if (!inIframeView) return;
4289
- hoveredImageRef.current = null;
4290
- resumeAnimTracks();
4291
- postToParentRef.current({ type: "ow:image-unhover" });
5285
+ if (imageUnhoverTimerRef.current) {
5286
+ clearTimeout(imageUnhoverTimerRef.current);
5287
+ imageUnhoverTimerRef.current = null;
5288
+ }
5289
+ if (imageShowTimerRef.current) {
5290
+ clearTimeout(imageShowTimerRef.current);
5291
+ imageShowTimerRef.current = null;
5292
+ }
5293
+ if (hoveredImageRef.current) {
5294
+ hoveredImageRef.current = null;
5295
+ hoveredImageHasTextOverlapRef.current = false;
5296
+ resumeAnimTracks();
5297
+ postToParentRef.current({ type: "ow:image-unhover" });
5298
+ }
5299
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
5300
+ const textEl = Array.from(
5301
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
5302
+ ).find((el) => {
5303
+ const er = el.getBoundingClientRect();
5304
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
5305
+ });
5306
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5307
+ if (textEl && !textEl.hasAttribute("contenteditable")) textEl.setAttribute("data-ohw-hovered", "");
4292
5308
  }
4293
5309
  };
4294
5310
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
@@ -4307,27 +5323,53 @@ function OhhwellsBridge() {
4307
5323
  }) ?? null;
4308
5324
  const currentLocked = activeStateElRef.current?.hasAttribute("data-ohw-active-state") ? activeStateElRef.current : null;
4309
5325
  const active = found ?? currentLocked;
5326
+ const prev = activeStateElRef.current;
4310
5327
  activeStateElRef.current = active;
4311
- document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
4312
- if (active) {
4313
- if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
4314
- const states = deriveStates(active.dataset.ohwEditableState ?? "");
4315
- const activeState = active.hasAttribute("data-ohw-active-state") ? (active.getAttribute("data-ohw-active-state") ?? "Default").charAt(0).toUpperCase() + (active.getAttribute("data-ohw-active-state") ?? "").slice(1) : "Default";
4316
- setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
4317
- } else {
4318
- setToggleState(null);
5328
+ if (active !== prev) {
5329
+ if (prev) prev.removeAttribute("data-ohw-state-hovered");
5330
+ if (active) {
5331
+ if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
5332
+ const states = deriveStates(active.dataset.ohwEditableState ?? "");
5333
+ const activeState = active.hasAttribute("data-ohw-active-state") ? (active.getAttribute("data-ohw-active-state") ?? "Default").charAt(0).toUpperCase() + (active.getAttribute("data-ohw-active-state") ?? "").slice(1) : "Default";
5334
+ setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
5335
+ } else {
5336
+ setToggleState(null);
5337
+ }
5338
+ }
5339
+ };
5340
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
5341
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
5342
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
5343
+ const ZONE = 20;
5344
+ for (let i = 0; i < sections.length; i++) {
5345
+ const a = sections[i];
5346
+ const b = sections[i + 1] ?? null;
5347
+ const boundaryY = a.getBoundingClientRect().bottom;
5348
+ if (Math.abs(y - boundaryY) <= ZONE) {
5349
+ const insertAfter = a.dataset.ohwSection ?? "";
5350
+ const insertBefore = b?.dataset.ohwSection ?? null;
5351
+ hoveredGapRef.current = { insertAfter, insertBefore };
5352
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
5353
+ return;
5354
+ }
5355
+ }
5356
+ if (hoveredGapRef.current) {
5357
+ hoveredGapRef.current = null;
5358
+ setSectionGap(null);
4319
5359
  }
4320
5360
  };
4321
5361
  const handleMouseMove = (e) => {
4322
5362
  const { clientX, clientY } = e;
5363
+ probeSectionGapAt(clientX, clientY);
4323
5364
  probeImageAt(clientX, clientY);
4324
5365
  probeHoverCardsAt(clientX, clientY);
4325
5366
  };
4326
5367
  const handlePointerSync = (e) => {
4327
5368
  if (e.data?.type !== "ow:pointer-sync") return;
4328
- const { clientX, clientY, fromOverlay } = e.data;
5369
+ const { clientX, clientY } = e.data;
4329
5370
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4330
- probeImageAt(clientX, clientY, false, false, fromOverlay ?? false);
5371
+ probeSectionGapAt(clientX, clientY);
5372
+ probeImageAt(clientX, clientY);
4331
5373
  probeHoverCardsAt(clientX, clientY);
4332
5374
  };
4333
5375
  const handleDragOver = (e) => {
@@ -4340,7 +5382,8 @@ function OhhwellsBridge() {
4340
5382
  e.dataTransfer.dropEffect = "copy";
4341
5383
  if (hoveredImageRef.current !== el) {
4342
5384
  hoveredImageRef.current = el;
4343
- postImageHover(el, true);
5385
+ const isStateCard = !!el.closest("[data-ohw-editable-state]");
5386
+ postImageHover(el, true, isStateCard ? true : void 0);
4344
5387
  }
4345
5388
  };
4346
5389
  const handleDragLeave = (e) => {
@@ -4483,7 +5526,12 @@ function OhhwellsBridge() {
4483
5526
  if (e.data?.type !== "ow:hydrate") return;
4484
5527
  const content = e.data.content;
4485
5528
  if (!content) return;
5529
+ let sectionsJson = null;
4486
5530
  for (const [key, val] of Object.entries(content)) {
5531
+ if (key === "__ohw_sections") {
5532
+ sectionsJson = val;
5533
+ continue;
5534
+ }
4487
5535
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4488
5536
  if (el.dataset.ohwEditable === "image") {
4489
5537
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4495,6 +5543,9 @@ function OhhwellsBridge() {
4495
5543
  }
4496
5544
  });
4497
5545
  }
5546
+ if (sectionsJson) {
5547
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
5548
+ }
4498
5549
  postToParentRef.current({ type: "ow:hydrate-done" });
4499
5550
  };
4500
5551
  window.addEventListener("message", handleHydrate);
@@ -4526,17 +5577,75 @@ function OhhwellsBridge() {
4526
5577
  setToggleState((prev) => prev ? { ...prev, rect } : null);
4527
5578
  }
4528
5579
  if (hoveredImageRef.current) {
4529
- const r2 = hoveredImageRef.current.getBoundingClientRect();
5580
+ const el = hoveredImageRef.current;
5581
+ const r2 = el.getBoundingClientRect();
4530
5582
  postToParentRef.current({
4531
5583
  type: "ow:image-hover",
4532
- key: hoveredImageRef.current.dataset.ohwKey ?? "",
4533
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
5584
+ key: el.dataset.ohwKey ?? "",
5585
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
5586
+ hasTextOverlap: hoveredImageHasTextOverlapRef.current
4534
5587
  });
4535
5588
  }
4536
5589
  };
4537
5590
  const handleSave = (e) => {
4538
5591
  if (e.data?.type !== "ow:save") return;
4539
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
5592
+ const nodes = collectEditableNodes();
5593
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
5594
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
5595
+ postToParentRef.current({ type: "ow:save-result", nodes });
5596
+ };
5597
+ const handleInsertSection = (e) => {
5598
+ if (e.data?.type !== "ow:insert-section") return;
5599
+ const { widgetType, insertAfter, insertBefore } = e.data;
5600
+ if (widgetType !== "scheduling") return;
5601
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
5602
+ if (inserted) {
5603
+ const tracker = getSectionsTracker();
5604
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
5605
+ const h = document.documentElement.scrollHeight;
5606
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5607
+ }
5608
+ };
5609
+ const handleSwitchSchedule = (e) => {
5610
+ if (e.data?.type !== "ow:switch-schedule") return;
5611
+ const scheduleId = e.data.scheduleId;
5612
+ const insertAfter = e.data.insertAfter;
5613
+ if (!scheduleId || !insertAfter) return;
5614
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5615
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5616
+ };
5617
+ const handleScheduleLinked = (e) => {
5618
+ if (e.data?.type !== "ow:schedule-linked") return;
5619
+ const scheduleId = e.data.scheduleId;
5620
+ const insertAfter = e.data.insertAfter;
5621
+ if (!scheduleId || !insertAfter) return;
5622
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5623
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5624
+ };
5625
+ const handleClearSchedulingWidget = (e) => {
5626
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
5627
+ const insertAfter = e.data.insertAfter;
5628
+ if (!insertAfter) return;
5629
+ const text = updateSectionScheduleId(insertAfter, null);
5630
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5631
+ };
5632
+ const handleRemoveSchedulingSection = (e) => {
5633
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
5634
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
5635
+ el.remove();
5636
+ });
5637
+ const tracker = getSectionsTracker();
5638
+ let sections = [];
5639
+ try {
5640
+ sections = JSON.parse(tracker.textContent || "[]");
5641
+ } catch {
5642
+ }
5643
+ const currentPath = window.location.pathname;
5644
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
5645
+ tracker.textContent = JSON.stringify(updated);
5646
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5647
+ const h = document.documentElement.scrollHeight;
5648
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
4540
5649
  };
4541
5650
  const handleSelectionChange = () => {
4542
5651
  if (!activeElRef.current) return;
@@ -4604,12 +5713,54 @@ function OhhwellsBridge() {
4604
5713
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
4605
5714
  if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
4606
5715
  };
5716
+ const handleClickAt = (e) => {
5717
+ if (e.data?.type !== "ow:click-at") return;
5718
+ const { clientX, clientY } = e.data;
5719
+ const allImages = Array.from(
5720
+ document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
5721
+ ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
5722
+ const stateCardImage = allImages.find((el) => {
5723
+ const ownerCard = el.closest("[data-ohw-editable-state]");
5724
+ if (ownerCard) {
5725
+ const inHoverState = ownerCard.getAttribute("data-ohw-active-state") === "hover";
5726
+ const elState = el.dataset.ohwState;
5727
+ if (el === ownerCard && inHoverState) return false;
5728
+ if (elState === "default" && inHoverState) return false;
5729
+ if (elState === "hover" && !inHoverState) return false;
5730
+ }
5731
+ const r2 = el.getBoundingClientRect();
5732
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
5733
+ });
5734
+ if (stateCardImage) {
5735
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
5736
+ return;
5737
+ }
5738
+ const textEditable = Array.from(
5739
+ document.querySelectorAll(
5740
+ '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
5741
+ )
5742
+ ).find((el) => {
5743
+ const r2 = el.getBoundingClientRect();
5744
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
5745
+ });
5746
+ if (textEditable) {
5747
+ activateRef.current(textEditable);
5748
+ return;
5749
+ }
5750
+ deactivateRef.current();
5751
+ };
4607
5752
  window.addEventListener("message", handleSave);
5753
+ window.addEventListener("message", handleInsertSection);
5754
+ window.addEventListener("message", handleSwitchSchedule);
5755
+ window.addEventListener("message", handleScheduleLinked);
5756
+ window.addEventListener("message", handleClearSchedulingWidget);
5757
+ window.addEventListener("message", handleRemoveSchedulingSection);
4608
5758
  window.addEventListener("message", handleImageUrl);
4609
5759
  window.addEventListener("message", handleAnimLock);
4610
5760
  window.addEventListener("message", handleCanvasHeight);
4611
5761
  window.addEventListener("message", handleParentScroll);
4612
5762
  window.addEventListener("message", handlePointerSync);
5763
+ window.addEventListener("message", handleClickAt);
4613
5764
  document.addEventListener("click", handleClick, true);
4614
5765
  document.addEventListener("paste", handlePaste, true);
4615
5766
  document.addEventListener("input", handleInput, true);
@@ -4638,18 +5789,26 @@ function OhhwellsBridge() {
4638
5789
  document.removeEventListener("selectionchange", handleSelectionChange);
4639
5790
  window.removeEventListener("scroll", handleScroll, true);
4640
5791
  window.removeEventListener("message", handleSave);
5792
+ window.removeEventListener("message", handleInsertSection);
5793
+ window.removeEventListener("message", handleSwitchSchedule);
5794
+ window.removeEventListener("message", handleScheduleLinked);
5795
+ window.removeEventListener("message", handleClearSchedulingWidget);
5796
+ window.removeEventListener("message", handleRemoveSchedulingSection);
4641
5797
  window.removeEventListener("message", handleImageUrl);
4642
5798
  window.removeEventListener("message", handleAnimLock);
4643
5799
  window.removeEventListener("message", handleCanvasHeight);
4644
5800
  window.removeEventListener("message", handleParentScroll);
4645
5801
  window.removeEventListener("message", handlePointerSync);
5802
+ window.removeEventListener("message", handleClickAt);
4646
5803
  window.removeEventListener("message", handleHydrate);
4647
5804
  window.removeEventListener("message", handleDeactivate);
4648
5805
  autoSaveTimers.current.forEach(clearTimeout);
4649
5806
  autoSaveTimers.current.clear();
5807
+ if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
5808
+ if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4650
5809
  };
4651
5810
  }, [isEditMode, refreshStateRules]);
4652
- useEffect(() => {
5811
+ useEffect2(() => {
4653
5812
  if (!isEditMode) return;
4654
5813
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
4655
5814
  el.removeAttribute("data-ohw-active-state");
@@ -4691,12 +5850,12 @@ function OhhwellsBridge() {
4691
5850
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4692
5851
  }, [deactivate]);
4693
5852
  return bridgeRoot ? createPortal(
4694
- /* @__PURE__ */ jsxs(Fragment, { children: [
4695
- toolbarRect && /* @__PURE__ */ jsxs(Fragment, { children: [
4696
- /* @__PURE__ */ jsx3(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4697
- /* @__PURE__ */ jsx3(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
5853
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
5854
+ toolbarRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
5855
+ /* @__PURE__ */ jsx6(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5856
+ /* @__PURE__ */ jsx6(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4698
5857
  ] }),
4699
- maxBadge && /* @__PURE__ */ jsxs(
5858
+ maxBadge && /* @__PURE__ */ jsxs3(
4700
5859
  "div",
4701
5860
  {
4702
5861
  "data-ohw-max-badge": "",
@@ -4722,7 +5881,7 @@ function OhhwellsBridge() {
4722
5881
  ]
4723
5882
  }
4724
5883
  ),
4725
- toggleState && /* @__PURE__ */ jsx3(
5884
+ toggleState && /* @__PURE__ */ jsx6(
4726
5885
  StateToggle,
4727
5886
  {
4728
5887
  rect: toggleState.rect,
@@ -4730,6 +5889,31 @@ function OhhwellsBridge() {
4730
5889
  states: toggleState.states,
4731
5890
  onStateChange: handleStateChange
4732
5891
  }
5892
+ ),
5893
+ sectionGap && /* @__PURE__ */ jsxs3(
5894
+ "div",
5895
+ {
5896
+ "data-ohw-section-insert-line": "",
5897
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
5898
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
5899
+ children: [
5900
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5901
+ /* @__PURE__ */ jsx6(
5902
+ Badge,
5903
+ {
5904
+ 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",
5905
+ onClick: () => {
5906
+ window.parent.postMessage(
5907
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
5908
+ "*"
5909
+ );
5910
+ },
5911
+ children: "Add Section"
5912
+ }
5913
+ ),
5914
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5915
+ ]
5916
+ }
4733
5917
  )
4734
5918
  ] }),
4735
5919
  bridgeRoot
@@ -4737,6 +5921,7 @@ function OhhwellsBridge() {
4737
5921
  }
4738
5922
  export {
4739
5923
  OhhwellsBridge,
5924
+ SchedulingWidget,
4740
5925
  Toggle,
4741
5926
  ToggleGroup,
4742
5927
  ToggleGroupItem,