@ohhwells/bridge 0.1.19 → 0.1.21

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,12 +1,167 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef, useState as useState2 } from "react";
4
+ import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
+ import { flushSync } from "react-dom";
6
7
 
7
8
  // src/ui/SchedulingWidget.tsx
8
- import React, { useEffect, useMemo, useState } from "react";
9
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
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";
10
165
  var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
11
166
  var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
12
167
  function getApiDomain() {
@@ -67,8 +222,8 @@ function EmptyState({ inEditor }) {
67
222
  window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
68
223
  }
69
224
  };
70
- return /* @__PURE__ */ 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: [
71
- /* @__PURE__ */ jsx("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs(
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(
72
227
  "svg",
73
228
  {
74
229
  width: "20",
@@ -80,20 +235,20 @@ function EmptyState({ inEditor }) {
80
235
  strokeLinecap: "round",
81
236
  strokeLinejoin: "round",
82
237
  children: [
83
- /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
84
- /* @__PURE__ */ jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
85
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
86
- /* @__PURE__ */ jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
87
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
88
- /* @__PURE__ */ jsx("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
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" })
89
244
  ]
90
245
  }
91
246
  ) }),
92
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
93
- /* @__PURE__ */ jsx("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
94
- /* @__PURE__ */ 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." })
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." })
95
250
  ] }),
96
- /* @__PURE__ */ jsx(
251
+ /* @__PURE__ */ jsx2(
97
252
  "button",
98
253
  {
99
254
  onClick: handleAddSchedule,
@@ -109,32 +264,90 @@ function LoadingSkeleton() {
109
264
  backgroundSize: "200% 100%",
110
265
  animation: "ohw-shimmer 1.5s infinite"
111
266
  };
112
- return /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-10", children: [
113
- /* @__PURE__ */ jsx("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
114
- /* @__PURE__ */ jsx("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
115
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
116
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
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%" } })
117
272
  ] }, i)) }),
118
- [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { children: [
119
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[60px] py-4", children: [
120
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
121
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
122
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
123
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
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" } })
124
291
  ] }),
125
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
126
- /* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
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 } })
127
294
  ] }),
128
- i < 2 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
295
+ i < 2 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
129
296
  ] }, i))
130
297
  ] });
131
298
  }
132
- function ScheduleView({ schedule, dates, selectedIdx, onSelectDate }) {
299
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
133
300
  const todayMs = useMemo(() => {
134
301
  const d = /* @__PURE__ */ new Date();
135
302
  d.setHours(0, 0, 0, 0);
136
303
  return d.getTime();
