@ohhwells/bridge 0.1.17 → 0.1.19

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,428 @@
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, useState as useState2 } from "react";
5
+ import { createRoot } from "react-dom/client";
6
+
7
+ // src/ui/SchedulingWidget.tsx
8
+ import React, { useEffect, useMemo, useState } from "react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
11
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
12
+ function getApiDomain() {
13
+ const h = window.location.hostname;
14
+ for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
15
+ if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
16
+ }
17
+ return h;
18
+ }
19
+ function classRunsOnDate(cls, date, type) {
20
+ if (type === "FIXED") {
21
+ const d = String(date.getDate()).padStart(2, "0");
22
+ const m = String(date.getMonth() + 1).padStart(2, "0");
23
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
24
+ }
25
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
26
+ }
27
+ function formatClassTime(cls) {
28
+ let h = parseInt(cls.startTime.hour, 10);
29
+ const m = parseInt(cls.startTime.minutes, 10);
30
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
31
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
32
+ const endTotal = h * 60 + m + cls.duration;
33
+ const eh = Math.floor(endTotal / 60) % 24;
34
+ const em = endTotal % 60;
35
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
36
+ }
37
+ function getBookingsOnDate(cls, date) {
38
+ if (!cls.bookings?.length) return 0;
39
+ const ds = date.toISOString().split("T")[0];
40
+ return cls.bookings.filter((b) => {
41
+ try {
42
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }).length;
47
+ }
48
+ function buildTimezoneLabel(tz) {
49
+ try {
50
+ const now = /* @__PURE__ */ new Date();
51
+ const timeStr = new Intl.DateTimeFormat("en-US", {
52
+ hour: "2-digit",
53
+ minute: "2-digit",
54
+ hour12: false,
55
+ timeZone: tz
56
+ }).format(now);
57
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
58
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
59
+ return `${timeStr} (${offsetPart}) ${city} time`;
60
+ } catch {
61
+ return tz;
62
+ }
63
+ }
64
+ function EmptyState({ inEditor }) {
65
+ const handleAddSchedule = () => {
66
+ if (inEditor) {
67
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
68
+ }
69
+ };
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(
72
+ "svg",
73
+ {
74
+ width: "20",
75
+ height: "20",
76
+ viewBox: "0 0 24 24",
77
+ fill: "none",
78
+ stroke: "var(--color-accent, #A89B83)",
79
+ strokeWidth: "1.5",
80
+ strokeLinecap: "round",
81
+ strokeLinejoin: "round",
82
+ 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" })
89
+ ]
90
+ }
91
+ ) }),
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." })
95
+ ] }),
96
+ /* @__PURE__ */ jsx(
97
+ "button",
98
+ {
99
+ onClick: handleAddSchedule,
100
+ 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",
101
+ children: "Add Schedule"
102
+ }
103
+ )
104
+ ] });
105
+ }
106
+ function LoadingSkeleton() {
107
+ const shimmer = {
108
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
109
+ backgroundSize: "200% 100%",
110
+ animation: "ohw-shimmer 1.5s infinite"
111
+ };
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%" } })
117
+ ] }, 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" } })
124
+ ] }),
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 } })
127
+ ] }),
128
+ i < 2 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
129
+ ] }, i))
130
+ ] });
131
+ }
132
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate }) {
133
+ const todayMs = useMemo(() => {
134
+ const d = /* @__PURE__ */ new Date();
135
+ d.setHours(0, 0, 0, 0);
136
+ return d.getTime();
137
+ }, []);
138
+ const datesWithClasses = useMemo(
139
+ () => new Set(dates.map(
140
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
141
+ ).filter((i) => i >= 0)),
142
+ [dates, schedule]
143
+ );
144
+ const selectedDate = dates[selectedIdx];
145
+ const selectedClasses = useMemo(
146
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
147
+ [schedule, selectedDate]
148
+ );
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(
156
+ "div",
157
+ {
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(
163
+ "div",
164
+ {
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) => {
183
+ const booked = getBookingsOnDate(cls, selectedDate);
184
+ const available = cls.maxParticipants - booked;
185
+ 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 })
192
+ ] }),
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
198
+ ] }),
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
+ )
217
+ ] }),
218
+ i < selectedClasses.length - 1 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
219
+ ] }, cls.id ?? i);
220
+ }) })
221
+ ] });
222
+ }
223
+ function CalendarFoldIcon() {
224
+ return /* @__PURE__ */ jsxs(
225
+ "svg",
226
+ {
227
+ width: "16",
228
+ height: "16",
229
+ viewBox: "0 0 24 24",
230
+ fill: "none",
231
+ stroke: "currentColor",
232
+ strokeWidth: "2",
233
+ strokeLinecap: "round",
234
+ strokeLinejoin: "round",
235
+ style: { flexShrink: 0 },
236
+ 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" })
242
+ ]
243
+ }
244
+ );
245
+ }
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);
252
+ const dates = useMemo(() => {
253
+ const today = /* @__PURE__ */ new Date();
254
+ today.setHours(0, 0, 0, 0);
255
+ return Array.from({ length: 14 }, (_, i) => {
256
+ const d = new Date(today);
257
+ d.setDate(today.getDate() + i);
258
+ return d;
259
+ });
260
+ }, []);
261
+ const [selectedIdx, setSelectedIdx] = useState(0);
262
+ useEffect(() => {
263
+ if (!schedule?.classes) return;
264
+ for (let i = 0; i < dates.length; i++) {
265
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
266
+ setSelectedIdx(i);
267
+ return;
268
+ }
269
+ }
270
+ }, [schedule, dates]);
271
+ useEffect(() => {
272
+ if (typeof window === "undefined") return;
273
+ const isInEditor = window.self !== window.top;
274
+ setInEditor(isInEditor);
275
+ if (isInEditor) {
276
+ const startEmpty = initialScheduleId === null;
277
+ if (startEmpty) setLoading(false);
278
+ let initialized = startEmpty;
279
+ const timer = !startEmpty ? setTimeout(() => {
280
+ if (!initialized) {
281
+ initialized = true;
282
+ setLoading(false);
283
+ }
284
+ }, 5e3) : null;
285
+ const handler = (e) => {
286
+ if (e.data?.type === "ow:clear-scheduling-widget") {
287
+ setSchedule(null);
288
+ setLoading(false);
289
+ return;
290
+ }
291
+ if (e.data?.type === "ow:switch-schedule") {
292
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
293
+ initialized = false;
294
+ setLoading(true);
295
+ window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
296
+ return;
297
+ }
298
+ if (e.data?.type !== "ow:user-schedules-response") return;
299
+ if (initialized && switchScheduleIdRef.current === null) return;
300
+ initialized = true;
301
+ if (timer) clearTimeout(timer);
302
+ const schedules = e.data.schedules ?? [];
303
+ const isSwitching = switchScheduleIdRef.current !== null;
304
+ 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;
305
+ switchScheduleIdRef.current = null;
306
+ setSchedule(target);
307
+ setLoading(false);
308
+ if (notifyOnConnect && target && !isSwitching) {
309
+ window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
310
+ }
311
+ };
312
+ window.addEventListener("message", handler);
313
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
314
+ return () => {
315
+ window.removeEventListener("message", handler);
316
+ if (timer) clearTimeout(timer);
317
+ };
318
+ }
319
+ if (initialScheduleId === null) {
320
+ setLoading(false);
321
+ return;
322
+ }
323
+ ;
324
+ (async () => {
325
+ try {
326
+ if (initialScheduleId) {
327
+ const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
328
+ const data = await res.json();
329
+ setSchedule(data?.id ? data : null);
330
+ } else {
331
+ const domain = getApiDomain();
332
+ const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
333
+ const slugs = await sitemapRes.json();
334
+ if (!Array.isArray(slugs) || !slugs.length) {
335
+ setLoading(false);
336
+ return;
337
+ }
338
+ const { slug } = [...slugs].sort(
339
+ (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
340
+ )[0];
341
+ const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
342
+ const data = await res.json();
343
+ setSchedule(data?.id ? data : null);
344
+ }
345
+ } catch {
346
+ }
347
+ setLoading(false);
348
+ })();
349
+ }, []);
350
+ const timezoneLabel = schedule?.timezone ? (() => {
351
+ try {
352
+ return buildTimezoneLabel(schedule.timezone);
353
+ } catch {
354
+ return void 0;
355
+ }
356
+ })() : void 0;
357
+ const handleReplaceSchedule = () => {
358
+ setIsHovered(false);
359
+ if (schedule) {
360
+ window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
361
+ } else {
362
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
363
+ }
364
+ };
365
+ return /* @__PURE__ */ jsxs(
366
+ "section",
367
+ {
368
+ "data-ohw-section": "scheduling",
369
+ className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
370
+ onMouseEnter: () => inEditor && setIsHovered(true),
371
+ onMouseLeave: () => setIsHovered(false),
372
+ children: [
373
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs(
374
+ "div",
375
+ {
376
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
377
+ onClick: handleReplaceSchedule,
378
+ 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(
382
+ "div",
383
+ {
384
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
385
+ style: {
386
+ fontSize: 14,
387
+ lineHeight: "24px",
388
+ fontFamily: "'Figtree', system-ui, sans-serif",
389
+ fontWeight: 500,
390
+ color: "#0C0A09"
391
+ },
392
+ children: [
393
+ /* @__PURE__ */ jsx(CalendarFoldIcon, {}),
394
+ "Replace schedule"
395
+ ]
396
+ }
397
+ ) })
398
+ ]
399
+ }
400
+ ),
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 })
405
+ ] }),
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(
409
+ ScheduleView,
410
+ {
411
+ schedule,
412
+ dates,
413
+ selectedIdx,
414
+ onSelectDate: setSelectedIdx
415
+ }
416
+ ) })
417
+ ] })
418
+ ] })
419
+ ]
420
+ }
421
+ );
422
+ }
5
423
 
