@ohhwells/bridge 0.1.15 → 0.1.16-next.8

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/README.md CHANGED
@@ -4,138 +4,266 @@ The OhhWells canvas editor bridge — a standalone npm package that enables inli
4
4
 
5
5
  ## What it does
6
6
 
7
- When a studio owner opens their site in the OhhWells dashboard canvas editor, the bridge:
7
+ When a studio owner opens their site in the OhhWells canvas editor, the bridge:
8
8
 
9
9
  - Connects the iframe (the live site) to the parent canvas editor via `postMessage`
10
10
  - Enables click-to-edit for text, images, and background images
11
11
  - Handles draft saving and content hydration
12
- - Provides state toggle UI for editing hidden content (hover states, form success/error views)
12
+ - Shows the "Add Section" insert line between sections in the canvas editor
13
+ - Provides state toggle UI for editing hidden content (hover states, form views)
13
14
  - Injects scoped styles that never leak into the host template
14
15
 
16
+ ---
17
+
15
18
  ## Installation
16
19
 
17
20
  ```bash
18
21
  npm install @ohhwells/bridge
19
22
  ```
20
23
 
21
- Import the styles once in your template's root layout:
24
+ ---
25
+
26
+ ## Template setup (required steps)
27
+
28
+ ### 1. Import styles
29
+
30
+ In your root layout file, import the bridge stylesheet **once**:
22
31
 
23
32
  ```ts
24
33
  import '@ohhwells/bridge/styles'
25
34
  ```
26
35
 
27
- ## Usage
36
+ ### 2. Add the loader surface
37
+
38
+ The loader is a full-screen spinner shown while the bridge fetches personalised content. It hides itself once content is ready. Add this **before** your main content in the `<body>`:
39
+
40
+ ```tsx
41
+ import { OHW_LOADER_STYLE, OhwLoaderSpinner } from '@ohhwells/bridge'
42
+
43
+ // Inside <body>:
44
+ <div
45
+ id="ohw-loader"
46
+ suppressHydrationWarning
47
+ style={{ ...OHW_LOADER_STYLE, display: 'none' }}
48
+ >
49
+ <OhwLoaderSpinner />
50
+ </div>
51
+
52
+ {/* Inline script — shows the loader immediately on the client before React hydrates */}
53
+ <script
54
+ dangerouslySetInnerHTML={{
55
+ __html: `(function(){try{
56
+ var p=location.hostname.split(".");
57
+ var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";
58
+ var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";
59
+ if(!fromHost&&!fromQuery)return;
60
+ var e=document.getElementById("ohw-loader");
61
+ if(e)e.style.display="flex"
62
+ }catch(e){}})();`,
63
+ }}
64
+ />
65
+ ```
66
+
67
+ The inline script detects whether the page is being loaded under a subdomain (personalised content mode) and shows the loader before React has a chance to hydrate, preventing a flash of the default content.
28
68
 
29
- Add `OhhwellsBridge` to your template's root layout:
69
+ ### 3. Mount `OhhwellsBridge`
70
+
71
+ Add `<OhhwellsBridge />` inside a `<Suspense>` boundary in your root layout. It must be in `<Suspense>` because it calls `useSearchParams()` internally.
30
72
 
31
73
  ```tsx
74
+ import { Suspense } from 'react'
32
75
  import { OhhwellsBridge } from '@ohhwells/bridge'
33
76
 
34
77
  export default function RootLayout({ children }) {
35
78
  return (
36
- <html>
79
+ <html lang="en">
37
80
  <body>
81
+ {/* loader + inline script here (see step 2) */}
82
+
83
+ <Suspense>
84
+ <OhhwellsBridge />
85
+ </Suspense>
86
+
87
+ {/* rest of your layout */}
38
88
  {children}
39
- <OhhwellsBridge />
40
89
  </body>
41
90
  </html>
42
91
  )
43
92
  }
44
93
  ```
45
94
 
46
- The bridge activates automatically when the page is loaded inside the OhhWells canvas editor. It has no effect in production (live site) mode.
95
+ The bridge activates automatically when the page is loaded inside the OhhWells canvas editor. It does nothing in production (live site) mode.
47
96
 
48
- ## Template attributes
97
+ ### 4. Mark sections
49
98
 
