@ohhwells/bridge 0.1.64-next.176 → 0.1.64

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
@@ -96,7 +96,6 @@ function applyTreeToState(state, payload) {
96
96
  const entry = {
97
97
  id: payload.id,
98
98
  label: payload.label ?? "Generated section",
99
- ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
100
99
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
101
100
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
102
101
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -119,317 +118,6 @@ function deleteSectionFromState(state, sectionId) {
119
118
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
120
119
  }
121
120
 
122
- // src/lib/brand-chrome.ts
123
- var BRAND_NAME_KEY = "__ohw_brand_name";
124
- var BRAND_TITLE_KEY = "__ohw_site_title";
125
- var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
126
- var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
127
- BRAND_NAME_KEY,
128
- BRAND_TITLE_KEY,
129
- BRAND_FAVICON_LETTER_KEY
130
- ]);
131
- function upsertMeta(selector, attr, token, value) {
132
- let el = document.head.querySelector(selector);
133
- if (!el) {
134
- el = document.createElement("meta");
135
- el.setAttribute(attr, token);
136
- document.head.appendChild(el);
137
- }
138
- if (el.getAttribute("content") !== value) el.setAttribute("content", value);
139
- }
140
- function escapeXml(value) {
141
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
142
- }
143
- function applyLetterFavicon(letter) {
144
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#111827"/><text x="32" y="46" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-size="40" font-weight="700" text-anchor="middle" fill="#ffffff">${escapeXml(letter)}</text></svg>`;
145
- const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
146
- let link = document.head.querySelector('link[rel="icon"]');
147
- if (!link) {
148
- link = document.createElement("link");
149
- link.rel = "icon";
150
- document.head.appendChild(link);
151
- }
152
- link.type = "image/svg+xml";
153
- if (link.href !== href) link.href = href;
154
- }
155
- function applyBrandChrome(content) {
156
- const name = content[BRAND_NAME_KEY];
157
- if (typeof name === "string" && name.length > 0) {
158
- document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
159
- if (el.textContent !== name) el.textContent = name;
160
- if (el.getAttribute("title") !== name) el.setAttribute("title", name);
161
- });
162
- }
163
- const title = content[BRAND_TITLE_KEY];
164
- if (typeof title === "string" && title.length > 0) {
165
- if (document.title !== title) document.title = title;
166
- upsertMeta('meta[property="og:title"]', "property", "og:title", title);
167
- upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
168
- }
169
- const letter = content[BRAND_FAVICON_LETTER_KEY];
170
- if (typeof letter === "string" && letter.length > 0) {
171
- applyLetterFavicon(letter);
172
- }
173
- }
174
-
175
- // src/lib/brand-kit.ts
176
- var BRAND_KIT_KEY = "__ohw_brand";
177
- var BRAND_VAR_PREFIX = "--ohw-brand-";
178
- var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
179
- (role) => `${BRAND_VAR_PREFIX}${role}`
180
- );
181
- var FONT_VARS = {
182
- heading: ["--font-heading", "--font-display", "--brand-font-heading"],
183
- body: ["--font-body", "--brand-font-body"]
184
- };
185
- var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
186
- function brandColorVars(kit) {
187
- const { dark, primary, accent, light } = kit.palette;
188
- const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
189
- return {
190
- [`${BRAND_VAR_PREFIX}primary`]: primary,
191
- [`${BRAND_VAR_PREFIX}accent`]: accent,
192
- [`${BRAND_VAR_PREFIX}light`]: light,
193
- [`${BRAND_VAR_PREFIX}dark`]: dark,
194
- [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
195
- [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
196
- [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
197
- };
198
- }
199
- function parseBrandKit(raw) {
200
- if (!raw) return null;
201
- try {
202
- const parsed = JSON.parse(raw);
203
- const p = parsed?.palette;
204
- const f = parsed?.fonts;
205
- if (!p || !f || typeof p.dark !== "string" || typeof p.primary !== "string" || typeof p.accent !== "string" || typeof p.light !== "string" || typeof f.heading !== "string" || typeof f.body !== "string") {
206
- return null;
207
- }
208
- return {
209
- palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
210
- fonts: { heading: f.heading, body: f.body }
211
- };
212
- } catch {
213
- return null;
214
- }
215
- }
216
- function familyOf(stack) {
217
- const first = stack.split(",")[0]?.trim() ?? "";
218
- return first.replace(/^['"]|['"]$/g, "");
219
- }
220
- function loadBrandFonts(families) {
221
- const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
222
- if (unique.length === 0) return;
223
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
224
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
225
- let link = document.getElementById(BRAND_FONT_LINK_ID);
226
- if (!link) {
227
- link = document.createElement("link");
228
- link.id = BRAND_FONT_LINK_ID;
229
- link.rel = "stylesheet";
230
- document.head.appendChild(link);
231
- }
232
- if (link.href !== href) link.href = href;
233
- }
234
- function applyBrandToDom(kit) {
235
- const root = document.documentElement;
236
- if (!kit) {
237
- for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
238
- for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
239
- document.getElementById(BRAND_FONT_LINK_ID)?.remove();
240
- return;
241
- }
242
- for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
243
- for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
244
- for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
245
- loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
246
- }
247
-
248
- // src/lib/section-styles.ts
249
- var STYLE_STORE_KEY = "__ohw_styles";
250
- var STYLE_SHEET_ID = "ohw-section-styles";
251
- function parseStyleStore(raw) {
252
- if (!raw) return null;
253
- try {
254
- const parsed = JSON.parse(raw);
255
- if (parsed?.v !== 1) return null;
256
- return {
257
- v: 1,
258
- sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
259
- nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
260
- };
261
- } catch {
262
- return null;
263
- }
264
- }
265
- var BG_VALUES = {
266
- surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
267
- accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
268
- "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
269
- };
270
- var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
271
- function styleSheetCss() {
272
- const rules = [];
273
- for (const [tone, value] of Object.entries(BG_VALUES)) {
274
- rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
275
- }
276
- rules.push(
277
- `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
278
- );
279
- rules.push(
280
- `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
281
- `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
282
- );
283
- for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
284
- rules.push(
285
- `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
286
- );
287
- }
288
- rules.push(
289
- `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
290
- );
291
- for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
292
- rules.push(
293
- `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
294
- );
295
- }
296
- const pad = { tight: 40, balanced: 64, airy: 96 };
297
- for (const [spacing, px] of Object.entries(pad)) {
298
- rules.push(
299
- `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
300
- );
301
- }
302
- return rules.join("\n");
303
- }
304
- var STYLE_FONT_LINK_ID = "ohw-style-fonts";
305
- function loadStyleFonts(families) {
306
- const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
307
- const existing = document.getElementById(STYLE_FONT_LINK_ID);
308
- if (unique.length === 0) {
309
- existing?.remove();
310
- return;
311
- }
312
- const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
313
- const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
314
- let link = existing;
315
- if (!link) {
316
- link = document.createElement("link");
317
- link.id = STYLE_FONT_LINK_ID;
318
- link.rel = "stylesheet";
319
- document.head.appendChild(link);
320
- }
321
- if (link.href !== href) link.href = href;
322
- }
323
- var SECTION_ATTRS = {
324
- sectionBackground: "data-ohw-style-bg",
325
- textDistribution: "data-ohw-style-distribution",
326
- headlineScale: "data-ohw-style-headline",
327
- imageAspect: "data-ohw-style-aspect",
328
- spacing: "data-ohw-style-spacing"
329
- };
330
- var NODE_WROTE_ATTR = "data-ohw-style-node";
331
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
332
- function saveInline(el, prop) {
333
- const attr = `data-ohw-style-prev-${prop}`;
334
- if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
335
- }
336
- function restoreInline(el, prop) {
337
- const attr = `data-ohw-style-prev-${prop}`;
338
- if (!el.hasAttribute(attr)) return;
339
- const prev = el.getAttribute(attr) ?? "";
340
- if (prev) el.style.setProperty(prop, prev);
341
- else el.style.removeProperty(prop);
342
- el.removeAttribute(attr);
343
- }
344
- function ensureStyleSheet() {
345
- let el = document.getElementById(STYLE_SHEET_ID);
346
- if (!el) {
347
- el = document.createElement("style");
348
- el.id = STYLE_SHEET_ID;
349
- document.head.appendChild(el);
350
- }
351
- const css = styleSheetCss();
352
- if (el.textContent !== css) el.textContent = css;
353
- }
354
- function clearSectionAttrs(root) {
355
- for (const attr of Object.values(SECTION_ATTRS)) {
356
- for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
357
- }
358
- for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
359
- restoreInline(el, "background");
360
- el.removeAttribute("data-ohw-style-bgcolor");
361
- }
362
- }
363
- function clearNodeProps(root) {
364
- for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
365
- const h = el;
366
- for (const prop of NODE_PROPS) restoreInline(h, prop);
367
- h.removeAttribute(NODE_WROTE_ATTR);
368
- }
369
- }
370
- function buttonSurfaceOf(el) {
371
- return el.closest("a, button") ?? el;
372
- }
373
- function applyStylesToDom(store) {
374
- ensureStyleSheet();
375
- clearSectionAttrs(document);
376
- clearNodeProps(document);
377
- loadStyleFonts(
378
- store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
379
- );
380
- if (!store) return;
381
- for (const [sectionId, override] of Object.entries(store.sections)) {
382
- const sections = document.querySelectorAll(
383
- `[data-ohw-section="${CSS.escape(sectionId)}"]`
384
- );
385
- for (const section of Array.from(sections)) {
386
- for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
387
- const value = override[prop];
388
- if (value === void 0) continue;
389
- if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
390
- section.setAttribute(attr, String(value).replace(":", "-"));
391
- }
392
- if (override.sectionBackgroundColor !== void 0) {
393
- saveInline(section, "background");
394
- section.style.setProperty("background", override.sectionBackgroundColor, "important");
395
- section.setAttribute("data-ohw-style-bgcolor", "");
396
- }
397
- }
398
- }
399
- for (const [key, override] of Object.entries(store.nodes)) {
400
- const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
401
- for (const el of Array.from(nodes)) {
402
- if (override.color !== void 0) {
403
- saveInline(el, "color");
404
- el.style.setProperty("color", override.color, "important");
405
- el.setAttribute(NODE_WROTE_ATTR, "");
406
- }
407
- if (override.fontFamily !== void 0) {
408
- saveInline(el, "font-family");
409
- el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
410
- el.setAttribute(NODE_WROTE_ATTR, "");
411
- }
412
- if (override.fontSize !== void 0) {
413
- saveInline(el, "font-size");
414
- el.style.setProperty("font-size", `${override.fontSize}px`, "important");
415
- el.setAttribute(NODE_WROTE_ATTR, "");
416
- }
417
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
418
- const surface = buttonSurfaceOf(el);
419
- if (override.buttonBackground !== void 0) {
420
- saveInline(surface, "background");
421
- surface.style.setProperty("background", override.buttonBackground, "important");
422
- }
423
- if (override.buttonText !== void 0) {
424
- saveInline(surface, "color");
425
- surface.style.setProperty("color", override.buttonText, "important");
426
- }
427
- surface.setAttribute(NODE_WROTE_ATTR, "");
428
- }
429
- }
430
- }
431
- }
432
-
433
121
  // src/ui/ai-tree/aiSectionsManager.tsx
434
122
  import { flushSync } from "react-dom";
435
123
  import { createRoot } from "react-dom/client";
@@ -444,8 +132,7 @@ function lucideByName(name) {
444
132
  }
445
133
  var typeStyle = (spec, font) => ({
446
134
  fontFamily: font,
447
- // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
448
- fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
135
+ fontSize: spec.size,
449
136
  lineHeight: spec.line,
450
137
  fontWeight: spec.weight
451
138
  });
@@ -472,8 +159,6 @@ var AI_RESPONSIVE_CSS = [
472
159
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
473
160
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
474
161
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
475
- " [data-ai-responsive] { overflow-x: hidden; }",
476
- " [data-ai-responsive] img { max-width: 100%; }",
477
162
  "}"
478
163
  ].join("\n");
479
164
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -1481,20 +1166,6 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1481
1166
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1482
1167
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1483
1168
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1484
- const toneBackground = (() => {
1485
- const { dark, primary, light } = resolvedBrand.palette;
1486
- switch (settings.sectionBackground) {
1487
- case "surface":
1488
- return `color-mix(in srgb, ${light} 94%, ${dark})`;
1489
- case "accent":
1490
- return primary;
1491
- case "accent-soft":
1492
- return `color-mix(in srgb, ${primary} 12%, ${light})`;
1493
- default:
1494
- return void 0;
1495
- }
1496
- })();
1497
- const distributed = !isOverlay && settings.textDistribution;
1498
1169
  return /* @__PURE__ */ jsxs(
1499
1170
  "section",
1500
1171
  {
@@ -1504,11 +1175,10 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1504
1175
  style: {
1505
1176
  position: "relative",
1506
1177
  padding: `${pad}px 0`,
1507
- background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1178
+ background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1508
1179
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1509
1180
  backgroundSize: "cover",
1510
- backgroundPosition: "center",
1511
- color: settings.sectionBackground === "accent" ? resolvedBrand.palette.light : void 0
1181
+ backgroundPosition: "center"
1512
1182
  },
1513
1183
  children: [
1514
1184
  /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
@@ -1532,24 +1202,10 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1532
1202
  display: "grid",
1533
1203
  gridTemplateColumns: "repeat(12, 1fr)",
1534
1204
  gap: AI_TREE_TOKENS.spacing6,
1535
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1205
+ alignItems: settings.verticalPosition === "top" ? "start" : "center",
1536
1206
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1537
1207
  },
1538
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
1539
- "div",
1540
- {
1541
- "data-ai-cell": "",
1542
- style: {
1543
- gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1544
- minWidth: 0,
1545
- // space-between: each column becomes a flex column whose content spreads over
1546
- // the full row height instead of clumping at the top.
1547
- ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1548
- },
1549
- children: renderNode(block, ctx, `r${r2}.b${b}`)
1550
- },
1551
- b
1552
- ))
1208
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1553
1209
  },
1554
1210
  r2
1555
1211
  ))
@@ -1565,34 +1221,17 @@ import { jsx as jsx2 } from "react/jsx-runtime";
1565
1221
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1566
1222
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1567
1223
  var REMOVED_ATTR = "data-ohw-ai-removed";
1568
- function readRootVar(name) {
1569
- if (typeof document === "undefined") return "";
1570
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1571
- }
1572
- function deriveBrandOverride() {
1573
- const dark = readRootVar("--ohw-brand-dark");
1574
- const primary = readRootVar("--ohw-brand-primary");
1575
- const light = readRootVar("--ohw-brand-light");
1576
- if (!dark || !primary || !light) return null;
1577
- const accent = readRootVar("--ohw-brand-accent");
1578
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1579
- const body = readRootVar("--font-body");
1580
- return {
1581
- palette: { dark, primary, accent: accent || dark, light },
1582
- fonts: {
1583
- heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1584
- body: body || AI_DEFAULT_BRAND.fonts.body
1585
- }
1586
- };
1587
- }
1588
1224
  function deriveTemplateBrand() {
1589
- const dark = readRootVar("--color-dark");
1590
- const primary = readRootVar("--color-primary");
1591
- const light = readRootVar("--color-light");
1225
+ if (typeof document === "undefined") return null;
1226
+ const cs = getComputedStyle(document.documentElement);
1227
+ const read = (name) => cs.getPropertyValue(name).trim();
1228
+ const dark = read("--color-dark");
1229
+ const primary = read("--color-primary");
1230
+ const light = read("--color-light");
1592
1231
  if (!dark || !primary || !light) return null;
1593
- const accent = readRootVar("--color-accent");
1594
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1595
- const body = readRootVar("--font-body");
1232
+ const accent = read("--color-accent");
1233
+ const heading = read("--font-heading") || read("--font-display");
1234
+ const body = read("--font-body");
1596
1235
  return {
1597
1236
  palette: { dark, primary, accent: accent || dark, light },
1598
1237
  fonts: {
@@ -1684,12 +1323,8 @@ function syncReplacedOriginals(state) {
1684
1323
  }
1685
1324
  function applyAiSectionsToDom(state, options) {
1686
1325
  if (typeof document === "undefined") return;
1687
- const brandOverride = deriveBrandOverride();
1688
1326
  const templateBrand = deriveTemplateBrand();
1689
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1690
- const pagePath = window.location.pathname;
1691
- const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1692
- const activeIds = new Set(pageSections.map((entry) => entry.id));
1327
+ const activeIds = new Set(state.sections.map((entry) => entry.id));
1693
1328
  for (const [id, section] of mounted) {
1694
1329
  if (!activeIds.has(id)) {
1695
1330
  section.root.unmount();
@@ -1697,8 +1332,8 @@ function applyAiSectionsToDom(state, options) {
1697
1332
  mounted.delete(id);
1698
1333
  }
1699
1334
  }
1700
- for (const entry of pageSections) {
1701
- const serialized = JSON.stringify(entry) + brandKey;
1335
+ for (const entry of state.sections) {
1336
+ const serialized = JSON.stringify(entry);
1702
1337
  const existing = mounted.get(entry.id);
1703
1338
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1704
1339
  continue;
@@ -1712,7 +1347,6 @@ function applyAiSectionsToDom(state, options) {
1712
1347
  mounted.delete(entry.id);
1713
1348
  }
1714
1349
  container.setAttribute("data-ohw-section", entry.id);
1715
- container.setAttribute("data-ohw-instance", entry.id);
1716
1350
  container.setAttribute("data-ohw-section-label", entry.label);
1717
1351
  placeContainer(container, entry);
1718
1352
  const root = mounted.get(entry.id)?.root ?? createRoot(container);
@@ -1723,7 +1357,7 @@ function applyAiSectionsToDom(state, options) {
1723
1357
  AiTreeRenderer,
1724
1358
  {
1725
1359
  tree: entry.tree,
1726
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1360
+ brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1727
1361
  resolveMedia,
1728
1362
  editKeyPrefix: `ai.${entry.id}`
1729
1363
  }
@@ -2340,7 +1974,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2340
1974
  const autoId = useId();
2341
1975
  const insertAfter = insertAfterProp ?? autoId;
2342
1976
  const [schedule, setSchedule] = useState2(null);
2343
- const [loading, setLoading] = useState2(initialScheduleId !== null);
1977
+ const [loading, setLoading] = useState2(true);
2344
1978
  const [inEditor, setInEditor] = useState2(false);
2345
1979
  const [isHovered, setIsHovered] = useState2(false);
2346
1980
  const [modalState, setModalState] = useState2(null);
@@ -2514,10 +2148,8 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2514
2148
  "*"
2515
2149
  );
2516
2150
  };
2151
+ if (!inEditor && !loading && !schedule) return null;
2517
2152
  const sectionId = `scheduling-${insertAfter}`;
2518
- if (!inEditor && !loading && !schedule) {
2519
- return /* @__PURE__ */ jsx4("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2520
- }
2521
2153
  return /* @__PURE__ */ jsxs3(
2522
2154
  "section",
2523
2155
  {
@@ -6515,6 +6147,7 @@ function getChromeStyle(state) {
6515
6147
  }
6516
6148
  function ClampedToolbarSlot({
6517
6149
  placement,
6150
+ align = "center",
6518
6151
  children
6519
6152
  }) {
6520
6153
  const slotRef = useRef2(null);
@@ -6536,7 +6169,7 @@ function ClampedToolbarSlot({
6536
6169
  Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
6537
6170
  );
6538
6171
  const offsetX = clampedCenter - centerX;
6539
- slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
6172
+ slot.style.transform = align === "left" ? "none" : `translateX(calc(-50% + ${offsetX}px))`;
6540
6173
  };
6541
6174
  clamp();
6542
6175
  const ro = new ResizeObserver(clamp);
@@ -6547,19 +6180,20 @@ function ClampedToolbarSlot({
6547
6180
  ro.disconnect();
6548
6181
  window.removeEventListener("resize", clamp);
6549
6182
  };
6550
- }, [placement, children]);
6183
+ }, [placement, children, align]);
6551
6184
  return /* @__PURE__ */ jsx12(
6552
6185
  "div",
6553
6186
  {
6554
6187
  ref: slotRef,
6555
6188
  className: cn(
6556
- "pointer-events-auto absolute left-1/2",
6189
+ "pointer-events-auto absolute",
6190
+ align === "left" ? "left-0" : "left-1/2",
6557
6191
  placement === "top" ? "bottom-full" : "top-full"
6558
6192
  ),
6559
6193
  style: {
6560
6194
  marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
6561
6195
  marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
6562
- transform: "translateX(-50%)"
6196
+ transform: align === "left" ? "none" : "translateX(-50%)"
6563
6197
  },
6564
6198
  "data-ohw-item-toolbar-anchor": placement,
6565
6199
  children
@@ -6628,6 +6262,7 @@ function ItemInteractionLayer({
6628
6262
  onItemClick,
6629
6263
  itemDragSurface = true,
6630
6264
  chromeGap,
6265
+ toolbarAlign = "center",
6631
6266
  className
6632
6267
  }) {
6633
6268
  if (state === "default") return null;
@@ -6715,8 +6350,8 @@ function ItemInteractionLayer({
6715
6350
  )
6716
6351
  }
6717
6352
  ),
6718
- showToolbar && state === "active-top" && /* @__PURE__ */ jsx12(ClampedToolbarSlot, { placement: "top", children: toolbar }),
6719
- showToolbar && state === "active-bottom" && !useDetachedBelowToolbar && /* @__PURE__ */ jsx12(ClampedToolbarSlot, { placement: "bottom", children: toolbar }),
6353
+ showToolbar && state === "active-top" && /* @__PURE__ */ jsx12(ClampedToolbarSlot, { align: toolbarAlign, placement: "top", children: toolbar }),
6354
+ showToolbar && state === "active-bottom" && !useDetachedBelowToolbar && /* @__PURE__ */ jsx12(ClampedToolbarSlot, { align: toolbarAlign, placement: "bottom", children: toolbar }),
6720
6355
  useDetachedBelowToolbar && toolbarBelowRect ? /* @__PURE__ */ jsx12(
6721
6356
  DetachedBelowToolbarSlot,
6722
6357
  {
@@ -6730,6 +6365,616 @@ function ItemInteractionLayer({
6730
6365
  );
6731
6366
  }
6732
6367
 
6368
+ // src/lib/forms.ts
6369
+ var FORM_SELECTOR = '[data-ohw-editable="form"]';
6370
+ function getFormElement(el) {
6371
+ if (!el) return null;
6372
+ return el.closest(FORM_SELECTOR);
6373
+ }
6374
+ function formKeyOf(form) {
6375
+ return form.getAttribute("data-ohw-key");
6376
+ }
6377
+ function formHasLongText(form) {
6378
+ return form.querySelector("textarea") !== null;
6379
+ }
6380
+ function collectFormFields(form) {
6381
+ const fields = {};
6382
+ form.querySelectorAll(
6383
+ "input, textarea, select"
6384
+ ).forEach((input) => {
6385
+ const type = input.type;
6386
+ if (type === "submit" || type === "button" || type === "reset") return;
6387
+ const key = input.getAttribute("name") ?? input.getAttribute("data-ohw-key");
6388
+ if (!key) return;
6389
+ if (type === "checkbox") {
6390
+ fields[key] = input.checked ? "yes" : "no";
6391
+ return;
6392
+ }
6393
+ fields[key] = input.value;
6394
+ });
6395
+ return fields;
6396
+ }
6397
+ function formStateContainer(form) {
6398
+ return form.closest("[data-ohw-editable-state]") ?? form.parentElement ?? form;
6399
+ }
6400
+ function showSuccess(form, message) {
6401
+ const container = formStateContainer(form);
6402
+ const views = container.querySelectorAll("[data-ohw-state-view]");
6403
+ if (views.length > 0) {
6404
+ views.forEach((view) => {
6405
+ view.style.display = view.getAttribute("data-ohw-state-view") === "success" ? "block" : "none";
6406
+ });
6407
+ return;
6408
+ }
6409
+ const text = message || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6410
+ const note = document.createElement("p");
6411
+ note.setAttribute("data-ohw-form-success", "");
6412
+ note.setAttribute("role", "status");
6413
+ note.innerHTML = text;
6414
+ form.replaceChildren(note);
6415
+ }
6416
+ function showSubmitError(form, message = "Something went wrong. Please try again.") {
6417
+ let note = form.querySelector("[data-ohw-form-error]");
6418
+ if (!note) {
6419
+ note = document.createElement("p");
6420
+ note.setAttribute("data-ohw-form-error", "");
6421
+ note.setAttribute("role", "alert");
6422
+ note.style.marginTop = "8px";
6423
+ form.appendChild(note);
6424
+ }
6425
+ note.textContent = message;
6426
+ note.style.display = "";
6427
+ }
6428
+ function clearSubmitError(form) {
6429
+ const note = form.querySelector("[data-ohw-form-error]");
6430
+ if (note) note.style.display = "none";
6431
+ }
6432
+ var SUCCESS_TEXT_ATTR = "data-ohw-form-success-text";
6433
+ var HIDDEN_ATTR = "data-ohw-form-hidden";
6434
+ var DEFAULT_SUCCESS_TEXT = "Thanks! We'll be in touch.";
6435
+ function formSuccessKey(formKey) {
6436
+ return `${formKey}-success`;
6437
+ }
6438
+ function ensureSuccessTextEl(form, formKey, initialText) {
6439
+ let el = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
6440
+ if (el) return el;
6441
+ el = document.createElement("p");
6442
+ el.setAttribute(SUCCESS_TEXT_ATTR, "");
6443
+ el.setAttribute("data-ohw-editable", "text");
6444
+ el.setAttribute("data-ohw-key", formSuccessKey(formKey));
6445
+ el.innerHTML = initialText;
6446
+ el.style.display = "none";
6447
+ el.style.margin = "56px 4px 16px";
6448
+ form.appendChild(el);
6449
+ return el;
6450
+ }
6451
+ function successInitialFor(form, formKey, content) {
6452
+ return content[formSuccessKey(formKey)] || form.getAttribute("data-ohw-success-text") || DEFAULT_SUCCESS_TEXT;
6453
+ }
6454
+ function setFormViewState(form, formKey, state, initialText) {
6455
+ const success = ensureSuccessTextEl(form, formKey, initialText);
6456
+ Array.from(form.children).forEach((child) => {
6457
+ if (!(child instanceof HTMLElement) || child === success) return;
6458
+ if (state === "success") {
6459
+ if (!child.hasAttribute(HIDDEN_ATTR)) {
6460
+ child.setAttribute(HIDDEN_ATTR, child.style.display);
6461
+ child.style.display = "none";
6462
+ }
6463
+ } else if (child.hasAttribute(HIDDEN_ATTR)) {
6464
+ child.style.display = child.getAttribute(HIDDEN_ATTR) ?? "";
6465
+ child.removeAttribute(HIDDEN_ATTR);
6466
+ }
6467
+ });
6468
+ success.style.display = state === "success" ? "" : "none";
6469
+ }
6470
+ var BOUND_ATTR = "data-ohw-form-bound";
6471
+ var publishedFormContext = {
6472
+ apiUrl: "",
6473
+ subdomain: "",
6474
+ content: {}
6475
+ };
6476
+ function bindPublishedForms(apiUrl, subdomain, content = {}) {
6477
+ publishedFormContext = { apiUrl, subdomain, content };
6478
+ if (document.documentElement.hasAttribute(BOUND_ATTR)) return;
6479
+ document.documentElement.setAttribute(BOUND_ATTR, "");
6480
+ document.addEventListener(
6481
+ "submit",
6482
+ async (e) => {
6483
+ const target = e.target;
6484
+ const form = target?.closest(FORM_SELECTOR);
6485
+ if (!form || !(form instanceof HTMLFormElement)) return;
6486
+ const formKey = formKeyOf(form);
6487
+ if (!formKey) return;
6488
+ e.preventDefault();
6489
+ clearSubmitError(form);
6490
+ const filled = collectFormFields(form);
6491
+ if (!Object.values(filled).some((value) => value.trim() !== "")) {
6492
+ showSubmitError(form, "Please fill in at least one field.");
6493
+ return;
6494
+ }
6495
+ const submitButton = form.querySelector(
6496
+ 'button[type="submit"], input[type="submit"], button:not([type])'
6497
+ );
6498
+ if (submitButton) submitButton.disabled = true;
6499
+ try {
6500
+ const { apiUrl: api, subdomain: site, content: latest } = publishedFormContext;
6501
+ const response = await fetch(`${api}/api/public/sites/${site}/forms/submissions`, {
6502
+ method: "POST",
6503
+ headers: { "Content-Type": "application/json" },
6504
+ body: JSON.stringify({
6505
+ formKey,
6506
+ fields: filled,
6507
+ hasLongText: formHasLongText(form)
6508
+ })
6509
+ });
6510
+ if (!response.ok) throw new Error(`submit failed: ${response.status}`);
6511
+ showSuccess(form, latest[formSuccessKey(formKey)]);
6512
+ } catch {
6513
+ showSubmitError(form);
6514
+ if (submitButton) submitButton.disabled = false;
6515
+ }
6516
+ },
6517
+ true
6518
+ );
6519
+ }
6520
+
6521
+ // src/lib/form-fields.ts
6522
+ var FIELD_DEFAULTS = {
6523
+ "short-text": { label: "Short text", placeholder: "Type placeholder text..." },
6524
+ "long-text": { label: "Long text", placeholder: "How can we help?" },
6525
+ email: { label: "Email", placeholder: "hello@example.com" },
6526
+ phone: { label: "Phone number", placeholder: "(555) 000-0000" }
6527
+ };
6528
+ var FIELD_TYPES = [
6529
+ { type: "short-text", label: "Short text" },
6530
+ { type: "long-text", label: "Long text" },
6531
+ { type: "email", label: "Email" },
6532
+ { type: "phone", label: "Phone" }
6533
+ ];
6534
+ var FIELD_ATTR = "data-ohw-form-field";
6535
+ var FIELD_TYPE_ATTR = "data-ohw-field-type";
6536
+ var PLACEHOLDER_EDIT_ATTR = "data-ohw-placeholder-edit";
6537
+ function fieldsKey(formKey) {
6538
+ return `${formKey}-fields`;
6539
+ }
6540
+ function inputTagFor(type) {
6541
+ if (type === "long-text") return { tag: "textarea" };
6542
+ if (type === "email") return { tag: "input", inputType: "email" };
6543
+ if (type === "phone") return { tag: "input", inputType: "tel" };
6544
+ return { tag: "input", inputType: "text" };
6545
+ }
6546
+ function inferType(input) {
6547
+ if (input.tagName === "TEXTAREA") return "long-text";
6548
+ const type = input.type;
6549
+ if (type === "email") return "email";
6550
+ if (type === "tel") return "phone";
6551
+ return "short-text";
6552
+ }
6553
+ function ensureFieldLabel(wrapper, key) {
6554
+ const existing = fieldLabelOf(wrapper);
6555
+ if (existing) {
6556
+ if (!existing.hasAttribute("data-ohw-editable")) {
6557
+ existing.setAttribute("data-ohw-editable", "text");
6558
+ existing.setAttribute("data-ohw-key", `${key}-label`);
6559
+ }
6560
+ return existing;
6561
+ }
6562
+ const input = fieldInputOf(wrapper);
6563
+ const label = document.createElement("label");
6564
+ label.setAttribute("data-ohw-editable", "text");
6565
+ label.setAttribute("data-ohw-key", `${key}-label`);
6566
+ label.setAttribute("data-ohw-field-label", "");
6567
+ label.style.display = "block";
6568
+ label.style.marginBottom = "6px";
6569
+ label.style.fontSize = "13px";
6570
+ label.style.fontWeight = "500";
6571
+ label.style.lineHeight = "1.3";
6572
+ const fromPlaceholder = input?.getAttribute("placeholder")?.trim();
6573
+ const text = fromPlaceholder && fromPlaceholder.length < 40 ? fromPlaceholder : key.replace(/[-_]/g, " ");
6574
+ label.textContent = text;
6575
+ if (input && input.parentElement === wrapper) wrapper.insertBefore(label, input);
6576
+ else wrapper.insertBefore(label, wrapper.firstChild);
6577
+ return label;
6578
+ }
6579
+ function listFieldWrappers(form) {
6580
+ return Array.from(form.querySelectorAll(`[${FIELD_ATTR}]`));
6581
+ }
6582
+ function getFieldWrapper(el) {
6583
+ return el?.closest(`[${FIELD_ATTR}]`) ?? null;
6584
+ }
6585
+ function fieldInputOf(wrapper) {
6586
+ return wrapper.querySelector("input, textarea");
6587
+ }
6588
+ function fieldLabelOf(wrapper) {
6589
+ return wrapper.querySelector("label");
6590
+ }
6591
+ function fieldKeyOf(wrapper) {
6592
+ return wrapper.getAttribute(FIELD_ATTR) ?? "";
6593
+ }
6594
+ function fieldTypeOf(wrapper) {
6595
+ return wrapper.getAttribute(FIELD_TYPE_ATTR) ?? "short-text";
6596
+ }
6597
+ function isFieldRequired(wrapper) {
6598
+ return fieldInputOf(wrapper)?.hasAttribute("required") ?? false;
6599
+ }
6600
+ function markFormFields(form) {
6601
+ form.querySelectorAll("input, textarea").forEach((input) => {
6602
+ const type = input.type;
6603
+ if (type === "submit" || type === "button" || type === "reset" || type === "hidden") return;
6604
+ if (getFieldWrapper(input)) return;
6605
+ let wrapper = input;
6606
+ while (wrapper.parentElement && wrapper.parentElement !== form && wrapper.parentElement.querySelectorAll("input, textarea").length === 1) {
6607
+ wrapper = wrapper.parentElement;
6608
+ }
6609
+ if (wrapper === input) {
6610
+ const box = document.createElement("div");
6611
+ box.setAttribute("data-ohw-field-box", "");
6612
+ input.replaceWith(box);
6613
+ box.appendChild(input);
6614
+ wrapper = box;
6615
+ }
6616
+ const key = input.getAttribute("name") ?? input.getAttribute("data-ohw-key") ?? `field-${Date.now()}`;
6617
+ wrapper.setAttribute(FIELD_ATTR, key);
6618
+ wrapper.setAttribute(FIELD_TYPE_ATTR, inferType(input));
6619
+ ensureFieldLabel(wrapper, key);
6620
+ syncRequiredMark(wrapper);
6621
+ });
6622
+ return listFieldWrappers(form);
6623
+ }
6624
+ function readFieldsFromDom(form) {
6625
+ return listFieldWrappers(form).map((wrapper) => {
6626
+ const input = fieldInputOf(wrapper);
6627
+ return {
6628
+ key: fieldKeyOf(wrapper),
6629
+ type: fieldTypeOf(wrapper),
6630
+ label: fieldLabelText(fieldLabelOf(wrapper)).trim(),
6631
+ // While a field is selected its placeholder text lives in the value (see
6632
+ // beginPlaceholderEdit), so reading the attribute would store an empty one — whoever
6633
+ // happens to save at that moment must still record what the owner typed.
6634
+ placeholder: input ? input.hasAttribute(PLACEHOLDER_EDIT_ATTR) ? input.value : input.getAttribute("placeholder") ?? "" : "",
6635
+ required: Boolean(input?.hasAttribute("required"))
6636
+ };
6637
+ });
6638
+ }
6639
+ function parseFieldSpecs(raw) {
6640
+ if (!raw) return null;
6641
+ try {
6642
+ const parsed = JSON.parse(raw);
6643
+ return Array.isArray(parsed) ? parsed : null;
6644
+ } catch {
6645
+ return null;
6646
+ }
6647
+ }
6648
+ function isDefaultText(value, pick) {
6649
+ const trimmed = value.trim().replace(/\s*\*$/, "");
6650
+ if (!trimmed) return true;
6651
+ return Object.values(FIELD_DEFAULTS).some((defaults) => pick(defaults) === trimmed);
6652
+ }
6653
+ function applyFieldType(wrapper, type) {
6654
+ const input = fieldInputOf(wrapper);
6655
+ if (!input) return;
6656
+ const defaults = FIELD_DEFAULTS[type];
6657
+ const placeholder = input.getAttribute("placeholder") ?? "";
6658
+ const label = fieldLabelOf(wrapper);
6659
+ const followsDefaults = {
6660
+ // A label the owner wrote stays; one that still reads as a type name (or as the old
6661
+ // placeholder the template shipped) follows the new type.
6662
+ label: isDefaultText(label?.textContent ?? "", (d) => d.label) || fieldLabelText(label).trim() === placeholder.trim()
6663
+ };
6664
+ const { tag, inputType } = inputTagFor(type);
6665
+ wrapper.setAttribute(FIELD_TYPE_ATTR, type);
6666
+ const shedSize = (el) => {
6667
+ if (type === "long-text") return;
6668
+ el.style.removeProperty("height");
6669
+ el.style.removeProperty("min-height");
6670
+ el.style.removeProperty("resize");
6671
+ el.removeAttribute("rows");
6672
+ };
6673
+ const applyDefaults = (el) => {
6674
+ el.setAttribute("placeholder", defaults.placeholder);
6675
+ if (label && followsDefaults.label) {
6676
+ label.textContent = defaults.label;
6677
+ }
6678
+ };
6679
+ if (input.tagName.toLowerCase() === tag) {
6680
+ if (inputType) input.type = inputType;
6681
+ shedSize(input);
6682
+ applyDefaults(input);
6683
+ return;
6684
+ }
6685
+ const next = document.createElement(tag);
6686
+ Array.from(input.attributes).forEach((attr) => {
6687
+ if (attr.name === "type") return;
6688
+ next.setAttribute(attr.name, attr.value);
6689
+ });
6690
+ if (inputType) next.setAttribute("type", inputType);
6691
+ shedSize(next);
6692
+ if (type === "long-text") next.setAttribute("rows", "4");
6693
+ input.replaceWith(next);
6694
+ applyDefaults(next);
6695
+ }
6696
+ var REQUIRED_MARK_ATTR = "data-ohw-field-required";
6697
+ function fieldLabelText(label) {
6698
+ if (!label) return "";
6699
+ return (label.textContent ?? "").replace(/\s*\*\s*$/, "").trimEnd();
6700
+ }
6701
+ function syncRequiredMark(wrapper) {
6702
+ const label = fieldLabelOf(wrapper);
6703
+ if (!label) return;
6704
+ label.querySelectorAll("[data-ohw-required-mark]").forEach((el) => el.remove());
6705
+ const words = fieldLabelText(label);
6706
+ if ((label.textContent ?? "") !== words) label.textContent = words;
6707
+ if (isFieldRequired(wrapper)) label.setAttribute(REQUIRED_MARK_ATTR, "");
6708
+ else label.removeAttribute(REQUIRED_MARK_ATTR);
6709
+ }
6710
+ function setFieldRequired(wrapper, required) {
6711
+ const input = fieldInputOf(wrapper);
6712
+ const label = fieldLabelOf(wrapper);
6713
+ if (!input) return;
6714
+ if (required) input.setAttribute("required", "");
6715
+ else input.removeAttribute("required");
6716
+ if (label) syncRequiredMark(wrapper);
6717
+ }
6718
+ function beginPlaceholderEdit(wrapper) {
6719
+ const input = fieldInputOf(wrapper);
6720
+ if (!input || input.hasAttribute(PLACEHOLDER_EDIT_ATTR)) return;
6721
+ input.setAttribute(PLACEHOLDER_EDIT_ATTR, "");
6722
+ input.value = input.getAttribute("placeholder") ?? "";
6723
+ input.setAttribute("placeholder", "");
6724
+ }
6725
+ function commitPlaceholderEdit(wrapper) {
6726
+ if (!wrapper) return false;
6727
+ const input = fieldInputOf(wrapper);
6728
+ if (!input || !input.hasAttribute(PLACEHOLDER_EDIT_ATTR)) return false;
6729
+ const typed = input.value;
6730
+ input.removeAttribute(PLACEHOLDER_EDIT_ATTR);
6731
+ input.value = "";
6732
+ const previous = input.getAttribute("placeholder") ?? "";
6733
+ input.setAttribute("placeholder", typed);
6734
+ return previous !== typed;
6735
+ }
6736
+ function setFieldPlaceholder(wrapper, placeholder) {
6737
+ fieldInputOf(wrapper)?.setAttribute("placeholder", placeholder);
6738
+ }
6739
+ function uniqueKey(form, base) {
6740
+ const taken = new Set(listFieldWrappers(form).map(fieldKeyOf));
6741
+ if (!taken.has(base)) return base;
6742
+ let n = 2;
6743
+ while (taken.has(`${base}-${n}`)) n += 1;
6744
+ return `${base}-${n}`;
6745
+ }
6746
+ function insertField(form, type) {
6747
+ const existing = listFieldWrappers(form);
6748
+ const source = existing[existing.length - 1] ?? null;
6749
+ const key = uniqueKey(form, type);
6750
+ let wrapper;
6751
+ if (source) {
6752
+ wrapper = source.cloneNode(true);
6753
+ source.after(wrapper);
6754
+ } else {
6755
+ wrapper = document.createElement("div");
6756
+ const label2 = document.createElement("label");
6757
+ const input2 = document.createElement("input");
6758
+ wrapper.append(label2, input2);
6759
+ const submit = form.querySelector('button, input[type="submit"]');
6760
+ if (submit) submit.before(wrapper);
6761
+ else form.appendChild(wrapper);
6762
+ }
6763
+ wrapper.setAttribute(FIELD_ATTR, key);
6764
+ wrapper.setAttribute(FIELD_TYPE_ATTR, type);
6765
+ applyFieldType(wrapper, type);
6766
+ const defaults = FIELD_DEFAULTS[type];
6767
+ const input = fieldInputOf(wrapper);
6768
+ if (input) {
6769
+ input.setAttribute("name", key);
6770
+ input.removeAttribute("required");
6771
+ input.setAttribute("placeholder", defaults.placeholder);
6772
+ input.value = "";
6773
+ }
6774
+ const label = ensureFieldLabel(wrapper, key);
6775
+ label.textContent = defaults.label;
6776
+ label.setAttribute("data-ohw-key", `${key}-label`);
6777
+ return wrapper;
6778
+ }
6779
+ function duplicateField(form, wrapper) {
6780
+ const copy = wrapper.cloneNode(true);
6781
+ const key = uniqueKey(form, `${fieldKeyOf(wrapper)}-copy`);
6782
+ copy.setAttribute(FIELD_ATTR, key);
6783
+ const input = fieldInputOf(copy);
6784
+ if (input) {
6785
+ input.setAttribute("name", key);
6786
+ input.removeAttribute("data-ohw-key");
6787
+ input.value = "";
6788
+ }
6789
+ const label = fieldLabelOf(copy);
6790
+ if (label) label.setAttribute("data-ohw-key", `${key}-label`);
6791
+ wrapper.after(copy);
6792
+ return copy;
6793
+ }
6794
+ function removeField(wrapper) {
6795
+ wrapper.remove();
6796
+ }
6797
+ function applyFieldOrder(form, keys) {
6798
+ const wrappers = listFieldWrappers(form);
6799
+ const byKey = new Map(wrappers.map((wrapper) => [fieldKeyOf(wrapper), wrapper]));
6800
+ const ordered = keys.map((key) => byKey.get(key)).filter((w) => Boolean(w));
6801
+ if (ordered.length !== wrappers.length) return;
6802
+ const slots = wrappers.map((wrapper) => {
6803
+ const slot = document.createComment("ohw-field-slot");
6804
+ wrapper.replaceWith(slot);
6805
+ return slot;
6806
+ });
6807
+ ordered.forEach((wrapper, index) => slots[index].replaceWith(wrapper));
6808
+ }
6809
+ function moveField(form, key, toIndex) {
6810
+ const keys = listFieldWrappers(form).map(fieldKeyOf);
6811
+ const from = keys.indexOf(key);
6812
+ if (from === -1) return;
6813
+ const rest = keys.filter((k) => k !== key);
6814
+ rest.splice(Math.max(0, Math.min(rest.length, toIndex)), 0, key);
6815
+ applyFieldOrder(form, rest);
6816
+ }
6817
+ function reconcileFieldsFromContent(form, content) {
6818
+ const formKey = form.getAttribute("data-ohw-key");
6819
+ if (!formKey) return;
6820
+ markFormFields(form);
6821
+ const stored = parseFieldSpecs(content[fieldsKey(formKey)]);
6822
+ if (!stored) return;
6823
+ const byKey = new Map(listFieldWrappers(form).map((wrapper) => [fieldKeyOf(wrapper), wrapper]));
6824
+ stored.forEach((spec) => {
6825
+ let wrapper = byKey.get(spec.key) ?? null;
6826
+ if (!wrapper) {
6827
+ wrapper = insertField(form, spec.type);
6828
+ if (!wrapper) return;
6829
+ wrapper.setAttribute(FIELD_ATTR, spec.key);
6830
+ const input = fieldInputOf(wrapper);
6831
+ if (input) {
6832
+ input.setAttribute("name", spec.key);
6833
+ }
6834
+ fieldLabelOf(wrapper)?.setAttribute("data-ohw-key", `${spec.key}-label`);
6835
+ byKey.set(spec.key, wrapper);
6836
+ }
6837
+ applyFieldType(wrapper, spec.type);
6838
+ setFieldRequired(wrapper, spec.required);
6839
+ setFieldPlaceholder(wrapper, spec.placeholder);
6840
+ const label = fieldLabelOf(wrapper);
6841
+ if (label && spec.label) label.textContent = spec.label.replace(/\s*\*\s*$/, "").trimEnd();
6842
+ syncRequiredMark(wrapper);
6843
+ });
6844
+ const wanted = new Set(stored.map((spec) => spec.key));
6845
+ listFieldWrappers(form).forEach((wrapper) => {
6846
+ if (!wanted.has(fieldKeyOf(wrapper))) wrapper.remove();
6847
+ });
6848
+ const currentOrder = listFieldWrappers(form).map(fieldKeyOf).join("\0");
6849
+ const storedOrder = stored.map((spec) => spec.key).join("\0");
6850
+ if (currentOrder !== storedOrder) {
6851
+ applyFieldOrder(form, stored.map((spec) => spec.key));
6852
+ }
6853
+ }
6854
+
6855
+ // src/ui/form-field-toolbar.tsx
6856
+ import { AtSign, Asterisk, ChevronDown as ChevronDown2, Copy as Copy2, MoreHorizontal as MoreHorizontal2, Phone, TextQuote, Trash2 as Trash22, Type } from "lucide-react";
6857
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
6858
+ var TYPE_ICONS = {
6859
+ "short-text": Type,
6860
+ "long-text": TextQuote,
6861
+ email: AtSign,
6862
+ phone: Phone
6863
+ };
6864
+ function FormFieldToolbar({
6865
+ type,
6866
+ required,
6867
+ onTypeChange,
6868
+ onRequiredToggle,
6869
+ onDuplicate,
6870
+ onDelete
6871
+ }) {
6872
+ const TypeIcon = TYPE_ICONS[type];
6873
+ const typeLabel = FIELD_TYPES.find((entry) => entry.type === type)?.label ?? "Short text";
6874
+ return /* @__PURE__ */ jsxs6(
6875
+ "div",
6876
+ {
6877
+ "data-ohw-field-toolbar": "",
6878
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
6879
+ children: [
6880
+ /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
6881
+ /* @__PURE__ */ jsx13(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs6(
6882
+ "button",
6883
+ {
6884
+ type: "button",
6885
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted/70",
6886
+ "data-ohw-field-type-trigger": "",
6887
+ children: [
6888
+ /* @__PURE__ */ jsx13(TypeIcon, { size: 14, strokeWidth: 1.75, "aria-hidden": true }),
6889
+ typeLabel,
6890
+ /* @__PURE__ */ jsx13(ChevronDown2, { size: 13, className: "text-muted-foreground", "aria-hidden": true })
6891
+ ]
6892
+ }
6893
+ ) }),
6894
+ /* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[190px] p-1", children: FIELD_TYPES.map((entry) => {
6895
+ const Icon = TYPE_ICONS[entry.type];
6896
+ return /* @__PURE__ */ jsxs6(
6897
+ DropdownMenuItem,
6898
+ {
6899
+ onSelect: () => onTypeChange(entry.type),
6900
+ className: "rounded-md py-2 text-[13px] " + (entry.type === type ? "bg-primary/10" : ""),
6901
+ children: [
6902
+ /* @__PURE__ */ jsx13(Icon, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
6903
+ entry.label
6904
+ ]
6905
+ },
6906
+ entry.type
6907
+ );
6908
+ }) })
6909
+ ] }),
6910
+ /* @__PURE__ */ jsx13("div", { className: "mx-0.5 h-5 w-px bg-border" }),
6911
+ /* @__PURE__ */ jsxs6(
6912
+ "button",
6913
+ {
6914
+ type: "button",
6915
+ "aria-pressed": required,
6916
+ onClick: onRequiredToggle,
6917
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-medium transition-colors " + (required ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted/70"),
6918
+ "data-ohw-field-required": "",
6919
+ children: [
6920
+ /* @__PURE__ */ jsx13(Asterisk, { size: 14, strokeWidth: 2, "aria-hidden": true }),
6921
+ "Required"
6922
+ ]
6923
+ }
6924
+ ),
6925
+ /* @__PURE__ */ jsx13("div", { className: "mx-0.5 h-5 w-px bg-border" }),
6926
+ /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
6927
+ /* @__PURE__ */ jsx13(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(
6928
+ "button",
6929
+ {
6930
+ type: "button",
6931
+ title: "More",
6932
+ "aria-label": "More",
6933
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/70",
6934
+ children: /* @__PURE__ */ jsx13(MoreHorizontal2, { size: 15, "aria-hidden": true })
6935
+ }
6936
+ ) }),
6937
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, { align: "start", sideOffset: 8, className: "min-w-[170px] p-1", children: [
6938
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, { onSelect: onDuplicate, className: "rounded-md py-2 text-[13px]", children: [
6939
+ /* @__PURE__ */ jsx13(Copy2, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
6940
+ "Duplicate"
6941
+ ] }),
6942
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, { variant: "destructive", onSelect: onDelete, className: "rounded-md py-2 text-[13px]", children: [
6943
+ /* @__PURE__ */ jsx13(Trash22, { size: 14, strokeWidth: 1.75, className: "shrink-0", "aria-hidden": true }),
6944
+ "Delete"
6945
+ ] })
6946
+ ] })
6947
+ ] })
6948
+ ]
6949
+ }
6950
+ );
6951
+ }
6952
+ function FieldTypePicker({ onPick }) {
6953
+ return /* @__PURE__ */ jsx13(
6954
+ "div",
6955
+ {
6956
+ "data-ohw-field-type-picker": "",
6957
+ className: "pointer-events-auto grid w-[420px] grid-cols-3 gap-3 rounded-xl border border-border bg-background p-4 shadow-lg",
6958
+ children: FIELD_TYPES.map((entry) => {
6959
+ const Icon = TYPE_ICONS[entry.type];
6960
+ return /* @__PURE__ */ jsxs6(
6961
+ "button",
6962
+ {
6963
+ type: "button",
6964
+ onClick: () => onPick(entry.type),
6965
+ className: "flex h-[104px] flex-col items-center justify-center gap-3 rounded-xl border border-border text-[15px] font-medium text-foreground transition-colors hover:border-primary hover:bg-primary/5",
6966
+ children: [
6967
+ /* @__PURE__ */ jsx13(Icon, { size: 26, strokeWidth: 1.5, "aria-hidden": true }),
6968
+ entry.label
6969
+ ]
6970
+ },
6971
+ entry.type
6972
+ );
6973
+ })
6974
+ }
6975
+ );
6976
+ }
6977
+
6733
6978
  // src/ui/MediaOverlay.tsx
6734
6979
  import * as React7 from "react";
6735
6980
  import { Film, ImageIcon, Pause, Play, Volume2, VolumeX } from "lucide-react";
@@ -6737,7 +6982,7 @@ import { Film, ImageIcon, Pause, Play, Volume2, VolumeX } from "lucide-react";
6737
6982
  // src/ui/button.tsx
6738
6983
  import * as React6 from "react";
6739
6984
  import { Slot } from "radix-ui";
6740
- import { jsx as jsx13 } from "react/jsx-runtime";
6985
+ import { jsx as jsx14 } from "react/jsx-runtime";
6741
6986
  var buttonVariants = cva(
6742
6987
  "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
6743
6988
  {
@@ -6761,7 +7006,7 @@ var buttonVariants = cva(
6761
7006
  var Button = React6.forwardRef(
6762
7007
  ({ className, variant, size, asChild = false, ...props }, ref) => {
6763
7008
  const Comp = asChild ? Slot.Root : "button";
6764
- return /* @__PURE__ */ jsx13(
7009
+ return /* @__PURE__ */ jsx14(
6765
7010
  Comp,
6766
7011
  {
6767
7012
  ref,
@@ -6775,7 +7020,7 @@ var Button = React6.forwardRef(
6775
7020
  Button.displayName = "Button";
6776
7021
 
6777
7022
  // src/ui/MediaOverlay.tsx
6778
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
7023
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
6779
7024
  var MEDIA_UPLOAD_FADE_MS = 300;
6780
7025
  var VIDEO_SETTINGS_BAR_INSET = 8;
6781
7026
  var OVERLAY_BUTTON_STYLE = {
@@ -6833,7 +7078,7 @@ function MediaOverlay({
6833
7078
  return () => anim.cancel();
6834
7079
  }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
6835
7080
  if (isUploading) {
6836
- return /* @__PURE__ */ jsx14(
7081
+ return /* @__PURE__ */ jsx15(
6837
7082
  "div",
6838
7083
  {
6839
7084
  ref: skeletonRef,
@@ -6842,11 +7087,11 @@ function MediaOverlay({
6842
7087
  "data-ohw-media-skeleton": "",
6843
7088
  "aria-hidden": true,
6844
7089
  style: { ...box, pointerEvents: "none" },
6845
- children: /* @__PURE__ */ jsx14("style", { children: SKELETON_CSS })
7090
+ children: /* @__PURE__ */ jsx15("style", { children: SKELETON_CSS })
6846
7091
  }
6847
7092
  );
6848
7093
  }
6849
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs6(
7094
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs7(
6850
7095
  "div",
6851
7096
  {
6852
7097
  "data-ohw-bridge": "",
@@ -6862,7 +7107,7 @@ function MediaOverlay({
6862
7107
  },
6863
7108
  onClick: (e) => e.stopPropagation(),
6864
7109
  children: [
6865
- /* @__PURE__ */ jsx14(
7110
+ /* @__PURE__ */ jsx15(
6866
7111
  Button,
6867
7112
  {
6868
7113
  "data-ohw-media-overlay": "",
@@ -6877,10 +7122,10 @@ function MediaOverlay({
6877
7122
  e.stopPropagation();
6878
7123
  onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
6879
7124
  },
6880
- children: autoplay ? /* @__PURE__ */ jsx14(Pause, { size: 14 }) : /* @__PURE__ */ jsx14(Play, { size: 14 })
7125
+ children: autoplay ? /* @__PURE__ */ jsx15(Pause, { size: 14 }) : /* @__PURE__ */ jsx15(Play, { size: 14 })
6881
7126
  }
6882
7127
  ),
6883
- /* @__PURE__ */ jsx14(
7128
+ /* @__PURE__ */ jsx15(
6884
7129
  Button,
6885
7130
  {
6886
7131
  "data-ohw-media-overlay": "",
@@ -6895,15 +7140,15 @@ function MediaOverlay({
6895
7140
  e.stopPropagation();
6896
7141
  onVideoSettingsChange?.(hover.key, { muted: !muted });
6897
7142
  },
6898
- children: muted ? /* @__PURE__ */ jsx14(VolumeX, { size: 14 }) : /* @__PURE__ */ jsx14(Volume2, { size: 14 })
7143
+ children: muted ? /* @__PURE__ */ jsx15(VolumeX, { size: 14 }) : /* @__PURE__ */ jsx15(Volume2, { size: 14 })
6899
7144
  }
6900
7145
  )
6901
7146
  ]
6902
7147
  }
6903
7148
  ) : null;
6904
- return /* @__PURE__ */ jsxs6(Fragment4, { children: [
7149
+ return /* @__PURE__ */ jsxs7(Fragment4, { children: [
6905
7150
  settingsBar,
6906
- /* @__PURE__ */ jsx14(
7151
+ /* @__PURE__ */ jsx15(
6907
7152
  "div",
6908
7153
  {
6909
7154
  "data-ohw-bridge": "",
@@ -6920,7 +7165,7 @@ function MediaOverlay({
6920
7165
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
6921
7166
  },
6922
7167
  onClick: () => onReplace(hover.key),
6923
- children: /* @__PURE__ */ jsxs6(
7168
+ children: /* @__PURE__ */ jsxs7(
6924
7169
  Button,
6925
7170
  {
6926
7171
  "data-ohw-media-overlay": "",
@@ -6938,7 +7183,7 @@ function MediaOverlay({
6938
7183
  onReplace(hover.key);
6939
7184
  },
6940
7185
  children: [
6941
- isVideo ? /* @__PURE__ */ jsx14(Film, { size: 14 }) : /* @__PURE__ */ jsx14(ImageIcon, { size: 14 }),
7186
+ isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
6942
7187
  isVideo ? "Replace video" : "Replace image"
6943
7188
  ]
6944
7189
  }
@@ -6950,7 +7195,7 @@ function MediaOverlay({
6950
7195
 
6951
7196
  // src/ui/CarouselOverlay.tsx
6952
7197
  import { GalleryHorizontal } from "lucide-react";
6953
- import { jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
7198
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
6954
7199
  var OVERLAY_BUTTON_STYLE2 = {
6955
7200
  pointerEvents: "auto",
6956
7201
  fontFamily: "Inter, sans-serif",
@@ -6962,7 +7207,7 @@ function CarouselOverlay({
6962
7207
  onEdit
6963
7208
  }) {
6964
7209
  const { rect } = hover;
6965
- return /* @__PURE__ */ jsx15(
7210
+ return /* @__PURE__ */ jsx16(
6966
7211
  "div",
6967
7212
  {
6968
7213
  "data-ohw-bridge": "",
@@ -6980,7 +7225,7 @@ function CarouselOverlay({
6980
7225
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
6981
7226
  },
6982
7227
  onClick: () => onEdit(hover.key),
6983
- children: /* @__PURE__ */ jsxs7(
7228
+ children: /* @__PURE__ */ jsxs8(
6984
7229
  Button,
6985
7230
  {
6986
7231
  "data-ohw-carousel-overlay": "",
@@ -6994,7 +7239,7 @@ function CarouselOverlay({
6994
7239
  onEdit(hover.key);
6995
7240
  },
6996
7241
  children: [
6997
- /* @__PURE__ */ jsx15(GalleryHorizontal, { size: 14 }),
7242
+ /* @__PURE__ */ jsx16(GalleryHorizontal, { size: 14 }),
6998
7243
  "Edit gallery"
6999
7244
  ]
7000
7245
  }
@@ -7035,13 +7280,9 @@ function parseSectionsFromHtml(html) {
7035
7280
  }
7036
7281
 
7037
7282
  // src/ui/ai-section/AiSectionOverlay.tsx
7038
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
7039
- function findSectionElement(instanceId) {
7040
- const escaped = CSS.escape(instanceId);
7041
- return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7042
- }
7043
- function readRect(instanceId) {
7044
- const el = findSectionElement(instanceId);
7283
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
7284
+ function readRect(sectionId) {
7285
+ const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7045
7286
  if (!el) return null;
7046
7287
  const r2 = el.getBoundingClientRect();
7047
7288
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -7064,7 +7305,7 @@ function useLiveSectionRect(sectionId) {
7064
7305
  const opts = { capture: true, passive: true };
7065
7306
  window.addEventListener("scroll", update, opts);
7066
7307
  window.addEventListener("resize", update);
7067
- const el = findSectionElement(sectionId);
7308
+ const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7068
7309
  const ro = el ? new ResizeObserver(update) : null;
7069
7310
  if (el && ro) ro.observe(el);
7070
7311
  const interval = setInterval(update, 500);
@@ -7077,14 +7318,6 @@ function useLiveSectionRect(sectionId) {
7077
7318
  }, [sectionId]);
7078
7319
  return rect;
7079
7320
  }
7080
- function computeSectionBoundaryFlags(instanceId) {
7081
- const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7082
- (el) => !el.parentElement?.closest("[data-ohw-section]")
7083
- );
7084
- const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7085
- if (index === -1) return { isFirst: true, isLast: true };
7086
- return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7087
- }
7088
7321
  var PRIMARY2 = "#0885FE";
7089
7322
  function edgeAwareRadius(rect) {
7090
7323
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -7103,8 +7336,8 @@ function ReviewButton({
7103
7336
  color
7104
7337
  }) {
7105
7338
  const [hover, setHover] = useState4(false);
7106
- return /* @__PURE__ */ jsxs8("div", { style: { position: "relative" }, children: [
7107
- /* @__PURE__ */ jsx16(
7339
+ return /* @__PURE__ */ jsxs9("div", { style: { position: "relative" }, children: [
7340
+ /* @__PURE__ */ jsx17(
7108
7341
  "button",
7109
7342
  {
7110
7343
  type: "button",
@@ -7128,7 +7361,7 @@ function ReviewButton({
7128
7361
  children
7129
7362
  }
7130
7363
  ),
7131
- hover && /* @__PURE__ */ jsx16(
7364
+ hover && /* @__PURE__ */ jsx17(
7132
7365
  "div",
7133
7366
  {
7134
7367
  style: {
@@ -7158,7 +7391,6 @@ function AiSectionOverlay({
7158
7391
  }) {
7159
7392
  const [selectedId, setSelectedId] = useState4(null);
7160
7393
  const [reviewId, setReviewId] = useState4(null);
7161
- const [reviewButtonsHidden, setReviewButtonsHidden] = useState4(false);
7162
7394
  const reviewIdRef = useRef4(null);
7163
7395
  reviewIdRef.current = reviewId;
7164
7396
  const selectedIdRef = useRef4(null);
@@ -7167,7 +7399,7 @@ function AiSectionOverlay({
7167
7399
  (el) => {
7168
7400
  postToParent2({
7169
7401
  type: "ow:section-selected",
7170
- sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
7402
+ sectionId: el?.dataset.ohwSection ?? null,
7171
7403
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
7172
7404
  });
7173
7405
  },
@@ -7176,7 +7408,7 @@ function AiSectionOverlay({
7176
7408
  const selectFromElement = useCallback2(
7177
7409
  (el, options) => {
7178
7410
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
7179
- const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
7411
+ const id = sectionEl?.dataset.ohwSection ?? null;
7180
7412
  if (id === selectedIdRef.current) return;
7181
7413
  setSelectedId(id);
7182
7414
  if (options?.report !== false) report(sectionEl);
@@ -7217,10 +7449,9 @@ function AiSectionOverlay({
7217
7449
  }
7218
7450
  const found = readRect(sectionId) != null;
7219
7451
  setReviewId(found ? sectionId : null);
7220
- setReviewButtonsHidden(e.data.hideButtons === true);
7221
7452
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
7222
7453
  if (found) {
7223
- document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7454
+ document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7224
7455
  }
7225
7456
  }
7226
7457
  };
@@ -7239,7 +7470,7 @@ function AiSectionOverlay({
7239
7470
  return;
7240
7471
  }
7241
7472
  const sec = t.closest("[data-ohw-section]");
7242
- setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
7473
+ setHoveredId(sec?.dataset.ohwSection ?? null);
7243
7474
  };
7244
7475
  const onLeave = () => setHoveredId(null);
7245
7476
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -7271,31 +7502,11 @@ function AiSectionOverlay({
7271
7502
  },
7272
7503
  [postToParent2]
7273
7504
  );
7274
- const activeSelectionId = reviewId ? null : selectedId;
7275
- const selectionRect = useLiveSectionRect(activeSelectionId);
7505
+ const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7276
7506
  const reviewRect = useLiveSectionRect(reviewId);
7277
7507
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7278
- useEffect4(() => {
7279
- if (!activeSelectionId || !selectionRect) {
7280
- postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7281
- return;
7282
- }
7283
- const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7284
- postToParent2({
7285
- type: "ow:section-rect",
7286
- instanceId: activeSelectionId,
7287
- rect: {
7288
- top: selectionRect.top + window.scrollY,
7289
- left: selectionRect.left + window.scrollX,
7290
- width: selectionRect.width,
7291
- height: selectionRect.height
7292
- },
7293
- isFirst,
7294
- isLast
7295
- });
7296
- }, [activeSelectionId, selectionRect, postToParent2]);
7297
- return /* @__PURE__ */ jsxs8(Fragment5, { children: [
7298
- hoverRect && /* @__PURE__ */ jsx16(
7508
+ return /* @__PURE__ */ jsxs9(Fragment5, { children: [
7509
+ hoverRect && /* @__PURE__ */ jsx17(
7299
7510
  "div",
7300
7511
  {
7301
7512
  "data-ohw-ai-section-hover": "",
@@ -7313,7 +7524,7 @@ function AiSectionOverlay({
7313
7524
  }
7314
7525
  }
7315
7526
  ),
7316
- selectionRect && /* @__PURE__ */ jsx16(
7527
+ selectionRect && /* @__PURE__ */ jsx17(
7317
7528
  "div",
7318
7529
  {
7319
7530
  "data-ohw-ai-section-selected": "",
@@ -7331,7 +7542,7 @@ function AiSectionOverlay({
7331
7542
  }
7332
7543
  }
7333
7544
  ),
7334
- reviewRect && /* @__PURE__ */ jsx16(
7545
+ reviewRect && /* @__PURE__ */ jsx17(
7335
7546
  "div",
7336
7547
  {
7337
7548
  "data-ohw-ai-review": "",
@@ -7344,16 +7555,13 @@ function AiSectionOverlay({
7344
7555
  border: `2px solid ${PRIMARY2}`,
7345
7556
  borderRadius: edgeAwareRadius(reviewRect),
7346
7557
  zIndex: 2147483200,
7347
- // The veil itself: swallows clicks so the section stays locked until decided. This
7348
- // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7349
- // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7350
- // Accept/Discard resolves to the media beneath and opens the file picker.
7558
+ // The veil itself: swallows clicks so the section stays locked until decided.
7351
7559
  background: "rgba(8, 133, 254, 0.04)",
7352
7560
  pointerEvents: "auto",
7353
7561
  cursor: "default"
7354
7562
  },
7355
7563
  onClick: (e) => e.stopPropagation(),
7356
- children: !reviewButtonsHidden && /* @__PURE__ */ jsxs8(
7564
+ children: /* @__PURE__ */ jsxs9(
7357
7565
  "div",
7358
7566
  {
7359
7567
  style: {
@@ -7366,8 +7574,8 @@ function AiSectionOverlay({
7366
7574
  paddingTop: 12
7367
7575
  },
7368
7576
  children: [
7369
- /* @__PURE__ */ jsx16(ReviewButton, { label: "Accept", onClick: () => decide("accept"), background: PRIMARY2, color: "#ffffff", children: /* @__PURE__ */ jsx16(Check, { size: 16, strokeWidth: 2.5, "aria-hidden": true }) }),
7370
- /* @__PURE__ */ jsx16(ReviewButton, { label: "Discard", onClick: () => decide("discard"), background: "#EFF6FF", color: "#0c0a09", children: /* @__PURE__ */ jsx16(X, { size: 16, strokeWidth: 2, "aria-hidden": true }) })
7577
+ /* @__PURE__ */ jsx17(ReviewButton, { label: "Accept", onClick: () => decide("accept"), background: PRIMARY2, color: "#ffffff", children: /* @__PURE__ */ jsx17(Check, { size: 16, strokeWidth: 2.5, "aria-hidden": true }) }),
7578
+ /* @__PURE__ */ jsx17(ReviewButton, { label: "Discard", onClick: () => decide("discard"), background: "#EFF6FF", color: "#0c0a09", children: /* @__PURE__ */ jsx17(X, { size: 16, strokeWidth: 2, "aria-hidden": true }) })
7371
7579
  ]
7372
7580
  }
7373
7581
  )
@@ -7727,22 +7935,22 @@ import { useEffect as useEffect8 } from "react";
7727
7935
  import * as React8 from "react";
7728
7936
  import { Dialog as DialogPrimitive } from "radix-ui";
7729
7937
  import { X as X2 } from "lucide-react";
7730
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
7938
+ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
7731
7939
  function Dialog2({
7732
7940
  ...props
7733
7941
  }) {
7734
- return /* @__PURE__ */ jsx17(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
7942
+ return /* @__PURE__ */ jsx18(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
7735
7943
  }
7736
7944
  function DialogPortal({
7737
7945
  ...props
7738
7946
  }) {
7739
- return /* @__PURE__ */ jsx17(DialogPrimitive.Portal, { ...props });
7947
+ return /* @__PURE__ */ jsx18(DialogPrimitive.Portal, { ...props });
7740
7948
  }
7741
7949
  function DialogOverlay({
7742
7950
  className,
7743
7951
  ...props
7744
7952
  }) {
7745
- return /* @__PURE__ */ jsx17(
7953
+ return /* @__PURE__ */ jsx18(
7746
7954
  DialogPrimitive.Overlay,
7747
7955
  {
7748
7956
  "data-slot": "dialog-overlay",
@@ -7755,9 +7963,9 @@ function DialogOverlay({
7755
7963
  var DialogContent = React8.forwardRef(
7756
7964
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
7757
7965
  const positionMode = container ? "absolute" : "fixed";
7758
- return /* @__PURE__ */ jsxs9(DialogPortal, { container: container ?? void 0, children: [
7759
- /* @__PURE__ */ jsx17(DialogOverlay, { className: cn(positionMode, "inset-0") }),
7760
- /* @__PURE__ */ jsxs9(
7966
+ return /* @__PURE__ */ jsxs10(DialogPortal, { container: container ?? void 0, children: [
7967
+ /* @__PURE__ */ jsx18(DialogOverlay, { className: cn(positionMode, "inset-0") }),
7968
+ /* @__PURE__ */ jsxs10(
7761
7969
  DialogPrimitive.Content,
7762
7970
  {
7763
7971
  ref,
@@ -7773,13 +7981,13 @@ var DialogContent = React8.forwardRef(
7773
7981
  ...props,
7774
7982
  children: [
7775
7983
  children,
7776
- showCloseButton ? /* @__PURE__ */ jsx17(
7984
+ showCloseButton ? /* @__PURE__ */ jsx18(
7777
7985
  DialogPrimitive.Close,
7778
7986
  {
7779
7987
  type: "button",
7780
7988
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
7781
7989
  "aria-label": "Close",
7782
- children: /* @__PURE__ */ jsx17(X2, { size: 16, "aria-hidden": true })
7990
+ children: /* @__PURE__ */ jsx18(X2, { size: 16, "aria-hidden": true })
7783
7991
  }
7784
7992
  ) : null
7785
7993
  ]
@@ -7793,13 +8001,13 @@ function DialogHeader({
7793
8001
  className,
7794
8002
  ...props
7795
8003
  }) {
7796
- return /* @__PURE__ */ jsx17("div", { className: cn("flex flex-col gap-1.5", className), ...props });
8004
+ return /* @__PURE__ */ jsx18("div", { className: cn("flex flex-col gap-1.5", className), ...props });
7797
8005
  }
7798
8006
  function DialogFooter({
7799
8007
  className,
7800
8008
  ...props
7801
8009
  }) {
7802
- return /* @__PURE__ */ jsx17(
8010
+ return /* @__PURE__ */ jsx18(
7803
8011
  "div",
7804
8012
  {
7805
8013
  className: cn("flex items-center justify-end gap-2", className),
@@ -7807,7 +8015,7 @@ function DialogFooter({
7807
8015
  }
7808
8016
  );
7809
8017
  }
7810
- var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx17(
8018
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx18(
7811
8019
  DialogPrimitive.Title,
7812
8020
  {
7813
8021
  ref,
@@ -7819,7 +8027,7 @@ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE
7819
8027
  }
7820
8028
  ));
7821
8029
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
7822
- var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx17(
8030
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx18(
7823
8031
  DialogPrimitive.Description,
7824
8032
  {
7825
8033
  ref,
@@ -7835,19 +8043,19 @@ import { Info, X as X4 } from "lucide-react";
7835
8043
 
7836
8044
  // src/ui/link-modal/DestinationBreadcrumb.tsx
7837
8045
  import { ArrowRight as ArrowRight2, File, GalleryVertical } from "lucide-react";
7838
- import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
8046
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
7839
8047
  function DestinationBreadcrumb({
7840
8048
  pageTitle,
7841
8049
  sectionLabel
7842
8050
  }) {
7843
- return /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-2", children: [
7844
- /* @__PURE__ */ jsx18("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
7845
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-3", children: [
7846
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
7847
- /* @__PURE__ */ jsx18(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
7848
- /* @__PURE__ */ jsx18("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
8051
+ return /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-2", children: [
8052
+ /* @__PURE__ */ jsx19("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
8053
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-3", children: [
8054
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
8055
+ /* @__PURE__ */ jsx19(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
8056
+ /* @__PURE__ */ jsx19("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
7849
8057
  ] }),
7850
- /* @__PURE__ */ jsx18(
8058
+ /* @__PURE__ */ jsx19(
7851
8059
  ArrowRight2,
7852
8060
  {
7853
8061
  size: 16,
@@ -7855,8 +8063,8 @@ function DestinationBreadcrumb({
7855
8063
  "aria-hidden": true
7856
8064
  }
7857
8065
  ),
7858
- /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
7859
- /* @__PURE__ */ jsx18(
8066
+ /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
8067
+ /* @__PURE__ */ jsx19(
7860
8068
  GalleryVertical,
7861
8069
  {
7862
8070
  size: 16,
@@ -7864,7 +8072,7 @@ function DestinationBreadcrumb({
7864
8072
  "aria-hidden": true
7865
8073
  }
7866
8074
  ),
7867
- /* @__PURE__ */ jsx18("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
8075
+ /* @__PURE__ */ jsx19("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
7868
8076
  ] })
7869
8077
  ] })
7870
8078
  ] });
@@ -7872,22 +8080,22 @@ function DestinationBreadcrumb({
7872
8080
 
7873
8081
  // src/ui/link-modal/SectionTreeItem.tsx
7874
8082
  import { GalleryVertical as GalleryVertical2 } from "lucide-react";
7875
- import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
8083
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
7876
8084
  function SectionTreeItem({
7877
8085
  section,
7878
8086
  onSelect,
7879
8087
  selected
7880
8088
  }) {
7881
8089
  const interactive = Boolean(onSelect);
7882
- return /* @__PURE__ */ jsxs11("div", { className: "flex h-9 w-full items-end pl-3", children: [
7883
- /* @__PURE__ */ jsx19(
8090
+ return /* @__PURE__ */ jsxs12("div", { className: "flex h-9 w-full items-end pl-3", children: [
8091
+ /* @__PURE__ */ jsx20(
7884
8092
  "div",
7885
8093
  {
7886
8094
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
7887
8095
  "aria-hidden": true
7888
8096
  }
7889
8097
  ),
7890
- /* @__PURE__ */ jsxs11(
8098
+ /* @__PURE__ */ jsxs12(
7891
8099
  "div",
7892
8100
  {
7893
8101
  role: interactive ? "button" : void 0,
@@ -7905,7 +8113,7 @@ function SectionTreeItem({
7905
8113
  interactive && selected && "border-primary"
7906
8114
  ),
7907
8115
  children: [
7908
- /* @__PURE__ */ jsx19(
8116
+ /* @__PURE__ */ jsx20(
7909
8117
  GalleryVertical2,
7910
8118
  {
7911
8119
  size: 16,
@@ -7913,7 +8121,7 @@ function SectionTreeItem({
7913
8121
  "aria-hidden": true
7914
8122
  }
7915
8123
  ),
7916
- /* @__PURE__ */ jsx19("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
8124
+ /* @__PURE__ */ jsx20("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
7917
8125
  ]
7918
8126
  }
7919
8127
  )
@@ -7925,10 +8133,10 @@ import { useEffect as useEffect5, useId as useId2, useRef as useRef5, useState a
7925
8133
 
7926
8134
  // src/ui/input.tsx
7927
8135
  import * as React9 from "react";
7928
- import { jsx as jsx20 } from "react/jsx-runtime";
8136
+ import { jsx as jsx21 } from "react/jsx-runtime";
7929
8137
  var Input = React9.forwardRef(
7930
8138
  ({ className, type, ...props }, ref) => {
7931
- return /* @__PURE__ */ jsx20(
8139
+ return /* @__PURE__ */ jsx21(
7932
8140
  "input",
7933
8141
  {
7934
8142
  type,
@@ -7947,9 +8155,9 @@ Input.displayName = "Input";
7947
8155
 
7948
8156
  // src/ui/label.tsx
7949
8157
  import { Label as LabelPrimitive } from "radix-ui";
7950
- import { jsx as jsx21 } from "react/jsx-runtime";
8158
+ import { jsx as jsx22 } from "react/jsx-runtime";
7951
8159
  function Label({ className, ...props }) {
7952
- return /* @__PURE__ */ jsx21(
8160
+ return /* @__PURE__ */ jsx22(
7953
8161
  LabelPrimitive.Root,
7954
8162
  {
7955
8163
  "data-slot": "label",
@@ -7960,12 +8168,12 @@ function Label({ className, ...props }) {
7960
8168
  }
7961
8169
 
7962
8170
  // src/ui/link-modal/UrlOrPageInput.tsx
7963
- import { ChevronDown as ChevronDown2, File as File2, X as X3 } from "lucide-react";
7964
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
8171
+ import { ChevronDown as ChevronDown3, File as File2, X as X3 } from "lucide-react";
8172
+ import { jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
7965
8173
  function FieldChevron({
7966
8174
  onClick
7967
8175
  }) {
7968
- return /* @__PURE__ */ jsx22(
8176
+ return /* @__PURE__ */ jsx23(
7969
8177
  "button",
7970
8178
  {
7971
8179
  type: "button",
@@ -7973,7 +8181,7 @@ function FieldChevron({
7973
8181
  onClick,
7974
8182
  "aria-label": "Open page list",
7975
8183
  tabIndex: -1,
7976
- children: /* @__PURE__ */ jsx22(ChevronDown2, { size: 16 })
8184
+ children: /* @__PURE__ */ jsx23(ChevronDown3, { size: 16 })
7977
8185
  }
7978
8186
  );
7979
8187
  }
@@ -8034,11 +8242,11 @@ function UrlOrPageInput({
8034
8242
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
8035
8243
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
8036
8244
  );
8037
- return /* @__PURE__ */ jsxs12("div", { className: "flex w-full flex-col gap-2 p-0", children: [
8038
- /* @__PURE__ */ jsx22(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
8039
- /* @__PURE__ */ jsxs12("div", { ref: rootRef, className: "relative w-full", children: [
8040
- /* @__PURE__ */ jsxs12("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
8041
- selectedPage ? /* @__PURE__ */ jsx22("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx22(
8245
+ return /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-2 p-0", children: [
8246
+ /* @__PURE__ */ jsx23(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
8247
+ /* @__PURE__ */ jsxs13("div", { ref: rootRef, className: "relative w-full", children: [
8248
+ /* @__PURE__ */ jsxs13("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
8249
+ selectedPage ? /* @__PURE__ */ jsx23("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx23(
8042
8250
  File2,
8043
8251
  {
8044
8252
  size: 16,
@@ -8046,7 +8254,7 @@ function UrlOrPageInput({
8046
8254
  "aria-hidden": true
8047
8255
  }
8048
8256
  ) }) : null,
8049
- readOnly ? /* @__PURE__ */ jsx22("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx22(
8257
+ readOnly ? /* @__PURE__ */ jsx23("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx23(
8050
8258
  Input,
8051
8259
  {
8052
8260
  ref: inputRef,
@@ -8072,7 +8280,7 @@ function UrlOrPageInput({
8072
8280
  )
8073
8281
  }
8074
8282
  ),
8075
- selectedPage && !readOnly ? /* @__PURE__ */ jsx22(
8283
+ selectedPage && !readOnly ? /* @__PURE__ */ jsx23(
8076
8284
  "button",
8077
8285
  {
8078
8286
  type: "button",
@@ -8080,26 +8288,26 @@ function UrlOrPageInput({
8080
8288
  onMouseDown: clearSelection,
8081
8289
  "aria-label": "Clear selected page",
8082
8290
  tabIndex: -1,
8083
- children: /* @__PURE__ */ jsx22(X3, { size: 16, "aria-hidden": true })
8291
+ children: /* @__PURE__ */ jsx23(X3, { size: 16, "aria-hidden": true })
8084
8292
  }
8085
8293
  ) : null,
8086
- !readOnly ? /* @__PURE__ */ jsx22(FieldChevron, { onClick: toggleDropdown }) : null
8294
+ !readOnly ? /* @__PURE__ */ jsx23(FieldChevron, { onClick: toggleDropdown }) : null
8087
8295
  ] }),
8088
- dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx22(
8296
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx23(
8089
8297
  "div",
8090
8298
  {
8091
8299
  "data-ohw-link-page-dropdown": "",
8092
8300
  className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
8093
8301
  onMouseDown: (e) => e.preventDefault(),
8094
- children: filteredPages.map((page) => /* @__PURE__ */ jsxs12(
8302
+ children: filteredPages.map((page) => /* @__PURE__ */ jsxs13(
8095
8303
  "button",
8096
8304
  {
8097
8305
  type: "button",
8098
8306
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
8099
8307
  onClick: () => onPageSelect(page),
8100
8308
  children: [
8101
- /* @__PURE__ */ jsx22(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
8102
- /* @__PURE__ */ jsx22("span", { className: "truncate", children: page.title })
8309
+ /* @__PURE__ */ jsx23(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
8310
+ /* @__PURE__ */ jsx23("span", { className: "truncate", children: page.title })
8103
8311
  ]
8104
8312
  },
8105
8313
  page.path
@@ -8107,34 +8315,34 @@ function UrlOrPageInput({
8107
8315
  }
8108
8316
  ) : null
8109
8317
  ] }),
8110
- urlError ? /* @__PURE__ */ jsx22("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
8318
+ urlError ? /* @__PURE__ */ jsx23("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
8111
8319
  ] });
8112
8320
  }
8113
8321
 
8114
8322
  // src/ui/link-modal/LinkEditorPanel.tsx
8115
- import { Fragment as Fragment6, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
8323
+ import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
8116
8324
  function LinkEditorPanel({ state, onClose }) {
8117
8325
  const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
8118
- return /* @__PURE__ */ jsxs13(Fragment6, { children: [
8119
- /* @__PURE__ */ jsx23(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx23(
8326
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
8327
+ /* @__PURE__ */ jsx24(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx24(
8120
8328
  "button",
8121
8329
  {
8122
8330
  type: "button",
8123
8331
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
8124
8332
  "aria-label": "Close",
8125
8333
  onClick: onClose,
8126
- children: /* @__PURE__ */ jsx23(X4, { size: 16, "aria-hidden": true })
8334
+ children: /* @__PURE__ */ jsx24(X4, { size: 16, "aria-hidden": true })
8127
8335
  }
8128
8336
  ) }),
8129
- /* @__PURE__ */ jsx23(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx23(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
8130
- /* @__PURE__ */ jsxs13("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
8131
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx23(
8337
+ /* @__PURE__ */ jsx24(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx24(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
8338
+ /* @__PURE__ */ jsxs14("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
8339
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx24(
8132
8340
  DestinationBreadcrumb,
8133
8341
  {
8134
8342
  pageTitle: state.selectedPage.title,
8135
8343
  sectionLabel: state.selectedSection.label
8136
8344
  }
8137
- ) : /* @__PURE__ */ jsx23(
8345
+ ) : /* @__PURE__ */ jsx24(
8138
8346
  UrlOrPageInput,
8139
8347
  {
8140
8348
  value: state.searchValue,
@@ -8147,8 +8355,8 @@ function LinkEditorPanel({ state, onClose }) {
8147
8355
  urlError: state.urlError
8148
8356
  }
8149
8357
  ),
8150
- state.showChooseSection ? /* @__PURE__ */ jsxs13("div", { className: "flex flex-col justify-center gap-2", children: [
8151
- /* @__PURE__ */ jsx23(
8358
+ state.showChooseSection ? /* @__PURE__ */ jsxs14("div", { className: "flex flex-col justify-center gap-2", children: [
8359
+ /* @__PURE__ */ jsx24(
8152
8360
  Button,
8153
8361
  {
8154
8362
  type: "button",
@@ -8159,15 +8367,15 @@ function LinkEditorPanel({ state, onClose }) {
8159
8367
  children: "Choose a section"
8160
8368
  }
8161
8369
  ),
8162
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
8163
- /* @__PURE__ */ jsx23(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
8164
- /* @__PURE__ */ jsx23("span", { children: "Pick a section this link should scroll to." })
8370
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
8371
+ /* @__PURE__ */ jsx24(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
8372
+ /* @__PURE__ */ jsx24("span", { children: "Pick a section this link should scroll to." })
8165
8373
  ] })
8166
8374
  ] }) : null,
8167
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx23(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
8375
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx24(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
8168
8376
  ] }),
8169
- /* @__PURE__ */ jsxs13(DialogFooter, { className: "w-full px-6 pb-6", children: [
8170
- /* @__PURE__ */ jsx23(
8377
+ /* @__PURE__ */ jsxs14(DialogFooter, { className: "w-full px-6 pb-6", children: [
8378
+ /* @__PURE__ */ jsx24(
8171
8379
  Button,
8172
8380
  {
8173
8381
  type: "button",
@@ -8182,7 +8390,7 @@ function LinkEditorPanel({ state, onClose }) {
8182
8390
  children: state.secondaryLabel
8183
8391
  }
8184
8392
  ),
8185
- /* @__PURE__ */ jsx23(
8393
+ /* @__PURE__ */ jsx24(
8186
8394
  Button,
8187
8395
  {
8188
8396
  type: "button",
@@ -8205,7 +8413,7 @@ import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMem
8205
8413
  import { createPortal } from "react-dom";
8206
8414
  import { ArrowLeft as ArrowLeft2, Check as Check2 } from "lucide-react";
8207
8415
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
8208
- import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
8416
+ import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
8209
8417
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
8210
8418
  function rectsEqual(a, b) {
8211
8419
  if (a.size !== b.size) return false;
@@ -8434,7 +8642,7 @@ function SectionPickerOverlay({
8434
8642
  const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
8435
8643
  if (!portalRoot) return null;
8436
8644
  return createPortal(
8437
- /* @__PURE__ */ jsxs14(
8645
+ /* @__PURE__ */ jsxs15(
8438
8646
  "div",
8439
8647
  {
8440
8648
  "data-ohw-section-picker": "",
@@ -8444,12 +8652,12 @@ function SectionPickerOverlay({
8444
8652
  role: "dialog",
8445
8653
  "aria-label": "Choose a section",
8446
8654
  children: [
8447
- /* @__PURE__ */ jsx24(
8655
+ /* @__PURE__ */ jsx25(
8448
8656
  "div",
8449
8657
  {
8450
8658
  className: "pointer-events-auto fixed left-5 z-[2]",
8451
8659
  style: { top: chromeClip.top + 20 },
8452
- children: /* @__PURE__ */ jsxs14(
8660
+ children: /* @__PURE__ */ jsxs15(
8453
8661
  Button,
8454
8662
  {
8455
8663
  type: "button",
@@ -8458,14 +8666,14 @@ function SectionPickerOverlay({
8458
8666
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
8459
8667
  onClick: onBack,
8460
8668
  children: [
8461
- /* @__PURE__ */ jsx24(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
8669
+ /* @__PURE__ */ jsx25(ArrowLeft2, { className: "size-4 shrink-0", "aria-hidden": true }),
8462
8670
  "Back"
8463
8671
  ]
8464
8672
  }
8465
8673
  )
8466
8674
  }
8467
8675
  ),
8468
- /* @__PURE__ */ jsx24(
8676
+ /* @__PURE__ */ jsx25(
8469
8677
  "div",
8470
8678
  {
8471
8679
  className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
@@ -8478,7 +8686,7 @@ function SectionPickerOverlay({
8478
8686
  children: "Click on section to select"
8479
8687
  }
8480
8688
  ),
8481
- !isOnTargetPage ? /* @__PURE__ */ jsx24(
8689
+ !isOnTargetPage ? /* @__PURE__ */ jsx25(
8482
8690
  "div",
8483
8691
  {
8484
8692
  className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md px-3 py-2 text-sm text-muted-foreground shadow-sm",
@@ -8486,14 +8694,14 @@ function SectionPickerOverlay({
8486
8694
  children: "Loading page preview\u2026"
8487
8695
  }
8488
8696
  ) : null,
8489
- isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx24("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx24("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
8697
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx25("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx25("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
8490
8698
  isOnTargetPage ? liveSections.map((section) => {
8491
8699
  const rect = rects.get(section.id);
8492
8700
  if (!rect || rect.width <= 0 || rect.height <= 0) return null;
8493
8701
  const isSelected = selectedId === section.id;
8494
8702
  const isHovered = hoveredId === section.id;
8495
8703
  const isLit = isSelected || isHovered;
8496
- return /* @__PURE__ */ jsxs14(
8704
+ return /* @__PURE__ */ jsxs15(
8497
8705
  "button",
8498
8706
  {
8499
8707
  type: "button",
@@ -8508,7 +8716,7 @@ function SectionPickerOverlay({
8508
8716
  "aria-label": `Select section ${section.label}`,
8509
8717
  onClick: () => handleSelect(section),
8510
8718
  children: [
8511
- isLit ? /* @__PURE__ */ jsx24(
8719
+ isLit ? /* @__PURE__ */ jsx25(
8512
8720
  "span",
8513
8721
  {
8514
8722
  className: "pointer-events-none absolute",
@@ -8521,13 +8729,13 @@ function SectionPickerOverlay({
8521
8729
  "aria-hidden": true
8522
8730
  }
8523
8731
  ) : null,
8524
- isSelected ? /* @__PURE__ */ jsx24(
8732
+ isSelected ? /* @__PURE__ */ jsx25(
8525
8733
  "span",
8526
8734
  {
8527
8735
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
8528
8736
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
8529
8737
  "aria-hidden": true,
8530
- children: /* @__PURE__ */ jsx24(Check2, { className: "size-5" })
8738
+ children: /* @__PURE__ */ jsx25(Check2, { className: "size-5" })
8531
8739
  }
8532
8740
  ) : null
8533
8741
  ]
@@ -8707,7 +8915,7 @@ function useLinkModalState({
8707
8915
  }
8708
8916
 
8709
8917
  // src/ui/link-modal/LinkPopover.tsx
8710
- import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
8918
+ import { Fragment as Fragment7, jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
8711
8919
  function postToParent(data) {
8712
8920
  window.parent?.postMessage(data, "*");
8713
8921
  }
@@ -8803,15 +9011,15 @@ function LinkPopover({
8803
9011
  );
8804
9012
  };
8805
9013
  }, [open, sectionPickerActive]);
8806
- return /* @__PURE__ */ jsxs15(Fragment7, { children: [
8807
- /* @__PURE__ */ jsx25(
9014
+ return /* @__PURE__ */ jsxs16(Fragment7, { children: [
9015
+ /* @__PURE__ */ jsx26(
8808
9016
  Dialog2,
8809
9017
  {
8810
9018
  open: open && !sectionPickerActive,
8811
9019
  onOpenChange: (next) => {
8812
9020
  if (!next) onClose?.();
8813
9021
  },
8814
- children: /* @__PURE__ */ jsx25(
9022
+ children: /* @__PURE__ */ jsx26(
8815
9023
  DialogContent,
8816
9024
  {
8817
9025
  ref: panelRef,
@@ -8821,12 +9029,12 @@ function LinkPopover({
8821
9029
  "data-ohw-bridge": "",
8822
9030
  showCloseButton: false,
8823
9031
  className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
8824
- children: /* @__PURE__ */ jsx25(LinkEditorPanel, { state, onClose })
9032
+ children: /* @__PURE__ */ jsx26(LinkEditorPanel, { state, onClose })
8825
9033
  }
8826
9034
  )
8827
9035
  }
8828
9036
  ),
8829
- sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx25(
9037
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx26(
8830
9038
  SectionPickerOverlay,
8831
9039
  {
8832
9040
  pagePath: state.selectedPage.path,
@@ -10052,13 +10260,14 @@ function listSocialItems(row) {
10052
10260
  return isSocialItem(anchor) ? anchor : null;
10053
10261
  }).filter((item) => item !== null);
10054
10262
  }
10055
- function socialRowUnit(item) {
10056
- const row = findSocialsRow(item);
10263
+ function socialRowUnit(item, knownRow) {
10264
+ const row = knownRow ?? findSocialsRow(item);
10265
+ if (!row || !row.contains(item)) return null;
10057
10266
  let node = item;
10058
10267
  while (node.parentElement && node.parentElement !== row) {
10059
10268
  node = node.parentElement;
10060
10269
  }
10061
- return node;
10270
+ return node.parentElement === row ? node : null;
10062
10271
  }
10063
10272
  function listSocialsRows(root = document) {
10064
10273
  const rows = /* @__PURE__ */ new Set();
@@ -10077,7 +10286,8 @@ function markSocialsRows(root = document) {
10077
10286
  listSocialsRows(root).forEach((row) => {
10078
10287
  row.setAttribute(SOCIALS_ROW_ATTR, "");
10079
10288
  const items = listSocialItems(row);
10080
- if (items[0]) rowTemplates.set(rowKeyOf(row), socialRowUnit(items[0]).outerHTML);
10289
+ const firstUnit = items[0] ? socialRowUnit(items[0], row) : null;
10290
+ if (firstUnit) rowTemplates.set(rowKeyOf(row), firstUnit.outerHTML);
10081
10291
  items.forEach((item, index) => {
10082
10292
  item.setAttribute(SOCIALS_ITEM_ATTR, String(index));
10083
10293
  const iconKey = socialIconKey(item);
@@ -10221,7 +10431,8 @@ function removeSocialItem(item, content) {
10221
10431
  const previousContent = Object.fromEntries(
10222
10432
  removedKeys.filter((key) => key in content).map((key) => [key, content[key]])
10223
10433
  );
10224
- const unit = socialRowUnit(item);
10434
+ const unit = socialRowUnit(item, row);
10435
+ if (!unit) return null;
10225
10436
  const nextSibling = unit.nextElementSibling;
10226
10437
  unit.remove();
10227
10438
  markSocialsRows(row.ownerDocument);
@@ -10244,7 +10455,9 @@ function applySocialsOrder(order, root = document) {
10244
10455
  const byKey = new Map(listSocialItems(row).map((item) => [socialHrefKey(item), item]));
10245
10456
  wanted.forEach((key) => {
10246
10457
  const item = byKey.get(key);
10247
- if (item) row.appendChild(socialRowUnit(item));
10458
+ if (!item) return;
10459
+ const unit = socialRowUnit(item, row);
10460
+ if (unit) row.appendChild(unit);
10248
10461
  });
10249
10462
  });
10250
10463
  markSocialsRows(root);
@@ -10274,7 +10487,7 @@ function reconcileSocialsFromContent(content, root = document) {
10274
10487
  });
10275
10488
  if (surviving.length) {
10276
10489
  present.forEach((item) => {
10277
- if (!surviving.includes(item)) socialRowUnit(item).remove();
10490
+ if (!surviving.includes(item)) socialRowUnit(item)?.remove();
10278
10491
  });
10279
10492
  }
10280
10493
  });
@@ -11253,296 +11466,42 @@ function deleteFooterColumn(column) {
11253
11466
  };
11254
11467
  }
11255
11468
 
11256
- // src/lib/logo-identity.ts
11257
- var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11258
- var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11259
- var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11260
- var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11261
- var LOGO_ALT_KEY = "logo-alt";
11262
- var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11263
- var PLACEHOLDER_BUSINESS_NAME = "Business name";
11264
- function resolveLogoDisplayText(text) {
11265
- const trimmed = (text ?? "").trim();
11266
- return trimmed || PLACEHOLDER_BUSINESS_NAME;
11267
- }
11268
- function isFooterLogoRoot(root) {
11269
- return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11270
- }
11271
- function imageKeyForRoot(root) {
11272
- return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11273
- }
11274
- function textKeyForRoot(root) {
11275
- return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11276
- }
11277
- function ensureLogoHrefKey(root) {
11278
- if (!(root instanceof HTMLAnchorElement)) return;
11279
- if (root.hasAttribute("data-ohw-href-key")) return;
11280
- root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11281
- }
11282
- function applyLogoIdentity(text, isPlaceholder) {
11283
- const display = resolveLogoDisplayText(text);
11284
- const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11285
- for (const key of LOGO_TEXT_KEYS) {
11286
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11287
- if (el.textContent !== display) el.textContent = display;
11288
- });
11289
- }
11290
- for (const key of LOGO_IMAGE_KEYS) {
11291
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11292
- const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11293
- if (img) img.alt = display;
11469
+ // src/lib/add-footer-column.ts
11470
+ function buildFooterColumnEditContentPatch(result) {
11471
+ return {
11472
+ [result.headingKey]: result.heading,
11473
+ [result.hrefKey]: result.href,
11474
+ [result.labelKey]: result.label,
11475
+ [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
11476
+ };
11477
+ }
11478
+ function addFooterColumnWithPersist({
11479
+ postToParent: postToParent2
11480
+ }) {
11481
+ if (!canAddFooterColumn()) {
11482
+ postToParent2({
11483
+ type: "ow:toast",
11484
+ title: `Maximum ${MAX_FOOTER_COLUMNS} columns`,
11485
+ toastType: "error"
11294
11486
  });
11487
+ return null;
11295
11488
  }
11296
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11297
- if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11298
- else el.removeAttribute("data-ohw-placeholder");
11299
- });
11300
- return display;
11301
- }
11302
- function applyLogoImage(url, alt) {
11303
- const displayAlt = resolveLogoDisplayText(alt);
11304
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11305
- ensureLogoHrefKey(root);
11306
- const imageKey = imageKeyForRoot(root);
11307
- const textKey = textKeyForRoot(root);
11308
- let img = root.querySelector(`img[data-ohw-key="${imageKey}"]`) ?? (root.querySelector(`[data-ohw-key="${imageKey}"]`) instanceof HTMLImageElement ? root.querySelector(`[data-ohw-key="${imageKey}"]`) : null) ?? root.querySelector("img");
11309
- let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11310
- if (url) {
11311
- if (!img) {
11312
- img = document.createElement("img");
11313
- img.setAttribute("data-ohw-editable", "image");
11314
- img.setAttribute("data-ohw-key", imageKey);
11315
- img.alt = displayAlt;
11316
- img.style.height = "";
11317
- img.style.maxHeight = "none";
11318
- img.style.width = "auto";
11319
- img.style.display = "block";
11320
- img.style.objectFit = "contain";
11321
- root.insertBefore(img, root.firstChild);
11322
- } else {
11323
- img.setAttribute("data-ohw-editable", "image");
11324
- img.setAttribute("data-ohw-key", imageKey);
11325
- }
11326
- img.removeAttribute("srcset");
11327
- img.removeAttribute("sizes");
11328
- img.src = url;
11329
- img.alt = displayAlt;
11330
- img.style.display = "block";
11331
- if (textEl) textEl.style.display = "none";
11332
- root.removeAttribute("data-ohw-placeholder");
11333
- return;
11334
- }
11335
- if (img) {
11336
- img.removeAttribute("src");
11337
- img.removeAttribute("srcset");
11338
- img.removeAttribute("sizes");
11339
- img.alt = displayAlt;
11340
- img.style.display = "none";
11341
- }
11342
- if (!textEl) {
11343
- textEl = document.createElement("span");
11344
- textEl.setAttribute("data-ohw-editable", "plain");
11345
- textEl.setAttribute("data-ohw-key", textKey);
11346
- root.appendChild(textEl);
11347
- }
11348
- textEl.style.display = "";
11349
- if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11350
- if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11351
- root.setAttribute("data-ohw-placeholder", "");
11352
- } else {
11353
- root.removeAttribute("data-ohw-placeholder");
11354
- }
11355
- });
11356
- }
11357
- function applyLogoHref(href) {
11358
- const target = href.trim() || "/";
11359
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11360
- ensureLogoHrefKey(root);
11361
- if (root instanceof HTMLAnchorElement) {
11362
- root.setAttribute("href", target);
11363
- }
11364
- });
11365
- for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11366
- }
11367
- function readLogoIdentityFromDom() {
11368
- let imageUrl = null;
11369
- for (const key of LOGO_IMAGE_KEYS) {
11370
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
11371
- const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11372
- const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11373
- if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11374
- imageUrl = img.currentSrc || img.src;
11375
- break;
11376
- }
11377
- }
11378
- let text = PLACEHOLDER_BUSINESS_NAME;
11379
- let isPlaceholder = true;
11380
- for (const key of LOGO_TEXT_KEYS) {
11381
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
11382
- if (el?.textContent?.trim()) {
11383
- text = el.textContent.trim();
11384
- const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11385
- isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11386
- break;
11387
- }
11388
- }
11389
- if (imageUrl) {
11390
- const logoImg = document.querySelector(
11391
- '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11392
- );
11393
- const alt = logoImg?.alt?.trim() || text;
11394
- isPlaceholder = false;
11395
- const hrefEl = document.querySelector(
11396
- 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11397
- );
11398
- const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11399
- return { text, isPlaceholder, imageUrl, href: href2, alt };
11400
- }
11401
- const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11402
- const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11403
- return { text, isPlaceholder, imageUrl: null, href, alt: text };
11404
- }
11405
- function applyLogoFromContent(content) {
11406
- const hasLogoIdentity = LOGO_PLACEHOLDER_KEY in content || LOGO_TEXT_KEYS.some((key) => key in content) || LOGO_IMAGE_KEYS.some((key) => key in content) || LOGO_ALT_KEY in content || LOGO_HREF_KEYS.some((key) => key in content);
11407
- if (!hasLogoIdentity) return false;
11408
- const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11409
- const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11410
- const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11411
- const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11412
- const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11413
- const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11414
- if (logoImageUrl) {
11415
- applyLogoImage(logoImageUrl, logoAlt);
11416
- } else {
11417
- if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11418
- applyLogoIdentity(logoText, logoIsPlaceholder);
11419
- }
11420
- const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11421
- if (typeof logoHref === "string" && logoHref.trim()) {
11422
- applyLogoHref(logoHref);
11423
- }
11424
- return true;
11425
- }
11426
-
11427
- // src/lib/logo-size.ts
11428
- var LOGO_SIZE_DEFAULTS = {
11429
- navbar: 28,
11430
- footer: 32
11431
- };
11432
- var LOGO_SIZE_MIN = 16;
11433
- var LOGO_SIZE_MAX = 80;
11434
- var LOGO_SIZE_DESKTOP_KEYS = {
11435
- navbar: "nav-logo-size",
11436
- footer: "footer-logo-size"
11437
- };
11438
- var LOGO_SIZE_MOBILE_KEYS = {
11439
- navbar: "nav-logo-size-mobile",
11440
- footer: "footer-logo-size-mobile"
11441
- };
11442
- var LOGO_SIZE_KEYS = [
11443
- LOGO_SIZE_DESKTOP_KEYS.navbar,
11444
- LOGO_SIZE_DESKTOP_KEYS.footer,
11445
- LOGO_SIZE_MOBILE_KEYS.navbar,
11446
- LOGO_SIZE_MOBILE_KEYS.footer
11447
- ];
11448
- function isFooterLogoRoot2(root) {
11449
- return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11450
- }
11451
- function getLogoPlacement(root) {
11452
- return isFooterLogoRoot2(root) ? "footer" : "navbar";
11453
- }
11454
- function parseLogoSizePx(raw, fallback) {
11455
- if (raw == null || raw === "") return fallback;
11456
- const n = Number.parseFloat(raw);
11457
- if (!Number.isFinite(n)) return fallback;
11458
- return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11459
- }
11460
- function isMobileLogoSizeFollowing(content, placement) {
11461
- const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11462
- return raw == null || raw.trim() === "";
11463
- }
11464
- function resolveDesktopLogoSize(content, placement) {
11465
- return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11466
- }
11467
- function resolveMobileLogoSize(content, placement) {
11468
- if (isMobileLogoSizeFollowing(content, placement)) {
11469
- return resolveDesktopLogoSize(content, placement);
11470
- }
11471
- return parseLogoSizePx(
11472
- content[LOGO_SIZE_MOBILE_KEYS[placement]],
11473
- resolveDesktopLogoSize(content, placement)
11474
- );
11475
- }
11476
- function setRootSizeVars(root, desktopPx, mobilePx, following) {
11477
- root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11478
- if (following) {
11479
- root.style.removeProperty("--ohw-logo-size-mobile");
11480
- } else {
11481
- root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11482
- }
11483
- root.querySelectorAll("img").forEach((img) => {
11484
- img.style.height = "";
11485
- img.style.maxHeight = "none";
11486
- img.style.width = "auto";
11487
- img.style.objectFit = "contain";
11488
- });
11489
- }
11490
- function applyLogoSizes(content) {
11491
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11492
- const placement = getLogoPlacement(root);
11493
- const desktop = resolveDesktopLogoSize(content, placement);
11494
- const following = isMobileLogoSizeFollowing(content, placement);
11495
- const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11496
- setRootSizeVars(root, desktop, mobile, following);
11497
- });
11498
- }
11499
- function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11500
- document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11501
- if (getLogoPlacement(root) !== placement) return;
11502
- setRootSizeVars(root, desktopPx, mobilePx, following);
11489
+ const result = insertFooterColumn();
11490
+ const patch = buildFooterColumnEditContentPatch(result);
11491
+ setStoredLinkHref(result.hrefKey, result.href);
11492
+ postToParent2({
11493
+ type: "ow:change",
11494
+ nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
11503
11495
  });
11504
- }
11505
- function logoHasUploadedImage(logoEl) {
11506
- if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11507
- const img = logoEl.querySelector('img[data-ohw-key="nav-logo-image"], img[data-ohw-key="footer-logo"], img[data-ohw-key="footer-logo-image"]') ?? logoEl.querySelector("img");
11508
- if (!img) return false;
11509
- const src = img.getAttribute("src")?.trim() ?? "";
11510
- if (!src || src.startsWith("data:")) return false;
11511
- if (img.style.display === "none") return false;
11512
- return true;
11513
- }
11514
- function getLogoInteractionRect(logoEl) {
11515
- if (logoHasUploadedImage(logoEl)) {
11516
- const img = logoEl.querySelector('img[data-ohw-key="nav-logo-image"], img[data-ohw-key="footer-logo"], img[data-ohw-key="footer-logo-image"]') ?? logoEl.querySelector("img");
11517
- if (img) {
11518
- const r2 = img.getBoundingClientRect();
11519
- if (r2.width > 0 && r2.height > 0) return r2;
11520
- }
11521
- }
11522
- const text = logoEl.querySelector(
11523
- '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11524
- );
11525
- if (text) {
11526
- const style = window.getComputedStyle(text);
11527
- if (style.display !== "none" && style.visibility !== "hidden") {
11528
- const r2 = text.getBoundingClientRect();
11529
- if (r2.width > 0 && r2.height > 0) return r2;
11530
- }
11531
- }
11532
- return logoEl.getBoundingClientRect();
11533
- }
11534
- function readLogoSizeState(content, placement) {
11535
- const desktopPx = resolveDesktopLogoSize(content, placement);
11536
- const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11537
- const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11538
- return { desktopPx, mobilePx, mobileFollowing };
11496
+ postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
11497
+ enforceLinkHrefs();
11498
+ return result;
11539
11499
  }
11540
11500
 
11541
11501
  // src/lib/site-wide-scope.ts
11542
11502
  function getLogoElement(el) {
11543
11503
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11544
11504
  if (marked) return marked;
11545
- if (el.closest('[data-ohw-editable="icon"]')) return null;
11546
11505
  const root = el.closest("nav, [data-ohw-nav-root], footer");
11547
11506
  if (!root) return null;
11548
11507
  const anchor = el.closest("a");
@@ -11576,38 +11535,6 @@ function isSiteWideScopeActive(args) {
11576
11535
  return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11577
11536
  }
11578
11537
 
11579
- // src/lib/add-footer-column.ts
11580
- function buildFooterColumnEditContentPatch(result) {
11581
- return {
11582
- [result.headingKey]: result.heading,
11583
- [result.hrefKey]: result.href,
11584
- [result.labelKey]: result.label,
11585
- [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
11586
- };
11587
- }
11588
- function addFooterColumnWithPersist({
11589
- postToParent: postToParent2
11590
- }) {
11591
- if (!canAddFooterColumn()) {
11592
- postToParent2({
11593
- type: "ow:toast",
11594
- title: `Maximum ${MAX_FOOTER_COLUMNS} columns`,
11595
- toastType: "error"
11596
- });
11597
- return null;
11598
- }
11599
- const result = insertFooterColumn();
11600
- const patch = buildFooterColumnEditContentPatch(result);
11601
- setStoredLinkHref(result.hrefKey, result.href);
11602
- postToParent2({
11603
- type: "ow:change",
11604
- nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
11605
- });
11606
- postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
11607
- enforceLinkHrefs();
11608
- return result;
11609
- }
11610
-
11611
11538
  // src/ui/FloatingPanel.tsx
11612
11539
  import {
11613
11540
  useCallback as useCallback5,
@@ -11617,7 +11544,7 @@ import {
11617
11544
  useState as useState8
11618
11545
  } from "react";
11619
11546
  import { X as X5 } from "lucide-react";
11620
- import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
11547
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
11621
11548
  var PANEL_WIDTH = 256;
11622
11549
  var EDGE_MARGIN = 16;
11623
11550
  function getVisibleClip(parentScroll) {
@@ -11732,7 +11659,7 @@ function FloatingPanel({
11732
11659
  }, [open]);
11733
11660
  useEffect9(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
11734
11661
  if (!open) return null;
11735
- return /* @__PURE__ */ jsxs16(
11662
+ return /* @__PURE__ */ jsxs17(
11736
11663
  "div",
11737
11664
  {
11738
11665
  ref: panelRef,
@@ -11749,7 +11676,7 @@ function FloatingPanel({
11749
11676
  onPointerDown: (e) => e.stopPropagation(),
11750
11677
  onClick: (e) => e.stopPropagation(),
11751
11678
  children: [
11752
- /* @__PURE__ */ jsxs16(
11679
+ /* @__PURE__ */ jsxs17(
11753
11680
  "div",
11754
11681
  {
11755
11682
  "data-ohw-floating-panel-header": "",
@@ -11759,14 +11686,14 @@ function FloatingPanel({
11759
11686
  onPointerUp: endDrag,
11760
11687
  onPointerCancel: endDrag,
11761
11688
  children: [
11762
- /* @__PURE__ */ jsxs16("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
11763
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", children: [
11764
- icon ? /* @__PURE__ */ jsx26("span", { className: "shrink-0 text-foreground", children: icon }) : null,
11765
- /* @__PURE__ */ jsx26("p", { className: "min-w-0 flex-1 text-lg font-semibold leading-7 text-foreground", children: title })
11689
+ /* @__PURE__ */ jsxs17("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
11690
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
11691
+ icon ? /* @__PURE__ */ jsx27("span", { className: "shrink-0 text-foreground", children: icon }) : null,
11692
+ /* @__PURE__ */ jsx27("p", { className: "min-w-0 flex-1 text-lg font-semibold leading-7 text-foreground", children: title })
11766
11693
  ] }),
11767
- context ? /* @__PURE__ */ jsx26("p", { className: "w-full text-sm leading-5 text-muted-foreground", children: context }) : null
11694
+ context ? /* @__PURE__ */ jsx27("p", { className: "w-full text-sm leading-5 text-muted-foreground", children: context }) : null
11768
11695
  ] }),
11769
- /* @__PURE__ */ jsx26(
11696
+ /* @__PURE__ */ jsx27(
11770
11697
  "button",
11771
11698
  {
11772
11699
  type: "button",
@@ -11778,13 +11705,13 @@ function FloatingPanel({
11778
11705
  onClose();
11779
11706
  },
11780
11707
  onPointerDown: (e) => e.stopPropagation(),
11781
- children: /* @__PURE__ */ jsx26(X5, { size: 16, "aria-hidden": true })
11708
+ children: /* @__PURE__ */ jsx27(X5, { size: 16, "aria-hidden": true })
11782
11709
  }
11783
11710
  )
11784
11711
  ]
11785
11712
  }
11786
11713
  ),
11787
- /* @__PURE__ */ jsx26(
11714
+ /* @__PURE__ */ jsx27(
11788
11715
  "div",
11789
11716
  {
11790
11717
  "data-ohw-floating-panel-body": "",
@@ -11797,117 +11724,6 @@ function FloatingPanel({
11797
11724
  );
11798
11725
  }
11799
11726
 
11800
- // src/ui/logo-size-panel.tsx
11801
- import { ArrowUpRight, Link as Link2 } from "lucide-react";
11802
- import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
11803
- function SizeSlider({
11804
- value,
11805
- onChange
11806
- }) {
11807
- const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11808
- return /* @__PURE__ */ jsxs17("div", { className: "flex w-full flex-col gap-3", children: [
11809
- /* @__PURE__ */ jsxs17("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11810
- /* @__PURE__ */ jsx27("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11811
- /* @__PURE__ */ jsxs17("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11812
- value,
11813
- " px"
11814
- ] })
11815
- ] }),
11816
- /* @__PURE__ */ jsxs17("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11817
- /* @__PURE__ */ jsx27(
11818
- "div",
11819
- {
11820
- className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11821
- style: { width: `${pct}%` }
11822
- }
11823
- ),
11824
- /* @__PURE__ */ jsx27(
11825
- "input",
11826
- {
11827
- type: "range",
11828
- min: LOGO_SIZE_MIN,
11829
- max: LOGO_SIZE_MAX,
11830
- step: 1,
11831
- value,
11832
- "aria-label": "Logo size",
11833
- className: cn(
11834
- "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11835
- "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11836
- "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11837
- "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11838
- "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11839
- "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11840
- "[&::-moz-range-thumb]:bg-background"
11841
- ),
11842
- onChange: (e) => onChange(Number(e.target.value))
11843
- }
11844
- )
11845
- ] })
11846
- ] });
11847
- }
11848
- function LogoSizePanel({
11849
- viewport,
11850
- sizePx,
11851
- mobileFollowing = true,
11852
- onSizeChange,
11853
- onCustomizeMobile,
11854
- onResetMobile,
11855
- onUpdateEverywhere,
11856
- className
11857
- }) {
11858
- const showFollowing = viewport === "mobile" && mobileFollowing;
11859
- const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11860
- const showDesktopSlider = viewport === "desktop";
11861
- return /* @__PURE__ */ jsxs17("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11862
- showFollowing ? /* @__PURE__ */ jsxs17("div", { className: "flex w-full flex-col gap-2", children: [
11863
- /* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-1", children: [
11864
- /* @__PURE__ */ jsx27(Link2, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11865
- /* @__PURE__ */ jsx27("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11866
- ] }),
11867
- /* @__PURE__ */ jsx27("p", { className: "text-sm leading-5 text-muted-foreground", children: "Mobile uses the desktop size until you customize it. Change the desktop size and it follows automatically." }),
11868
- /* @__PURE__ */ jsx27(
11869
- Button,
11870
- {
11871
- type: "button",
11872
- variant: "outline",
11873
- size: "sm",
11874
- className: "h-9 w-full min-w-0 cursor-pointer",
11875
- onClick: onCustomizeMobile,
11876
- children: "Customize for mobile"
11877
- }
11878
- )
11879
- ] }) : null,
11880
- showDesktopSlider || showMobileSlider ? /* @__PURE__ */ jsx27(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11881
- showMobileSlider ? /* @__PURE__ */ jsx27(
11882
- Button,
11883
- {
11884
- type: "button",
11885
- variant: "outline",
11886
- size: "sm",
11887
- className: "h-9 w-full min-w-0 cursor-pointer",
11888
- onClick: onResetMobile,
11889
- children: "Reset to desktop size"
11890
- }
11891
- ) : null,
11892
- /* @__PURE__ */ jsx27("div", { className: "h-px w-full bg-border", role: "separator" }),
11893
- /* @__PURE__ */ jsxs17(
11894
- Button,
11895
- {
11896
- type: "button",
11897
- variant: "outline",
11898
- size: "sm",
11899
- className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11900
- onClick: onUpdateEverywhere,
11901
- children: [
11902
- "Update logo everywhere",
11903
- /* @__PURE__ */ jsx27(ArrowUpRight, { size: 16, "aria-hidden": true })
11904
- ]
11905
- }
11906
- ),
11907
- /* @__PURE__ */ jsx27("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11908
- ] });
11909
- }
11910
-
11911
11727
  // src/ui/socials-display-panel.tsx
11912
11728
  import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
11913
11729
  function DisplaySwitch({
@@ -12676,8 +12492,10 @@ function getLinkHref3(el) {
12676
12492
  }
12677
12493
  function collectEditableNodes(extraContent, root = document) {
12678
12494
  const isScoped = root !== document;
12679
- const editableEls = Array.from(root.querySelectorAll("[data-ohw-editable]"));
12680
- if (isScoped && root instanceof HTMLElement && root.matches("[data-ohw-editable]")) {
12495
+ const editableEls = Array.from(
12496
+ root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
12497
+ );
12498
+ if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
12681
12499
  editableEls.unshift(root);
12682
12500
  }
12683
12501
  const nodes = editableEls.map((el) => {
@@ -12752,18 +12570,6 @@ function collectEditableNodes(extraContent, root = document) {
12752
12570
  }
12753
12571
  if (extraContent && !isScoped) {
12754
12572
  applyNavFooterDeleteOverrides(byKey, extraContent);
12755
- for (const key of LOGO_IMAGE_KEYS) {
12756
- if (!(key in extraContent)) continue;
12757
- byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12758
- }
12759
- for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12760
- if (!(key in extraContent)) continue;
12761
- byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12762
- }
12763
- for (const key of LOGO_SIZE_KEYS) {
12764
- if (!(key in extraContent)) continue;
12765
- byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12766
- }
12767
12573
  }
12768
12574
  return Array.from(byKey.values());
12769
12575
  }
@@ -13137,7 +12943,7 @@ function Badge({ className, variant, ...props }) {
13137
12943
  }
13138
12944
 
13139
12945
  // src/OhhwellsBridge.tsx
13140
- import { Link as Link3 } from "lucide-react";
12946
+ import { Link as Link2, Plus as Plus4, Settings } from "lucide-react";
13141
12947
  import { Fragment as Fragment8, jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
13142
12948
  var PRIMARY3 = "#0885FE";
13143
12949
  var IMAGE_FADE_MS = 300;
@@ -13232,10 +13038,21 @@ function parseSchedulingInsertAfter(insertAfter) {
13232
13038
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
13233
13039
  };
13234
13040
  }
13235
- function resolveEntryAnchor(entry) {
13236
- if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13237
- const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13238
- return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
13041
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
13042
+ const parsed = parseSchedulingInsertAfter(insertAfter);
13043
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
13044
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
13045
+ return { effectiveInsertAfter, insertBefore };
13046
+ }
13047
+ function getSchedulingMountPoint(insertAfter) {
13048
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
13049
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
13050
+ if (!anchorEl && anchor === "scheduling") {
13051
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
13052
+ anchorEl = widgets.at(-1) ?? null;
13053
+ }
13054
+ if (!anchorEl) return null;
13055
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13239
13056
  }
13240
13057
  function schedulingMountDepth(insertAfter) {
13241
13058
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -13252,7 +13069,8 @@ function getPageSchedulingEntries(raw) {
13252
13069
  }
13253
13070
  }
13254
13071
  function isSchedulingWidgetMissing(entry) {
13255
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
13072
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
13073
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13256
13074
  }
13257
13075
  function hasMissingSchedulingWidgets(entries) {
13258
13076
  return entries.some(isSchedulingWidgetMissing);
@@ -13282,17 +13100,16 @@ function initSectionsFromContent(content, removeExisting = false) {
13282
13100
  } catch {
13283
13101
  }
13284
13102
  }
13285
- function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13286
- const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13287
- const sectionId = schedulingSectionId(widgetId);
13103
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
13104
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
13105
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
13288
13106
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
13289
- const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13290
- if (!anchorEl) return false;
13291
- const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13107
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
13108
+ if (!mountPoint) return false;
13292
13109
  const container = document.createElement("div");
13293
13110
  container.dataset.ohwSectionContainer = "scheduling";
13294
- if (beforeId) {
13295
- const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
13111
+ if (insertBefore) {
13112
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13296
13113
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
13297
13114
  if (!beforePoint) return false;
13298
13115
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -13303,25 +13120,19 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
13303
13120
  }
13304
13121
  tail.insertAdjacentElement("afterend", container);
13305
13122
  }
13306
- try {
13307
- const root = createRoot2(container);
13308
- flushSync2(() => {
13309
- root.render(
13310
- /* @__PURE__ */ jsx33(
13311
- SchedulingWidget,
13312
- {
13313
- notifyOnConnect,
13314
- initialScheduleId: scheduleId,
13315
- insertAfter: widgetId
13316
- }
13317
- )
13318
- );
13319
- });
13320
- } catch (err) {
13321
- console.error("[ow:scheduling] render threw", err);
13322
- container.remove();
13323
- return false;
13324
- }
13123
+ const root = createRoot2(container);
13124
+ flushSync2(() => {
13125
+ root.render(
13126
+ /* @__PURE__ */ jsx33(
13127
+ SchedulingWidget,
13128
+ {
13129
+ notifyOnConnect,
13130
+ initialScheduleId: scheduleId,
13131
+ insertAfter: effectiveInsertAfter
13132
+ }
13133
+ )
13134
+ );
13135
+ });
13325
13136
  const tracker = getSectionsTracker();
13326
13137
  let sections = [];
13327
13138
  try {
@@ -13329,12 +13140,10 @@ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, be
13329
13140
  } catch {
13330
13141
  }
13331
13142
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
13332
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
13143
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13333
13144
  sections.push({
13334
13145
  type: "scheduling",
13335
- insertAfter: widgetId,
13336
- anchorId,
13337
- beforeId: beforeId ?? null,
13146
+ insertAfter: effectiveInsertAfter,
13338
13147
  pagePath: window.location.pathname,
13339
13148
  ...scheduleId ? { scheduleId } : {}
13340
13149
  });
@@ -13348,8 +13157,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
13348
13157
  for (let i = pending.length - 1; i >= 0; i--) {
13349
13158
  const entry = pending[i];
13350
13159
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
13351
- const { anchorId, beforeId } = resolveEntryAnchor(entry);
13352
- if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
13160
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13353
13161
  pending.splice(i, 1);
13354
13162
  }
13355
13163
  }
@@ -13422,7 +13230,11 @@ function isIconEditable(el) {
13422
13230
  return el.dataset.ohwEditable === "icon";
13423
13231
  }
13424
13232
  var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
13425
- var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"])';
13233
+ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data-ohw-field-toolbar], [data-ohw-item-toolbar], [data-ohw-more-menu], [data-ohw-state-toggle], [data-ohw-max-badge], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]';
13234
+ function isOverEditorChrome(x, y) {
13235
+ return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
13236
+ }
13237
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"])';
13426
13238
  function getVideoEl2(el) {
13427
13239
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
13428
13240
  }
@@ -13493,13 +13305,6 @@ function isInsideLinkEditor(target) {
13493
13305
  target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest("[data-ohw-more-menu]") || target.closest('[data-slot="dropdown-menu-content"]') || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
13494
13306
  );
13495
13307
  }
13496
- function isInsideFloatingPanel(target) {
13497
- return Boolean(target.closest("[data-ohw-floating-panel]"));
13498
- }
13499
- function isPointOverFloatingPanel(clientX, clientY) {
13500
- const el = document.elementFromPoint(clientX, clientY);
13501
- return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13502
- }
13503
13308
  function getHrefKeyFromElement(el) {
13504
13309
  if (!el) return null;
13505
13310
  const anchor = el.closest("[data-ohw-href-key]");
@@ -13547,7 +13352,8 @@ function isNavItemPointerTarget(el) {
13547
13352
  function getNavigationItemAnchor(el) {
13548
13353
  const anchor = el.matches("[data-ohw-href-key]") ? el : el.closest("[data-ohw-href-key]");
13549
13354
  if (!anchor) return null;
13550
- if (!anchor.querySelector('[data-ohw-editable="text"]') && !getSocialItem(anchor)) return null;
13355
+ if (!anchor.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') && !anchor.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') && !getSocialItem(anchor))
13356
+ return null;
13551
13357
  if (!isNavItemPointerTarget(anchor)) return null;
13552
13358
  return anchor;
13553
13359
  }
@@ -13737,7 +13543,7 @@ function getNavigationSelectionParent(el) {
13737
13543
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
13738
13544
  return getFooterLinksContainer();
13739
13545
  }
13740
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
13546
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13741
13547
  return getNavigationRoot(el);
13742
13548
  }
13743
13549
  return null;
@@ -14294,7 +14100,7 @@ function FloatingToolbar({
14294
14100
  e.preventDefault();
14295
14101
  e.stopPropagation();
14296
14102
  },
14297
- children: /* @__PURE__ */ jsx33(Link3, { className: "size-4 shrink-0", "aria-hidden": true })
14103
+ children: /* @__PURE__ */ jsx33(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
14298
14104
  }
14299
14105
  ) : null
14300
14106
  ] })
@@ -14330,7 +14136,6 @@ function StateToggle({
14330
14136
  );
14331
14137
  }
14332
14138
  var contentCache = /* @__PURE__ */ new Map();
14333
- var fetchedContentPaths = /* @__PURE__ */ new Set();
14334
14139
  function resolveSubdomain(subdomainFromQuery) {
14335
14140
  if (subdomainFromQuery) return subdomainFromQuery;
14336
14141
  if (typeof window !== "undefined") {
@@ -14425,14 +14230,8 @@ function OhhwellsBridge() {
14425
14230
  });
14426
14231
  const selectFrameRef = useRef9(() => {
14427
14232
  });
14428
- const selectLogoRef = useRef9(() => {
14429
- });
14430
- const openLogoSizePanelRef = useRef9(() => {
14431
- });
14432
14233
  const deselectRef = useRef9(() => {
14433
14234
  });
14434
- const closeFloatingPanelOnlyRef = useRef9(() => {
14435
- });
14436
14235
  const reselectNavigationItemRef = useRef9(() => {
14437
14236
  });
14438
14237
  const commitNavigationTextEditRef = useRef9(() => {
@@ -14448,6 +14247,182 @@ function OhhwellsBridge() {
14448
14247
  const sectionsLoadedRef = useRef9(false);
14449
14248
  const pendingScheduleConfigRequests = useRef9([]);
14450
14249
  const [toolbarRect, setToolbarRect] = useState11(null);
14250
+ const [formPickRect, setFormPickRect] = useState11(null);
14251
+ const formPickElRef = useRef9(null);
14252
+ const [formViewState, setFormViewStateUi] = useState11("default");
14253
+ const [formPickCount, setFormPickCount] = useState11(null);
14254
+ const [formHoverRect, setFormHoverRect] = useState11(null);
14255
+ const formHoverElRef = useRef9(null);
14256
+ const [fieldPickRect, setFieldPickRect] = useState11(null);
14257
+ const fieldPickElRef = useRef9(null);
14258
+ const [fieldPickState, setFieldPickState] = useState11(null);
14259
+ const [fieldTypePickerOpen, setFieldTypePickerOpen] = useState11(false);
14260
+ const clearFormPick = useCallback7(() => {
14261
+ const form = formPickElRef.current;
14262
+ const editing = fieldPickElRef.current;
14263
+ if (commitPlaceholderEdit(editing) && editing) {
14264
+ const owner = editing.closest('[data-ohw-editable="form"]');
14265
+ if (owner) persistFieldsRef.current(owner);
14266
+ }
14267
+ if (form) {
14268
+ const key = formKeyOf(form);
14269
+ if (key) setFormViewState(form, key, "default", successInitialFor(form, key, editContentRef.current));
14270
+ }
14271
+ setFormViewStateUi("default");
14272
+ setFormPickCount(null);
14273
+ setFieldTypePickerOpen(false);
14274
+ fieldPickElRef.current = null;
14275
+ setFieldPickRect(null);
14276
+ setFieldPickState(null);
14277
+ formPickElRef.current = null;
14278
+ setFormPickRect(null);
14279
+ }, []);
14280
+ const clearFieldPick = useCallback7(() => {
14281
+ const wrapper = fieldPickElRef.current;
14282
+ if (commitPlaceholderEdit(wrapper) && wrapper) {
14283
+ const form = wrapper.closest('[data-ohw-editable="form"]');
14284
+ if (form) persistFieldsRef.current(form);
14285
+ }
14286
+ fieldPickElRef.current = null;
14287
+ setFieldPickRect(null);
14288
+ setFieldPickState(null);
14289
+ }, []);
14290
+ const persistFieldsRef = useRef9(() => {
14291
+ });
14292
+ const persistFields = useCallback7(
14293
+ (form) => {
14294
+ const key = formKeyOf(form);
14295
+ if (!key) return;
14296
+ const json = JSON.stringify(readFieldsFromDom(form));
14297
+ editContentRef.current = { ...editContentRef.current, [fieldsKey(key)]: json };
14298
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: fieldsKey(key), text: json }] });
14299
+ },
14300
+ []
14301
+ );
14302
+ persistFieldsRef.current = persistFields;
14303
+ const selectField = useCallback7((wrapper) => {
14304
+ if (fieldPickElRef.current && fieldPickElRef.current !== wrapper) {
14305
+ commitPlaceholderEdit(fieldPickElRef.current);
14306
+ }
14307
+ syncRequiredMark(wrapper);
14308
+ beginPlaceholderEdit(wrapper);
14309
+ formHoverElRef.current = null;
14310
+ setFormHoverRect(null);
14311
+ fieldPickElRef.current = wrapper;
14312
+ setFieldPickRect(wrapper.getBoundingClientRect());
14313
+ setFieldPickState({ type: fieldTypeOf(wrapper), required: isFieldRequired(wrapper) });
14314
+ setFieldTypePickerOpen(false);
14315
+ }, []);
14316
+ const withSelectedField = useCallback7(
14317
+ (run) => {
14318
+ const wrapper = fieldPickElRef.current;
14319
+ const form = formPickElRef.current;
14320
+ if (!wrapper || !form) return;
14321
+ commitPlaceholderEdit(wrapper);
14322
+ run(form, wrapper);
14323
+ persistFields(form);
14324
+ beginPlaceholderEdit(wrapper);
14325
+ setFormPickRect(form.getBoundingClientRect());
14326
+ },
14327
+ [persistFields]
14328
+ );
14329
+ const handleFieldTypeChange = useCallback7(
14330
+ (type) => withSelectedField((_form, wrapper) => {
14331
+ applyFieldType(wrapper, type);
14332
+ selectField(wrapper);
14333
+ }),
14334
+ [selectField, withSelectedField]
14335
+ );
14336
+ const handleFieldRequiredToggle = useCallback7(
14337
+ () => withSelectedField((_form, wrapper) => {
14338
+ setFieldRequired(wrapper, !isFieldRequired(wrapper));
14339
+ selectField(wrapper);
14340
+ }),
14341
+ [selectField, withSelectedField]
14342
+ );
14343
+ const handleFieldDuplicate = useCallback7(
14344
+ () => withSelectedField((form, wrapper) => {
14345
+ const copy = duplicateField(form, wrapper);
14346
+ selectField(copy);
14347
+ }),
14348
+ [selectField, withSelectedField]
14349
+ );
14350
+ const handleFieldDelete = useCallback7(
14351
+ () => withSelectedField((_form, wrapper) => {
14352
+ removeField(wrapper);
14353
+ clearFieldPick();
14354
+ postToParentRef.current({ type: "ow:toast", title: "Form field deleted" });
14355
+ }),
14356
+ [clearFieldPick, withSelectedField]
14357
+ );
14358
+ const handleAddField = useCallback7(
14359
+ (type) => {
14360
+ const form = formPickElRef.current;
14361
+ if (!form) return;
14362
+ const wrapper = insertField(form, type);
14363
+ setFieldTypePickerOpen(false);
14364
+ if (!wrapper) return;
14365
+ persistFields(form);
14366
+ setFormPickRect(null);
14367
+ requestAnimationFrame(() => {
14368
+ selectField(wrapper);
14369
+ wrapper.scrollIntoView({ block: "nearest", behavior: "smooth" });
14370
+ });
14371
+ },
14372
+ [persistFields, selectField]
14373
+ );
14374
+ const fieldDragRef = useRef9(null);
14375
+ const buildFieldDropSlots = useCallback7((form, draggedKey) => {
14376
+ const others = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== draggedKey);
14377
+ const slots = others.map((el) => {
14378
+ const rect = el.getBoundingClientRect();
14379
+ return { top: rect.top, left: rect.left, width: rect.width };
14380
+ });
14381
+ const last = others[others.length - 1];
14382
+ if (last) {
14383
+ const rect = last.getBoundingClientRect();
14384
+ slots.push({ top: rect.bottom, left: rect.left, width: rect.width });
14385
+ }
14386
+ return slots;
14387
+ }, []);
14388
+ const handleFieldDragStart = useCallback7(() => {
14389
+ const wrapper = fieldPickElRef.current;
14390
+ const form = formPickElRef.current;
14391
+ if (!wrapper || !form) return;
14392
+ const key = fieldKeyOf(wrapper);
14393
+ fieldDragRef.current = { key, form };
14394
+ setFieldDragging(true);
14395
+ setFieldDropSlots(buildFieldDropSlots(form, key));
14396
+ }, [buildFieldDropSlots]);
14397
+ const handleFieldDragEnd = useCallback7(() => {
14398
+ fieldDragRef.current = null;
14399
+ setFieldDropIndex(null);
14400
+ setFieldDropSlots([]);
14401
+ setFieldDragging(false);
14402
+ }, []);
14403
+ const [fieldDropIndex, setFieldDropIndex] = useState11(null);
14404
+ const [fieldDropSlots, setFieldDropSlots] = useState11([]);
14405
+ const [fieldDragging, setFieldDragging] = useState11(false);
14406
+ const clearFormPickRef = useRef9(clearFormPick);
14407
+ clearFormPickRef.current = clearFormPick;
14408
+ useEffect12(() => {
14409
+ const el = fieldPickElRef.current;
14410
+ if (!el || fieldPickRect === null) return;
14411
+ const observer = new ResizeObserver(() => {
14412
+ if (fieldPickElRef.current === el) setFieldPickRect(el.getBoundingClientRect());
14413
+ });
14414
+ observer.observe(el);
14415
+ return () => observer.disconnect();
14416
+ }, [fieldPickRect !== null, fieldPickState]);
14417
+ useEffect12(() => {
14418
+ const el = formPickElRef.current;
14419
+ if (!el || formPickRect === null) return;
14420
+ const observer = new ResizeObserver(() => {
14421
+ if (formPickElRef.current === el) setFormPickRect(el.getBoundingClientRect());
14422
+ });
14423
+ observer.observe(el);
14424
+ return () => observer.disconnect();
14425
+ }, [formPickRect !== null, formViewState]);
14451
14426
  const [toolbarVariant, setToolbarVariant] = useState11("none");
14452
14427
  const toolbarVariantRef = useRef9("none");
14453
14428
  toolbarVariantRef.current = toolbarVariant;
@@ -14469,7 +14444,7 @@ function OhhwellsBridge() {
14469
14444
  useEffect12(() => {
14470
14445
  const sync = () => {
14471
14446
  const el = document.querySelector(
14472
- "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
14447
+ '[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]):not([data-ohw-editable="form"] *)'
14473
14448
  );
14474
14449
  const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
14475
14450
  if (!target) {
@@ -14493,6 +14468,11 @@ function OhhwellsBridge() {
14493
14468
  const [isItemDragging, setIsItemDragging] = useState11(false);
14494
14469
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState11(false);
14495
14470
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
14471
+ const [floatingPanel, setFloatingPanel] = useState11(null);
14472
+ const floatingPanelOpenRef = useRef9(false);
14473
+ floatingPanelOpenRef.current = floatingPanel !== null;
14474
+ const [floatingPanelPos, setFloatingPanelPos] = useState11(null);
14475
+ const [parentScrollSnap, setParentScrollSnap] = useState11(null);
14496
14476
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState11(null);
14497
14477
  const [footerHeadingVisible, setFooterHeadingVisible] = useState11(null);
14498
14478
  const footerDragRef = useRef9(null);
@@ -14507,16 +14487,7 @@ function OhhwellsBridge() {
14507
14487
  const addNavAfterAnchorRef = useRef9(null);
14508
14488
  const editContentRef = useRef9({});
14509
14489
  const aiSectionsRef = useRef9("");
14510
- const brandKitRef = useRef9("");
14511
- const stylesRef = useRef9("");
14512
14490
  const pendingDeleteUndoRef = useRef9(null);
14513
- const [floatingPanel, setFloatingPanel] = useState11(null);
14514
- const floatingPanelOpenRef = useRef9(false);
14515
- const setFloatingPanelRef = useRef9(setFloatingPanel);
14516
- const [floatingPanelPos, setFloatingPanelPos] = useState11(null);
14517
- const [logoSizeDraft, setLogoSizeDraft] = useState11(null);
14518
- const [editorViewport, setEditorViewport] = useState11("desktop");
14519
- const [parentScrollSnap, setParentScrollSnap] = useState11(null);
14520
14491
  const [sitePages, setSitePages] = useState11([]);
14521
14492
  const [sectionsByPath, setSectionsByPath] = useState11({});
14522
14493
  const sectionsPrefetchGenRef = useRef9(0);
@@ -14525,18 +14496,7 @@ function OhhwellsBridge() {
14525
14496
  const linkPopoverOpenRef = useRef9(false);
14526
14497
  const linkPopoverGraceUntilRef = useRef9(0);
14527
14498
  setLinkPopoverRef.current = setLinkPopover;
14528
- setFloatingPanelRef.current = setFloatingPanel;
14529
14499
  linkPopoverSessionRef.current = linkPopover;
14530
- floatingPanelOpenRef.current = Boolean(floatingPanel);
14531
- useEffect12(() => {
14532
- const syncViewport = () => {
14533
- const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14534
- setEditorViewport((prev) => prev === next ? prev : next);
14535
- };
14536
- syncViewport();
14537
- window.addEventListener("resize", syncViewport);
14538
- return () => window.removeEventListener("resize", syncViewport);
14539
- }, []);
14540
14500
  const {
14541
14501
  navDragRef,
14542
14502
  navDropSlots,
@@ -14705,7 +14665,9 @@ function OhhwellsBridge() {
14705
14665
  const deactivate = useCallback7(() => {
14706
14666
  const el = activeElRef.current;
14707
14667
  if (!el) return;
14708
- const key = el.dataset.ohwKey;
14668
+ const isFormBlock = el.dataset.ohwEditable === "form";
14669
+ const isFormControl = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;
14670
+ const key = isFormBlock || isFormControl ? void 0 : el.dataset.ohwKey;
14709
14671
  if (key) {
14710
14672
  const timer = autoSaveTimers.current.get(key);
14711
14673
  if (timer !== void 0) {
@@ -14722,6 +14684,12 @@ function OhhwellsBridge() {
14722
14684
  }
14723
14685
  el.removeAttribute("contenteditable");
14724
14686
  el.removeAttribute("data-ohw-editing");
14687
+ const labelField = getFieldWrapper(el);
14688
+ if (labelField) {
14689
+ syncRequiredMark(labelField);
14690
+ const owner = labelField.closest('[data-ohw-editable="form"]');
14691
+ if (owner) persistFieldsRef.current(owner);
14692
+ }
14725
14693
  activeElRef.current = null;
14726
14694
  setNavGroupForceOpen(null, false);
14727
14695
  setReorderHrefKey(null);
@@ -14761,8 +14729,6 @@ function OhhwellsBridge() {
14761
14729
  setHoveredNavContainerRect(null);
14762
14730
  hoveredItemElRef.current = null;
14763
14731
  setHoveredItemRect(null);
14764
- setFloatingPanel(null);
14765
- setLogoSizeDraft(null);
14766
14732
  if (!activeElRef.current) {
14767
14733
  setNavGroupForceOpen(null, false);
14768
14734
  setToolbarRect(null);
@@ -14877,6 +14843,12 @@ function OhhwellsBridge() {
14877
14843
  }
14878
14844
  el.removeAttribute("contenteditable");
14879
14845
  el.removeAttribute("data-ohw-editing");
14846
+ const labelField = getFieldWrapper(el);
14847
+ if (labelField) {
14848
+ syncRequiredMark(labelField);
14849
+ const owner = labelField.closest('[data-ohw-editable="form"]');
14850
+ if (owner) persistFieldsRef.current(owner);
14851
+ }
14880
14852
  activeElRef.current = null;
14881
14853
  setMaxBadge(null);
14882
14854
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -15468,8 +15440,6 @@ function OhhwellsBridge() {
15468
15440
  setToolbarRect(anchor.getBoundingClientRect());
15469
15441
  setToolbarShowEditLink(false);
15470
15442
  setActiveCommands(/* @__PURE__ */ new Set());
15471
- setFloatingPanel(null);
15472
- setLogoSizeDraft(null);
15473
15443
  }, [deactivate, markSelected]);
15474
15444
  const selectFrame = useCallback7((el) => {
15475
15445
  if (!isNavigationContainer(el)) return;
@@ -15519,51 +15489,7 @@ function OhhwellsBridge() {
15519
15489
  setToolbarRect(el.getBoundingClientRect());
15520
15490
  setToolbarShowEditLink(false);
15521
15491
  setActiveCommands(/* @__PURE__ */ new Set());
15522
- setFloatingPanel(null);
15523
- setLogoSizeDraft(null);
15524
15492
  }, [deactivate, markSelected, postToParent2]);
15525
- const selectLogo = useCallback7(
15526
- (logoEl) => {
15527
- if (activeElRef.current) deactivate();
15528
- selectedElRef.current = logoEl;
15529
- selectedHrefKeyRef.current = null;
15530
- selectedFooterColAttrRef.current = null;
15531
- markSelected(logoEl);
15532
- setSelectedIsCta(false);
15533
- setSelectedIsSocial(false);
15534
- setSelectedIsSocialsRow(false);
15535
- clearHrefKeyHover(logoEl);
15536
- hoveredNavContainerRef.current = null;
15537
- setHoveredNavContainerRect(null);
15538
- setHoveredItemRect(null);
15539
- hoveredItemElRef.current = null;
15540
- siblingHintElRef.current = null;
15541
- setSiblingHintRect(null);
15542
- setSiblingHintRects([]);
15543
- setIsItemDragging(false);
15544
- setReorderHrefKey(null);
15545
- setReorderDragDisabled(false);
15546
- setIsFooterFrameSelection(false);
15547
- setToolbarVariant("logo");
15548
- setToolbarRect(getLogoInteractionRect(logoEl));
15549
- setToolbarShowEditLink(false);
15550
- setActiveCommands(/* @__PURE__ */ new Set());
15551
- },
15552
- [deactivate, markSelected]
15553
- );
15554
- const openLogoSizePanel = useCallback7((logoEl) => {
15555
- const placement = getLogoPlacement(logoEl);
15556
- const draft = readLogoSizeState(editContentRef.current, placement);
15557
- setLogoSizeDraft(draft);
15558
- setParentScrollSnap(parentScrollRef.current);
15559
- setFloatingPanel({
15560
- key: `logo-size:${placement}`,
15561
- title: "Logo",
15562
- context: placement === "navbar" ? "Navbar" : "Footer",
15563
- kind: "logo-size",
15564
- placement
15565
- });
15566
- }, []);
15567
15493
  const openSocialsDisplayPanel = useCallback7((row) => {
15568
15494
  setParentScrollSnap(parentScrollRef.current);
15569
15495
  setFloatingPanel({
@@ -15599,53 +15525,13 @@ function OhhwellsBridge() {
15599
15525
  );
15600
15526
  const closeFloatingPanelOnly = useCallback7(() => {
15601
15527
  setFloatingPanel(null);
15602
- setLogoSizeDraft(null);
15603
15528
  }, []);
15529
+ const closeFloatingPanelOnlyRef = useRef9(closeFloatingPanelOnly);
15530
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15604
15531
  const closeFloatingPanelAndDeselect = useCallback7(() => {
15605
15532
  setFloatingPanel(null);
15606
- setLogoSizeDraft(null);
15607
15533
  deselectRef.current();
15608
15534
  }, []);
15609
- const persistLogoSizeDraft = useCallback7(
15610
- (placement, draft) => {
15611
- const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15612
- const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15613
- const nodes = [
15614
- { key: desktopKey, text: String(draft.desktopPx) }
15615
- ];
15616
- if (draft.mobileFollowing) {
15617
- nodes.push({ key: mobileKey, text: "" });
15618
- } else {
15619
- nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15620
- }
15621
- editContentRef.current = {
15622
- ...editContentRef.current,
15623
- [desktopKey]: String(draft.desktopPx),
15624
- [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15625
- };
15626
- applyLogoSizeToPlacement(
15627
- placement,
15628
- draft.desktopPx,
15629
- draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15630
- draft.mobileFollowing
15631
- );
15632
- postToParent2({ type: "ow:change", nodes });
15633
- requestAnimationFrame(() => {
15634
- const selected = selectedElRef.current;
15635
- if (!selected || toolbarVariantRef.current !== "logo") return;
15636
- const rect = getLogoInteractionRect(selected);
15637
- setToolbarRect(rect);
15638
- if (glowElRef.current) {
15639
- const GAP = SELECTION_CHROME_GAP2;
15640
- glowElRef.current.style.top = `${rect.top - GAP}px`;
15641
- glowElRef.current.style.left = `${rect.left - GAP}px`;
15642
- glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15643
- glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15644
- }
15645
- });
15646
- },
15647
- [postToParent2]
15648
- );
15649
15535
  const activate = useCallback7((el, options) => {
15650
15536
  if (activeElRef.current === el) return;
15651
15537
  if (isIconEditable(el)) return;
@@ -15726,10 +15612,7 @@ function OhhwellsBridge() {
15726
15612
  deactivateRef.current = deactivate;
15727
15613
  selectRef.current = select;
15728
15614
  selectFrameRef.current = selectFrame;
15729
- selectLogoRef.current = selectLogo;
15730
- openLogoSizePanelRef.current = openLogoSizePanel;
15731
15615
  deselectRef.current = deselect;
15732
- closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15733
15616
  const lastSiteWideScopeRef = useRef9(null);
15734
15617
  useEffect12(() => {
15735
15618
  if (!isEditMode) {
@@ -15768,23 +15651,9 @@ function OhhwellsBridge() {
15768
15651
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
15769
15652
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
15770
15653
  }
15771
- if (typeof content[BRAND_KIT_KEY] === "string") {
15772
- brandKitRef.current = content[BRAND_KIT_KEY];
15773
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15774
- }
15775
- if (typeof content[STYLE_STORE_KEY] === "string") {
15776
- stylesRef.current = content[STYLE_STORE_KEY];
15777
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15778
- }
15779
- applyBrandChrome(content);
15780
15654
  for (const [key, val] of Object.entries(content)) {
15781
15655
  if (key === "__ohw_sections") continue;
15782
15656
  if (key === AI_SECTIONS_KEY) continue;
15783
- if (key === LOGO_PLACEHOLDER_KEY) continue;
15784
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
15785
- if (key === BRAND_KIT_KEY) continue;
15786
- if (key === STYLE_STORE_KEY) continue;
15787
- if (BRAND_CHROME_KEYS.has(key)) continue;
15788
15657
  if (applyVideoSettingNode(key, val)) continue;
15789
15658
  if (applyCarouselNode(key, val)) continue;
15790
15659
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -15813,14 +15682,13 @@ function OhhwellsBridge() {
15813
15682
  applyLinkHref(el, val);
15814
15683
  } else if (el.dataset.ohwEditable === "icon") {
15815
15684
  applyIconMarkup(el, val);
15685
+ } else if (el.dataset.ohwEditable === "form") {
15816
15686
  } else if (el.innerHTML !== val) {
15817
15687
  el.innerHTML = val;
15818
15688
  }
15819
15689
  });
15820
15690
  applyLinkByKey(key, val);
15821
15691
  }
15822
- applyLogoFromContent(content);
15823
- applyLogoSizes(content);
15824
15692
  reconcileNavbarItemsFromContent(content);
15825
15693
  reconcileFooterOrderFromContent(content);
15826
15694
  reconcileSocialsFromContent(content);
@@ -15841,9 +15709,7 @@ function OhhwellsBridge() {
15841
15709
  let cancelled = false;
15842
15710
  setFetchState("loading");
15843
15711
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15844
- const initialPath = pathname;
15845
- fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15846
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15712
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15847
15713
  if (cancelled) return;
15848
15714
  const content = data?.content ?? {};
15849
15715
  contentCache.set(subdomain, content);
@@ -15856,6 +15722,102 @@ function OhhwellsBridge() {
15856
15722
  cancelled = true;
15857
15723
  };
15858
15724
  }, [subdomain, isEditMode]);
15725
+ useEffect12(() => {
15726
+ if (!isEditMode) return;
15727
+ const resolveIndex = (form, clientY) => {
15728
+ const wrappers = listFieldWrappers(form).filter((el) => fieldKeyOf(el) !== fieldDragRef.current?.key);
15729
+ for (let i = 0; i < wrappers.length; i += 1) {
15730
+ const rect = wrappers[i].getBoundingClientRect();
15731
+ if (clientY < rect.top + rect.height / 2) return i;
15732
+ }
15733
+ return wrappers.length;
15734
+ };
15735
+ const onDragOver = (e) => {
15736
+ const session = fieldDragRef.current;
15737
+ if (!session) return;
15738
+ e.preventDefault();
15739
+ e.stopPropagation();
15740
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
15741
+ setFieldDropSlots(buildFieldDropSlots(session.form, session.key));
15742
+ setFieldDropIndex(resolveIndex(session.form, e.clientY));
15743
+ };
15744
+ const onDrop = (e) => {
15745
+ const session = fieldDragRef.current;
15746
+ if (!session) return;
15747
+ e.preventDefault();
15748
+ e.stopPropagation();
15749
+ moveField(session.form, session.key, resolveIndex(session.form, e.clientY));
15750
+ persistFields(session.form);
15751
+ const moved = listFieldWrappers(session.form).find((el) => fieldKeyOf(el) === session.key);
15752
+ if (moved) selectField(moved);
15753
+ setFormPickRect(session.form.getBoundingClientRect());
15754
+ fieldDragRef.current = null;
15755
+ setFieldDropIndex(null);
15756
+ setFieldDropSlots([]);
15757
+ setFieldDragging(false);
15758
+ };
15759
+ window.addEventListener("dragover", onDragOver, true);
15760
+ window.addEventListener("drop", onDrop, true);
15761
+ return () => {
15762
+ window.removeEventListener("dragover", onDragOver, true);
15763
+ window.removeEventListener("drop", onDrop, true);
15764
+ };
15765
+ }, [buildFieldDropSlots, isEditMode, persistFields, selectField]);
15766
+ useEffect12(() => {
15767
+ if (!isEditMode) return;
15768
+ const mark = () => document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15769
+ markFormFields(form);
15770
+ });
15771
+ mark();
15772
+ const observer = new MutationObserver(() => mark());
15773
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15774
+ observer.observe(form, { childList: true, subtree: true, characterData: true });
15775
+ });
15776
+ return () => observer.disconnect();
15777
+ }, [isEditMode, fetchState, pathname]);
15778
+ useEffect12(() => {
15779
+ if (!isEditMode) return;
15780
+ let saveTimer = null;
15781
+ const onInput = (e) => {
15782
+ const input = e.target;
15783
+ if (!input || !("value" in input)) return;
15784
+ const wrapper = getFieldWrapper(input);
15785
+ if (!wrapper || wrapper !== fieldPickElRef.current) return;
15786
+ if (saveTimer) clearTimeout(saveTimer);
15787
+ const owner = wrapper.closest('[data-ohw-editable="form"]');
15788
+ saveTimer = setTimeout(() => {
15789
+ const form = owner ?? formPickElRef.current;
15790
+ if (!form) return;
15791
+ const previous = input.getAttribute("placeholder");
15792
+ input.setAttribute("placeholder", input.value);
15793
+ persistFields(form);
15794
+ input.setAttribute("placeholder", previous ?? "");
15795
+ }, 400);
15796
+ };
15797
+ document.addEventListener("input", onInput, true);
15798
+ return () => document.removeEventListener("input", onInput, true);
15799
+ }, [isEditMode, persistFields]);
15800
+ useEffect12(() => {
15801
+ if (isEditMode || fetchState !== "done") return;
15802
+ const content = contentCache.get(subdomain) ?? {};
15803
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15804
+ reconcileFieldsFromContent(form, content);
15805
+ });
15806
+ }, [isEditMode, fetchState, subdomain]);
15807
+ useEffect12(() => {
15808
+ if (!isEditMode) return;
15809
+ const swallow = (e) => {
15810
+ const target = e.target;
15811
+ if (target && getFormElement(target)) e.preventDefault();
15812
+ };
15813
+ document.addEventListener("submit", swallow, true);
15814
+ return () => document.removeEventListener("submit", swallow, true);
15815
+ }, [isEditMode]);
15816
+ useEffect12(() => {
15817
+ if (isEditMode || fetchState !== "done") return;
15818
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15819
+ bindPublishedForms(apiUrl, subdomain, contentCache.get(subdomain) ?? {});
15820
+ }, [isEditMode, fetchState, subdomain]);
15859
15821
  useEffect12(() => {
15860
15822
  if (!subdomain || isEditMode) return;
15861
15823
  let debounceTimer = null;
@@ -15867,21 +15829,8 @@ function OhhwellsBridge() {
15867
15829
  initSectionInstancesFromContent(content, window.location.pathname);
15868
15830
  observer?.disconnect();
15869
15831
  try {
15870
- applyBrandChrome(content);
15871
- if (typeof content[BRAND_KIT_KEY] === "string") {
15872
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15873
- }
15874
- if (typeof content[STYLE_STORE_KEY] === "string") {
15875
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15876
- }
15877
15832
  for (const [key, val] of Object.entries(content)) {
15878
15833
  if (key === "__ohw_sections") continue;
15879
- if (key === LOGO_PLACEHOLDER_KEY) continue;
15880
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
15881
- if (key === BRAND_KIT_KEY) continue;
15882
- if (key === STYLE_STORE_KEY) continue;
15883
- if (key === STYLE_STORE_KEY) continue;
15884
- if (BRAND_CHROME_KEYS.has(key)) continue;
15885
15834
  if (applyVideoSettingNode(key, val)) continue;
15886
15835
  if (applyCarouselNode(key, val)) continue;
15887
15836
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -15896,17 +15845,23 @@ function OhhwellsBridge() {
15896
15845
  if (video && video.src !== val) applyVideoSrc(video, val);
15897
15846
  } else if (el.dataset.ohwEditable === "link") {
15898
15847
  applyLinkHref(el, val);
15848
+ } else if (el.dataset.ohwEditable === "form") {
15899
15849
  } else if (el.innerHTML !== val) {
15900
15850
  el.innerHTML = val;
15901
15851
  }
15902
15852
  });
15903
15853
  applyLinkByKey(key, val);
15904
15854
  }
15905
- applyLogoFromContent(content);
15906
15855
  reconcileNavbarItemsFromContent(content);
15907
15856
  reconcileFooterOrderFromContent(content);
15908
15857
  reconcileSocialsFromContent(content);
15909
15858
  applySocialsDisplayFromContent(content);
15859
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
15860
+ if (!form.querySelector("[data-ohw-form-success]")) {
15861
+ reconcileFieldsFromContent(form, content);
15862
+ }
15863
+ listFieldWrappers(form).forEach(syncRequiredMark);
15864
+ });
15910
15865
  } finally {
15911
15866
  observer?.observe(document.body, { childList: true, subtree: true });
15912
15867
  }
@@ -15917,17 +15872,6 @@ function OhhwellsBridge() {
15917
15872
  debounceTimer = setTimeout(applyFromCache, 150);
15918
15873
  };
15919
15874
  applyFromCache();
15920
- const pathCacheKey = `${subdomain}::${pathname}`;
15921
- if (!fetchedContentPaths.has(pathCacheKey)) {
15922
- fetchedContentPaths.add(pathCacheKey);
15923
- const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15924
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15925
- if (!data?.content) return;
15926
- contentCache.set(subdomain, data.content);
15927
- applyFromCache();
15928
- }).catch(() => {
15929
- });
15930
- }
15931
15875
  observer = new MutationObserver(scheduleApply);
15932
15876
  observer.observe(document.body, { childList: true, subtree: true });
15933
15877
  return () => {
@@ -16021,31 +15965,26 @@ function OhhwellsBridge() {
16021
15965
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
16022
15966
  useEffect12(() => {
16023
15967
  if (!isEditMode) return;
16024
- let lastPosted = 0;
16025
15968
  const measure = () => {
16026
15969
  const h = document.body.scrollHeight;
16027
- if (h > 50 && Math.abs(h - lastPosted) > 1) {
16028
- lastPosted = h;
16029
- postToParent2({ type: "ow:height", height: h });
16030
- }
16031
- };
16032
- let raf = null;
16033
- const schedule = () => {
16034
- if (raf != null) return;
16035
- raf = requestAnimationFrame(() => {
16036
- raf = null;
16037
- measure();
16038
- });
15970
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
16039
15971
  };
16040
15972
  const t1 = setTimeout(measure, 50);
16041
15973
  const t2 = setTimeout(measure, 500);
16042
- const ro = new ResizeObserver(schedule);
16043
- ro.observe(document.body);
15974
+ let lastWidth = window.innerWidth;
15975
+ let resizeTimer = null;
15976
+ const handleResize = () => {
15977
+ if (window.innerWidth === lastWidth) return;
15978
+ lastWidth = window.innerWidth;
15979
+ if (resizeTimer) clearTimeout(resizeTimer);
15980
+ resizeTimer = setTimeout(measure, 150);
15981
+ };
15982
+ window.addEventListener("resize", handleResize);
16044
15983
  return () => {
16045
15984
  clearTimeout(t1);
16046
15985
  clearTimeout(t2);
16047
- if (raf != null) cancelAnimationFrame(raf);
16048
- ro.disconnect();
15986
+ if (resizeTimer) clearTimeout(resizeTimer);
15987
+ window.removeEventListener("resize", handleResize);
16049
15988
  };
16050
15989
  }, [pathname, isEditMode, postToParent2]);
16051
15990
  useEffect12(() => {
@@ -16089,7 +16028,9 @@ function OhhwellsBridge() {
16089
16028
  [style*="100vh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16090
16029
  [style*="100svh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16091
16030
  [style*="100dvh"] { min-height: ${initialVh}px !important; height: ${initialVh}px !important; }
16092
- [data-ohw-editable] {
16031
+ /* Not the form: it is a layout container (flex/grid with gaps), and forcing block
16032
+ crushed its fields together (OHH-490). */
16033
+ [data-ohw-editable]:not([data-ohw-editable="form"]) {
16093
16034
  display: block;
16094
16035
  }
16095
16036
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
@@ -16115,6 +16056,35 @@ function OhhwellsBridge() {
16115
16056
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
16116
16057
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
16117
16058
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
16059
+ /* A form field is a design surface in the editor: its input takes the pointer so the
16060
+ field can be picked and hovered from anywhere inside it (OHH-642). */
16061
+ [data-ohw-editable="form"] [data-ohw-form-field] input,
16062
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea,
16063
+ [data-ohw-editable="form"] [data-ohw-form-field] label {
16064
+ pointer-events: auto !important;
16065
+ cursor: pointer !important;
16066
+ }
16067
+ /* While a field is selected its placeholder is being written in the value, so the
16068
+ text reads as a placeholder rather than as an answer (OHH-642). */
16069
+ /* A field is a design surface here: dragging its corner resized it past the form and
16070
+ left the chrome behind. Height stays adjustable, width does not (OHH-642). */
16071
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea {
16072
+ resize: vertical !important;
16073
+ max-width: 100% !important;
16074
+ }
16075
+ [data-ohw-editable="form"] [data-ohw-form-field] input,
16076
+ [data-ohw-editable="form"] [data-ohw-form-field] textarea {
16077
+ box-sizing: border-box !important;
16078
+ width: 100% !important;
16079
+ }
16080
+ /* Somewhere to click the block itself, on every side (OHH-642) \u2014 editor only. */
16081
+ [data-ohw-editable="form"] {
16082
+ padding: 18px !important;
16083
+ }
16084
+ [data-ohw-placeholder-edit] {
16085
+ color: color-mix(in srgb, currentColor 55%, transparent) !important;
16086
+ cursor: text !important;
16087
+ }
16118
16088
  /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
16119
16089
  that used to draw it dashes denser than the overlay border, so identical specs
16120
16090
  still read as two different frames (OHH-695). The attribute stays: hover paths
@@ -16195,12 +16165,53 @@ function OhhwellsBridge() {
16195
16165
  return;
16196
16166
  }
16197
16167
  const target = e.target;
16198
- if (target.closest("[data-ohw-ai-review]")) return;
16199
16168
  if (target.closest("[data-ohw-toolbar]")) return;
16200
16169
  if (target.closest("[data-ohw-state-toggle]")) return;
16201
16170
  if (target.closest("[data-ohw-max-badge]")) return;
16202
16171
  if (isInsideLinkEditor(target)) return;
16203
- if (isInsideFloatingPanel(target)) return;
16172
+ if (target.closest("[data-ohw-form-toolbar]")) return;
16173
+ if (target.closest(
16174
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
16175
+ )) {
16176
+ return;
16177
+ }
16178
+ {
16179
+ const formEl = getFormElement(target);
16180
+ const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
16181
+ if (formEl && formKeyOf(formEl) && !onSuccessText) {
16182
+ const fieldEl = getFieldWrapper(target);
16183
+ if (fieldEl) {
16184
+ const label = target.closest("label");
16185
+ if (label && fieldPickElRef.current === fieldEl) return;
16186
+ e.preventDefault();
16187
+ e.stopPropagation();
16188
+ deactivateRef.current();
16189
+ deselectRef.current();
16190
+ formPickElRef.current = formEl;
16191
+ setFormPickRect(null);
16192
+ selectField(fieldEl);
16193
+ if (!label) fieldEl.querySelector("input, textarea")?.focus();
16194
+ return;
16195
+ }
16196
+ clearFieldPick();
16197
+ e.preventDefault();
16198
+ e.stopPropagation();
16199
+ deactivateRef.current();
16200
+ deselectRef.current();
16201
+ markFormFields(formEl);
16202
+ formHoverElRef.current = null;
16203
+ setFormHoverRect(null);
16204
+ formPickElRef.current = formEl;
16205
+ setFormPickRect(formEl.getBoundingClientRect());
16206
+ postToParentRef.current({
16207
+ type: "ow:form-selected",
16208
+ formKey: formKeyOf(formEl),
16209
+ hasLongText: formHasLongText(formEl)
16210
+ });
16211
+ return;
16212
+ }
16213
+ if (!formEl && formPickElRef.current) clearFormPick();
16214
+ }
16204
16215
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16205
16216
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
16206
16217
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -16268,15 +16279,9 @@ function OhhwellsBridge() {
16268
16279
  if (logoEl) {
16269
16280
  e.preventDefault();
16270
16281
  e.stopPropagation();
16271
- if (!logoHasUploadedImage(logoEl)) {
16272
- deselectRef.current();
16273
- deactivateRef.current();
16274
- const identity = readLogoIdentityFromDom();
16275
- postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16276
- return;
16277
- }
16278
- selectLogoRef.current(logoEl);
16279
- openLogoSizePanelRef.current(logoEl);
16282
+ deselectRef.current();
16283
+ deactivateRef.current();
16284
+ postToParentRef.current({ type: "ow:open-logo-settings" });
16280
16285
  return;
16281
16286
  }
16282
16287
  const editable = target.closest("[data-ohw-editable]");
@@ -16431,12 +16436,10 @@ function OhhwellsBridge() {
16431
16436
  };
16432
16437
  const handleDblClick = (e) => {
16433
16438
  const target = e.target;
16434
- if (target.closest("[data-ohw-ai-review]")) return;
16435
16439
  if (target.closest("[data-ohw-toolbar]")) return;
16436
16440
  if (target.closest("[data-ohw-state-toggle]")) return;
16437
16441
  if (target.closest("[data-ohw-max-badge]")) return;
16438
16442
  if (isInsideLinkEditor(target)) return;
16439
- if (isInsideFloatingPanel(target)) return;
16440
16443
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
16441
16444
  return;
16442
16445
  }
@@ -16464,16 +16467,26 @@ function OhhwellsBridge() {
16464
16467
  setHoveredNavContainerRect(null);
16465
16468
  return;
16466
16469
  }
16467
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
16470
+ if (target.closest(EDITOR_CHROME_SELECTOR) || isOverEditorChrome(e.clientX, e.clientY) || isInsideLinkEditor(target)) {
16471
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
16468
16472
  hoveredItemElRef.current = null;
16469
16473
  setHoveredItemRect(null);
16470
16474
  hoveredNavContainerRef.current = null;
16471
16475
  setHoveredNavContainerRect(null);
16476
+ formHoverElRef.current = null;
16477
+ setFormHoverRect(null);
16472
16478
  siblingHintElRef.current = null;
16473
16479
  setSiblingHintRect(null);
16474
16480
  setSiblingHintRects([]);
16475
16481
  return;
16476
16482
  }
16483
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
16484
+ hoveredItemElRef.current = null;
16485
+ setHoveredItemRect(null);
16486
+ hoveredNavContainerRef.current = null;
16487
+ setHoveredNavContainerRect(null);
16488
+ return;
16489
+ }
16477
16490
  {
16478
16491
  const selected2 = selectedElRef.current;
16479
16492
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -16516,7 +16529,7 @@ function OhhwellsBridge() {
16516
16529
  setHoveredNavContainerRect(null);
16517
16530
  if (selectedElRef.current === logoEl) return;
16518
16531
  hoveredItemElRef.current = logoEl;
16519
- setHoveredItemRect(getLogoInteractionRect(logoEl));
16532
+ setHoveredItemRect(logoEl.getBoundingClientRect());
16520
16533
  return;
16521
16534
  }
16522
16535
  const navAnchor = getNavigationItemAnchor(target);
@@ -16540,6 +16553,26 @@ function OhhwellsBridge() {
16540
16553
  return;
16541
16554
  }
16542
16555
  const editable = target.closest("[data-ohw-editable]");
16556
+ const hoverForm = getFormElement(target);
16557
+ if (hoverForm) {
16558
+ const hoverField = getFieldWrapper(target);
16559
+ const hoverTarget = hoverField ?? hoverForm;
16560
+ const selectedHere = hoverTarget === fieldPickElRef.current || hoverTarget === formPickElRef.current;
16561
+ hoveredItemElRef.current = null;
16562
+ setHoveredItemRect(null);
16563
+ if (selectedHere) {
16564
+ formHoverElRef.current = null;
16565
+ setFormHoverRect(null);
16566
+ } else {
16567
+ formHoverElRef.current = hoverTarget;
16568
+ setFormHoverRect(hoverTarget.getBoundingClientRect());
16569
+ }
16570
+ return;
16571
+ }
16572
+ if (formHoverElRef.current) {
16573
+ formHoverElRef.current = null;
16574
+ setFormHoverRect(null);
16575
+ }
16543
16576
  if (!editable) return;
16544
16577
  const selected = selectedElRef.current;
16545
16578
  if (selected && (selected === editable || selected.contains(editable))) return;
@@ -16560,6 +16593,7 @@ function OhhwellsBridge() {
16560
16593
  hoveredNavContainerRef.current = null;
16561
16594
  setHoveredNavContainerRect(null);
16562
16595
  hoveredItemElRef.current = editable;
16596
+ setHoveredItemRect(editable.getBoundingClientRect());
16563
16597
  }
16564
16598
  }
16565
16599
  }
@@ -16767,7 +16801,7 @@ function OhhwellsBridge() {
16767
16801
  setHoveredNavContainerRect(null);
16768
16802
  if (selectedElRef.current !== logo) {
16769
16803
  hoveredItemElRef.current = logo;
16770
- setHoveredItemRect(getLogoInteractionRect(logo));
16804
+ setHoveredItemRect(logo.getBoundingClientRect());
16771
16805
  }
16772
16806
  return;
16773
16807
  }
@@ -16856,7 +16890,7 @@ function OhhwellsBridge() {
16856
16890
  }
16857
16891
  };
16858
16892
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
16859
- if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
16893
+ if (linkPopoverOpenRef.current) {
16860
16894
  if (hoveredImageRef.current) {
16861
16895
  hoveredImageRef.current = null;
16862
16896
  hoveredImageHasTextOverlapRef.current = false;
@@ -17110,7 +17144,7 @@ function OhhwellsBridge() {
17110
17144
  }
17111
17145
  };
17112
17146
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
17113
- if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17147
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17114
17148
  if (activeStateElRef.current) {
17115
17149
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
17116
17150
  activeStateElRef.current = null;
@@ -17176,19 +17210,24 @@ function OhhwellsBridge() {
17176
17210
  setSectionGap(null);
17177
17211
  }
17178
17212
  };
17213
+ const pointOwnedByFloatingPanel = (clientX, clientY) => {
17214
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return true;
17215
+ const panel = document.querySelector("[data-ohw-floating-panel]");
17216
+ if (!panel) return false;
17217
+ const rect = panel.getBoundingClientRect();
17218
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
17219
+ };
17179
17220
  const handleMouseMove = (e) => {
17180
17221
  const { clientX, clientY } = e;
17181
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17222
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17223
+ if (isOverEditorChrome(clientX, clientY)) {
17224
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
17225
+ formHoverElRef.current = null;
17226
+ setFormHoverRect(null);
17182
17227
  hoveredItemElRef.current = null;
17183
17228
  setHoveredItemRect(null);
17184
17229
  hoveredNavContainerRef.current = null;
17185
17230
  setHoveredNavContainerRect(null);
17186
- siblingHintElRef.current = null;
17187
- setSiblingHintRect(null);
17188
- setSiblingHintRects([]);
17189
- dismissImageHover();
17190
- clearImageHover();
17191
- setSectionGap(null);
17192
17231
  return;
17193
17232
  }
17194
17233
  probeSectionGapAt(clientX, clientY);
@@ -17199,11 +17238,7 @@ function OhhwellsBridge() {
17199
17238
  if (e.data?.type !== "ow:pointer-sync") return;
17200
17239
  const { clientX, clientY } = e.data;
17201
17240
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
17202
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17203
- dismissImageHover();
17204
- clearImageHover();
17205
- return;
17206
- }
17241
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17207
17242
  probeSectionGapAt(clientX, clientY);
17208
17243
  probeImageAt(clientX, clientY);
17209
17244
  probeHoverCardsAt(clientX, clientY);
@@ -17453,15 +17488,6 @@ function OhhwellsBridge() {
17453
17488
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
17454
17489
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
17455
17490
  }
17456
- if (typeof content[BRAND_KIT_KEY] === "string") {
17457
- brandKitRef.current = content[BRAND_KIT_KEY];
17458
- applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17459
- }
17460
- if (typeof content[STYLE_STORE_KEY] === "string") {
17461
- stylesRef.current = content[STYLE_STORE_KEY];
17462
- applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17463
- }
17464
- applyBrandChrome(content);
17465
17491
  let sectionsJson = null;
17466
17492
  for (const [key, val] of Object.entries(content)) {
17467
17493
  if (key === "__ohw_sections") {
@@ -17469,11 +17495,6 @@ function OhhwellsBridge() {
17469
17495
  continue;
17470
17496
  }
17471
17497
  if (key === AI_SECTIONS_KEY) continue;
17472
- if (key === LOGO_PLACEHOLDER_KEY) continue;
17473
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17474
- if (key === BRAND_KIT_KEY) continue;
17475
- if (key === STYLE_STORE_KEY) continue;
17476
- if (BRAND_CHROME_KEYS.has(key)) continue;
17477
17498
  if (applyVideoSettingNode(key, val)) continue;
17478
17499
  if (applyCarouselNode(key, val)) continue;
17479
17500
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -17493,8 +17514,6 @@ function OhhwellsBridge() {
17493
17514
  });
17494
17515
  applyLinkByKey(key, val);
17495
17516
  }
17496
- applyLogoFromContent(content);
17497
- applyLogoSizes(content);
17498
17517
  if (sectionsJson) {
17499
17518
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
17500
17519
  sectionsLoadedRef.current = true;
@@ -17510,58 +17529,6 @@ function OhhwellsBridge() {
17510
17529
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
17511
17530
  postToParentRef.current({ type: "ow:hydrate-done" });
17512
17531
  };
17513
- const handleUpdateLogoIdentity = (e) => {
17514
- if (e.data?.type !== "ow:update-logo-identity") return;
17515
- const rawText = typeof e.data.text === "string" ? e.data.text : "";
17516
- const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17517
- const href = typeof e.data.href === "string" ? e.data.href : void 0;
17518
- const imageProvided = "image" in e.data;
17519
- const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17520
- let isPlaceholder = e.data.isPlaceholder !== false;
17521
- if (imageUrl) isPlaceholder = false;
17522
- else if (imageProvided && imageUrl === null) {
17523
- isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17524
- }
17525
- const display = applyLogoIdentity(rawText, isPlaceholder);
17526
- const displayAlt = resolveLogoDisplayText(alt || display);
17527
- if (imageUrl !== void 0) {
17528
- applyLogoImage(imageUrl, displayAlt);
17529
- } else {
17530
- for (const key of LOGO_IMAGE_KEYS) {
17531
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17532
- const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17533
- if (img) img.alt = displayAlt;
17534
- });
17535
- }
17536
- }
17537
- if (href !== void 0) {
17538
- applyLogoHref(href);
17539
- applyLinkByKey("nav-logo-href", href);
17540
- applyLinkByKey("footer-logo-href", href);
17541
- applyLinkByKey("logo-href", href);
17542
- }
17543
- const nodes = [
17544
- ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17545
- { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17546
- { key: LOGO_ALT_KEY, text: displayAlt }
17547
- ];
17548
- if (imageUrl !== void 0) {
17549
- if (imageUrl) {
17550
- for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17551
- } else {
17552
- for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17553
- }
17554
- }
17555
- if (href !== void 0) {
17556
- for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17557
- }
17558
- editContentRef.current = {
17559
- ...editContentRef.current,
17560
- ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17561
- };
17562
- applyLogoSizes(editContentRef.current);
17563
- postToParentRef.current({ type: "ow:change", nodes });
17564
- };
17565
17532
  window.addEventListener("message", handleHydrate);
17566
17533
  const postAiSectionsChanged = () => {
17567
17534
  postToParentRef.current({
@@ -17575,10 +17542,7 @@ function OhhwellsBridge() {
17575
17542
  const payload = e.data.payload;
17576
17543
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
17577
17544
  const previous = aiSectionsRef.current;
17578
- const nextState = applyTreeToState(parseAiSectionsState(previous), {
17579
- ...payload,
17580
- path: payload.path ?? window.location.pathname
17581
- });
17545
+ const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
17582
17546
  const nextValue = serializeAiSectionsState(nextState);
17583
17547
  aiSectionsRef.current = nextValue;
17584
17548
  applyAiSectionsToDom(nextState);
@@ -17615,42 +17579,12 @@ function OhhwellsBridge() {
17615
17579
  const value = typeof e.data.value === "string" ? e.data.value : "";
17616
17580
  aiSectionsRef.current = value;
17617
17581
  applyAiSectionsToDom(parseAiSectionsState(value));
17618
- applyStylesToDom(parseStyleStore(stylesRef.current));
17619
17582
  const restoredHeight = document.documentElement.scrollHeight;
17620
17583
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
17621
17584
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
17622
17585
  postAiSectionsChanged();
17623
17586
  };
17624
17587
  window.addEventListener("message", handleAiSetSections);
17625
- const handleAiSetBrand = (e) => {
17626
- if (e.data?.type !== "ow:ai-set-brand") return;
17627
- const value = typeof e.data.value === "string" ? e.data.value : "";
17628
- const previous = brandKitRef.current;
17629
- brandKitRef.current = value;
17630
- applyBrandToDom(parseBrandKit(value));
17631
- if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17632
- applyStylesToDom(parseStyleStore(stylesRef.current));
17633
- postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17634
- postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17635
- };
17636
- window.addEventListener("message", handleAiSetBrand);
17637
- const handleAiSetStyles = (e) => {
17638
- if (e.data?.type !== "ow:ai-set-styles") return;
17639
- const value = typeof e.data.value === "string" ? e.data.value : "";
17640
- const previous = stylesRef.current;
17641
- stylesRef.current = value;
17642
- applyStylesToDom(parseStyleStore(value));
17643
- postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
17644
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
17645
- };
17646
- window.addEventListener("message", handleAiSetStyles);
17647
- const handleGetBrand = (e) => {
17648
- if (e.data?.type !== "ow:get-brand") return;
17649
- const template = deriveTemplateBrand();
17650
- const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
17651
- postToParentRef.current({ type: "ow:brand-value", value });
17652
- };
17653
- window.addEventListener("message", handleGetBrand);
17654
17588
  const handleDeactivate = (e) => {
17655
17589
  if (e.data?.type !== "ow:deactivate") return;
17656
17590
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -17659,12 +17593,6 @@ function OhhwellsBridge() {
17659
17593
  closeLinkPopoverRef.current();
17660
17594
  return;
17661
17595
  }
17662
- if (floatingPanelOpenRef.current) {
17663
- setFloatingPanelRef.current(null);
17664
- deselectRef.current();
17665
- deactivateRef.current();
17666
- return;
17667
- }
17668
17596
  deselectRef.current();
17669
17597
  deactivateRef.current();
17670
17598
  };
@@ -17718,10 +17646,6 @@ function OhhwellsBridge() {
17718
17646
  return;
17719
17647
  }
17720
17648
  if (selectedElRef.current) {
17721
- if (toolbarVariantRef.current === "logo") {
17722
- deselectRef.current();
17723
- return;
17724
- }
17725
17649
  if (toolbarVariantRef.current === "select-frame") {
17726
17650
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
17727
17651
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -17741,6 +17665,13 @@ function OhhwellsBridge() {
17741
17665
  }
17742
17666
  }
17743
17667
  };
17668
+ const handleFormCount = (e) => {
17669
+ if (e.data?.type !== "ow:form-count") return;
17670
+ const form = formPickElRef.current;
17671
+ if (!form || formKeyOf(form) !== e.data.formKey) return;
17672
+ setFormPickCount(typeof e.data.count === "number" ? e.data.count : null);
17673
+ };
17674
+ window.addEventListener("message", handleFormCount);
17744
17675
  window.addEventListener("message", handleUiEscape);
17745
17676
  const handleKeyDown = (e) => {
17746
17677
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
@@ -17760,11 +17691,12 @@ function OhhwellsBridge() {
17760
17691
  closeFloatingPanelOnlyRef.current();
17761
17692
  return;
17762
17693
  }
17694
+ if (e.key === "Escape" && formPickElRef.current) {
17695
+ e.preventDefault();
17696
+ clearFormPickRef.current();
17697
+ return;
17698
+ }
17763
17699
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17764
- if (toolbarVariantRef.current === "logo") {
17765
- deselectRef.current();
17766
- return;
17767
- }
17768
17700
  if (toolbarVariantRef.current === "select-frame") {
17769
17701
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
17770
17702
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -17842,8 +17774,7 @@ function OhhwellsBridge() {
17842
17774
  const handleScroll = () => {
17843
17775
  const focusEl = activeElRef.current ?? selectedElRef.current;
17844
17776
  if (focusEl) {
17845
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17846
- const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
17777
+ const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17847
17778
  applyToolbarPos(r2);
17848
17779
  setToolbarRect(r2);
17849
17780
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -17853,9 +17784,7 @@ function OhhwellsBridge() {
17853
17784
  setToggleState((prev) => prev ? { ...prev, rect } : null);
17854
17785
  }
17855
17786
  if (hoveredItemElRef.current) {
17856
- const hoverEl = hoveredItemElRef.current;
17857
- const logo = getLogoElement(hoverEl);
17858
- setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
17787
+ setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17859
17788
  }
17860
17789
  if (hoveredNavContainerRef.current) {
17861
17790
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -17899,12 +17828,13 @@ function OhhwellsBridge() {
17899
17828
  if (aiSectionsRef.current) {
17900
17829
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
17901
17830
  }
17902
- if (stylesRef.current) {
17903
- nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
17904
- }
17905
- if (brandKitRef.current) {
17906
- nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17907
- }
17831
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
17832
+ const formKey = formKeyOf(form);
17833
+ if (!formKey) return;
17834
+ const specs = readFieldsFromDom(form);
17835
+ const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
17836
+ if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
17837
+ });
17908
17838
  postToParentRef.current({ type: "ow:save-result", nodes });
17909
17839
  };
17910
17840
  const handleInsertSection = (e) => {
@@ -17915,12 +17845,8 @@ function OhhwellsBridge() {
17915
17845
  if (inserted) {
17916
17846
  const tracker = getSectionsTracker();
17917
17847
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
17918
- const reportHeight = () => {
17919
- const h = document.body.scrollHeight;
17920
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17921
- };
17922
- reportHeight();
17923
- setTimeout(reportHeight, 500);
17848
+ const h = document.documentElement.scrollHeight;
17849
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17924
17850
  }
17925
17851
  };
17926
17852
  const handleSwitchSchedule = (e) => {
@@ -18113,17 +18039,13 @@ function OhhwellsBridge() {
18113
18039
  if (e.data?.type !== "ow:parent-scroll") return;
18114
18040
  const { iframeOffsetTop, headerH, canvasH } = e.data;
18115
18041
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
18116
- if (floatingPanelOpenRef.current) {
18117
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
18118
- }
18119
18042
  if (visibleViewportRef.current) {
18120
18043
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
18121
18044
  }
18122
18045
  const focusEl = activeElRef.current ?? selectedElRef.current;
18123
18046
  if (focusEl) {
18124
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
18125
- const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
18126
- applyToolbarPos(r2);
18047
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
18048
+ applyToolbarPos(measureEl.getBoundingClientRect());
18127
18049
  }
18128
18050
  };
18129
18051
  const handleClickAt = (e) => {
@@ -18156,15 +18078,9 @@ function OhhwellsBridge() {
18156
18078
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
18157
18079
  });
18158
18080
  if (logoAtPoint) {
18159
- if (!logoHasUploadedImage(logoAtPoint)) {
18160
- deselectRef.current();
18161
- deactivateRef.current();
18162
- const identity = readLogoIdentityFromDom();
18163
- postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
18164
- return;
18165
- }
18166
- selectLogoRef.current(logoAtPoint);
18167
- openLogoSizePanelRef.current(logoAtPoint);
18081
+ deselectRef.current();
18082
+ deactivateRef.current();
18083
+ postToParentRef.current({ type: "ow:open-logo-settings" });
18168
18084
  return;
18169
18085
  }
18170
18086
  const textEditable = Array.from(
@@ -18238,14 +18154,6 @@ function OhhwellsBridge() {
18238
18154
  window.addEventListener("message", handleParentScroll);
18239
18155
  window.addEventListener("message", handlePointerSync);
18240
18156
  window.addEventListener("message", handleClickAt);
18241
- window.addEventListener("message", handleUpdateLogoIdentity);
18242
- const handleViewMode = (e) => {
18243
- if (e.data?.type !== "ow:view-mode") return;
18244
- const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
18245
- setEditorViewport(mode);
18246
- applyLogoSizes(editContentRef.current);
18247
- };
18248
- window.addEventListener("message", handleViewMode);
18249
18157
  const handleViewportResize = () => {
18250
18158
  if (visibleViewportRef.current) {
18251
18159
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -18301,17 +18209,13 @@ function OhhwellsBridge() {
18301
18209
  window.removeEventListener("resize", handleViewportResize);
18302
18210
  window.removeEventListener("message", handlePointerSync);
18303
18211
  window.removeEventListener("message", handleClickAt);
18304
- window.removeEventListener("message", handleUpdateLogoIdentity);
18305
- window.removeEventListener("message", handleViewMode);
18306
18212
  window.removeEventListener("message", handleHydrate);
18307
18213
  window.removeEventListener("message", handleAiApplyTree);
18308
18214
  window.removeEventListener("message", handleAiDeleteSection);
18309
18215
  window.removeEventListener("message", handleAiSetSections);
18310
- window.removeEventListener("message", handleAiSetBrand);
18311
- window.removeEventListener("message", handleAiSetStyles);
18312
- window.removeEventListener("message", handleGetBrand);
18313
18216
  window.removeEventListener("message", handleDeactivate);
18314
18217
  window.removeEventListener("message", handleToastAction);
18218
+ window.removeEventListener("message", handleFormCount);
18315
18219
  window.removeEventListener("message", handleUiEscape);
18316
18220
  autoSaveTimers.current.forEach(clearTimeout);
18317
18221
  autoSaveTimers.current.clear();
@@ -18335,7 +18239,9 @@ function OhhwellsBridge() {
18335
18239
  if (footerDragRef.current) return;
18336
18240
  const target = e.target;
18337
18241
  if (!target) return;
18338
- if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel]')) {
18242
+ if (target.closest(
18243
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel], [data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"]'
18244
+ )) {
18339
18245
  return;
18340
18246
  }
18341
18247
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -18513,7 +18419,7 @@ function OhhwellsBridge() {
18513
18419
  postToParent2({
18514
18420
  type: "ow:ready",
18515
18421
  version: "1",
18516
- bridgeVersion: "0.1.64",
18422
+ bridgeVersion: "0.1.63",
18517
18423
  path: pathname,
18518
18424
  nodes: collectEditableNodes(editContentRef.current),
18519
18425
  sections
@@ -18980,6 +18886,151 @@ function OhhwellsBridge() {
18980
18886
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
18981
18887
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
18982
18888
  hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
18889
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx33(
18890
+ ItemInteractionLayer,
18891
+ {
18892
+ rect: formPickRect,
18893
+ state: "active-top",
18894
+ itemDragSurface: false,
18895
+ toolbarAlign: "left",
18896
+ chromeGap: 24,
18897
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs20(
18898
+ "div",
18899
+ {
18900
+ "data-ohw-form-toolbar": "",
18901
+ className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
18902
+ children: [
18903
+ /* @__PURE__ */ jsx33(
18904
+ "button",
18905
+ {
18906
+ type: "button",
18907
+ "aria-label": "Add field",
18908
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
18909
+ onClick: () => setFieldTypePickerOpen((open) => !open),
18910
+ "data-ohw-add-field": "",
18911
+ children: /* @__PURE__ */ jsx33(Plus4, { size: 15, "aria-hidden": true })
18912
+ }
18913
+ ),
18914
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
18915
+ /* @__PURE__ */ jsxs20(
18916
+ "button",
18917
+ {
18918
+ type: "button",
18919
+ className: "flex h-7 items-center gap-1.5 whitespace-nowrap rounded-md px-2 text-[13px] font-semibold text-foreground transition-colors hover:bg-muted/80",
18920
+ onClick: () => {
18921
+ const form = formPickElRef.current;
18922
+ if (!form) return;
18923
+ postToParent2({
18924
+ type: "ow:form-pick",
18925
+ formKey: formKeyOf(form),
18926
+ hasLongText: formHasLongText(form)
18927
+ });
18928
+ },
18929
+ children: [
18930
+ /* @__PURE__ */ jsx33(Settings, { size: 14, "aria-hidden": true }),
18931
+ "Form settings",
18932
+ formPickCount ? (
18933
+ // Counter pill, per the design — not a text suffix.
18934
+ /* @__PURE__ */ jsx33(
18935
+ "span",
18936
+ {
18937
+ "data-ohw-form-count": "",
18938
+ className: "ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground",
18939
+ children: formPickCount
18940
+ }
18941
+ )
18942
+ ) : null
18943
+ ]
18944
+ }
18945
+ ),
18946
+ /* @__PURE__ */ jsx33("div", { className: "mx-0.5 h-5 w-px bg-border" }),
18947
+ /* @__PURE__ */ jsx33("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx33(
18948
+ "button",
18949
+ {
18950
+ type: "button",
18951
+ "aria-pressed": formViewState === state,
18952
+ className: "rounded-md px-2.5 py-1 text-[13px] font-semibold capitalize transition-colors " + (formViewState === state ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
18953
+ onClick: () => {
18954
+ const form = formPickElRef.current;
18955
+ const key = form ? formKeyOf(form) : null;
18956
+ if (!form || !key) return;
18957
+ const initial = successInitialFor(form, key, editContentRef.current);
18958
+ setFormViewState(form, key, state, initial);
18959
+ setFormViewStateUi(state);
18960
+ setFormPickRect(form.getBoundingClientRect());
18961
+ if (state === "success") {
18962
+ const successEl = form.querySelector(`[${SUCCESS_TEXT_ATTR}]`);
18963
+ if (successEl) requestAnimationFrame(() => activateRef.current(successEl));
18964
+ } else {
18965
+ deactivateRef.current();
18966
+ }
18967
+ },
18968
+ children: state
18969
+ },
18970
+ state
18971
+ )) })
18972
+ ]
18973
+ }
18974
+ )
18975
+ }
18976
+ ),
18977
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx33(
18978
+ ItemInteractionLayer,
18979
+ {
18980
+ rect: formHoverRect,
18981
+ state: "hover",
18982
+ chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
18983
+ }
18984
+ ),
18985
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(
18986
+ ItemInteractionLayer,
18987
+ {
18988
+ rect: fieldPickRect,
18989
+ state: fieldDragging ? "dragging" : "active-top",
18990
+ itemDragSurface: false,
18991
+ toolbarAlign: "left",
18992
+ chromeGap: 10,
18993
+ showHandle: true,
18994
+ dragHandleLabel: "Reorder field",
18995
+ onDragHandleDragStart: handleFieldDragStart,
18996
+ onDragHandleDragEnd: handleFieldDragEnd,
18997
+ toolbar: /* @__PURE__ */ jsx33(
18998
+ FormFieldToolbar,
18999
+ {
19000
+ type: fieldPickState.type,
19001
+ required: fieldPickState.required,
19002
+ onTypeChange: handleFieldTypeChange,
19003
+ onRequiredToggle: handleFieldRequiredToggle,
19004
+ onDuplicate: handleFieldDuplicate,
19005
+ onDelete: handleFieldDelete
19006
+ }
19007
+ )
19008
+ }
19009
+ ),
19010
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
19011
+ "div",
19012
+ {
19013
+ className: "pointer-events-none fixed z-[2147483644]",
19014
+ style: { top: slot.top, left: slot.left, width: slot.width },
19015
+ children: /* @__PURE__ */ jsx33(
19016
+ DropIndicator,
19017
+ {
19018
+ direction: "horizontal",
19019
+ state: fieldDropIndex === i ? "dragActive" : "dragIdle",
19020
+ className: "!w-full"
19021
+ }
19022
+ )
19023
+ },
19024
+ `field-drop-${i}`
19025
+ )) : null,
19026
+ fieldTypePickerOpen && formPickRect && /* @__PURE__ */ jsx33(
19027
+ "div",
19028
+ {
19029
+ className: "pointer-events-none fixed z-[2147483645]",
19030
+ style: { top: formPickRect.top + 16, left: formPickRect.left + 24 },
19031
+ children: /* @__PURE__ */ jsx33(FieldTypePicker, { onPick: handleAddField })
19032
+ }
19033
+ ),
18983
19034
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
18984
19035
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
18985
19036
  FooterContainerChrome,
@@ -18989,7 +19040,7 @@ function OhhwellsBridge() {
18989
19040
  addDisabled: !canAddFooterColumn()
18990
19041
  }
18991
19042
  ),
18992
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
19043
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx33(
18993
19044
  ItemInteractionLayer,
18994
19045
  {
18995
19046
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -19001,9 +19052,9 @@ function OhhwellsBridge() {
19001
19052
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
19002
19053
  onDragHandleDragStart: handleItemDragStart,
19003
19054
  onDragHandleDragEnd: handleItemDragEnd,
19004
- onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19005
- onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19006
- itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19055
+ onItemPointerDown: handleItemChromePointerDown,
19056
+ onItemClick: handleItemChromeClick,
19057
+ itemDragSurface: !isFooterFrameSelection,
19007
19058
  toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
19008
19059
  ItemActionToolbar,
19009
19060
  {
@@ -19166,115 +19217,11 @@ function OhhwellsBridge() {
19166
19217
  }
19167
19218
  )
19168
19219
  }
19169
- ) : null,
19170
- floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ jsx33(
19171
- FloatingPanel,
19172
- {
19173
- open: true,
19174
- title: floatingPanel.title,
19175
- context: floatingPanel.context,
19176
- position: floatingPanelPos,
19177
- onPositionChange: setFloatingPanelPos,
19178
- parentScroll: parentScrollSnap ?? parentScrollRef.current,
19179
- onClose: closeFloatingPanelAndDeselect,
19180
- children: /* @__PURE__ */ jsx33(
19181
- LogoSizePanel,
19182
- {
19183
- viewport: editorViewport,
19184
- sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
19185
- mobileFollowing: logoSizeDraft.mobileFollowing,
19186
- onSizeChange: (px) => {
19187
- const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
19188
- ...logoSizeDraft,
19189
- desktopPx: px,
19190
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
19191
- };
19192
- setLogoSizeDraft(next);
19193
- persistLogoSizeDraft(floatingPanel.placement, next);
19194
- },
19195
- onCustomizeMobile: () => {
19196
- const next = {
19197
- ...logoSizeDraft,
19198
- mobileFollowing: false,
19199
- mobilePx: logoSizeDraft.desktopPx
19200
- };
19201
- setLogoSizeDraft(next);
19202
- persistLogoSizeDraft(floatingPanel.placement, next);
19203
- },
19204
- onResetMobile: () => {
19205
- const next = {
19206
- ...logoSizeDraft,
19207
- mobileFollowing: true,
19208
- mobilePx: logoSizeDraft.desktopPx
19209
- };
19210
- setLogoSizeDraft(next);
19211
- persistLogoSizeDraft(floatingPanel.placement, next);
19212
- },
19213
- onUpdateEverywhere: () => {
19214
- const identity = readLogoIdentityFromDom();
19215
- postToParent2({ type: "ow:open-logo-settings", ...identity });
19216
- }
19217
- }
19218
- )
19219
- }
19220
19220
  ) : null
19221
19221
  ] }),
19222
19222
  bridgeRoot
19223
19223
  ) : null;
19224
19224
  }
19225
-
19226
- // src/ui/EmptySection.tsx
19227
- import Link4 from "next/link";
19228
- import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
19229
- function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
19230
- return /* @__PURE__ */ jsxs21(Fragment9, { children: [
19231
- /* @__PURE__ */ jsx34(
19232
- "p",
19233
- {
19234
- style: {
19235
- fontFamily: "var(--brand-font-body)",
19236
- fontSize: "0.75rem",
19237
- fontWeight: 500,
19238
- letterSpacing: "0.15em",
19239
- textTransform: "uppercase",
19240
- color: "var(--brand-accent)",
19241
- marginBottom: "1.5rem"
19242
- },
19243
- children: /* @__PURE__ */ jsx34(Link4, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
19244
- }
19245
- ),
19246
- /* @__PURE__ */ jsx34(
19247
- "h1",
19248
- {
19249
- style: {
19250
- fontFamily: "var(--brand-font-heading)",
19251
- fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
19252
- lineHeight: 1.1,
19253
- letterSpacing: "-0.025em",
19254
- color: "var(--brand-text)",
19255
- marginBottom: "1rem"
19256
- },
19257
- ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
19258
- children: title
19259
- }
19260
- ),
19261
- /* @__PURE__ */ jsx34(
19262
- "p",
19263
- {
19264
- style: {
19265
- fontFamily: "var(--brand-font-body)",
19266
- fontSize: "1rem",
19267
- lineHeight: 1.7,
19268
- fontWeight: 300,
19269
- color: "var(--brand-text-muted)",
19270
- maxWidth: "340px"
19271
- },
19272
- ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19273
- children: "This page doesn't have any content yet."
19274
- }
19275
- )
19276
- ] });
19277
- }
19278
19225
  export {
19279
19226
  AI_DEFAULT_BRAND,
19280
19227
  AI_TREE_SCHEMA_VERSIONS,
@@ -19291,7 +19238,6 @@ export {
19291
19238
  DropdownMenuItem,
19292
19239
  DropdownMenuSeparator,
19293
19240
  DropdownMenuTrigger,
19294
- EmptySection,
19295
19241
  ItemActionToolbar,
19296
19242
  ItemInteractionLayer,
19297
19243
  LinkEditorPanel,