137
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
+ };
138
351
  const datesWithClasses = useMemo(
139
352
  () => new Set(dates.map(
140
353
  (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
@@ -146,82 +359,154 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate }) {
146
359
  () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
147
360
  [schedule, selectedDate]
148
361
  );
149
- return /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-10", children: [
150
- /* @__PURE__ */ jsx("div", { className: "overflow-x-auto w-full", children: /* @__PURE__ */ jsx("div", { className: "flex min-w-max", children: dates.map((date, i) => {
151
- const isToday = date.getTime() === todayMs;
152
- const isSelected = i === selectedIdx;
153
- const clickable = datesWithClasses.has(i);
154
- const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
155
- return /* @__PURE__ */ jsxs(
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(
156
385
  "div",
157
386
  {
158
- onClick: () => clickable && onSelectDate(i),
159
- className: `flex-none w-[72px] h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
160
- children: [
161
- /* @__PURE__ */ jsx("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
162
- /* @__PURE__ */ jsx(
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(
163
414
  "div",
164
415
  {
165
- className: "w-10 h-10 rounded-full flex items-center justify-center",
166
- style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
167
- children: /* @__PURE__ */ jsx(
168
- "span",
169
- {
170
- className: "font-body text-xl font-bold text-center tracking-display-tight",
171
- style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
172
- children: date.getDate()
173
- }
174
- )
175
- }
176
- )
177
- ]
178
- },
179
- i
180
- );
181
- }) }) }),
182
- selectedClasses.length === 0 ? /* @__PURE__ */ 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__ */ jsx("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
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) => {
183
444
  const booked = getBookingsOnDate(cls, selectedDate);
184
445
  const available = cls.maxParticipants - booked;
185
446
  const isFull = available <= 0;
186
- return /* @__PURE__ */ jsxs("div", { children: [
187
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
188
- /* @__PURE__ */ jsx("div", { className: "w-[120px] shrink-0", children: /* @__PURE__ */ jsx("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
189
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
190
- /* @__PURE__ */ jsx("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
191
- cls.hostName && /* @__PURE__ */ jsx("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
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
+ ] }) })
192
467
  ] }),
193
- /* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
194
- /* @__PURE__ */ jsxs("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
195
- available,
196
- "/",
197
- cls.maxParticipants
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 })
198
472
  ] }),
199
- /* @__PURE__ */ jsx("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
200
- ] }) }),
201
- /* @__PURE__ */ jsx(
202
- "button",
203
- {
204
- className: "shrink-0 w-[200px] px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
205
- style: isFull ? {
206
- background: "transparent",
207
- border: "1px solid var(--color-dark, #200C02)",
208
- color: "var(--color-dark, #200C02)"
209
- } : {
210
- background: "var(--color-primary, #3D312B)",
211
- border: "none",
212
- color: "var(--color-light, #FEFFF0)"
213
- },
214
- children: isFull ? "Join Waitlist" : "Book Now"
215
- }
216
- )
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
+ ] })
217
502
  ] }),
218
- i < selectedClasses.length - 1 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
503
+ i < selectedClasses.length - 1 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
219
504
  ] }, cls.id ?? i);
220
505
  }) })
221
506
  ] });
222
507
  }
223
508
  function CalendarFoldIcon() {
224
- return /* @__PURE__ */ jsxs(
509
+ return /* @__PURE__ */ jsxs2(
225
510
  "svg",
226
511
  {
227
512
  width: "16",
@@ -234,21 +519,22 @@ function CalendarFoldIcon() {
234
519
  strokeLinejoin: "round",
235
520
  style: { flexShrink: 0 },
236
521
  children: [
237
- /* @__PURE__ */ jsx("path", { d: "M8 2v4" }),
238
- /* @__PURE__ */ jsx("path", { d: "M16 2v4" }),
239
- /* @__PURE__ */ jsx("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
240
- /* @__PURE__ */ jsx("path", { d: "M3 10h18" }),
241
- /* @__PURE__ */ jsx("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
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" })
242
527
  ]
243
528
  }
244
529
  );
245
530
  }
246
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
247
- const [schedule, setSchedule] = useState(null);
248
- const [loading, setLoading] = useState(true);
249
- const [inEditor, setInEditor] = useState(false);
250
- const [isHovered, setIsHovered] = useState(false);
251
- const switchScheduleIdRef = React.useRef(null);
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);
252
538
  const dates = useMemo(() => {
253
539
  const today = /* @__PURE__ */ new Date();
254
540
  today.setHours(0, 0, 0, 0);
@@ -258,7 +544,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
258
544
  return d;
259
545
  });
260
546
  }, []);
261
- const [selectedIdx, setSelectedIdx] = useState(0);
547
+ const [selectedIdx, setSelectedIdx] = useState2(0);
262
548
  useEffect(() => {
263
549
  if (!schedule?.classes) return;
264
550
  for (let i = 0; i < dates.length; i++) {
@@ -284,18 +570,21 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
284
570
  }, 5e3) : null;
285
571
  const handler = (e) => {
286
572
  if (e.data?.type === "ow:clear-scheduling-widget") {
573
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
287
574
  setSchedule(null);
288
575
  setLoading(false);
289
576
  return;
290
577
  }
291
578
  if (e.data?.type === "ow:switch-schedule") {
579
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
292
580
  switchScheduleIdRef.current = e.data.scheduleId ?? null;
293
581
  initialized = false;
294
582
  setLoading(true);
295
- window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
583
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
296
584
  return;
297
585
  }
298
586
  if (e.data?.type !== "ow:user-schedules-response") return;
587
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
299
588
  if (initialized && switchScheduleIdRef.current === null) return;
300
589
  initialized = true;
301
590
  if (timer) clearTimeout(timer);
@@ -306,11 +595,16 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
306
595
  setSchedule(target);
307
596
  setLoading(false);
308
597
  if (notifyOnConnect && target && !isSwitching) {
309
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
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 }, "*");
310
604
  }
311
605
  };
312
606
  window.addEventListener("message", handler);
313
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
607
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
314
608
  return () => {
315
609
  window.removeEventListener("message", handler);
316
610
  if (timer) clearTimeout(timer);
@@ -354,31 +648,51 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
354
648
  return void 0;
355
649
  }
356
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
+ };
357
665
  const handleReplaceSchedule = () => {
358
666
  setIsHovered(false);
359
667
  if (schedule) {
360
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
668
+ window.parent.postMessage({
669
+ type: "ow:schedule-connected",
670
+ schedule: { id: schedule.id, name: schedule.name },
671
+ insertAfter
672
+ }, "*");
361
673
  } else {
362
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
674
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
363
675
  }
364
676
  };
365
- return /* @__PURE__ */ jsxs(
677
+ const sectionId = `scheduling-${insertAfter}`;
678
+ return /* @__PURE__ */ jsxs2(
366
679
  "section",
367
680
  {
368
- "data-ohw-section": "scheduling",
369
- className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
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",
370
684
  onMouseEnter: () => inEditor && setIsHovered(true),
371
685
  onMouseLeave: () => setIsHovered(false),
372
686
  children: [
373
- inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs(
687
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs2(
374
688
  "div",
375
689
  {
376
690
  className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
377
691
  onClick: handleReplaceSchedule,
378
692
  children: [
379
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
380
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
381
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs(
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(
382
696
  "div",
383
697
  {
384
698
  className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
@@ -390,7 +704,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
390
704
  color: "#0C0A09"
391
705
  },
392
706
  children: [
393
- /* @__PURE__ */ jsx(CalendarFoldIcon, {}),
707
+ /* @__PURE__ */ jsx2(CalendarFoldIcon, {}),
394
708
  "Replace schedule"
395
709
  ]
396
710
  }
@@ -398,24 +712,34 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
398
712
  ]
399
713
  }
400
714
  ),
401
- /* @__PURE__ */ jsxs("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
402
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
403
- /* @__PURE__ */ jsx("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
404
- schedule?.description && /* @__PURE__ */ jsx("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
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 })
405
719
  ] }),
406
- /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-end gap-3", children: [
407
- timezoneLabel && /* @__PURE__ */ jsx("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
408
- /* @__PURE__ */ jsx("div", { className: "w-full p-10 box-border", children: loading ? /* @__PURE__ */ jsx(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx(EmptyState, { inEditor }) : /* @__PURE__ */ jsx(
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(
409
723
  ScheduleView,
410
724
  {
411
725
  schedule,
412
726
  dates,
413
727
  selectedIdx,
414
- onSelectDate: setSelectedIdx
728
+ onSelectDate: setSelectedIdx,
729
+ onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
415
730
  }
416
731
  ) })
417
732
  ] })
418
- ] })
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
+ )
419
743
  ]
420
744
  }
421
745
  );
@@ -3742,7 +4066,7 @@ var cva = (base, config) => (props) => {
3742
4066
 
3743
4067
  // src/ui/toggle.tsx
3744
4068
  import { Toggle as TogglePrimitive } from "radix-ui";
3745
- import { jsx as jsx2 } from "react/jsx-runtime";
4069
+ import { jsx as jsx3 } from "react/jsx-runtime";
3746
4070
  var toggleVariants = cva(
3747
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",
3748
4072
  {
@@ -3769,7 +4093,7 @@ function Toggle({
3769
4093
  size,
3770
4094
  ...props
3771
4095
  }) {
3772
- return /* @__PURE__ */ jsx2(
4096
+ return /* @__PURE__ */ jsx3(
3773
4097
  TogglePrimitive.Root,
3774
4098
  {
3775
4099
  "data-slot": "toggle",
@@ -3780,7 +4104,7 @@ function Toggle({
3780
4104
  }
3781
4105
 
3782
4106
  // src/ui/toggle-group.tsx
3783
- import { jsx as jsx3 } from "react/jsx-runtime";
4107
+ import { jsx as jsx4 } from "react/jsx-runtime";
3784
4108
  var ToggleGroupContext = React2.createContext({
3785
4109
  value: "",
3786
4110
  onValueChange: () => {
@@ -3793,13 +4117,13 @@ function ToggleGroup({
3793
4117
  children,
3794
4118
  ...props
3795
4119
  }) {
3796
- return /* @__PURE__ */ jsx3(
4120
+ return /* @__PURE__ */ jsx4(
3797
4121
  "div",
3798
4122
  {
3799
4123
  role: "group",
3800
4124
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3801
4125
  ...props,
3802
- children: /* @__PURE__ */ jsx3(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4126
+ children: /* @__PURE__ */ jsx4(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3803
4127
  }
3804
4128
  );
3805
4129
  }
@@ -3810,7 +4134,7 @@ function ToggleGroupItem({
3810
4134
  ...props
3811
4135
  }) {
3812
4136
  const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
3813
- return /* @__PURE__ */ jsx3(
4137
+ return /* @__PURE__ */ jsx4(
3814
4138
  Toggle,
3815
4139
  {
3816
4140
  pressed: groupValue === value,
@@ -3852,7 +4176,7 @@ function isEditSessionActive() {
3852
4176
  }
3853
4177
 
3854
4178
  // src/ui/badge.tsx
3855
- import { jsx as jsx4 } from "react/jsx-runtime";
4179
+ import { jsx as jsx5 } from "react/jsx-runtime";
3856
4180
  var badgeVariants = cva(
3857
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",
3858
4182
  {
@@ -3870,11 +4194,11 @@ var badgeVariants = cva(
3870
4194
  }
3871
4195
  );
3872
4196
  function Badge({ className, variant, ...props }) {
3873
- return /* @__PURE__ */ jsx4("div", { className: cn(badgeVariants({ variant }), className), ...props });
4197
+ return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
3874
4198
  }
3875
4199
 
3876
4200
  // src/OhhwellsBridge.tsx
3877
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
4201
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
3878
4202
  var PRIMARY = "#0885FE";
3879
4203
  var IMAGE_FADE_MS = 300;
3880
4204
  function runOpacityFade(el, onDone) {
@@ -3935,24 +4259,135 @@ function getSectionsTracker() {
3935
4259
  }
3936
4260
  return el;
3937
4261
  }
3938
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
3939
- const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
3940
- if (!anchor) return false;
3941
- if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
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;
3942
4353
  const container = document.createElement("div");
3943
4354
  container.dataset.ohwSectionContainer = "scheduling";
3944
- anchor.insertAdjacentElement("afterend", container);
3945
- createRoot(container).render(/* @__PURE__ */ jsx5(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
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
+ });
3946
4380
  const tracker = getSectionsTracker();
3947
4381
  let sections = [];
3948
4382
  try {
3949
4383
  sections = JSON.parse(tracker.textContent || "[]");
3950
4384
  } catch {
3951
4385
  }
3952
- if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
4386
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4387
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
3953
4388
  sections.push({
3954
4389
  type: "scheduling",
3955
- insertAfter,
4390
+ insertAfter: effectiveInsertAfter,
3956
4391
  pagePath: window.location.pathname,
3957
4392
  ...scheduleId ? { scheduleId } : {}
3958
4393
  });
@@ -3960,6 +4395,17 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId)
3960
4395
  }
3961
4396
  return true;
3962
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
+ }
3963
4409
  function collectEditableNodes() {
3964
4410
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
3965
4411
  if (el.dataset.ohwEditable === "image") {
@@ -4117,7 +4563,7 @@ var TOOLBAR_GROUPS = [
4117
4563
  ];
4118
4564
  function GlowFrame({ rect, elRef }) {
4119
4565
  const GAP = 6;
4120
- return /* @__PURE__ */ jsx5(
4566
+ return /* @__PURE__ */ jsx6(
4121
4567
  "div",
4122
4568
  {
4123
4569
  ref: elRef,
@@ -4168,7 +4614,7 @@ function FloatingToolbar({
4168
4614
  activeCommands
4169
4615
  }) {
4170
4616
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4171
- return /* @__PURE__ */ jsx5(
4617
+ return /* @__PURE__ */ jsx6(
4172
4618
  "div",
4173
4619
  {
4174
4620
  ref: elRef,
@@ -4191,11 +4637,11 @@ function FloatingToolbar({
4191
4637
  pointerEvents: "auto",
4192
4638
  whiteSpace: "nowrap"
4193
4639
  },
4194
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
4195
- gi > 0 && /* @__PURE__ */ jsx5("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 } }),
4196
4642
  btns.map((btn) => {
4197
4643
  const isActive = activeCommands.has(btn.cmd);
4198
- return /* @__PURE__ */ jsx5(
4644
+ return /* @__PURE__ */ jsx6(
4199
4645
  "button",
4200
4646
  {
4201
4647
  title: btn.title,
@@ -4221,7 +4667,7 @@ function FloatingToolbar({
4221
4667
  flexShrink: 0,
4222
4668
  padding: 6
4223
4669
  },
4224
- children: /* @__PURE__ */ jsx5(
4670
+ children: /* @__PURE__ */ jsx6(
4225
4671
  "svg",
4226
4672
  {
4227
4673
  width: "16",
@@ -4254,7 +4700,7 @@ function StateToggle({
4254
4700
  states,
4255
4701
  onStateChange
4256
4702
  }) {
4257
- return /* @__PURE__ */ jsx5(
4703
+ return /* @__PURE__ */ jsx6(
4258
4704
  ToggleGroup,
4259
4705
  {
4260
4706
  "data-ohw-state-toggle": "",
@@ -4268,17 +4714,34 @@ function StateToggle({
4268
4714
  left: rect.right - 8,
4269
4715
  transform: "translateX(-100%)"
4270
4716
  },
4271
- children: states.map((state) => /* @__PURE__ */ jsx5(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4717
+ children: states.map((state) => /* @__PURE__ */ jsx6(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4272
4718
  }
4273
4719
  );
4274
4720
  }
4275
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
+ }
4276
4739
  function OhhwellsBridge() {
4277
4740
  const pathname = usePathname();
4278
4741
  const router = useRouter();
4279
4742
  const searchParams = useSearchParams();
4280
4743
  const isEditMode = isEditSessionActive();
4281
- const [bridgeRoot, setBridgeRoot] = useState2(null);
4744
+ const [bridgeRoot, setBridgeRoot] = useState3(null);
4282
4745
  useEffect2(() => {
4283
4746
  const figtreeFontId = "ohw-figtree-font";
4284
4747
  if (!document.getElementById(figtreeFontId)) {
@@ -4302,43 +4765,39 @@ function OhhwellsBridge() {
4302
4765
  };
4303
4766
  }, []);
4304
4767
  const subdomainFromQuery = searchParams.get("subdomain");
4305
- const subdomain = subdomainFromQuery ?? (() => {
4306
- if (typeof window === "undefined") return "";
4307
- const parts = window.location.hostname.split(".");
4308
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4309
- })();
4768
+ const subdomain = resolveSubdomain(subdomainFromQuery);
4310
4769
  const postToParent = useCallback((data) => {
4311
4770
  if (typeof window !== "undefined" && window.parent !== window) {
4312
4771
  window.parent.postMessage(data, "*");
4313
4772
  }
4314
4773
  }, []);
4315
- const [fetchState, setFetchState] = useState2("idle");
4316
- const autoSaveTimers = useRef(/* @__PURE__ */ new Map());
4317
- const activeElRef = useRef(null);
4318
- const originalContentRef = useRef(null);
4319
- const activeStateElRef = useRef(null);
4320
- const parentScrollRef = useRef(null);
4321
- const toolbarElRef = useRef(null);
4322
- const glowElRef = useRef(null);
4323
- const hoveredImageRef = useRef(null);
4324
- const hoveredImageHasTextOverlapRef = useRef(false);
4325
- const hoveredGapRef = useRef(null);
4326
- const imageUnhoverTimerRef = useRef(null);
4327
- const imageShowTimerRef = useRef(null);
4328
- const editStylesRef = useRef(null);
4329
- 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(() => {
4330
4789
  });
4331
- const deactivateRef = useRef(() => {
4790
+ const deactivateRef = useRef2(() => {
4332
4791
  });
4333
- const refreshActiveCommandsRef = useRef(() => {
4792
+ const refreshActiveCommandsRef = useRef2(() => {
4334
4793
  });
4335
- const postToParentRef = useRef(postToParent);
4794
+ const postToParentRef = useRef2(postToParent);
4336
4795
  postToParentRef.current = postToParent;
4337
- const [toolbarRect, setToolbarRect] = useState2(null);
4338
- const [toggleState, setToggleState] = useState2(null);
4339
- const [maxBadge, setMaxBadge] = useState2(null);
4340
- const [activeCommands, setActiveCommands] = useState2(/* @__PURE__ */ new Set());
4341
- const [sectionGap, setSectionGap] = useState2(null);
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);
4342
4801
  useEffect2(() => {
4343
4802
  const update = () => {
4344
4803
  const el = activeElRef.current;
@@ -4416,18 +4875,6 @@ function OhhwellsBridge() {
4416
4875
  }
4417
4876
  const applyContent = (content) => {
4418
4877
  const imageLoads = [];
4419
- document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
4420
- if (content["__ohw_sections"]) {
4421
- try {
4422
- const entries = JSON.parse(content["__ohw_sections"]);
4423
- const currentPath = window.location.pathname;
4424
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4425
- if (pagePath && pagePath !== currentPath) return;
4426
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4427
- });
4428
- } catch {
4429
- }
4430
- }
4431
4878
  for (const [key, val] of Object.entries(content)) {
4432
4879
  if (key === "__ohw_sections") continue;
4433
4880
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4448,6 +4895,7 @@ function OhhwellsBridge() {
4448
4895
  }
4449
4896
  });
4450
4897
  }
4898
+ initSectionsFromContent(content, true);
4451
4899
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
4452
4900
  }) : Promise.resolve();
4453
4901
  };
@@ -4474,20 +4922,11 @@ function OhhwellsBridge() {
4474
4922
  }, [subdomain, isEditMode, pathname]);
4475
4923
  useEffect2(() => {
4476
4924
  if (!subdomain || isEditMode) return;
4925
+ let debounceTimer = null;
4477
4926
  const applyFromCache = () => {
4478
4927
  const content = contentCache.get(subdomain);
4479
4928
  if (!content) return;
4480
- if (content["__ohw_sections"]) {
4481
- try {
4482
- const entries = JSON.parse(content["__ohw_sections"]);
4483
- const currentPath = window.location.pathname;
4484
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4485
- if (pagePath && pagePath !== currentPath) return;
4486
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4487
- });
4488
- } catch {
4489
- }
4490
- }
4929
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
4491
4930
  for (const [key, val] of Object.entries(content)) {
4492
4931
  if (key === "__ohw_sections") continue;
4493
4932
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4503,11 +4942,18 @@ function OhhwellsBridge() {
4503
4942
  });
4504
4943
  }
4505
4944
  };
4506
- applyFromCache();
4507
- 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);
4508
4951
  observer.observe(document.body, { childList: true, subtree: true });
4509
- return () => observer.disconnect();
4510
- }, [subdomain, isEditMode]);
4952
+ return () => {
4953
+ observer.disconnect();
4954
+ if (debounceTimer) clearTimeout(debounceTimer);
4955
+ };
4956
+ }, [subdomain, isEditMode, pathname]);
4511
4957
  useLayoutEffect(() => {
4512
4958
  const el = document.getElementById("ohw-loader");
4513
4959
  if (!el) return;
@@ -5080,17 +5526,10 @@ function OhhwellsBridge() {
5080
5526
  if (e.data?.type !== "ow:hydrate") return;
5081
5527
  const content = e.data.content;
5082
5528
  if (!content) return;
5529
+ let sectionsJson = null;
5083
5530
  for (const [key, val] of Object.entries(content)) {
5084
5531
  if (key === "__ohw_sections") {
5085
- try {
5086
- const entries = JSON.parse(val);
5087
- const tracker = getSectionsTracker();
5088
- tracker.textContent = val;
5089
- entries.forEach(({ type, insertAfter, scheduleId }) => {
5090
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
5091
- });
5092
- } catch {
5093
- }
5532
+ sectionsJson = val;
5094
5533
  continue;
5095
5534
  }
5096
5535
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5104,6 +5543,9 @@ function OhhwellsBridge() {
5104
5543
  }
5105
5544
  });
5106
5545
  }
5546
+ if (sectionsJson) {
5547
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
5548
+ }
5107
5549
  postToParentRef.current({ type: "ow:hydrate-done" });
5108
5550
  };
5109
5551
  window.addEventListener("message", handleHydrate);
@@ -5154,9 +5596,9 @@ function OhhwellsBridge() {
5154
5596
  };
5155
5597
  const handleInsertSection = (e) => {
5156
5598
  if (e.data?.type !== "ow:insert-section") return;
5157
- const { widgetType, insertAfter } = e.data;
5599
+ const { widgetType, insertAfter, insertBefore } = e.data;
5158
5600
  if (widgetType !== "scheduling") return;
5159
- const inserted = mountSchedulingWidget(insertAfter, true);
5601
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
5160
5602
  if (inserted) {
5161
5603
  const tracker = getSectionsTracker();
5162
5604
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
@@ -5167,39 +5609,31 @@ function OhhwellsBridge() {
5167
5609
  const handleSwitchSchedule = (e) => {
5168
5610
  if (e.data?.type !== "ow:switch-schedule") return;
5169
5611
  const scheduleId = e.data.scheduleId;
5170
- if (!scheduleId) return;
5171
- const tracker = getSectionsTracker();
5172
- let sections = [];
5173
- try {
5174
- sections = JSON.parse(tracker.textContent || "[]");
5175
- } catch {
5176
- }
5177
- const currentPath = window.location.pathname;
5178
- const updated = sections.map(
5179
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
5180
- );
5181
- tracker.textContent = JSON.stringify(updated);
5182
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
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 }] });
5183
5624
  };
5184
5625
  const handleClearSchedulingWidget = (e) => {
5185
5626
  if (e.data?.type !== "ow:clear-scheduling-widget") return;
5186
- const tracker = getSectionsTracker();
5187
- let sections = [];
5188
- try {
5189
- sections = JSON.parse(tracker.textContent || "[]");
5190
- } catch {
5191
- }
5192
- const currentPath = window.location.pathname;
5193
- const updated = sections.map(
5194
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
5195
- );
5196
- tracker.textContent = JSON.stringify(updated);
5197
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
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 }] });
5198
5631
  };
5199
5632
  const handleRemoveSchedulingSection = (e) => {
5200
5633
  if (e.data?.type !== "ow:remove-scheduling-section") return;
5201
- const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
5202
- els.forEach((el) => el.parentElement?.removeChild(el));
5634
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
5635
+ el.remove();
5636
+ });
5203
5637
  const tracker = getSectionsTracker();
5204
5638
  let sections = [];
5205
5639
  try {
@@ -5318,6 +5752,7 @@ function OhhwellsBridge() {
5318
5752
  window.addEventListener("message", handleSave);
5319
5753
  window.addEventListener("message", handleInsertSection);
5320
5754
  window.addEventListener("message", handleSwitchSchedule);
5755
+ window.addEventListener("message", handleScheduleLinked);
5321
5756
  window.addEventListener("message", handleClearSchedulingWidget);
5322
5757
  window.addEventListener("message", handleRemoveSchedulingSection);
5323
5758
  window.addEventListener("message", handleImageUrl);
@@ -5356,6 +5791,7 @@ function OhhwellsBridge() {
5356
5791
  window.removeEventListener("message", handleSave);
5357
5792
  window.removeEventListener("message", handleInsertSection);
5358
5793
  window.removeEventListener("message", handleSwitchSchedule);
5794
+ window.removeEventListener("message", handleScheduleLinked);
5359
5795
  window.removeEventListener("message", handleClearSchedulingWidget);
5360
5796
  window.removeEventListener("message", handleRemoveSchedulingSection);
5361
5797
  window.removeEventListener("message", handleImageUrl);
@@ -5414,12 +5850,12 @@ function OhhwellsBridge() {
5414
5850
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
5415
5851
  }, [deactivate]);
5416
5852
  return bridgeRoot ? createPortal(
5417
- /* @__PURE__ */ jsxs2(Fragment2, { children: [
5418
- toolbarRect && /* @__PURE__ */ jsxs2(Fragment2, { children: [
5419
- /* @__PURE__ */ jsx5(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5420
- /* @__PURE__ */ jsx5(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 })
5421
5857
  ] }),
5422
- maxBadge && /* @__PURE__ */ jsxs2(
5858
+ maxBadge && /* @__PURE__ */ jsxs3(
5423
5859
  "div",
5424
5860
  {
5425
5861
  "data-ohw-max-badge": "",
@@ -5445,7 +5881,7 @@ function OhhwellsBridge() {
5445
5881
  ]
5446
5882
  }
5447
5883
  ),
5448
- toggleState && /* @__PURE__ */ jsx5(
5884
+ toggleState && /* @__PURE__ */ jsx6(
5449
5885
  StateToggle,
5450
5886
  {
5451
5887
  rect: toggleState.rect,
@@ -5454,15 +5890,15 @@ function OhhwellsBridge() {
5454
5890
  onStateChange: handleStateChange
5455
5891
  }
5456
5892
  ),
5457
- sectionGap && /* @__PURE__ */ jsxs2(
5893
+ sectionGap && /* @__PURE__ */ jsxs3(
5458
5894
  "div",
5459
5895
  {
5460
5896
  "data-ohw-section-insert-line": "",
5461
5897
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
5462
5898
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
5463
5899
  children: [
5464
- /* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5465
- /* @__PURE__ */ jsx5(
5900
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5901
+ /* @__PURE__ */ jsx6(
5466
5902
  Badge,
5467
5903
  {
5468
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",
@@ -5475,7 +5911,7 @@ function OhhwellsBridge() {
5475
5911
  children: "Add Section"
5476
5912
  }
5477
5913
  ),
5478
- /* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5914
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5479
5915
  ]
5480
5916
  }
5481
5917
  )