6
424
  // src/ui/toggle-group.tsx
7
- import * as React from "react";
425
+ import * as React2 from "react";
8
426
 
9
427
  // node_modules/clsx/dist/clsx.mjs
10
428
  function r(e) {
@@ -3324,7 +3742,7 @@ var cva = (base, config) => (props) => {
3324
3742
 
3325
3743
  // src/ui/toggle.tsx
3326
3744
  import { Toggle as TogglePrimitive } from "radix-ui";
3327
- import { jsx } from "react/jsx-runtime";
3745
+ import { jsx as jsx2 } from "react/jsx-runtime";
3328
3746
  var toggleVariants = cva(
3329
3747
  "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
3748
  {
@@ -3351,7 +3769,7 @@ function Toggle({
3351
3769
  size,
3352
3770
  ...props
3353
3771
  }) {
3354
- return /* @__PURE__ */ jsx(
3772
+ return /* @__PURE__ */ jsx2(
3355
3773
  TogglePrimitive.Root,
3356
3774
  {
3357
3775
  "data-slot": "toggle",
@@ -3362,8 +3780,8 @@ function Toggle({
3362
3780
  }
3363
3781
 
3364
3782
  // src/ui/toggle-group.tsx
3365
- import { jsx as jsx2 } from "react/jsx-runtime";
3366
- var ToggleGroupContext = React.createContext({
3783
+ import { jsx as jsx3 } from "react/jsx-runtime";
3784
+ var ToggleGroupContext = React2.createContext({
3367
3785
  value: "",
3368
3786
  onValueChange: () => {
3369
3787
  }
@@ -3375,13 +3793,13 @@ function ToggleGroup({
3375
3793
  children,
3376
3794
  ...props
3377
3795
  }) {
3378
- return /* @__PURE__ */ jsx2(
3796
+ return /* @__PURE__ */ jsx3(
3379
3797
  "div",
3380
3798
  {
3381
3799
  role: "group",
3382
3800
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3383
3801
  ...props,
3384
- children: /* @__PURE__ */ jsx2(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3802
+ children: /* @__PURE__ */ jsx3(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3385
3803
  }
3386
3804
  );
3387
3805
  }
@@ -3391,8 +3809,8 @@ function ToggleGroupItem({
3391
3809
  children,
3392
3810
  ...props
3393
3811
  }) {
3394
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3395
- return /* @__PURE__ */ jsx2(
3812
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
3813
+ return /* @__PURE__ */ jsx3(
3396
3814
  Toggle,
3397
3815
  {
3398
3816
  pressed: groupValue === value,
@@ -3433,8 +3851,30 @@ function isEditSessionActive() {
3433
3851
  return new URLSearchParams(q).get("mode") === "edit";
3434
3852
  }
3435
3853
 
3854
+ // src/ui/badge.tsx
3855
+ import { jsx as jsx4 } from "react/jsx-runtime";
3856
+ var badgeVariants = cva(
3857
+ "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
+ {
3859
+ variants: {
3860
+ variant: {
3861
+ default: "border-transparent bg-primary text-primary-foreground",
3862
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
3863
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
3864
+ outline: "text-foreground"
3865
+ }
3866
+ },
3867
+ defaultVariants: {
3868
+ variant: "default"
3869
+ }
3870
+ }
3871
+ );
3872
+ function Badge({ className, variant, ...props }) {
3873
+ return /* @__PURE__ */ jsx4("div", { className: cn(badgeVariants({ variant }), className), ...props });
3874
+ }
3875
+
3436
3876
  // src/OhhwellsBridge.tsx
3437
- import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
3877
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
3438
3878
  var PRIMARY = "#0885FE";
3439
3879
  var IMAGE_FADE_MS = 300;
3440
3880
  function runOpacityFade(el, onDone) {
@@ -3485,6 +3925,41 @@ function fadeInBgImage(el, url, onReady) {
3485
3925
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
3486
3926
  });
3487
3927
  }
3928
+ function getSectionsTracker() {
3929
+ let el = document.querySelector("[data-ohw-sections-tracker]");
3930
+ if (!el) {
3931
+ el = document.createElement("div");
3932
+ el.setAttribute("data-ohw-sections-tracker", "");
3933
+ el.style.display = "none";
3934
+ document.body.appendChild(el);
3935
+ }
3936
+ return el;
3937
+ }
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;
3942
+ const container = document.createElement("div");
3943
+ container.dataset.ohwSectionContainer = "scheduling";
3944
+ anchor.insertAdjacentElement("afterend", container);
3945
+ createRoot(container).render(/* @__PURE__ */ jsx5(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
3946
+ const tracker = getSectionsTracker();
3947
+ let sections = [];
3948
+ try {
3949
+ sections = JSON.parse(tracker.textContent || "[]");
3950
+ } catch {
3951
+ }
3952
+ if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
3953
+ sections.push({
3954
+ type: "scheduling",
3955
+ insertAfter,
3956
+ pagePath: window.location.pathname,
3957
+ ...scheduleId ? { scheduleId } : {}
3958
+ });
3959
+ tracker.textContent = JSON.stringify(sections);
3960
+ }
3961
+ return true;
3962
+ }
3488
3963
  function collectEditableNodes() {
3489
3964
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
3490
3965
  if (el.dataset.ohwEditable === "image") {
@@ -3642,7 +4117,7 @@ var TOOLBAR_GROUPS = [
3642
4117
  ];
3643
4118
  function GlowFrame({ rect, elRef }) {
3644
4119
  const GAP = 6;
3645
- return /* @__PURE__ */ jsx3(
4120
+ return /* @__PURE__ */ jsx5(
3646
4121
  "div",
3647
4122
  {
3648
4123
  ref: elRef,
@@ -3693,7 +4168,7 @@ function FloatingToolbar({
3693
4168
  activeCommands
3694
4169
  }) {
3695
4170
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3696
- return /* @__PURE__ */ jsx3(
4171
+ return /* @__PURE__ */ jsx5(
3697
4172
  "div",
3698
4173
  {
3699
4174
  ref: elRef,
@@ -3716,11 +4191,11 @@ function FloatingToolbar({
3716
4191
  pointerEvents: "auto",
3717
4192
  whiteSpace: "nowrap"
3718
4193
  },
3719
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
3720
- gi > 0 && /* @__PURE__ */ jsx3("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
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 } }),
3721
4196
  btns.map((btn) => {
3722
4197
  const isActive = activeCommands.has(btn.cmd);
3723
- return /* @__PURE__ */ jsx3(
4198
+ return /* @__PURE__ */ jsx5(
3724
4199
  "button",
3725
4200
  {
3726
4201
  title: btn.title,
@@ -3746,7 +4221,7 @@ function FloatingToolbar({
3746
4221
  flexShrink: 0,
3747
4222
  padding: 6
3748
4223
  },
3749
- children: /* @__PURE__ */ jsx3(
4224
+ children: /* @__PURE__ */ jsx5(
3750
4225
  "svg",
3751
4226
  {
3752
4227
  width: "16",
@@ -3779,7 +4254,7 @@ function StateToggle({
3779
4254
  states,
3780
4255
  onStateChange
3781
4256
  }) {
3782
- return /* @__PURE__ */ jsx3(
4257
+ return /* @__PURE__ */ jsx5(
3783
4258
  ToggleGroup,
3784
4259
  {
3785
4260
  "data-ohw-state-toggle": "",
@@ -3793,7 +4268,7 @@ function StateToggle({
3793
4268
  left: rect.right - 8,
3794
4269
  transform: "translateX(-100%)"
3795
4270
  },
3796
- children: states.map((state) => /* @__PURE__ */ jsx3(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4271
+ children: states.map((state) => /* @__PURE__ */ jsx5(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3797
4272
  }
3798
4273
  );
3799
4274
  }
@@ -3803,8 +4278,8 @@ function OhhwellsBridge() {
3803
4278
  const router = useRouter();
3804
4279
  const searchParams = useSearchParams();
3805
4280
  const isEditMode = isEditSessionActive();
3806
- const [bridgeRoot, setBridgeRoot] = useState(null);
3807
- useEffect(() => {
4281
+ const [bridgeRoot, setBridgeRoot] = useState2(null);
4282
+ useEffect2(() => {
3808
4283
  const figtreeFontId = "ohw-figtree-font";
3809
4284
  if (!document.getElementById(figtreeFontId)) {
3810
4285
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -3837,7 +4312,7 @@ function OhhwellsBridge() {
3837
4312
  window.parent.postMessage(data, "*");
3838
4313
  }
3839
4314
  }, []);
3840
- const [fetchState, setFetchState] = useState("idle");
4315
+ const [fetchState, setFetchState] = useState2("idle");
3841
4316
  const autoSaveTimers = useRef(/* @__PURE__ */ new Map());
3842
4317
  const activeElRef = useRef(null);
3843
4318
  const originalContentRef = useRef(null);
@@ -3847,6 +4322,7 @@ function OhhwellsBridge() {
3847
4322
  const glowElRef = useRef(null);
3848
4323
  const hoveredImageRef = useRef(null);
3849
4324
  const hoveredImageHasTextOverlapRef = useRef(false);
4325
+ const hoveredGapRef = useRef(null);
3850
4326
  const imageUnhoverTimerRef = useRef(null);
3851
4327
  const imageShowTimerRef = useRef(null);
3852
4328
  const editStylesRef = useRef(null);
@@ -3858,11 +4334,12 @@ function OhhwellsBridge() {
3858
4334
  });
3859
4335
  const postToParentRef = useRef(postToParent);
3860
4336
  postToParentRef.current = postToParent;
3861
- const [toolbarRect, setToolbarRect] = useState(null);
3862
- const [toggleState, setToggleState] = useState(null);
3863
- const [maxBadge, setMaxBadge] = useState(null);
3864
- const [activeCommands, setActiveCommands] = useState(/* @__PURE__ */ new Set());
3865
- useEffect(() => {
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);
4342
+ useEffect2(() => {
3866
4343
  const update = () => {
3867
4344
  const el = activeElRef.current;
3868
4345
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -3870,6 +4347,12 @@ function OhhwellsBridge() {
3870
4347
  if (!prev || !activeStateElRef.current) return prev;
3871
4348
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3872
4349
  });
4350
+ setSectionGap((prev) => {
4351
+ if (!prev) return prev;
4352
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
4353
+ if (!anchor) return prev;
4354
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
4355
+ });
3873
4356
  };
3874
4357
  const vvp = window.visualViewport;
3875
4358
  if (!vvp) return;
@@ -3933,7 +4416,20 @@ function OhhwellsBridge() {
3933
4416
  }
3934
4417
  const applyContent = (content) => {
3935
4418
  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
+ }
3936
4431
  for (const [key, val] of Object.entries(content)) {
4432
+ if (key === "__ohw_sections") continue;
3937
4433
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
3938
4434
  if (el.dataset.ohwEditable === "image") {
3939
4435
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -3976,12 +4472,24 @@ function OhhwellsBridge() {
3976
4472
  cancelled = true;
3977
4473
  };
3978
4474
  }, [subdomain, isEditMode, pathname]);
3979
- useEffect(() => {
4475
+ useEffect2(() => {
3980
4476
  if (!subdomain || isEditMode) return;
3981
4477
  const applyFromCache = () => {
3982
4478
  const content = contentCache.get(subdomain);
3983
4479
  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
+ }
3984
4491
  for (const [key, val] of Object.entries(content)) {
4492
+ if (key === "__ohw_sections") continue;
3985
4493
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
3986
4494
  if (el.dataset.ohwEditable === "image") {
3987
4495
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4006,10 +4514,10 @@ function OhhwellsBridge() {
4006
4514
  const visible = Boolean(subdomain) && fetchState !== "done";
4007
4515
  el.style.display = visible ? "flex" : "none";
4008
4516
  }, [subdomain, fetchState]);
4009
- useEffect(() => {
4517
+ useEffect2(() => {
4010
4518
  postToParent({ type: "ow:navigation", path: pathname });
4011
4519
  }, [pathname, postToParent]);
4012
- useEffect(() => {
4520
+ useEffect2(() => {
4013
4521
  if (!isEditMode) return;
4014
4522
  const measure = () => {
4015
4523
  const h = document.body.scrollHeight;
@@ -4033,7 +4541,7 @@ function OhhwellsBridge() {
4033
4541
  window.removeEventListener("resize", handleResize);
4034
4542
  };
4035
4543
  }, [pathname, isEditMode, postToParent]);
4036
- useEffect(() => {
4544
+ useEffect2(() => {
4037
4545
  if (!subdomainFromQuery || isEditMode) return;
4038
4546
  const handleClick = (e) => {
4039
4547
  const anchor = e.target.closest("a");
@@ -4049,7 +4557,7 @@ function OhhwellsBridge() {
4049
4557
  document.addEventListener("click", handleClick, true);
4050
4558
  return () => document.removeEventListener("click", handleClick, true);
4051
4559
  }, [subdomainFromQuery, isEditMode, router]);
4052
- useEffect(() => {
4560
+ useEffect2(() => {
4053
4561
  if (!isEditMode) {
4054
4562
  editStylesRef.current?.base.remove();
4055
4563
  editStylesRef.current?.forceHover.remove();
@@ -4309,6 +4817,15 @@ function OhhwellsBridge() {
4309
4817
  textEditable.setAttribute("data-ohw-hovered", "");
4310
4818
  return;
4311
4819
  }
4820
+ if (hoveredGapRef.current) {
4821
+ if (hoveredImageRef.current) {
4822
+ hoveredImageRef.current = null;
4823
+ resumeAnimTracks();
4824
+ postToParentRef.current({ type: "ow:image-unhover" });
4825
+ }
4826
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4827
+ return;
4828
+ }
4312
4829
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4313
4830
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4314
4831
  hoveredImageRef.current = imgEl;
@@ -4374,8 +4891,30 @@ function OhhwellsBridge() {
4374
4891
  }
4375
4892
  }
4376
4893
  };
4894
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
4895
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
4896
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
4897
+ const ZONE = 20;
4898
+ for (let i = 0; i < sections.length; i++) {
4899
+ const a = sections[i];
4900
+ const b = sections[i + 1] ?? null;
4901
+ const boundaryY = a.getBoundingClientRect().bottom;
4902
+ if (Math.abs(y - boundaryY) <= ZONE) {
4903
+ const insertAfter = a.dataset.ohwSection ?? "";
4904
+ const insertBefore = b?.dataset.ohwSection ?? null;
4905
+ hoveredGapRef.current = { insertAfter, insertBefore };
4906
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
4907
+ return;
4908
+ }
4909
+ }
4910
+ if (hoveredGapRef.current) {
4911
+ hoveredGapRef.current = null;
4912
+ setSectionGap(null);
4913
+ }
4914
+ };
4377
4915
  const handleMouseMove = (e) => {
4378
4916
  const { clientX, clientY } = e;
4917
+ probeSectionGapAt(clientX, clientY);
4379
4918
  probeImageAt(clientX, clientY);
4380
4919
  probeHoverCardsAt(clientX, clientY);
4381
4920
  };
@@ -4383,6 +4922,7 @@ function OhhwellsBridge() {
4383
4922
  if (e.data?.type !== "ow:pointer-sync") return;
4384
4923
  const { clientX, clientY } = e.data;
4385
4924
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4925
+ probeSectionGapAt(clientX, clientY);
4386
4926
  probeImageAt(clientX, clientY);
4387
4927
  probeHoverCardsAt(clientX, clientY);
4388
4928
  };
@@ -4541,6 +5081,18 @@ function OhhwellsBridge() {
4541
5081
  const content = e.data.content;
4542
5082
  if (!content) return;
4543
5083
  for (const [key, val] of Object.entries(content)) {
5084
+ 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
+ }
5094
+ continue;
5095
+ }
4544
5096
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4545
5097
  if (el.dataset.ohwEditable === "image") {
4546
5098
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4595,7 +5147,71 @@ function OhhwellsBridge() {
4595
5147
  };
4596
5148
  const handleSave = (e) => {
4597
5149
  if (e.data?.type !== "ow:save") return;
4598
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
5150
+ const nodes = collectEditableNodes();
5151
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
5152
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
5153
+ postToParentRef.current({ type: "ow:save-result", nodes });
5154
+ };
5155
+ const handleInsertSection = (e) => {
5156
+ if (e.data?.type !== "ow:insert-section") return;
5157
+ const { widgetType, insertAfter } = e.data;
5158
+ if (widgetType !== "scheduling") return;
5159
+ const inserted = mountSchedulingWidget(insertAfter, true);
5160
+ if (inserted) {
5161
+ const tracker = getSectionsTracker();
5162
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
5163
+ const h = document.documentElement.scrollHeight;
5164
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5165
+ }
5166
+ };
5167
+ const handleSwitchSchedule = (e) => {
5168
+ if (e.data?.type !== "ow:switch-schedule") return;
5169
+ 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 }] });
5183
+ };
5184
+ const handleClearSchedulingWidget = (e) => {
5185
+ 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 }] });
5198
+ };
5199
+ const handleRemoveSchedulingSection = (e) => {
5200
+ 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));
5203
+ const tracker = getSectionsTracker();
5204
+ let sections = [];
5205
+ try {
5206
+ sections = JSON.parse(tracker.textContent || "[]");
5207
+ } catch {
5208
+ }
5209
+ const currentPath = window.location.pathname;
5210
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
5211
+ tracker.textContent = JSON.stringify(updated);
5212
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5213
+ const h = document.documentElement.scrollHeight;
5214
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
4599
5215
  };
4600
5216
  const handleSelectionChange = () => {
4601
5217
  if (!activeElRef.current) return;
@@ -4700,6 +5316,10 @@ function OhhwellsBridge() {
4700
5316
  deactivateRef.current();
4701
5317
  };
4702
5318
  window.addEventListener("message", handleSave);
5319
+ window.addEventListener("message", handleInsertSection);
5320
+ window.addEventListener("message", handleSwitchSchedule);
5321
+ window.addEventListener("message", handleClearSchedulingWidget);
5322
+ window.addEventListener("message", handleRemoveSchedulingSection);
4703
5323
  window.addEventListener("message", handleImageUrl);
4704
5324
  window.addEventListener("message", handleAnimLock);
4705
5325
  window.addEventListener("message", handleCanvasHeight);
@@ -4734,6 +5354,10 @@ function OhhwellsBridge() {
4734
5354
  document.removeEventListener("selectionchange", handleSelectionChange);
4735
5355
  window.removeEventListener("scroll", handleScroll, true);
4736
5356
  window.removeEventListener("message", handleSave);
5357
+ window.removeEventListener("message", handleInsertSection);
5358
+ window.removeEventListener("message", handleSwitchSchedule);
5359
+ window.removeEventListener("message", handleClearSchedulingWidget);
5360
+ window.removeEventListener("message", handleRemoveSchedulingSection);
4737
5361
  window.removeEventListener("message", handleImageUrl);
4738
5362
  window.removeEventListener("message", handleAnimLock);
4739
5363
  window.removeEventListener("message", handleCanvasHeight);
@@ -4748,7 +5372,7 @@ function OhhwellsBridge() {
4748
5372
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4749
5373
  };
4750
5374
  }, [isEditMode, refreshStateRules]);
4751
- useEffect(() => {
5375
+ useEffect2(() => {
4752
5376
  if (!isEditMode) return;
4753
5377
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
4754
5378
  el.removeAttribute("data-ohw-active-state");
@@ -4790,12 +5414,12 @@ function OhhwellsBridge() {
4790
5414
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4791
5415
  }, [deactivate]);
4792
5416
  return bridgeRoot ? createPortal(
4793
- /* @__PURE__ */ jsxs(Fragment, { children: [
4794
- toolbarRect && /* @__PURE__ */ jsxs(Fragment, { children: [
4795
- /* @__PURE__ */ jsx3(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4796
- /* @__PURE__ */ jsx3(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
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 })
4797
5421
  ] }),
4798
- maxBadge && /* @__PURE__ */ jsxs(
5422
+ maxBadge && /* @__PURE__ */ jsxs2(
4799
5423
  "div",
4800
5424
  {
4801
5425
  "data-ohw-max-badge": "",
@@ -4821,7 +5445,7 @@ function OhhwellsBridge() {
4821
5445
  ]
4822
5446
  }
4823
5447
  ),
4824
- toggleState && /* @__PURE__ */ jsx3(
5448
+ toggleState && /* @__PURE__ */ jsx5(
4825
5449
  StateToggle,
4826
5450
  {
4827
5451
  rect: toggleState.rect,
@@ -4829,6 +5453,31 @@ function OhhwellsBridge() {
4829
5453
  states: toggleState.states,
4830
5454
  onStateChange: handleStateChange
4831
5455
  }
5456
+ ),
5457
+ sectionGap && /* @__PURE__ */ jsxs2(
5458
+ "div",
5459
+ {
5460
+ "data-ohw-section-insert-line": "",
5461
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
5462
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
5463
+ children: [
5464
+ /* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5465
+ /* @__PURE__ */ jsx5(
5466
+ Badge,
5467
+ {
5468
+ 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",
5469
+ onClick: () => {
5470
+ window.parent.postMessage(
5471
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
5472
+ "*"
5473
+ );
5474
+ },
5475
+ children: "Add Section"
5476
+ }
5477
+ ),
5478
+ /* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5479
+ ]
5480
+ }
4832
5481
  )
4833
5482
  ] }),
4834
5483
  bridgeRoot
@@ -4836,6 +5485,7 @@ function OhhwellsBridge() {
4836
5485
  }
4837
5486
  export {
4838
5487
  OhhwellsBridge,
5488
+ SchedulingWidget,
4839
5489
  Toggle,
4840
5490
  ToggleGroup,
4841
5491
  ToggleGroupItem,