50
- Mark elements as editable using `data-ohw-*` attributes. See [VIBECODER.md](https://github.com/TheFlowOps-Eng/rebound-template/blob/main/VIBECODER.md) for the full authoring guide.
99
+ Every top-level section on each page must have a unique `data-ohw-section` attribute. This is what the canvas editor uses to:
100
+ - Show the "Add Section" insert line between sections
101
+ - Persist widget insertions (saving which section a widget was inserted after)
51
102
 
52
- | Attribute | Value | Description |
53
- |---|---|---|
54
- | `data-ohw-key` | unique string | Identifier for storing/hydrating content |
55
- | `data-ohw-editable` | `text` \| `plain` \| `image` \| `bg-image` | Edit mode |
56
- | `data-ohw-editable-state` | `"hover,focus"` etc. | Declares available states on a container |
57
- | `data-ohw-state-view` | `"default"` \| `"success"` \| `"error"` \| `"hover"` | Wraps each state's content |
103
+ ```tsx
104
+ // Good — section element is the direct content root
105
+ <section data-ohw-section="hero">
106
+ ...
107
+ </section>
108
+
109
+ // Also fine — any element type works
110
+ <div data-ohw-section="testimonials">
111
+ ...
112
+ </div>
113
+ ```
58
114
 
59
- ## Design tokens
115
+ **Naming rules:**
116
+ - Use `kebab-case`
117
+ - Must be unique across the entire page
118
+ - Must be stable — if a section is renamed, any saved widget insertions referencing that name will break
60
119
 
61
- The package ships with the full OhhWells design token set, scoped to `[data-ohw-bridge]` so styles never leak into the host template.
120
+ **Example a page with multiple sections:**
62
121
 
63
- ### Semantic colors
122
+ ```tsx
123
+ export default function HomePage() {
124
+ return (
125
+ <>
126
+ <section data-ohw-section="hero">...</section>
127
+ <section data-ohw-section="lagree-intro">...</section>
128
+ <section data-ohw-section="classes-strip">...</section>
129
+ <section data-ohw-section="testimonials">...</section>
130
+ <section data-ohw-section="plan-form">...</section>
131
+ </>
132
+ )
133
+ }
134
+ ```
64
135
 
65
- All colors are defined as CSS variables on `[data-ohw-bridge-root]` and override-able per host:
136
+ ### 5. Mark editable elements
66
137
 
67
- | Token | Light | Dark |
68
- |---|---|---|
69
- | `background` | `#ffffff` | `#020617` |
70
- | `foreground` | `#020617` | `#f8fafc` |
71
- | `primary` | `#0f172a` | `#f8fafc` |
72
- | `primary-foreground` | `#f8fafc` | `#0f172a` |
73
- | `secondary` | `#f1f5f9` | `#1e293b` |
74
- | `muted` | `#f1f5f9` | `#1e293b` |
75
- | `muted-foreground` | `#64748b` | `#94a3b8` |
76
- | `accent` | `#f1f5f9` | `#1e293b` |
77
- | `border` | `#e2e8f0` | `#334155` |
78
- | `destructive` | `#dc2626` | `#7f1d1d` |
79
- | `success` | `#16a34a` | `#22c55e` |
138
+ Add `data-ohw-editable` and `data-ohw-key` to any element the studio owner should be able to edit:
80
139
 
81
- ### Typography
82
-
83
- Standard Tailwind scale with Figma-specified tracking:
84
-
85
- | Class | Size | Line height | Tracking |
86
- |---|---|---|---|
87
- | `text-9xl` | 128px | 1 | -0.025em |
88
- | `text-8xl` | 96px | 1 | -0.025em |
89
- | `text-7xl` | 72px | 1 | -0.025em |
90
- | `text-6xl` | 60px | 1 | -0.025em |
91
- | `text-5xl` | 48px | 1 | -0.025em |
92
- | `text-4xl` | 36px | 40px | -0.025em |
93
- | `text-3xl` | 30px | 36px | -0.025em |
94
- | `text-2xl` | 24px | 32px | -0.025em |
95
- | `text-xl` | 20px | 28px | -0.025em |
96
- | `text-lg` | 18px | 28px | 0 |
97
- | `text-base` | 16px | 24px | 0 |
98
- | `text-sm` | 14px | 20px | 0 |
99
- | `text-xs` | 12px | 16px | 0 |
140
+ ```tsx
141
+ {/* Editable rich text (bold, italic, etc.) */}
142
+ <h1
143
+ data-ohw-editable="text"
144
+ data-ohw-key="hero-heading"
145
+ >
146
+ Welcome to the studio
147
+ </h1>
148
+
149
+ {/* Editable plain text (no formatting) */}
150
+ <p
151
+ data-ohw-editable="plain"
152
+ data-ohw-key="hero-subtitle"
153
+ >
154
+ Book your first class
155
+ </p>
156
+
157
+ {/* Editable image */}
158
+ <img
159
+ data-ohw-editable="image"
160
+ data-ohw-key="hero-image"
161
+ src="/hero.jpg"
162
+ alt="Hero"
163
+ />
164
+
165
+ {/* Editable background image */}
166
+ <div
167
+ data-ohw-editable="bg-image"
168
+ data-ohw-key="hero-bg"
169
+ style={{ backgroundImage: 'url(/bg.jpg)' }}
170
+ />
171
+ ```
100
172
 
101
- ### Brand palette
173
+ **Key naming rules:**
174
+ - Must be globally unique across all pages
175
+ - Use `kebab-case`
176
+ - Must be stable — changing a key orphans any saved content for that element
102
177
 
103
- Re:Bound brand colors are available as named tokens: `bone`, `ivory`, `sand`, `linen`, `stone`, `clay`, `umber`, `umber-deep`, `espresso`, `clove`, `ink`, `ash`, `carbon`.
178
+ ---
104
179
 
105
- ## Dark mode
180
+ ## Editable states (advanced)
106
181
 
107
- Add the `dark` class to `[data-ohw-bridge-root]` to activate dark token overrides:
182
+ For elements with multiple display states (e.g. a contact form with default/success/error views), wrap each state in a `data-ohw-state-view` and mark the container with `data-ohw-editable-state`:
108
183
 
109
- ```html
110
- <div data-ohw-bridge-root class="dark">...</div>
184
+ ```tsx
185
+ <div
186
+ data-ohw-editable-state="default,success,error"
187
+ data-ohw-key="contact-form"
188
+ >
189
+ <div data-ohw-state-view="default">
190
+ {/* default form UI */}
191
+ </div>
192
+ <div data-ohw-state-view="success">
193
+ {/* success message */}
194
+ </div>
195
+ <div data-ohw-state-view="error">
196
+ {/* error message */}
197
+ </div>
198
+ </div>
111
199
  ```
112
200
 
113
- ## Development
201
+ The bridge shows a state toggle in the canvas editor to switch between states.
114
202
 
115
- ```bash
116
- # Install dependencies
117
- npm install
203
+ ---
204
+
205
+ ## Local development workflow
206
+
207
+ When iterating on the bridge package itself:
118
208
 
119
- # Build (TypeScript + CSS)
209
+ ```bash
210
+ # 1. Build the package
211
+ cd ohhwells-bridge
120
212
  npm run build
121
213
 
122
- # Watch mode
123
- npm run dev
214
+ # 2. Link it globally (one-time setup)
215
+ npm link
216
+
217
+ # 3. In your template repo, use the local build instead of the npm version
218
+ cd rebound-template
219
+ npm link @ohhwells/bridge
124
220
  ```
125
221
 
126
- Output is written to `dist/`:
127
- - `dist/index.js` — CJS
128
- - `dist/index.mjs` — ESM
129
- - `dist/index.d.ts` — TypeScript declarations
130
- - `dist/styles.css` — Scoped Tailwind CSS
222
+ After that, every `npm run build` in `ohhwells-bridge` is picked up by the template immediately (no re-link needed). To go back to the npm version:
223
+
224
+ ```bash
225
+ cd rebound-template
226
+ npm unlink @ohhwells/bridge
227
+ npm install
228
+ ```
229
+
230
+ ---
131
231
 
132
232
  ## Publishing
133
233
 
234
+ The package publishes automatically via GitHub Actions on push to `main` (production tag) or `staging` (next tag). To publish manually:
235
+
134
236
  ```bash
135
237
  npm run build
136
238
  npm publish --access public
137
239
  ```
138
240
 
241
+ ---
242
+
243
+ ## Design tokens
244
+
245
+ The package ships the full OhhWells design token set, scoped to `[data-ohw-bridge-root]` so styles never leak into the host template.
246
+
247
+ ### Semantic colors (CSS variables on `[data-ohw-bridge-root]`)
248
+
249
+ | Token | Light | Dark |
250
+ |---|---|---|
251
+ | `primary` | `#0f172a` | `#f8fafc` |
252
+ | `primary-foreground` | `#f8fafc` | `#0f172a` |
253
+ | `background` | `#ffffff` | `#020617` |
254
+ | `foreground` | `#020617` | `#f8fafc` |
255
+ | `muted` | `#f1f5f9` | `#1e293b` |
256
+ | `muted-foreground` | `#64748b` | `#94a3b8` |
257
+ | `border` | `#e2e8f0` | `#334155` |
258
+ | `destructive` | `#dc2626` | `#7f1d1d` |
259
+ | `success` | `#16a34a` | `#22c55e` |
260
+
261
+ ### Brand palette
262
+
263
+ Re:Bound brand colors: `bone`, `ivory`, `sand`, `linen`, `stone`, `clay`, `umber`, `umber-deep`, `espresso`, `clove`, `ink`, `ash`, `carbon`.
264
+
265
+ ---
266
+
139
267
  ## License
140
268
 
141
269
  MIT
package/dist/index.cjs CHANGED
@@ -3472,8 +3472,30 @@ function isEditSessionActive() {
3472
3472
  return new URLSearchParams(q).get("mode") === "edit";
3473
3473
  }
3474
3474
 
3475
- // src/OhhwellsBridge.tsx
3475
+ // src/ui/badge.tsx
3476
3476
  var import_jsx_runtime3 = require("react/jsx-runtime");
3477
+ var badgeVariants = cva(
3478
+ "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",
3479
+ {
3480
+ variants: {
3481
+ variant: {
3482
+ default: "border-transparent bg-primary text-primary-foreground",
3483
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
3484
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
3485
+ outline: "text-foreground"
3486
+ }
3487
+ },
3488
+ defaultVariants: {
3489
+ variant: "default"
3490
+ }
3491
+ }
3492
+ );
3493
+ function Badge({ className, variant, ...props }) {
3494
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
3495
+ }
3496
+
3497
+ // src/OhhwellsBridge.tsx
3498
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3477
3499
  var PRIMARY = "#0885FE";
3478
3500
  var IMAGE_FADE_MS = 300;
3479
3501
  function runOpacityFade(el, onDone) {
@@ -3681,7 +3703,7 @@ var TOOLBAR_GROUPS = [
3681
3703
  ];
3682
3704
  function GlowFrame({ rect, elRef }) {
3683
3705
  const GAP = 6;
3684
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3706
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3685
3707
  "div",
3686
3708
  {
3687
3709
  ref: elRef,
@@ -3732,7 +3754,7 @@ function FloatingToolbar({
3732
3754
  activeCommands
3733
3755
  }) {
3734
3756
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3735
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3757
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3736
3758
  "div",
3737
3759
  {
3738
3760
  ref: elRef,
@@ -3755,11 +3777,11 @@ function FloatingToolbar({
3755
3777
  pointerEvents: "auto",
3756
3778
  whiteSpace: "nowrap"
3757
3779
  },
3758
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react.default.Fragment, { children: [
3759
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3780
+ children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react.default.Fragment, { children: [
3781
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3760
3782
  btns.map((btn) => {
3761
3783
  const isActive = activeCommands.has(btn.cmd);
3762
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3784
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3763
3785
  "button",
3764
3786
  {
3765
3787
  title: btn.title,
@@ -3785,7 +3807,7 @@ function FloatingToolbar({
3785
3807
  flexShrink: 0,
3786
3808
  padding: 6
3787
3809
  },
3788
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3810
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3789
3811
  "svg",
3790
3812
  {
3791
3813
  width: "16",
@@ -3818,7 +3840,7 @@ function StateToggle({
3818
3840
  states,
3819
3841
  onStateChange
3820
3842
  }) {
3821
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3843
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3822
3844
  ToggleGroup,
3823
3845
  {
3824
3846
  "data-ohw-state-toggle": "",
@@ -3832,7 +3854,7 @@ function StateToggle({
3832
3854
  left: rect.right - 8,
3833
3855
  transform: "translateX(-100%)"
3834
3856
  },
3835
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3857
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3836
3858
  }
3837
3859
  );
3838
3860
  }
@@ -3886,6 +3908,7 @@ function OhhwellsBridge() {
3886
3908
  const glowElRef = (0, import_react.useRef)(null);
3887
3909
  const hoveredImageRef = (0, import_react.useRef)(null);
3888
3910
  const hoveredImageHasTextOverlapRef = (0, import_react.useRef)(false);
3911
+ const hoveredGapRef = (0, import_react.useRef)(null);
3889
3912
  const imageUnhoverTimerRef = (0, import_react.useRef)(null);
3890
3913
  const imageShowTimerRef = (0, import_react.useRef)(null);
3891
3914
  const editStylesRef = (0, import_react.useRef)(null);
@@ -3901,6 +3924,7 @@ function OhhwellsBridge() {
3901
3924
  const [toggleState, setToggleState] = (0, import_react.useState)(null);
3902
3925
  const [maxBadge, setMaxBadge] = (0, import_react.useState)(null);
3903
3926
  const [activeCommands, setActiveCommands] = (0, import_react.useState)(/* @__PURE__ */ new Set());
3927
+ const [sectionGap, setSectionGap] = (0, import_react.useState)(null);
3904
3928
  (0, import_react.useEffect)(() => {
3905
3929
  const update = () => {
3906
3930
  const el = activeElRef.current;
@@ -3909,6 +3933,12 @@ function OhhwellsBridge() {
3909
3933
  if (!prev || !activeStateElRef.current) return prev;
3910
3934
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3911
3935
  });
3936
+ setSectionGap((prev) => {
3937
+ if (!prev) return prev;
3938
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
3939
+ if (!anchor) return prev;
3940
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
3941
+ });
3912
3942
  };
3913
3943
  const vvp = window.visualViewport;
3914
3944
  if (!vvp) return;
@@ -4001,7 +4031,7 @@ function OhhwellsBridge() {
4001
4031
  }
4002
4032
  let cancelled = false;
4003
4033
  setFetchState("loading");
4004
- const apiUrl = globalThis.process?.env?.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
4034
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
4005
4035
  fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
4006
4036
  if (cancelled) return;
4007
4037
  const content = data?.content ?? {};
@@ -4413,10 +4443,32 @@ function OhhwellsBridge() {
4413
4443
  }
4414
4444
  }
4415
4445
  };
4446
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
4447
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
4448
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
4449
+ const ZONE = 20;
4450
+ for (let i = 0; i < sections.length; i++) {
4451
+ const a = sections[i];
4452
+ const b = sections[i + 1] ?? null;
4453
+ const boundaryY = a.getBoundingClientRect().bottom;
4454
+ if (Math.abs(y - boundaryY) <= ZONE) {
4455
+ const insertAfter = a.dataset.ohwSection ?? "";
4456
+ const insertBefore = b?.dataset.ohwSection ?? null;
4457
+ hoveredGapRef.current = { insertAfter, insertBefore };
4458
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
4459
+ return;
4460
+ }
4461
+ }
4462
+ if (hoveredGapRef.current) {
4463
+ hoveredGapRef.current = null;
4464
+ setSectionGap(null);
4465
+ }
4466
+ };
4416
4467
  const handleMouseMove = (e) => {
4417
4468
  const { clientX, clientY } = e;
4418
4469
  probeImageAt(clientX, clientY);
4419
4470
  probeHoverCardsAt(clientX, clientY);
4471
+ probeSectionGapAt(clientX, clientY);
4420
4472
  };
4421
4473
  const handlePointerSync = (e) => {
4422
4474
  if (e.data?.type !== "ow:pointer-sync") return;
@@ -4424,6 +4476,7 @@ function OhhwellsBridge() {
4424
4476
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4425
4477
  probeImageAt(clientX, clientY);
4426
4478
  probeHoverCardsAt(clientX, clientY);
4479
+ probeSectionGapAt(clientX, clientY);
4427
4480
  };
4428
4481
  const handleDragOver = (e) => {
4429
4482
  e.preventDefault();
@@ -4829,12 +4882,12 @@ function OhhwellsBridge() {
4829
4882
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4830
4883
  }, [deactivate]);
4831
4884
  return bridgeRoot ? (0, import_react_dom.createPortal)(
4832
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4833
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4834
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4835
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4885
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4886
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4887
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4888
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4836
4889
  ] }),
4837
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4890
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4838
4891
  "div",
4839
4892
  {
4840
4893
  "data-ohw-max-badge": "",
@@ -4860,7 +4913,7 @@ function OhhwellsBridge() {
4860
4913
  ]
4861
4914
  }
4862
4915
  ),
4863
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4916
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4864
4917
  StateToggle,
4865
4918
  {
4866
4919
  rect: toggleState.rect,
@@ -4868,6 +4921,31 @@ function OhhwellsBridge() {
4868
4921
  states: toggleState.states,
4869
4922
  onStateChange: handleStateChange
4870
4923
  }
4924
+ ),
4925
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4926
+ "div",
4927
+ {
4928
+ "data-ohw-section-insert-line": "",
4929
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
4930
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
4931
+ children: [
4932
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
4933
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4934
+ Badge,
4935
+ {
4936
+ 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",
4937
+ onClick: () => {
4938
+ window.parent.postMessage(
4939
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
4940
+ "*"
4941
+ );
4942
+ },
4943
+ children: "Add Section"
4944
+ }
4945
+ ),
4946
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
4947
+ ]
4948
+ }
4871
4949
  )
4872
4950
  ] }),
4873
4951
  bridgeRoot