@ohhwells/bridge 0.1.57 → 0.1.59-next.170

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.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -191,6 +192,129 @@ function deleteSectionFromState(state, sectionId) {
191
192
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
192
193
  }
193
194
 
195
+ // src/lib/brand-chrome.ts
196
+ var BRAND_NAME_KEY = "__ohw_brand_name";
197
+ var BRAND_TITLE_KEY = "__ohw_site_title";
198
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
199
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
200
+ BRAND_NAME_KEY,
201
+ BRAND_TITLE_KEY,
202
+ BRAND_FAVICON_LETTER_KEY
203
+ ]);
204
+ function upsertMeta(selector, attr, token, value) {
205
+ let el = document.head.querySelector(selector);
206
+ if (!el) {
207
+ el = document.createElement("meta");
208
+ el.setAttribute(attr, token);
209
+ document.head.appendChild(el);
210
+ }
211
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
212
+ }
213
+ function escapeXml(value) {
214
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
215
+ }
216
+ function applyLetterFavicon(letter) {
217
+ 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>`;
218
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
219
+ let link = document.head.querySelector('link[rel="icon"]');
220
+ if (!link) {
221
+ link = document.createElement("link");
222
+ link.rel = "icon";
223
+ document.head.appendChild(link);
224
+ }
225
+ link.type = "image/svg+xml";
226
+ if (link.href !== href) link.href = href;
227
+ }
228
+ function applyBrandChrome(content) {
229
+ const name = content[BRAND_NAME_KEY];
230
+ if (typeof name === "string" && name.length > 0) {
231
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
232
+ if (el.textContent !== name) el.textContent = name;
233
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
234
+ });
235
+ }
236
+ const title = content[BRAND_TITLE_KEY];
237
+ if (typeof title === "string" && title.length > 0) {
238
+ if (document.title !== title) document.title = title;
239
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
240
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
241
+ }
242
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
243
+ if (typeof letter === "string" && letter.length > 0) {
244
+ applyLetterFavicon(letter);
245
+ }
246
+ }
247
+
248
+ // src/lib/brand-kit.ts
249
+ var BRAND_KIT_KEY = "__ohw_brand";
250
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
251
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
252
+ (role) => `${BRAND_VAR_PREFIX}${role}`
253
+ );
254
+ var FONT_VARS = { heading: ["--font-heading", "--font-display"], body: ["--font-body"] };
255
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
256
+ function brandColorVars(kit) {
257
+ const { dark, primary, accent, light } = kit.palette;
258
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
259
+ return {
260
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
261
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
262
+ [`${BRAND_VAR_PREFIX}light`]: light,
263
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
264
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
265
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
266
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
267
+ };
268
+ }
269
+ function parseBrandKit(raw) {
270
+ if (!raw) return null;
271
+ try {
272
+ const parsed = JSON.parse(raw);
273
+ const p = parsed?.palette;
274
+ const f = parsed?.fonts;
275
+ 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") {
276
+ return null;
277
+ }
278
+ return {
279
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
280
+ fonts: { heading: f.heading, body: f.body }
281
+ };
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+ function familyOf(stack) {
287
+ const first = stack.split(",")[0]?.trim() ?? "";
288
+ return first.replace(/^['"]|['"]$/g, "");
289
+ }
290
+ function loadBrandFonts(families) {
291
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
292
+ if (unique.length === 0) return;
293
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
294
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
295
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
296
+ if (!link) {
297
+ link = document.createElement("link");
298
+ link.id = BRAND_FONT_LINK_ID;
299
+ link.rel = "stylesheet";
300
+ document.head.appendChild(link);
301
+ }
302
+ if (link.href !== href) link.href = href;
303
+ }
304
+ function applyBrandToDom(kit) {
305
+ const root = document.documentElement;
306
+ if (!kit) {
307
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
308
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
309
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
310
+ return;
311
+ }
312
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
313
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
314
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
315
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
316
+ }
317
+
194
318
  // src/ui/ai-tree/aiSectionsManager.tsx
195
319
  var import_react_dom = require("react-dom");
196
320
  var import_client = require("react-dom/client");
@@ -223,6 +347,17 @@ var FEATURE_LINE_CSS = [
223
347
  function textAttrs(ctx, path) {
224
348
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
225
349
  }
350
+ var AI_RESPONSIVE_CSS = [
351
+ "@media (max-width: 960px) {",
352
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
353
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
354
+ "}",
355
+ "@media (max-width: 640px) {",
356
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
357
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
358
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
359
+ "}"
360
+ ].join("\n");
226
361
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
227
362
  function MediaBox({
228
363
  refValue,
@@ -965,6 +1100,7 @@ function Carousel({ items, itemsPerRow, ctx }) {
965
1100
  children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
966
1101
  "div",
967
1102
  {
1103
+ "data-ai-grid": String(itemsPerRow),
968
1104
  style: {
969
1105
  flex: "0 0 100%",
970
1106
  display: "grid",
@@ -1068,6 +1204,7 @@ function CollectionBlock({ node, ctx, path }) {
1068
1204
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1069
1205
  "div",
1070
1206
  {
1207
+ "data-ai-grid": String(itemsPerRow),
1071
1208
  style: {
1072
1209
  display: "grid",
1073
1210
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1231,6 +1368,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1231
1368
  {
1232
1369
  "data-ai-section": tree.tag ?? "",
1233
1370
  ...bgAttrs,
1371
+ "data-ai-responsive": "",
1234
1372
  style: {
1235
1373
  position: "relative",
1236
1374
  padding: `${pad}px 0`,
@@ -1240,10 +1378,12 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1240
1378
  backgroundPosition: "center"
1241
1379
  },
1242
1380
  children: [
1381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1243
1382
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1244
1383
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1245
1384
  "div",
1246
1385
  {
1386
+ "data-ai-section-inner": "",
1247
1387
  style: {
1248
1388
  position: "relative",
1249
1389
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1254,6 +1394,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1254
1394
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1255
1395
  "div",
1256
1396
  {
1397
+ "data-ai-columns": "",
1257
1398
  style: {
1258
1399
  display: "grid",
1259
1400
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1277,17 +1418,34 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1277
1418
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1278
1419
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1279
1420
  var REMOVED_ATTR = "data-ohw-ai-removed";
1421
+ function readRootVar(name) {
1422
+ if (typeof document === "undefined") return "";
1423
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1424
+ }
1425
+ function deriveBrandOverride() {
1426
+ const dark = readRootVar("--ohw-brand-dark");
1427
+ const primary = readRootVar("--ohw-brand-primary");
1428
+ const light = readRootVar("--ohw-brand-light");
1429
+ if (!dark || !primary || !light) return null;
1430
+ const accent = readRootVar("--ohw-brand-accent");
1431
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1432
+ const body = readRootVar("--font-body");
1433
+ return {
1434
+ palette: { dark, primary, accent: accent || dark, light },
1435
+ fonts: {
1436
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1437
+ body: body || AI_DEFAULT_BRAND.fonts.body
1438
+ }
1439
+ };
1440
+ }
1280
1441
  function deriveTemplateBrand() {
1281
- if (typeof document === "undefined") return null;
1282
- const cs = getComputedStyle(document.documentElement);
1283
- const read = (name) => cs.getPropertyValue(name).trim();
1284
- const dark = read("--color-dark");
1285
- const primary = read("--color-primary");
1286
- const light = read("--color-light");
1442
+ const dark = readRootVar("--color-dark");
1443
+ const primary = readRootVar("--color-primary");
1444
+ const light = readRootVar("--color-light");
1287
1445
  if (!dark || !primary || !light) return null;
1288
- const accent = read("--color-accent");
1289
- const heading = read("--font-heading") || read("--font-display");
1290
- const body = read("--font-body");
1446
+ const accent = readRootVar("--color-accent");
1447
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1448
+ const body = readRootVar("--font-body");
1291
1449
  return {
1292
1450
  palette: { dark, primary, accent: accent || dark, light },
1293
1451
  fonts: {
@@ -1379,7 +1537,9 @@ function syncReplacedOriginals(state) {
1379
1537
  }
1380
1538
  function applyAiSectionsToDom(state, options) {
1381
1539
  if (typeof document === "undefined") return;
1540
+ const brandOverride = deriveBrandOverride();
1382
1541
  const templateBrand = deriveTemplateBrand();
1542
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1383
1543
  const activeIds = new Set(state.sections.map((entry) => entry.id));
1384
1544
  for (const [id, section] of mounted) {
1385
1545
  if (!activeIds.has(id)) {
@@ -1389,7 +1549,7 @@ function applyAiSectionsToDom(state, options) {
1389
1549
  }
1390
1550
  }
1391
1551
  for (const entry of state.sections) {
1392
- const serialized = JSON.stringify(entry);
1552
+ const serialized = JSON.stringify(entry) + brandKey;
1393
1553
  const existing = mounted.get(entry.id);
1394
1554
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1395
1555
  continue;
@@ -1403,6 +1563,7 @@ function applyAiSectionsToDom(state, options) {
1403
1563
  mounted.delete(entry.id);
1404
1564
  }
1405
1565
  container.setAttribute("data-ohw-section", entry.id);
1566
+ container.setAttribute("data-ohw-instance", entry.id);
1406
1567
  container.setAttribute("data-ohw-section-label", entry.label);
1407
1568
  placeContainer(container, entry);
1408
1569
  const root = mounted.get(entry.id)?.root ?? (0, import_client.createRoot)(container);
@@ -1413,7 +1574,7 @@ function applyAiSectionsToDom(state, options) {
1413
1574
  AiTreeRenderer,
1414
1575
  {
1415
1576
  tree: entry.tree,
1416
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1577
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1417
1578
  resolveMedia,
1418
1579
  editKeyPrefix: `ai.${entry.id}`
1419
1580
  }
@@ -2030,7 +2191,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2030
2191
  const autoId = (0, import_react5.useId)();
2031
2192
  const insertAfter = insertAfterProp ?? autoId;
2032
2193
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2033
- const [loading, setLoading] = (0, import_react5.useState)(true);
2194
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2034
2195
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2035
2196
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2036
2197
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2204,8 +2365,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2204
2365
  "*"
2205
2366
  );
2206
2367
  };
2207
- if (!inEditor && !loading && !schedule) return null;
2208
2368
  const sectionId = `scheduling-${insertAfter}`;
2369
+ if (!inEditor && !loading && !schedule) {
2370
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2371
+ }
2209
2372
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2210
2373
  "section",
2211
2374
  {
@@ -6159,6 +6322,7 @@ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
6159
6322
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
6160
6323
  var TOOLBAR_EDGE_MARGIN = 4;
6161
6324
  var SELECTION_CHROME_GAP = 4;
6325
+ var HOVER_CHROME_GAP = 2;
6162
6326
  var TOOLBAR_STROKE_GAP = 4;
6163
6327
  function getChromeZIndex(state) {
6164
6328
  switch (state) {
@@ -6314,10 +6478,11 @@ function ItemInteractionLayer({
6314
6478
  onItemPointerDown,
6315
6479
  onItemClick,
6316
6480
  itemDragSurface = true,
6317
- chromeGap = SELECTION_CHROME_GAP,
6481
+ chromeGap,
6318
6482
  className
6319
6483
  }) {
6320
6484
  if (state === "default") return null;
6485
+ const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
6321
6486
  const isActive = state === "active-top" || state === "active-bottom";
6322
6487
  const isDragging = state === "dragging";
6323
6488
  const showToolbar = isActive && toolbar;
@@ -6333,10 +6498,10 @@ function ItemInteractionLayer({
6333
6498
  className: cn("pointer-events-none", className),
6334
6499
  style: {
6335
6500
  position: "fixed",
6336
- top: rect.top - chromeGap,
6337
- left: rect.left - chromeGap,
6338
- width: rect.width + chromeGap * 2,
6339
- height: rect.height + chromeGap * 2,
6501
+ top: rect.top - gap,
6502
+ left: rect.left - gap,
6503
+ width: rect.width + gap * 2,
6504
+ height: rect.height + gap * 2,
6340
6505
  zIndex: getChromeZIndex(state)
6341
6506
  },
6342
6507
  children: [
@@ -6722,8 +6887,12 @@ function parseSectionsFromHtml(html) {
6722
6887
 
6723
6888
  // src/ui/ai-section/AiSectionOverlay.tsx
6724
6889
  var import_jsx_runtime16 = require("react/jsx-runtime");
6725
- function readRect(sectionId) {
6726
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
6890
+ function findSectionElement(instanceId) {
6891
+ const escaped = CSS.escape(instanceId);
6892
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
6893
+ }
6894
+ function readRect(instanceId) {
6895
+ const el = findSectionElement(instanceId);
6727
6896
  if (!el) return null;
6728
6897
  const r2 = el.getBoundingClientRect();
6729
6898
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -6746,7 +6915,7 @@ function useLiveSectionRect(sectionId) {
6746
6915
  const opts = { capture: true, passive: true };
6747
6916
  window.addEventListener("scroll", update, opts);
6748
6917
  window.addEventListener("resize", update);
6749
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
6918
+ const el = findSectionElement(sectionId);
6750
6919
  const ro = el ? new ResizeObserver(update) : null;
6751
6920
  if (el && ro) ro.observe(el);
6752
6921
  const interval = setInterval(update, 500);
@@ -6759,6 +6928,14 @@ function useLiveSectionRect(sectionId) {
6759
6928
  }, [sectionId]);
6760
6929
  return rect;
6761
6930
  }
6931
+ function computeSectionBoundaryFlags(instanceId) {
6932
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
6933
+ (el) => !el.parentElement?.closest("[data-ohw-section]")
6934
+ );
6935
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
6936
+ if (index === -1) return { isFirst: true, isLast: true };
6937
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
6938
+ }
6762
6939
  var PRIMARY2 = "#0885FE";
6763
6940
  function edgeAwareRadius(rect) {
6764
6941
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -6840,7 +7017,7 @@ function AiSectionOverlay({
6840
7017
  (el) => {
6841
7018
  postToParent2({
6842
7019
  type: "ow:section-selected",
6843
- sectionId: el?.dataset.ohwSection ?? null,
7020
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
6844
7021
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
6845
7022
  });
6846
7023
  },
@@ -6849,7 +7026,7 @@ function AiSectionOverlay({
6849
7026
  const selectFromElement = (0, import_react8.useCallback)(
6850
7027
  (el, options) => {
6851
7028
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
6852
- const id = sectionEl?.dataset.ohwSection ?? null;
7029
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
6853
7030
  if (id === selectedIdRef.current) return;
6854
7031
  setSelectedId(id);
6855
7032
  if (options?.report !== false) report(sectionEl);
@@ -6892,7 +7069,7 @@ function AiSectionOverlay({
6892
7069
  setReviewId(found ? sectionId : null);
6893
7070
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
6894
7071
  if (found) {
6895
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7072
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
6896
7073
  }
6897
7074
  }
6898
7075
  };
@@ -6911,7 +7088,7 @@ function AiSectionOverlay({
6911
7088
  return;
6912
7089
  }
6913
7090
  const sec = t.closest("[data-ohw-section]");
6914
- setHoveredId(sec?.dataset.ohwSection ?? null);
7091
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
6915
7092
  };
6916
7093
  const onLeave = () => setHoveredId(null);
6917
7094
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -6943,9 +7120,29 @@ function AiSectionOverlay({
6943
7120
  },
6944
7121
  [postToParent2]
6945
7122
  );
6946
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7123
+ const activeSelectionId = reviewId ? null : selectedId;
7124
+ const selectionRect = useLiveSectionRect(activeSelectionId);
6947
7125
  const reviewRect = useLiveSectionRect(reviewId);
6948
7126
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7127
+ (0, import_react8.useEffect)(() => {
7128
+ if (!activeSelectionId || !selectionRect) {
7129
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7130
+ return;
7131
+ }
7132
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7133
+ postToParent2({
7134
+ type: "ow:section-rect",
7135
+ instanceId: activeSelectionId,
7136
+ rect: {
7137
+ top: selectionRect.top + window.scrollY,
7138
+ left: selectionRect.left + window.scrollX,
7139
+ width: selectionRect.width,
7140
+ height: selectionRect.height
7141
+ },
7142
+ isFirst,
7143
+ isLast
7144
+ });
7145
+ }, [activeSelectionId, selectionRect, postToParent2]);
6949
7146
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
6950
7147
  hoverRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6951
7148
  "div",
@@ -6996,7 +7193,10 @@ function AiSectionOverlay({
6996
7193
  border: `2px solid ${PRIMARY2}`,
6997
7194
  borderRadius: edgeAwareRadius(reviewRect),
6998
7195
  zIndex: 2147483200,
6999
- // The veil itself: swallows clicks so the section stays locked until decided.
7196
+ // The veil itself: swallows clicks so the section stays locked until decided. This
7197
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7198
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7199
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7000
7200
  background: "rgba(8, 133, 254, 0.04)",
7001
7201
  pointerEvents: "auto",
7002
7202
  cursor: "default"
@@ -7108,7 +7308,14 @@ function findClosestButtonLike(target) {
7108
7308
  function findButtonAtPoint(x, y, media) {
7109
7309
  const hits = Array.from(
7110
7310
  document.querySelectorAll(BUTTON_LIKE_SELECTOR)
7111
- ).filter((el) => containsPoint(el, x, y) && !(media && el.contains(media)));
7311
+ ).filter(
7312
+ (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
7313
+ // site button. Without this, once the media overlay renders its button lands right
7314
+ // under the cursor, gets detected as "a button on the media", and dismisses the very
7315
+ // overlay it belongs to — which removes the button, so the next hit-test finds nothing
7316
+ // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
7317
+ !el.closest("[data-ohw-bridge]")
7318
+ );
7112
7319
  if (hits.length === 0) return null;
7113
7320
  return hits.reduce((best, el) => best.contains(el) ? el : best);
7114
7321
  }
@@ -9683,6 +9890,10 @@ function isSocialsRow(el) {
9683
9890
  const anchors = Array.from(el.querySelectorAll("a"));
9684
9891
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9685
9892
  }
9893
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9894
+ function canAddSocialItem(row) {
9895
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9896
+ }
9686
9897
  function listSocialItems(row) {
9687
9898
  return Array.from(row.children).map((child) => {
9688
9899
  if (!(child instanceof HTMLElement)) return null;
@@ -10042,6 +10253,7 @@ function ensureIconSlot(item) {
10042
10253
  // src/lib/footer-items.ts
10043
10254
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
10044
10255
  var MAX_FOOTER_COLUMNS = 18;
10256
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
10045
10257
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
10046
10258
  function parseFooterHrefKey(key) {
10047
10259
  if (!key) return null;
@@ -10259,6 +10471,13 @@ function getNextFooterColumnIndex() {
10259
10471
  function canAddFooterColumn() {
10260
10472
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10261
10473
  }
10474
+ function canAddFooterItem(column) {
10475
+ return listFooterLinksInColumn(column).length < MAX_FOOTER_ITEMS_PER_COLUMN;
10476
+ }
10477
+ function resolveFooterColumnForAdd(selected) {
10478
+ if (selected.hasAttribute("data-ohw-footer-col")) return selected;
10479
+ return selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
10480
+ }
10262
10481
  function buildFooterHeading(colIndex, text) {
10263
10482
  const heading = document.createElement("p");
10264
10483
  heading.setAttribute("data-ohw-editable", "text");
@@ -10883,6 +11102,329 @@ function deleteFooterColumn(column) {
10883
11102
  };
10884
11103
  }
10885
11104
 
11105
+ // src/lib/logo-identity.ts
11106
+ var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11107
+ var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11108
+ var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11109
+ var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11110
+ var LOGO_ALT_KEY = "logo-alt";
11111
+ var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11112
+ var PLACEHOLDER_BUSINESS_NAME = "Business name";
11113
+ function resolveLogoDisplayText(text) {
11114
+ const trimmed = (text ?? "").trim();
11115
+ return trimmed || PLACEHOLDER_BUSINESS_NAME;
11116
+ }
11117
+ function isFooterLogoRoot(root) {
11118
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11119
+ }
11120
+ function imageKeyForRoot(root) {
11121
+ return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11122
+ }
11123
+ function textKeyForRoot(root) {
11124
+ return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11125
+ }
11126
+ function ensureLogoHrefKey(root) {
11127
+ if (!(root instanceof HTMLAnchorElement)) return;
11128
+ if (root.hasAttribute("data-ohw-href-key")) return;
11129
+ root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11130
+ }
11131
+ function applyLogoIdentity(text, isPlaceholder) {
11132
+ const display = resolveLogoDisplayText(text);
11133
+ const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11134
+ for (const key of LOGO_TEXT_KEYS) {
11135
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11136
+ if (el.textContent !== display) el.textContent = display;
11137
+ });
11138
+ }
11139
+ for (const key of LOGO_IMAGE_KEYS) {
11140
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11141
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11142
+ if (img) img.alt = display;
11143
+ });
11144
+ }
11145
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11146
+ if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11147
+ else el.removeAttribute("data-ohw-placeholder");
11148
+ });
11149
+ return display;
11150
+ }
11151
+ function applyLogoImage(url, alt) {
11152
+ const displayAlt = resolveLogoDisplayText(alt);
11153
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11154
+ ensureLogoHrefKey(root);
11155
+ const imageKey = imageKeyForRoot(root);
11156
+ const textKey = textKeyForRoot(root);
11157
+ 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");
11158
+ let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11159
+ if (url) {
11160
+ if (!img) {
11161
+ img = document.createElement("img");
11162
+ img.setAttribute("data-ohw-editable", "image");
11163
+ img.setAttribute("data-ohw-key", imageKey);
11164
+ img.alt = displayAlt;
11165
+ img.style.height = "";
11166
+ img.style.maxHeight = "none";
11167
+ img.style.width = "auto";
11168
+ img.style.display = "block";
11169
+ img.style.objectFit = "contain";
11170
+ root.insertBefore(img, root.firstChild);
11171
+ } else {
11172
+ img.setAttribute("data-ohw-editable", "image");
11173
+ img.setAttribute("data-ohw-key", imageKey);
11174
+ }
11175
+ img.removeAttribute("srcset");
11176
+ img.removeAttribute("sizes");
11177
+ img.src = url;
11178
+ img.alt = displayAlt;
11179
+ img.style.display = "block";
11180
+ if (textEl) textEl.style.display = "none";
11181
+ root.removeAttribute("data-ohw-placeholder");
11182
+ return;
11183
+ }
11184
+ if (img) {
11185
+ img.removeAttribute("src");
11186
+ img.removeAttribute("srcset");
11187
+ img.removeAttribute("sizes");
11188
+ img.alt = displayAlt;
11189
+ img.style.display = "none";
11190
+ }
11191
+ if (!textEl) {
11192
+ textEl = document.createElement("span");
11193
+ textEl.setAttribute("data-ohw-editable", "plain");
11194
+ textEl.setAttribute("data-ohw-key", textKey);
11195
+ root.appendChild(textEl);
11196
+ }
11197
+ textEl.style.display = "";
11198
+ if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11199
+ if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11200
+ root.setAttribute("data-ohw-placeholder", "");
11201
+ } else {
11202
+ root.removeAttribute("data-ohw-placeholder");
11203
+ }
11204
+ });
11205
+ }
11206
+ function applyLogoHref(href) {
11207
+ const target = href.trim() || "/";
11208
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11209
+ ensureLogoHrefKey(root);
11210
+ if (root instanceof HTMLAnchorElement) {
11211
+ root.setAttribute("href", target);
11212
+ }
11213
+ });
11214
+ for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11215
+ }
11216
+ function readLogoIdentityFromDom() {
11217
+ let imageUrl = null;
11218
+ for (const key of LOGO_IMAGE_KEYS) {
11219
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11220
+ const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11221
+ const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11222
+ if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11223
+ imageUrl = img.currentSrc || img.src;
11224
+ break;
11225
+ }
11226
+ }
11227
+ let text = PLACEHOLDER_BUSINESS_NAME;
11228
+ let isPlaceholder = true;
11229
+ for (const key of LOGO_TEXT_KEYS) {
11230
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11231
+ if (el?.textContent?.trim()) {
11232
+ text = el.textContent.trim();
11233
+ const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11234
+ isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11235
+ break;
11236
+ }
11237
+ }
11238
+ if (imageUrl) {
11239
+ const logoImg = document.querySelector(
11240
+ '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11241
+ );
11242
+ const alt = logoImg?.alt?.trim() || text;
11243
+ isPlaceholder = false;
11244
+ const hrefEl = document.querySelector(
11245
+ 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11246
+ );
11247
+ const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11248
+ return { text, isPlaceholder, imageUrl, href: href2, alt };
11249
+ }
11250
+ const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11251
+ const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11252
+ return { text, isPlaceholder, imageUrl: null, href, alt: text };
11253
+ }
11254
+ function applyLogoFromContent(content) {
11255
+ 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);
11256
+ if (!hasLogoIdentity) return false;
11257
+ const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11258
+ const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11259
+ const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11260
+ const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11261
+ const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11262
+ const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11263
+ if (logoImageUrl) {
11264
+ applyLogoImage(logoImageUrl, logoAlt);
11265
+ } else {
11266
+ if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11267
+ applyLogoIdentity(logoText, logoIsPlaceholder);
11268
+ }
11269
+ const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11270
+ if (typeof logoHref === "string" && logoHref.trim()) {
11271
+ applyLogoHref(logoHref);
11272
+ }
11273
+ return true;
11274
+ }
11275
+
11276
+ // src/lib/logo-size.ts
11277
+ var LOGO_SIZE_DEFAULTS = {
11278
+ navbar: 28,
11279
+ footer: 32
11280
+ };
11281
+ var LOGO_SIZE_MIN = 16;
11282
+ var LOGO_SIZE_MAX = 80;
11283
+ var LOGO_SIZE_DESKTOP_KEYS = {
11284
+ navbar: "nav-logo-size",
11285
+ footer: "footer-logo-size"
11286
+ };
11287
+ var LOGO_SIZE_MOBILE_KEYS = {
11288
+ navbar: "nav-logo-size-mobile",
11289
+ footer: "footer-logo-size-mobile"
11290
+ };
11291
+ var LOGO_SIZE_KEYS = [
11292
+ LOGO_SIZE_DESKTOP_KEYS.navbar,
11293
+ LOGO_SIZE_DESKTOP_KEYS.footer,
11294
+ LOGO_SIZE_MOBILE_KEYS.navbar,
11295
+ LOGO_SIZE_MOBILE_KEYS.footer
11296
+ ];
11297
+ function isFooterLogoRoot2(root) {
11298
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11299
+ }
11300
+ function getLogoPlacement(root) {
11301
+ return isFooterLogoRoot2(root) ? "footer" : "navbar";
11302
+ }
11303
+ function parseLogoSizePx(raw, fallback) {
11304
+ if (raw == null || raw === "") return fallback;
11305
+ const n = Number.parseFloat(raw);
11306
+ if (!Number.isFinite(n)) return fallback;
11307
+ return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11308
+ }
11309
+ function isMobileLogoSizeFollowing(content, placement) {
11310
+ const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11311
+ return raw == null || raw.trim() === "";
11312
+ }
11313
+ function resolveDesktopLogoSize(content, placement) {
11314
+ return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11315
+ }
11316
+ function resolveMobileLogoSize(content, placement) {
11317
+ if (isMobileLogoSizeFollowing(content, placement)) {
11318
+ return resolveDesktopLogoSize(content, placement);
11319
+ }
11320
+ return parseLogoSizePx(
11321
+ content[LOGO_SIZE_MOBILE_KEYS[placement]],
11322
+ resolveDesktopLogoSize(content, placement)
11323
+ );
11324
+ }
11325
+ function setRootSizeVars(root, desktopPx, mobilePx, following) {
11326
+ root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11327
+ if (following) {
11328
+ root.style.removeProperty("--ohw-logo-size-mobile");
11329
+ } else {
11330
+ root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11331
+ }
11332
+ root.querySelectorAll("img").forEach((img) => {
11333
+ img.style.height = "";
11334
+ img.style.maxHeight = "none";
11335
+ img.style.width = "auto";
11336
+ img.style.objectFit = "contain";
11337
+ });
11338
+ }
11339
+ function applyLogoSizes(content) {
11340
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11341
+ const placement = getLogoPlacement(root);
11342
+ const desktop = resolveDesktopLogoSize(content, placement);
11343
+ const following = isMobileLogoSizeFollowing(content, placement);
11344
+ const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11345
+ setRootSizeVars(root, desktop, mobile, following);
11346
+ });
11347
+ }
11348
+ function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11349
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11350
+ if (getLogoPlacement(root) !== placement) return;
11351
+ setRootSizeVars(root, desktopPx, mobilePx, following);
11352
+ });
11353
+ }
11354
+ function logoHasUploadedImage(logoEl) {
11355
+ if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11356
+ 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");
11357
+ if (!img) return false;
11358
+ const src = img.getAttribute("src")?.trim() ?? "";
11359
+ if (!src || src.startsWith("data:")) return false;
11360
+ if (img.style.display === "none") return false;
11361
+ return true;
11362
+ }
11363
+ function getLogoInteractionRect(logoEl) {
11364
+ if (logoHasUploadedImage(logoEl)) {
11365
+ 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");
11366
+ if (img) {
11367
+ const r2 = img.getBoundingClientRect();
11368
+ if (r2.width > 0 && r2.height > 0) return r2;
11369
+ }
11370
+ }
11371
+ const text = logoEl.querySelector(
11372
+ '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11373
+ );
11374
+ if (text) {
11375
+ const style = window.getComputedStyle(text);
11376
+ if (style.display !== "none" && style.visibility !== "hidden") {
11377
+ const r2 = text.getBoundingClientRect();
11378
+ if (r2.width > 0 && r2.height > 0) return r2;
11379
+ }
11380
+ }
11381
+ return logoEl.getBoundingClientRect();
11382
+ }
11383
+ function readLogoSizeState(content, placement) {
11384
+ const desktopPx = resolveDesktopLogoSize(content, placement);
11385
+ const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11386
+ const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11387
+ return { desktopPx, mobilePx, mobileFollowing };
11388
+ }
11389
+
11390
+ // src/lib/site-wide-scope.ts
11391
+ function getLogoElement(el) {
11392
+ const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11393
+ if (marked) return marked;
11394
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11395
+ const root = el.closest("nav, [data-ohw-nav-root], footer");
11396
+ if (!root) return null;
11397
+ const anchor = el.closest("a");
11398
+ if (anchor && root.contains(anchor) && !anchor.hasAttribute("data-ohw-href-key") && !anchor.closest("[data-ohw-nav-container]") && Boolean(anchor.querySelector("img") || anchor.matches("img"))) {
11399
+ return anchor;
11400
+ }
11401
+ const img = el.matches("img") ? el : null;
11402
+ if (img && !img.closest("[data-ohw-href-key]") && !img.closest("[data-ohw-nav-container]") && (img.closest("footer") || img.closest("nav, [data-ohw-nav-root]"))) {
11403
+ return img;
11404
+ }
11405
+ return null;
11406
+ }
11407
+ function isInFooter(el) {
11408
+ if (!el) return false;
11409
+ return Boolean(el.closest("footer") || el.closest('[data-ohw-section="footer"]'));
11410
+ }
11411
+ function isSiteWideElement(el) {
11412
+ if (!el) return false;
11413
+ if (getLogoElement(el)) return true;
11414
+ if (isInFooter(el)) return true;
11415
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-root")) {
11416
+ return true;
11417
+ }
11418
+ if (el.hasAttribute("data-ohw-href-key") && el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer], aside")) {
11419
+ return true;
11420
+ }
11421
+ if (el.closest('[data-ohw-role="navbar-button"]')) return true;
11422
+ return false;
11423
+ }
11424
+ function isSiteWideScopeActive(args) {
11425
+ return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11426
+ }
11427
+
10886
11428
  // src/lib/add-footer-column.ts
10887
11429
  function buildFooterColumnEditContentPatch(result) {
10888
11430
  return {
@@ -10988,6 +11530,7 @@ function FloatingPanel({
10988
11530
  e.stopPropagation();
10989
11531
  const el = e.currentTarget;
10990
11532
  el.setPointerCapture(e.pointerId);
11533
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10991
11534
  dragRef.current = {
10992
11535
  pointerId: e.pointerId,
10993
11536
  startX: e.clientX,
@@ -11020,11 +11563,17 @@ function FloatingPanel({
11020
11563
  const drag = dragRef.current;
11021
11564
  if (!drag || drag.pointerId !== e.pointerId) return;
11022
11565
  dragRef.current = null;
11566
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11023
11567
  try {
11024
11568
  e.currentTarget.releasePointerCapture(e.pointerId);
11025
11569
  } catch {
11026
11570
  }
11027
11571
  }, []);
11572
+ (0, import_react13.useEffect)(() => {
11573
+ if (open) return;
11574
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11575
+ }, [open]);
11576
+ (0, import_react13.useEffect)(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
11028
11577
  if (!open) return null;
11029
11578
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11030
11579
  "div",
@@ -11091,16 +11640,127 @@ function FloatingPanel({
11091
11640
  );
11092
11641
  }
11093
11642
 
11094
- // src/ui/socials-display-panel.tsx
11643
+ // src/ui/logo-size-panel.tsx
11644
+ var import_lucide_react14 = require("lucide-react");
11095
11645
  var import_jsx_runtime27 = require("react/jsx-runtime");
11646
+ function SizeSlider({
11647
+ value,
11648
+ onChange
11649
+ }) {
11650
+ const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11651
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-3", children: [
11652
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11653
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11654
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11655
+ value,
11656
+ " px"
11657
+ ] })
11658
+ ] }),
11659
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11660
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11661
+ "div",
11662
+ {
11663
+ className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11664
+ style: { width: `${pct}%` }
11665
+ }
11666
+ ),
11667
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11668
+ "input",
11669
+ {
11670
+ type: "range",
11671
+ min: LOGO_SIZE_MIN,
11672
+ max: LOGO_SIZE_MAX,
11673
+ step: 1,
11674
+ value,
11675
+ "aria-label": "Logo size",
11676
+ className: cn(
11677
+ "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11678
+ "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11679
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11680
+ "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11681
+ "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11682
+ "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11683
+ "[&::-moz-range-thumb]:bg-background"
11684
+ ),
11685
+ onChange: (e) => onChange(Number(e.target.value))
11686
+ }
11687
+ )
11688
+ ] })
11689
+ ] });
11690
+ }
11691
+ function LogoSizePanel({
11692
+ viewport,
11693
+ sizePx,
11694
+ mobileFollowing = true,
11695
+ onSizeChange,
11696
+ onCustomizeMobile,
11697
+ onResetMobile,
11698
+ onUpdateEverywhere,
11699
+ className
11700
+ }) {
11701
+ const showFollowing = viewport === "mobile" && mobileFollowing;
11702
+ const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11703
+ const showDesktopSlider = viewport === "desktop";
11704
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11705
+ showFollowing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
11706
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start gap-1", children: [
11707
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.Link, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11708
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11709
+ ] }),
11710
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("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." }),
11711
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11712
+ Button,
11713
+ {
11714
+ type: "button",
11715
+ variant: "outline",
11716
+ size: "sm",
11717
+ className: "h-9 w-full min-w-0 cursor-pointer",
11718
+ onClick: onCustomizeMobile,
11719
+ children: "Customize for mobile"
11720
+ }
11721
+ )
11722
+ ] }) : null,
11723
+ showDesktopSlider || showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11724
+ showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11725
+ Button,
11726
+ {
11727
+ type: "button",
11728
+ variant: "outline",
11729
+ size: "sm",
11730
+ className: "h-9 w-full min-w-0 cursor-pointer",
11731
+ onClick: onResetMobile,
11732
+ children: "Reset to desktop size"
11733
+ }
11734
+ ) : null,
11735
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-px w-full bg-border", role: "separator" }),
11736
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11737
+ Button,
11738
+ {
11739
+ type: "button",
11740
+ variant: "outline",
11741
+ size: "sm",
11742
+ className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11743
+ onClick: onUpdateEverywhere,
11744
+ children: [
11745
+ "Update logo everywhere",
11746
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.ArrowUpRight, { size: 16, "aria-hidden": true })
11747
+ ]
11748
+ }
11749
+ ),
11750
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11751
+ ] });
11752
+ }
11753
+
11754
+ // src/ui/socials-display-panel.tsx
11755
+ var import_jsx_runtime28 = require("react/jsx-runtime");
11096
11756
  function DisplaySwitch({
11097
11757
  label,
11098
11758
  checked,
11099
11759
  disabled,
11100
11760
  onChange
11101
11761
  }) {
11102
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2", children: [
11103
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11762
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex w-full items-center gap-2", children: [
11763
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11104
11764
  "span",
11105
11765
  {
11106
11766
  className: cn(
@@ -11110,7 +11770,7 @@ function DisplaySwitch({
11110
11770
  children: label
11111
11771
  }
11112
11772
  ),
11113
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11773
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11114
11774
  "button",
11115
11775
  {
11116
11776
  type: "button",
@@ -11124,7 +11784,7 @@ function DisplaySwitch({
11124
11784
  checked ? "bg-primary" : "bg-primary-50",
11125
11785
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
11126
11786
  ),
11127
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11787
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11128
11788
  "span",
11129
11789
  {
11130
11790
  className: cn(
@@ -11138,8 +11798,8 @@ function DisplaySwitch({
11138
11798
  ] });
11139
11799
  }
11140
11800
  function SocialsDisplayPanel({ display, onChange, className }) {
11141
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11142
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11801
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11802
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11143
11803
  DisplaySwitch,
11144
11804
  {
11145
11805
  label: "Text",
@@ -11148,7 +11808,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
11148
11808
  onChange: (text) => onChange({ ...display, text })
11149
11809
  }
11150
11810
  ),
11151
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11811
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11152
11812
  DisplaySwitch,
11153
11813
  {
11154
11814
  label: "Icon",
@@ -11708,8 +12368,8 @@ function useNavItemDrag({
11708
12368
  }
11709
12369
 
11710
12370
  // src/ui/footer-container-chrome.tsx
11711
- var import_lucide_react14 = require("lucide-react");
11712
- var import_jsx_runtime28 = require("react/jsx-runtime");
12371
+ var import_lucide_react15 = require("lucide-react");
12372
+ var import_jsx_runtime29 = require("react/jsx-runtime");
11713
12373
  function FooterContainerChrome({
11714
12374
  rect,
11715
12375
  onAdd,
@@ -11717,7 +12377,7 @@ function FooterContainerChrome({
11717
12377
  }) {
11718
12378
  const chromeGap = 6;
11719
12379
  const buttonMargin = 7;
11720
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
12380
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11721
12381
  "div",
11722
12382
  {
11723
12383
  "data-ohw-footer-container-chrome": "",
@@ -11729,8 +12389,8 @@ function FooterContainerChrome({
11729
12389
  width: rect.width + chromeGap * 2,
11730
12390
  height: rect.height + chromeGap * 2
11731
12391
  },
11732
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(Tooltip, { children: [
11733
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
12392
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Tooltip, { children: [
12393
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11734
12394
  "button",
11735
12395
  {
11736
12396
  type: "button",
@@ -11749,10 +12409,10 @@ function FooterContainerChrome({
11749
12409
  if (addDisabled) return;
11750
12410
  onAdd();
11751
12411
  },
11752
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react14.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12412
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
11753
12413
  }
11754
12414
  ) }),
11755
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12415
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
11756
12416
  ] })
11757
12417
  }
11758
12418
  ) });
@@ -11935,6 +12595,18 @@ function collectEditableNodes(extraContent, root = document) {
11935
12595
  }
11936
12596
  if (extraContent && !isScoped) {
11937
12597
  applyNavFooterDeleteOverrides(byKey, extraContent);
12598
+ for (const key of LOGO_IMAGE_KEYS) {
12599
+ if (!(key in extraContent)) continue;
12600
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12601
+ }
12602
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12603
+ if (!(key in extraContent)) continue;
12604
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12605
+ }
12606
+ for (const key of LOGO_SIZE_KEYS) {
12607
+ if (!(key in extraContent)) continue;
12608
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12609
+ }
11938
12610
  }
11939
12611
  return Array.from(byKey.values());
11940
12612
  }
@@ -12200,14 +12872,14 @@ function deleteSelectedNavFooterItem(deps) {
12200
12872
  }
12201
12873
 
12202
12874
  // src/ui/navbar-container-chrome.tsx
12203
- var import_lucide_react15 = require("lucide-react");
12204
- var import_jsx_runtime29 = require("react/jsx-runtime");
12875
+ var import_lucide_react16 = require("lucide-react");
12876
+ var import_jsx_runtime30 = require("react/jsx-runtime");
12205
12877
  function NavbarContainerChrome({
12206
12878
  rect,
12207
12879
  onAdd
12208
12880
  }) {
12209
12881
  const chromeGap = 6;
12210
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12882
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12211
12883
  "div",
12212
12884
  {
12213
12885
  "data-ohw-navbar-container-chrome": "",
@@ -12219,7 +12891,7 @@ function NavbarContainerChrome({
12219
12891
  width: rect.width + chromeGap * 2,
12220
12892
  height: rect.height + chromeGap * 2
12221
12893
  },
12222
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12894
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12223
12895
  "button",
12224
12896
  {
12225
12897
  type: "button",
@@ -12236,7 +12908,7 @@ function NavbarContainerChrome({
12236
12908
  e.stopPropagation();
12237
12909
  onAdd();
12238
12910
  },
12239
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12911
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react16.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12240
12912
  }
12241
12913
  )
12242
12914
  }
@@ -12245,7 +12917,7 @@ function NavbarContainerChrome({
12245
12917
 
12246
12918
  // src/ui/drop-indicator.tsx
12247
12919
  var React10 = __toESM(require("react"), 1);
12248
- var import_jsx_runtime30 = require("react/jsx-runtime");
12920
+ var import_jsx_runtime31 = require("react/jsx-runtime");
12249
12921
  var dropIndicatorVariants = cva(
12250
12922
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
12251
12923
  {
@@ -12269,7 +12941,7 @@ var dropIndicatorVariants = cva(
12269
12941
  );
12270
12942
  var DropIndicator = React10.forwardRef(
12271
12943
  ({ className, direction, state, ...props }, ref) => {
12272
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12944
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12273
12945
  "div",
12274
12946
  {
12275
12947
  ref,
@@ -12286,7 +12958,7 @@ var DropIndicator = React10.forwardRef(
12286
12958
  DropIndicator.displayName = "DropIndicator";
12287
12959
 
12288
12960
  // src/ui/badge.tsx
12289
- var import_jsx_runtime31 = require("react/jsx-runtime");
12961
+ var import_jsx_runtime32 = require("react/jsx-runtime");
12290
12962
  var badgeVariants = cva(
12291
12963
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
12292
12964
  {
@@ -12304,12 +12976,12 @@ var badgeVariants = cva(
12304
12976
  }
12305
12977
  );
12306
12978
  function Badge({ className, variant, ...props }) {
12307
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
12979
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
12308
12980
  }
12309
12981
 
12310
12982
  // src/OhhwellsBridge.tsx
12311
- var import_lucide_react16 = require("lucide-react");
12312
- var import_jsx_runtime32 = require("react/jsx-runtime");
12983
+ var import_lucide_react17 = require("lucide-react");
12984
+ var import_jsx_runtime33 = require("react/jsx-runtime");
12313
12985
  var PRIMARY3 = "#0885FE";
12314
12986
  var IMAGE_FADE_MS = 300;
12315
12987
  function runOpacityFade(el, onDone) {
@@ -12403,21 +13075,10 @@ function parseSchedulingInsertAfter(insertAfter) {
12403
13075
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
12404
13076
  };
12405
13077
  }
12406
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
12407
- const parsed = parseSchedulingInsertAfter(insertAfter);
12408
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
12409
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
12410
- return { effectiveInsertAfter, insertBefore };
12411
- }
12412
- function getSchedulingMountPoint(insertAfter) {
12413
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
12414
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
12415
- if (!anchorEl && anchor === "scheduling") {
12416
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
12417
- anchorEl = widgets.at(-1) ?? null;
12418
- }
12419
- if (!anchorEl) return null;
12420
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13078
+ function resolveEntryAnchor(entry) {
13079
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13080
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13081
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
12421
13082
  }
12422
13083
  function schedulingMountDepth(insertAfter) {
12423
13084
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -12434,8 +13095,7 @@ function getPageSchedulingEntries(raw) {
12434
13095
  }
12435
13096
  }
12436
13097
  function isSchedulingWidgetMissing(entry) {
12437
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
12438
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13098
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
12439
13099
  }
12440
13100
  function hasMissingSchedulingWidgets(entries) {
12441
13101
  return entries.some(isSchedulingWidgetMissing);
@@ -12465,16 +13125,17 @@ function initSectionsFromContent(content, removeExisting = false) {
12465
13125
  } catch {
12466
13126
  }
12467
13127
  }
12468
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
12469
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
12470
- const sectionId = schedulingSectionId(effectiveInsertAfter);
13128
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13129
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13130
+ const sectionId = schedulingSectionId(widgetId);
12471
13131
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
12472
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
12473
- if (!mountPoint) return false;
13132
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13133
+ if (!anchorEl) return false;
13134
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12474
13135
  const container = document.createElement("div");
12475
13136
  container.dataset.ohwSectionContainer = "scheduling";
12476
- if (insertBefore) {
12477
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13137
+ if (beforeId) {
13138
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
12478
13139
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
12479
13140
  if (!beforePoint) return false;
12480
13141
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -12485,19 +13146,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12485
13146
  }
12486
13147
  tail.insertAdjacentElement("afterend", container);
12487
13148
  }
12488
- const root = (0, import_client2.createRoot)(container);
12489
- (0, import_react_dom3.flushSync)(() => {
12490
- root.render(
12491
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12492
- SchedulingWidget,
12493
- {
12494
- notifyOnConnect,
12495
- initialScheduleId: scheduleId,
12496
- insertAfter: effectiveInsertAfter
12497
- }
12498
- )
12499
- );
12500
- });
13149
+ try {
13150
+ const root = (0, import_client2.createRoot)(container);
13151
+ (0, import_react_dom3.flushSync)(() => {
13152
+ root.render(
13153
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13154
+ SchedulingWidget,
13155
+ {
13156
+ notifyOnConnect,
13157
+ initialScheduleId: scheduleId,
13158
+ insertAfter: widgetId
13159
+ }
13160
+ )
13161
+ );
13162
+ });
13163
+ } catch (err) {
13164
+ console.error("[ow:scheduling] render threw", err);
13165
+ container.remove();
13166
+ return false;
13167
+ }
12501
13168
  const tracker = getSectionsTracker();
12502
13169
  let sections = [];
12503
13170
  try {
@@ -12505,10 +13172,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12505
13172
  } catch {
12506
13173
  }
12507
13174
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
12508
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13175
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
12509
13176
  sections.push({
12510
13177
  type: "scheduling",
12511
- insertAfter: effectiveInsertAfter,
13178
+ insertAfter: widgetId,
13179
+ anchorId,
13180
+ beforeId: beforeId ?? null,
12512
13181
  pagePath: window.location.pathname,
12513
13182
  ...scheduleId ? { scheduleId } : {}
12514
13183
  });
@@ -12522,7 +13191,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
12522
13191
  for (let i = pending.length - 1; i >= 0; i--) {
12523
13192
  const entry = pending[i];
12524
13193
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
12525
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13194
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
13195
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
12526
13196
  pending.splice(i, 1);
12527
13197
  }
12528
13198
  }
@@ -12666,6 +13336,13 @@ function isInsideLinkEditor(target) {
12666
13336
  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"]')
12667
13337
  );
12668
13338
  }
13339
+ function isInsideFloatingPanel(target) {
13340
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
13341
+ }
13342
+ function isPointOverFloatingPanel(clientX, clientY) {
13343
+ const el = document.elementFromPoint(clientX, clientY);
13344
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13345
+ }
12669
13346
  function getHrefKeyFromElement(el) {
12670
13347
  if (!el) return null;
12671
13348
  const anchor = el.closest("[data-ohw-href-key]");
@@ -12803,6 +13480,11 @@ function isNavbarLinksContainer2(el) {
12803
13480
  function getFooterColumn(el) {
12804
13481
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12805
13482
  }
13483
+ function isFooterAddItemDisabled(selected) {
13484
+ if (!selected) return false;
13485
+ const column = resolveFooterColumnForAdd(selected);
13486
+ return column ? !canAddFooterItem(column) : false;
13487
+ }
12806
13488
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12807
13489
  if (getNavigationItemAnchor(target)) return null;
12808
13490
  const column = getFooterColumn(target);
@@ -12898,7 +13580,7 @@ function getNavigationSelectionParent(el) {
12898
13580
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
12899
13581
  return getFooterLinksContainer();
12900
13582
  }
12901
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13583
+ 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)) {
12902
13584
  return getNavigationRoot(el);
12903
13585
  }
12904
13586
  return null;
@@ -13116,6 +13798,9 @@ var ICONS = {
13116
13798
  var SELECTION_CHROME_GAP2 = 4;
13117
13799
  var TOOLBAR_STROKE_GAP2 = 4;
13118
13800
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
13801
+ var HOVER_STROKE_WIDTH = 1.5;
13802
+ var TEXT_HOVER_SIDE_PAD = 4;
13803
+ var CHROME_PRIMARY = `var(--ohw-primary, ${PRIMARY3})`;
13119
13804
  var TOOLBAR_GROUPS = [
13120
13805
  [
13121
13806
  { cmd: "bold", title: "Bold" },
@@ -13141,7 +13826,7 @@ function EditGlowChrome({
13141
13826
  hideHandle = false
13142
13827
  }) {
13143
13828
  const GAP = SELECTION_CHROME_GAP2;
13144
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
13829
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
13145
13830
  "div",
13146
13831
  {
13147
13832
  ref: elRef,
@@ -13156,7 +13841,7 @@ function EditGlowChrome({
13156
13841
  zIndex: 2147483646
13157
13842
  },
13158
13843
  children: [
13159
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
13844
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13160
13845
  "div",
13161
13846
  {
13162
13847
  style: {
@@ -13169,7 +13854,7 @@ function EditGlowChrome({
13169
13854
  }
13170
13855
  }
13171
13856
  ),
13172
- reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
13857
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13173
13858
  "div",
13174
13859
  {
13175
13860
  "data-ohw-drag-handle-container": "",
@@ -13181,7 +13866,7 @@ function EditGlowChrome({
13181
13866
  transform: "translate(calc(-100% - 7px), -50%)",
13182
13867
  pointerEvents: dragDisabled ? "none" : "auto"
13183
13868
  },
13184
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
13869
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13185
13870
  DragHandle,
13186
13871
  {
13187
13872
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -13391,7 +14076,7 @@ function FloatingToolbar({
13391
14076
  return () => ro.disconnect();
13392
14077
  }, [showEditLink, activeCommands]);
13393
14078
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
13394
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14079
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13395
14080
  "div",
13396
14081
  {
13397
14082
  ref: setRefs,
@@ -13403,12 +14088,12 @@ function FloatingToolbar({
13403
14088
  zIndex: 2147483647,
13404
14089
  pointerEvents: "auto"
13405
14090
  },
13406
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(CustomToolbar, { children: [
13407
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react16.default.Fragment, { children: [
13408
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CustomToolbarDivider, {}),
14091
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(CustomToolbar, { children: [
14092
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react16.default.Fragment, { children: [
14093
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CustomToolbarDivider, {}),
13409
14094
  btns.map((btn) => {
13410
14095
  const isActive = activeCommands.has(btn.cmd);
13411
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14096
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13412
14097
  CustomToolbarButton,
13413
14098
  {
13414
14099
  title: btn.title,
@@ -13417,7 +14102,7 @@ function FloatingToolbar({
13417
14102
  e.preventDefault();
13418
14103
  onCommand(btn.cmd);
13419
14104
  },
13420
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14105
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13421
14106
  "svg",
13422
14107
  {
13423
14108
  width: "16",
@@ -13438,7 +14123,7 @@ function FloatingToolbar({
13438
14123
  );
13439
14124
  })
13440
14125
  ] }, gi)),
13441
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14126
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13442
14127
  CustomToolbarButton,
13443
14128
  {
13444
14129
  type: "button",
@@ -13452,7 +14137,7 @@ function FloatingToolbar({
13452
14137
  e.preventDefault();
13453
14138
  e.stopPropagation();
13454
14139
  },
13455
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react16.Link, { className: "size-4 shrink-0", "aria-hidden": true })
14140
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Link, { className: "size-4 shrink-0", "aria-hidden": true })
13456
14141
  }
13457
14142
  ) : null
13458
14143
  ] })
@@ -13469,7 +14154,7 @@ function StateToggle({
13469
14154
  states,
13470
14155
  onStateChange
13471
14156
  }) {
13472
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14157
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13473
14158
  ToggleGroup,
13474
14159
  {
13475
14160
  "data-ohw-state-toggle": "",
@@ -13483,11 +14168,12 @@ function StateToggle({
13483
14168
  left: rect.right - 8,
13484
14169
  transform: "translateX(-100%)"
13485
14170
  },
13486
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
14171
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
13487
14172
  }
13488
14173
  );
13489
14174
  }
13490
14175
  var contentCache = /* @__PURE__ */ new Map();
14176
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
13491
14177
  function resolveSubdomain(subdomainFromQuery) {
13492
14178
  if (subdomainFromQuery) return subdomainFromQuery;
13493
14179
  if (typeof window !== "undefined") {
@@ -13582,8 +14268,14 @@ function OhhwellsBridge() {
13582
14268
  });
13583
14269
  const selectFrameRef = (0, import_react16.useRef)(() => {
13584
14270
  });
14271
+ const selectLogoRef = (0, import_react16.useRef)(() => {
14272
+ });
14273
+ const openLogoSizePanelRef = (0, import_react16.useRef)(() => {
14274
+ });
13585
14275
  const deselectRef = (0, import_react16.useRef)(() => {
13586
14276
  });
14277
+ const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(() => {
14278
+ });
13587
14279
  const reselectNavigationItemRef = (0, import_react16.useRef)(() => {
13588
14280
  });
13589
14281
  const commitNavigationTextEditRef = (0, import_react16.useRef)(() => {
@@ -13616,15 +14308,34 @@ function OhhwellsBridge() {
13616
14308
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react16.useState)(null);
13617
14309
  const hoveredItemElRef = (0, import_react16.useRef)(null);
13618
14310
  const [hoveredItemRect, setHoveredItemRect] = (0, import_react16.useState)(null);
14311
+ const [hoveredTextRect, setHoveredTextRect] = (0, import_react16.useState)(null);
14312
+ (0, import_react16.useEffect)(() => {
14313
+ const sync = () => {
14314
+ const el = document.querySelector(
14315
+ "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
14316
+ );
14317
+ const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
14318
+ if (!target) {
14319
+ setHoveredTextRect(null);
14320
+ return;
14321
+ }
14322
+ const r2 = target.getBoundingClientRect();
14323
+ setHoveredTextRect(new DOMRect(r2.x - TEXT_HOVER_SIDE_PAD, r2.y, r2.width + TEXT_HOVER_SIDE_PAD * 2, r2.height));
14324
+ };
14325
+ const observer = new MutationObserver(sync);
14326
+ observer.observe(document.documentElement, {
14327
+ attributes: true,
14328
+ attributeFilter: ["data-ohw-hovered"],
14329
+ subtree: true
14330
+ });
14331
+ return () => observer.disconnect();
14332
+ }, []);
13619
14333
  const siblingHintElRef = (0, import_react16.useRef)(null);
13620
14334
  const [siblingHintRect, setSiblingHintRect] = (0, import_react16.useState)(null);
13621
14335
  const [siblingHintRects, setSiblingHintRects] = (0, import_react16.useState)([]);
13622
14336
  const [isItemDragging, setIsItemDragging] = (0, import_react16.useState)(false);
13623
14337
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react16.useState)(false);
13624
14338
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
13625
- const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
13626
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
13627
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13628
14339
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
13629
14340
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
13630
14341
  const footerDragRef = (0, import_react16.useRef)(null);
@@ -13639,7 +14350,15 @@ function OhhwellsBridge() {
13639
14350
  const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
13640
14351
  const editContentRef = (0, import_react16.useRef)({});
13641
14352
  const aiSectionsRef = (0, import_react16.useRef)("");
14353
+ const brandKitRef = (0, import_react16.useRef)("");
13642
14354
  const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14355
+ const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14356
+ const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14357
+ const setFloatingPanelRef = (0, import_react16.useRef)(setFloatingPanel);
14358
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14359
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react16.useState)(null);
14360
+ const [editorViewport, setEditorViewport] = (0, import_react16.useState)("desktop");
14361
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13643
14362
  const [sitePages, setSitePages] = (0, import_react16.useState)([]);
13644
14363
  const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
13645
14364
  const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
@@ -13648,7 +14367,18 @@ function OhhwellsBridge() {
13648
14367
  const linkPopoverOpenRef = (0, import_react16.useRef)(false);
13649
14368
  const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
13650
14369
  setLinkPopoverRef.current = setLinkPopover;
14370
+ setFloatingPanelRef.current = setFloatingPanel;
13651
14371
  linkPopoverSessionRef.current = linkPopover;
14372
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
14373
+ (0, import_react16.useEffect)(() => {
14374
+ const syncViewport = () => {
14375
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14376
+ setEditorViewport((prev) => prev === next ? prev : next);
14377
+ };
14378
+ syncViewport();
14379
+ window.addEventListener("resize", syncViewport);
14380
+ return () => window.removeEventListener("resize", syncViewport);
14381
+ }, []);
13652
14382
  const {
13653
14383
  navDragRef,
13654
14384
  navDropSlots,
@@ -13871,6 +14601,10 @@ function OhhwellsBridge() {
13871
14601
  setIsItemDragging(false);
13872
14602
  hoveredNavContainerRef.current = null;
13873
14603
  setHoveredNavContainerRect(null);
14604
+ hoveredItemElRef.current = null;
14605
+ setHoveredItemRect(null);
14606
+ setFloatingPanel(null);
14607
+ setLogoSizeDraft(null);
13874
14608
  if (!activeElRef.current) {
13875
14609
  setNavGroupForceOpen(null, false);
13876
14610
  setToolbarRect(null);
@@ -14067,6 +14801,14 @@ function OhhwellsBridge() {
14067
14801
  if (!selected) return;
14068
14802
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14069
14803
  if (socialsRow) {
14804
+ if (!canAddSocialItem(socialsRow)) {
14805
+ postToParent2({
14806
+ type: "ow:toast",
14807
+ title: `Maximum ${MAX_SOCIAL_ITEMS_PER_ROW} social icons`,
14808
+ toastType: "error"
14809
+ });
14810
+ return;
14811
+ }
14070
14812
  const after = getSocialItem(selected);
14071
14813
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14072
14814
  if (!result2) return;
@@ -14088,10 +14830,16 @@ function OhhwellsBridge() {
14088
14830
  return;
14089
14831
  }
14090
14832
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14091
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14092
- }
14093
- const column = (selected.hasAttribute("data-ohw-footer-col") ? selected : null) ?? selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
14833
+ const column = resolveFooterColumnForAdd(selected);
14094
14834
  if (!column) return;
14835
+ if (!canAddFooterItem(column)) {
14836
+ postToParent2({
14837
+ type: "ow:toast",
14838
+ title: `Maximum ${MAX_FOOTER_ITEMS_PER_COLUMN} items per column`,
14839
+ toastType: "error"
14840
+ });
14841
+ return;
14842
+ }
14095
14843
  const result2 = insertFooterItem(column, "/", "New link", null);
14096
14844
  applyLinkByKey(result2.hrefKey, result2.href);
14097
14845
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -14562,6 +15310,8 @@ function OhhwellsBridge() {
14562
15310
  setToolbarRect(anchor.getBoundingClientRect());
14563
15311
  setToolbarShowEditLink(false);
14564
15312
  setActiveCommands(/* @__PURE__ */ new Set());
15313
+ setFloatingPanel(null);
15314
+ setLogoSizeDraft(null);
14565
15315
  }, [deactivate, markSelected]);
14566
15316
  const selectFrame = (0, import_react16.useCallback)((el) => {
14567
15317
  if (!isNavigationContainer(el)) return;
@@ -14611,7 +15361,51 @@ function OhhwellsBridge() {
14611
15361
  setToolbarRect(el.getBoundingClientRect());
14612
15362
  setToolbarShowEditLink(false);
14613
15363
  setActiveCommands(/* @__PURE__ */ new Set());
15364
+ setFloatingPanel(null);
15365
+ setLogoSizeDraft(null);
14614
15366
  }, [deactivate, markSelected, postToParent2]);
15367
+ const selectLogo = (0, import_react16.useCallback)(
15368
+ (logoEl) => {
15369
+ if (activeElRef.current) deactivate();
15370
+ selectedElRef.current = logoEl;
15371
+ selectedHrefKeyRef.current = null;
15372
+ selectedFooterColAttrRef.current = null;
15373
+ markSelected(logoEl);
15374
+ setSelectedIsCta(false);
15375
+ setSelectedIsSocial(false);
15376
+ setSelectedIsSocialsRow(false);
15377
+ clearHrefKeyHover(logoEl);
15378
+ hoveredNavContainerRef.current = null;
15379
+ setHoveredNavContainerRect(null);
15380
+ setHoveredItemRect(null);
15381
+ hoveredItemElRef.current = null;
15382
+ siblingHintElRef.current = null;
15383
+ setSiblingHintRect(null);
15384
+ setSiblingHintRects([]);
15385
+ setIsItemDragging(false);
15386
+ setReorderHrefKey(null);
15387
+ setReorderDragDisabled(false);
15388
+ setIsFooterFrameSelection(false);
15389
+ setToolbarVariant("logo");
15390
+ setToolbarRect(getLogoInteractionRect(logoEl));
15391
+ setToolbarShowEditLink(false);
15392
+ setActiveCommands(/* @__PURE__ */ new Set());
15393
+ },
15394
+ [deactivate, markSelected]
15395
+ );
15396
+ const openLogoSizePanel = (0, import_react16.useCallback)((logoEl) => {
15397
+ const placement = getLogoPlacement(logoEl);
15398
+ const draft = readLogoSizeState(editContentRef.current, placement);
15399
+ setLogoSizeDraft(draft);
15400
+ setParentScrollSnap(parentScrollRef.current);
15401
+ setFloatingPanel({
15402
+ key: `logo-size:${placement}`,
15403
+ title: "Logo",
15404
+ context: placement === "navbar" ? "Navbar" : "Footer",
15405
+ kind: "logo-size",
15406
+ placement
15407
+ });
15408
+ }, []);
14615
15409
  const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
14616
15410
  setParentScrollSnap(parentScrollRef.current);
14617
15411
  setFloatingPanel({
@@ -14647,11 +15441,53 @@ function OhhwellsBridge() {
14647
15441
  );
14648
15442
  const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
14649
15443
  setFloatingPanel(null);
15444
+ setLogoSizeDraft(null);
14650
15445
  }, []);
14651
15446
  const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
14652
15447
  setFloatingPanel(null);
15448
+ setLogoSizeDraft(null);
14653
15449
  deselectRef.current();
14654
15450
  }, []);
15451
+ const persistLogoSizeDraft = (0, import_react16.useCallback)(
15452
+ (placement, draft) => {
15453
+ const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15454
+ const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15455
+ const nodes = [
15456
+ { key: desktopKey, text: String(draft.desktopPx) }
15457
+ ];
15458
+ if (draft.mobileFollowing) {
15459
+ nodes.push({ key: mobileKey, text: "" });
15460
+ } else {
15461
+ nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15462
+ }
15463
+ editContentRef.current = {
15464
+ ...editContentRef.current,
15465
+ [desktopKey]: String(draft.desktopPx),
15466
+ [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15467
+ };
15468
+ applyLogoSizeToPlacement(
15469
+ placement,
15470
+ draft.desktopPx,
15471
+ draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15472
+ draft.mobileFollowing
15473
+ );
15474
+ postToParent2({ type: "ow:change", nodes });
15475
+ requestAnimationFrame(() => {
15476
+ const selected = selectedElRef.current;
15477
+ if (!selected || toolbarVariantRef.current !== "logo") return;
15478
+ const rect = getLogoInteractionRect(selected);
15479
+ setToolbarRect(rect);
15480
+ if (glowElRef.current) {
15481
+ const GAP = SELECTION_CHROME_GAP2;
15482
+ glowElRef.current.style.top = `${rect.top - GAP}px`;
15483
+ glowElRef.current.style.left = `${rect.left - GAP}px`;
15484
+ glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15485
+ glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15486
+ }
15487
+ });
15488
+ },
15489
+ [postToParent2]
15490
+ );
14655
15491
  const activate = (0, import_react16.useCallback)((el, options) => {
14656
15492
  if (activeElRef.current === el) return;
14657
15493
  if (isIconEditable(el)) return;
@@ -14732,7 +15568,37 @@ function OhhwellsBridge() {
14732
15568
  deactivateRef.current = deactivate;
14733
15569
  selectRef.current = select;
14734
15570
  selectFrameRef.current = selectFrame;
15571
+ selectLogoRef.current = selectLogo;
15572
+ openLogoSizePanelRef.current = openLogoSizePanel;
14735
15573
  deselectRef.current = deselect;
15574
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15575
+ const lastSiteWideScopeRef = (0, import_react16.useRef)(null);
15576
+ (0, import_react16.useEffect)(() => {
15577
+ if (!isEditMode) {
15578
+ if (lastSiteWideScopeRef.current !== false) {
15579
+ lastSiteWideScopeRef.current = false;
15580
+ postToParent2({ type: "ow:site-wide-scope", active: false });
15581
+ }
15582
+ return;
15583
+ }
15584
+ const active = isSiteWideScopeActive({
15585
+ selected: selectedElRef.current,
15586
+ hoveredItem: hoveredItemElRef.current,
15587
+ hoveredNavContainer: hoveredNavContainerRef.current,
15588
+ active: activeElRef.current
15589
+ });
15590
+ if (lastSiteWideScopeRef.current === active) return;
15591
+ lastSiteWideScopeRef.current = active;
15592
+ postToParent2({ type: "ow:site-wide-scope", active });
15593
+ }, [
15594
+ isEditMode,
15595
+ hoveredItemRect,
15596
+ hoveredNavContainerRect,
15597
+ toolbarVariant,
15598
+ toolbarRect,
15599
+ isFooterFrameSelection,
15600
+ postToParent2
15601
+ ]);
14736
15602
  (0, import_react16.useLayoutEffect)(() => {
14737
15603
  if (!subdomain || isEditMode) {
14738
15604
  setFetchState("done");
@@ -14744,9 +15610,18 @@ function OhhwellsBridge() {
14744
15610
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
14745
15611
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
14746
15612
  }
15613
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15614
+ brandKitRef.current = content[BRAND_KIT_KEY];
15615
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15616
+ }
15617
+ applyBrandChrome(content);
14747
15618
  for (const [key, val] of Object.entries(content)) {
14748
15619
  if (key === "__ohw_sections") continue;
14749
15620
  if (key === AI_SECTIONS_KEY) continue;
15621
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15622
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15623
+ if (key === BRAND_KIT_KEY) continue;
15624
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14750
15625
  if (applyVideoSettingNode(key, val)) continue;
14751
15626
  if (applyCarouselNode(key, val)) continue;
14752
15627
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14781,6 +15656,8 @@ function OhhwellsBridge() {
14781
15656
  });
14782
15657
  applyLinkByKey(key, val);
14783
15658
  }
15659
+ applyLogoFromContent(content);
15660
+ applyLogoSizes(content);
14784
15661
  reconcileNavbarItemsFromContent(content);
14785
15662
  reconcileFooterOrderFromContent(content);
14786
15663
  reconcileSocialsFromContent(content);
@@ -14801,7 +15678,9 @@ function OhhwellsBridge() {
14801
15678
  let cancelled = false;
14802
15679
  setFetchState("loading");
14803
15680
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14804
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15681
+ const initialPath = pathname;
15682
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15683
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14805
15684
  if (cancelled) return;
14806
15685
  const content = data?.content ?? {};
14807
15686
  contentCache.set(subdomain, content);
@@ -14825,8 +15704,16 @@ function OhhwellsBridge() {
14825
15704
  initSectionInstancesFromContent(content, window.location.pathname);
14826
15705
  observer?.disconnect();
14827
15706
  try {
15707
+ applyBrandChrome(content);
15708
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15709
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15710
+ }
14828
15711
  for (const [key, val] of Object.entries(content)) {
14829
15712
  if (key === "__ohw_sections") continue;
15713
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15714
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15715
+ if (key === BRAND_KIT_KEY) continue;
15716
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14830
15717
  if (applyVideoSettingNode(key, val)) continue;
14831
15718
  if (applyCarouselNode(key, val)) continue;
14832
15719
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14847,6 +15734,7 @@ function OhhwellsBridge() {
14847
15734
  });
14848
15735
  applyLinkByKey(key, val);
14849
15736
  }
15737
+ applyLogoFromContent(content);
14850
15738
  reconcileNavbarItemsFromContent(content);
14851
15739
  reconcileFooterOrderFromContent(content);
14852
15740
  reconcileSocialsFromContent(content);
@@ -14861,6 +15749,17 @@ function OhhwellsBridge() {
14861
15749
  debounceTimer = setTimeout(applyFromCache, 150);
14862
15750
  };
14863
15751
  applyFromCache();
15752
+ const pathCacheKey = `${subdomain}::${pathname}`;
15753
+ if (!fetchedContentPaths.has(pathCacheKey)) {
15754
+ fetchedContentPaths.add(pathCacheKey);
15755
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15756
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15757
+ if (!data?.content) return;
15758
+ contentCache.set(subdomain, data.content);
15759
+ applyFromCache();
15760
+ }).catch(() => {
15761
+ });
15762
+ }
14864
15763
  observer = new MutationObserver(scheduleApply);
14865
15764
  observer.observe(document.body, { childList: true, subtree: true });
14866
15765
  return () => {
@@ -14954,26 +15853,31 @@ function OhhwellsBridge() {
14954
15853
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
14955
15854
  (0, import_react16.useEffect)(() => {
14956
15855
  if (!isEditMode) return;
15856
+ let lastPosted = 0;
14957
15857
  const measure = () => {
14958
15858
  const h = document.body.scrollHeight;
14959
- if (h > 50) postToParent2({ type: "ow:height", height: h });
15859
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
15860
+ lastPosted = h;
15861
+ postToParent2({ type: "ow:height", height: h });
15862
+ }
15863
+ };
15864
+ let raf = null;
15865
+ const schedule = () => {
15866
+ if (raf != null) return;
15867
+ raf = requestAnimationFrame(() => {
15868
+ raf = null;
15869
+ measure();
15870
+ });
14960
15871
  };
14961
15872
  const t1 = setTimeout(measure, 50);
14962
15873
  const t2 = setTimeout(measure, 500);
14963
- let lastWidth = window.innerWidth;
14964
- let resizeTimer = null;
14965
- const handleResize = () => {
14966
- if (window.innerWidth === lastWidth) return;
14967
- lastWidth = window.innerWidth;
14968
- if (resizeTimer) clearTimeout(resizeTimer);
14969
- resizeTimer = setTimeout(measure, 150);
14970
- };
14971
- window.addEventListener("resize", handleResize);
15874
+ const ro = new ResizeObserver(schedule);
15875
+ ro.observe(document.body);
14972
15876
  return () => {
14973
15877
  clearTimeout(t1);
14974
15878
  clearTimeout(t2);
14975
- if (resizeTimer) clearTimeout(resizeTimer);
14976
- window.removeEventListener("resize", handleResize);
15879
+ if (raf != null) cancelAnimationFrame(raf);
15880
+ ro.disconnect();
14977
15881
  };
14978
15882
  }, [pathname, isEditMode, postToParent2]);
14979
15883
  (0, import_react16.useEffect)(() => {
@@ -15043,9 +15947,12 @@ function OhhwellsBridge() {
15043
15947
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
15044
15948
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
15045
15949
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
15950
+ /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
15951
+ that used to draw it dashes denser than the overlay border, so identical specs
15952
+ still read as two different frames (OHH-695). The attribute stays: hover paths
15953
+ and suppression rules key off it. */
15046
15954
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
15047
- outline: 2px dashed ${PRIMARY3} !important;
15048
- outline-offset: 4px;
15955
+ outline: none !important;
15049
15956
  }
15050
15957
  [data-ohw-href-key] [data-ohw-hovered],
15051
15958
  [data-ohw-href-key][data-ohw-hovered],
@@ -15098,8 +16005,11 @@ function OhhwellsBridge() {
15098
16005
  stateViews.textContent = `
15099
16006
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15100
16007
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15101
- [data-ohw-state-hovered] { outline: 2px dashed ${PRIMARY3} !important; outline-offset: 4px; }
16008
+ [data-ohw-state-hovered] { outline: ${HOVER_STROKE_WIDTH}px dashed ${CHROME_PRIMARY} !important; outline-offset: ${HOVER_CHROME_GAP}px; }
15102
16009
  [data-ohw-state-hovered]:has([data-ohw-hovered]) { outline: none !important; }
16010
+ /* :has() only sees descendants \u2014 when the card itself is the hovered text, the overlay
16011
+ already frames it, and the card outline doubled it (OHH-695). */
16012
+ [data-ohw-state-hovered][data-ohw-hovered] { outline: none !important; }
15103
16013
  `;
15104
16014
  document.head.appendChild(base);
15105
16015
  document.head.appendChild(forceHover);
@@ -15117,10 +16027,12 @@ function OhhwellsBridge() {
15117
16027
  return;
15118
16028
  }
15119
16029
  const target = e.target;
16030
+ if (target.closest("[data-ohw-ai-review]")) return;
15120
16031
  if (target.closest("[data-ohw-toolbar]")) return;
15121
16032
  if (target.closest("[data-ohw-state-toggle]")) return;
15122
16033
  if (target.closest("[data-ohw-max-badge]")) return;
15123
16034
  if (isInsideLinkEditor(target)) return;
16035
+ if (isInsideFloatingPanel(target)) return;
15124
16036
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15125
16037
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
15126
16038
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -15184,6 +16096,21 @@ function OhhwellsBridge() {
15184
16096
  return;
15185
16097
  }
15186
16098
  }
16099
+ const logoEl = getLogoElement(target);
16100
+ if (logoEl) {
16101
+ e.preventDefault();
16102
+ e.stopPropagation();
16103
+ if (!logoHasUploadedImage(logoEl)) {
16104
+ deselectRef.current();
16105
+ deactivateRef.current();
16106
+ const identity = readLogoIdentityFromDom();
16107
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16108
+ return;
16109
+ }
16110
+ selectLogoRef.current(logoEl);
16111
+ openLogoSizePanelRef.current(logoEl);
16112
+ return;
16113
+ }
15187
16114
  const editable = target.closest("[data-ohw-editable]");
15188
16115
  if (editable) {
15189
16116
  if (editable.dataset.ohwEditable === "link") {
@@ -15336,10 +16263,12 @@ function OhhwellsBridge() {
15336
16263
  };
15337
16264
  const handleDblClick = (e) => {
15338
16265
  const target = e.target;
16266
+ if (target.closest("[data-ohw-ai-review]")) return;
15339
16267
  if (target.closest("[data-ohw-toolbar]")) return;
15340
16268
  if (target.closest("[data-ohw-state-toggle]")) return;
15341
16269
  if (target.closest("[data-ohw-max-badge]")) return;
15342
16270
  if (isInsideLinkEditor(target)) return;
16271
+ if (isInsideFloatingPanel(target)) return;
15343
16272
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15344
16273
  return;
15345
16274
  }
@@ -15367,6 +16296,16 @@ function OhhwellsBridge() {
15367
16296
  setHoveredNavContainerRect(null);
15368
16297
  return;
15369
16298
  }
16299
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
16300
+ hoveredItemElRef.current = null;
16301
+ setHoveredItemRect(null);
16302
+ hoveredNavContainerRef.current = null;
16303
+ setHoveredNavContainerRect(null);
16304
+ siblingHintElRef.current = null;
16305
+ setSiblingHintRect(null);
16306
+ setSiblingHintRects([]);
16307
+ return;
16308
+ }
15370
16309
  {
15371
16310
  const selected2 = selectedElRef.current;
15372
16311
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -15374,7 +16313,7 @@ function OhhwellsBridge() {
15374
16313
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
15375
16314
  if (allowNavContainerHover) {
15376
16315
  const navContainer = target.closest("[data-ohw-nav-container]");
15377
- if (navContainer && !getNavigationItemAnchor(target)) {
16316
+ if (navContainer && !getNavigationItemAnchor(target) && !getLogoElement(target)) {
15378
16317
  hoveredNavContainerRef.current = navContainer;
15379
16318
  setHoveredNavContainerRect(navContainer.getBoundingClientRect());
15380
16319
  hoveredItemElRef.current = null;
@@ -15403,6 +16342,15 @@ function OhhwellsBridge() {
15403
16342
  setHoveredNavContainerRect(null);
15404
16343
  }
15405
16344
  }
16345
+ const logoEl = getLogoElement(target);
16346
+ if (logoEl) {
16347
+ hoveredNavContainerRef.current = null;
16348
+ setHoveredNavContainerRect(null);
16349
+ if (selectedElRef.current === logoEl) return;
16350
+ hoveredItemElRef.current = logoEl;
16351
+ setHoveredItemRect(getLogoInteractionRect(logoEl));
16352
+ return;
16353
+ }
15406
16354
  const navAnchor = getNavigationItemAnchor(target);
15407
16355
  if (navAnchor) {
15408
16356
  hoveredNavContainerRef.current = null;
@@ -15440,6 +16388,11 @@ function OhhwellsBridge() {
15440
16388
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
15441
16389
  } else if (!isInsideNavigationItem(editable)) {
15442
16390
  hoverTarget.setAttribute("data-ohw-hovered", "");
16391
+ if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
16392
+ hoveredNavContainerRef.current = null;
16393
+ setHoveredNavContainerRect(null);
16394
+ hoveredItemElRef.current = editable;
16395
+ }
15443
16396
  }
15444
16397
  }
15445
16398
  };
@@ -15475,6 +16428,18 @@ function OhhwellsBridge() {
15475
16428
  }
15476
16429
  return;
15477
16430
  }
16431
+ const logoEl = getLogoElement(target);
16432
+ if (logoEl) {
16433
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
16434
+ if (related2 && (logoEl === related2 || logoEl.contains(related2) || related2.closest?.('[data-ohw-role="logo"], [data-ohw-logo]'))) {
16435
+ return;
16436
+ }
16437
+ if (hoveredItemElRef.current === logoEl) {
16438
+ hoveredItemElRef.current = null;
16439
+ setHoveredItemRect(null);
16440
+ }
16441
+ return;
16442
+ }
15478
16443
  const editable = target.closest("[data-ohw-editable]");
15479
16444
  if (!editable) return;
15480
16445
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -15495,6 +16460,13 @@ function OhhwellsBridge() {
15495
16460
  }
15496
16461
  } else {
15497
16462
  hoverTarget.removeAttribute("data-ohw-hovered");
16463
+ if (hoveredItemElRef.current === editable) {
16464
+ const stillOnEditable = related instanceof Element && related.closest("[data-ohw-editable]") === editable;
16465
+ if (!stillOnEditable) {
16466
+ hoveredItemElRef.current = null;
16467
+ setHoveredItemRect(null);
16468
+ }
16469
+ }
15498
16470
  }
15499
16471
  }
15500
16472
  };
@@ -15611,6 +16583,26 @@ function OhhwellsBridge() {
15611
16583
  hoveredNavContainerRef.current = null;
15612
16584
  setHoveredNavContainerRect(null);
15613
16585
  }
16586
+ const logoCandidates = [
16587
+ ...document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]'),
16588
+ ...document.querySelectorAll("nav a:not([data-ohw-href-key]), [data-ohw-nav-root] a:not([data-ohw-href-key])"),
16589
+ ...document.querySelectorAll("footer img")
16590
+ ];
16591
+ const seenLogos = /* @__PURE__ */ new Set();
16592
+ for (const candidate of logoCandidates) {
16593
+ const logo = getLogoElement(candidate);
16594
+ if (!logo || seenLogos.has(logo)) continue;
16595
+ seenLogos.add(logo);
16596
+ const r2 = logo.getBoundingClientRect();
16597
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
16598
+ hoveredNavContainerRef.current = null;
16599
+ setHoveredNavContainerRect(null);
16600
+ if (selectedElRef.current !== logo) {
16601
+ hoveredItemElRef.current = logo;
16602
+ setHoveredItemRect(getLogoInteractionRect(logo));
16603
+ }
16604
+ return;
16605
+ }
15614
16606
  const navContainers = Array.from(
15615
16607
  document.querySelectorAll("[data-ohw-nav-container]")
15616
16608
  );
@@ -15696,7 +16688,7 @@ function OhhwellsBridge() {
15696
16688
  }
15697
16689
  };
15698
16690
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
15699
- if (linkPopoverOpenRef.current) {
16691
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
15700
16692
  if (hoveredImageRef.current) {
15701
16693
  hoveredImageRef.current = null;
15702
16694
  hoveredImageHasTextOverlapRef.current = false;
@@ -15950,7 +16942,7 @@ function OhhwellsBridge() {
15950
16942
  }
15951
16943
  };
15952
16944
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
15953
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
16945
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
15954
16946
  if (activeStateElRef.current) {
15955
16947
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
15956
16948
  activeStateElRef.current = null;
@@ -16018,6 +17010,19 @@ function OhhwellsBridge() {
16018
17010
  };
16019
17011
  const handleMouseMove = (e) => {
16020
17012
  const { clientX, clientY } = e;
17013
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17014
+ hoveredItemElRef.current = null;
17015
+ setHoveredItemRect(null);
17016
+ hoveredNavContainerRef.current = null;
17017
+ setHoveredNavContainerRect(null);
17018
+ siblingHintElRef.current = null;
17019
+ setSiblingHintRect(null);
17020
+ setSiblingHintRects([]);
17021
+ dismissImageHover();
17022
+ clearImageHover();
17023
+ setSectionGap(null);
17024
+ return;
17025
+ }
16021
17026
  probeSectionGapAt(clientX, clientY);
16022
17027
  probeImageAt(clientX, clientY);
16023
17028
  probeHoverCardsAt(clientX, clientY);
@@ -16026,6 +17031,11 @@ function OhhwellsBridge() {
16026
17031
  if (e.data?.type !== "ow:pointer-sync") return;
16027
17032
  const { clientX, clientY } = e.data;
16028
17033
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
17034
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17035
+ dismissImageHover();
17036
+ clearImageHover();
17037
+ return;
17038
+ }
16029
17039
  probeSectionGapAt(clientX, clientY);
16030
17040
  probeImageAt(clientX, clientY);
16031
17041
  probeHoverCardsAt(clientX, clientY);
@@ -16275,6 +17285,11 @@ function OhhwellsBridge() {
16275
17285
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16276
17286
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16277
17287
  }
17288
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17289
+ brandKitRef.current = content[BRAND_KIT_KEY];
17290
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17291
+ }
17292
+ applyBrandChrome(content);
16278
17293
  let sectionsJson = null;
16279
17294
  for (const [key, val] of Object.entries(content)) {
16280
17295
  if (key === "__ohw_sections") {
@@ -16282,6 +17297,10 @@ function OhhwellsBridge() {
16282
17297
  continue;
16283
17298
  }
16284
17299
  if (key === AI_SECTIONS_KEY) continue;
17300
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
17301
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17302
+ if (key === BRAND_KIT_KEY) continue;
17303
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16285
17304
  if (applyVideoSettingNode(key, val)) continue;
16286
17305
  if (applyCarouselNode(key, val)) continue;
16287
17306
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16301,6 +17320,8 @@ function OhhwellsBridge() {
16301
17320
  });
16302
17321
  applyLinkByKey(key, val);
16303
17322
  }
17323
+ applyLogoFromContent(content);
17324
+ applyLogoSizes(content);
16304
17325
  if (sectionsJson) {
16305
17326
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
16306
17327
  sectionsLoadedRef.current = true;
@@ -16316,6 +17337,58 @@ function OhhwellsBridge() {
16316
17337
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
16317
17338
  postToParentRef.current({ type: "ow:hydrate-done" });
16318
17339
  };
17340
+ const handleUpdateLogoIdentity = (e) => {
17341
+ if (e.data?.type !== "ow:update-logo-identity") return;
17342
+ const rawText = typeof e.data.text === "string" ? e.data.text : "";
17343
+ const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17344
+ const href = typeof e.data.href === "string" ? e.data.href : void 0;
17345
+ const imageProvided = "image" in e.data;
17346
+ const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17347
+ let isPlaceholder = e.data.isPlaceholder !== false;
17348
+ if (imageUrl) isPlaceholder = false;
17349
+ else if (imageProvided && imageUrl === null) {
17350
+ isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17351
+ }
17352
+ const display = applyLogoIdentity(rawText, isPlaceholder);
17353
+ const displayAlt = resolveLogoDisplayText(alt || display);
17354
+ if (imageUrl !== void 0) {
17355
+ applyLogoImage(imageUrl, displayAlt);
17356
+ } else {
17357
+ for (const key of LOGO_IMAGE_KEYS) {
17358
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17359
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17360
+ if (img) img.alt = displayAlt;
17361
+ });
17362
+ }
17363
+ }
17364
+ if (href !== void 0) {
17365
+ applyLogoHref(href);
17366
+ applyLinkByKey("nav-logo-href", href);
17367
+ applyLinkByKey("footer-logo-href", href);
17368
+ applyLinkByKey("logo-href", href);
17369
+ }
17370
+ const nodes = [
17371
+ ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17372
+ { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17373
+ { key: LOGO_ALT_KEY, text: displayAlt }
17374
+ ];
17375
+ if (imageUrl !== void 0) {
17376
+ if (imageUrl) {
17377
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17378
+ } else {
17379
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17380
+ }
17381
+ }
17382
+ if (href !== void 0) {
17383
+ for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17384
+ }
17385
+ editContentRef.current = {
17386
+ ...editContentRef.current,
17387
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17388
+ };
17389
+ applyLogoSizes(editContentRef.current);
17390
+ postToParentRef.current({ type: "ow:change", nodes });
17391
+ };
16319
17392
  window.addEventListener("message", handleHydrate);
16320
17393
  const postAiSectionsChanged = () => {
16321
17394
  postToParentRef.current({
@@ -16372,6 +17445,17 @@ function OhhwellsBridge() {
16372
17445
  postAiSectionsChanged();
16373
17446
  };
16374
17447
  window.addEventListener("message", handleAiSetSections);
17448
+ const handleAiSetBrand = (e) => {
17449
+ if (e.data?.type !== "ow:ai-set-brand") return;
17450
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17451
+ const previous = brandKitRef.current;
17452
+ brandKitRef.current = value;
17453
+ applyBrandToDom(parseBrandKit(value));
17454
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17455
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17456
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17457
+ };
17458
+ window.addEventListener("message", handleAiSetBrand);
16375
17459
  const handleDeactivate = (e) => {
16376
17460
  if (e.data?.type !== "ow:deactivate") return;
16377
17461
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -16380,6 +17464,12 @@ function OhhwellsBridge() {
16380
17464
  closeLinkPopoverRef.current();
16381
17465
  return;
16382
17466
  }
17467
+ if (floatingPanelOpenRef.current) {
17468
+ setFloatingPanelRef.current(null);
17469
+ deselectRef.current();
17470
+ deactivateRef.current();
17471
+ return;
17472
+ }
16383
17473
  deselectRef.current();
16384
17474
  deactivateRef.current();
16385
17475
  };
@@ -16399,6 +17489,10 @@ function OhhwellsBridge() {
16399
17489
  closeLinkPopoverRef.current();
16400
17490
  return;
16401
17491
  }
17492
+ if (floatingPanelOpenRef.current) {
17493
+ closeFloatingPanelOnlyRef.current();
17494
+ return;
17495
+ }
16402
17496
  if (activeElRef.current) {
16403
17497
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
16404
17498
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
@@ -16429,6 +17523,10 @@ function OhhwellsBridge() {
16429
17523
  return;
16430
17524
  }
16431
17525
  if (selectedElRef.current) {
17526
+ if (toolbarVariantRef.current === "logo") {
17527
+ deselectRef.current();
17528
+ return;
17529
+ }
16432
17530
  if (toolbarVariantRef.current === "select-frame") {
16433
17531
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16434
17532
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16462,7 +17560,16 @@ function OhhwellsBridge() {
16462
17560
  closeLinkPopoverRef.current();
16463
17561
  return;
16464
17562
  }
17563
+ if (e.key === "Escape" && floatingPanelOpenRef.current) {
17564
+ e.preventDefault();
17565
+ closeFloatingPanelOnlyRef.current();
17566
+ return;
17567
+ }
16465
17568
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17569
+ if (toolbarVariantRef.current === "logo") {
17570
+ deselectRef.current();
17571
+ return;
17572
+ }
16466
17573
  if (toolbarVariantRef.current === "select-frame") {
16467
17574
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16468
17575
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16540,7 +17647,8 @@ function OhhwellsBridge() {
16540
17647
  const handleScroll = () => {
16541
17648
  const focusEl = activeElRef.current ?? selectedElRef.current;
16542
17649
  if (focusEl) {
16543
- const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17650
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17651
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
16544
17652
  applyToolbarPos(r2);
16545
17653
  setToolbarRect(r2);
16546
17654
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -16550,7 +17658,9 @@ function OhhwellsBridge() {
16550
17658
  setToggleState((prev) => prev ? { ...prev, rect } : null);
16551
17659
  }
16552
17660
  if (hoveredItemElRef.current) {
16553
- setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17661
+ const hoverEl = hoveredItemElRef.current;
17662
+ const logo = getLogoElement(hoverEl);
17663
+ setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
16554
17664
  }
16555
17665
  if (hoveredNavContainerRef.current) {
16556
17666
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -16594,6 +17704,9 @@ function OhhwellsBridge() {
16594
17704
  if (aiSectionsRef.current) {
16595
17705
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
16596
17706
  }
17707
+ if (brandKitRef.current) {
17708
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17709
+ }
16597
17710
  postToParentRef.current({ type: "ow:save-result", nodes });
16598
17711
  };
16599
17712
  const handleInsertSection = (e) => {
@@ -16604,8 +17717,12 @@ function OhhwellsBridge() {
16604
17717
  if (inserted) {
16605
17718
  const tracker = getSectionsTracker();
16606
17719
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
16607
- const h = document.documentElement.scrollHeight;
16608
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17720
+ const reportHeight = () => {
17721
+ const h = document.body.scrollHeight;
17722
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17723
+ };
17724
+ reportHeight();
17725
+ setTimeout(reportHeight, 500);
16609
17726
  }
16610
17727
  };
16611
17728
  const handleSwitchSchedule = (e) => {
@@ -16798,13 +17915,17 @@ function OhhwellsBridge() {
16798
17915
  if (e.data?.type !== "ow:parent-scroll") return;
16799
17916
  const { iframeOffsetTop, headerH, canvasH } = e.data;
16800
17917
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
17918
+ if (floatingPanelOpenRef.current) {
17919
+ setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
17920
+ }
16801
17921
  if (visibleViewportRef.current) {
16802
17922
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
16803
17923
  }
16804
17924
  const focusEl = activeElRef.current ?? selectedElRef.current;
16805
17925
  if (focusEl) {
16806
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
16807
- applyToolbarPos(measureEl.getBoundingClientRect());
17926
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17927
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
17928
+ applyToolbarPos(r2);
16808
17929
  }
16809
17930
  };
16810
17931
  const handleClickAt = (e) => {
@@ -16829,6 +17950,25 @@ function OhhwellsBridge() {
16829
17950
  postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
16830
17951
  return;
16831
17952
  }
17953
+ const logoAtPoint = Array.from(
17954
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]')
17955
+ ).map((el) => getLogoElement(el)).find((logo) => {
17956
+ if (!logo) return false;
17957
+ const r2 = logo.getBoundingClientRect();
17958
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
17959
+ });
17960
+ if (logoAtPoint) {
17961
+ if (!logoHasUploadedImage(logoAtPoint)) {
17962
+ deselectRef.current();
17963
+ deactivateRef.current();
17964
+ const identity = readLogoIdentityFromDom();
17965
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
17966
+ return;
17967
+ }
17968
+ selectLogoRef.current(logoAtPoint);
17969
+ openLogoSizePanelRef.current(logoAtPoint);
17970
+ return;
17971
+ }
16832
17972
  const textEditable = Array.from(
16833
17973
  document.querySelectorAll(NON_MEDIA_SELECTOR)
16834
17974
  ).find((el) => {
@@ -16900,6 +18040,14 @@ function OhhwellsBridge() {
16900
18040
  window.addEventListener("message", handleParentScroll);
16901
18041
  window.addEventListener("message", handlePointerSync);
16902
18042
  window.addEventListener("message", handleClickAt);
18043
+ window.addEventListener("message", handleUpdateLogoIdentity);
18044
+ const handleViewMode = (e) => {
18045
+ if (e.data?.type !== "ow:view-mode") return;
18046
+ const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
18047
+ setEditorViewport(mode);
18048
+ applyLogoSizes(editContentRef.current);
18049
+ };
18050
+ window.addEventListener("message", handleViewMode);
16903
18051
  const handleViewportResize = () => {
16904
18052
  if (visibleViewportRef.current) {
16905
18053
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -16955,10 +18103,13 @@ function OhhwellsBridge() {
16955
18103
  window.removeEventListener("resize", handleViewportResize);
16956
18104
  window.removeEventListener("message", handlePointerSync);
16957
18105
  window.removeEventListener("message", handleClickAt);
18106
+ window.removeEventListener("message", handleUpdateLogoIdentity);
18107
+ window.removeEventListener("message", handleViewMode);
16958
18108
  window.removeEventListener("message", handleHydrate);
16959
18109
  window.removeEventListener("message", handleAiApplyTree);
16960
18110
  window.removeEventListener("message", handleAiDeleteSection);
16961
18111
  window.removeEventListener("message", handleAiSetSections);
18112
+ window.removeEventListener("message", handleAiSetBrand);
16962
18113
  window.removeEventListener("message", handleDeactivate);
16963
18114
  window.removeEventListener("message", handleToastAction);
16964
18115
  window.removeEventListener("message", handleUiEscape);
@@ -16984,7 +18135,7 @@ function OhhwellsBridge() {
16984
18135
  if (footerDragRef.current) return;
16985
18136
  const target = e.target;
16986
18137
  if (!target) return;
16987
- 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]')) {
18138
+ 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]')) {
16988
18139
  return;
16989
18140
  }
16990
18141
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17162,7 +18313,7 @@ function OhhwellsBridge() {
17162
18313
  postToParent2({
17163
18314
  type: "ow:ready",
17164
18315
  version: "1",
17165
- bridgeVersion: "0.1.56",
18316
+ bridgeVersion: "0.1.59",
17166
18317
  path: pathname,
17167
18318
  nodes: collectEditableNodes(editContentRef.current),
17168
18319
  sections
@@ -17557,10 +18708,10 @@ function OhhwellsBridge() {
17557
18708
  [postToParent2]
17558
18709
  );
17559
18710
  return bridgeRoot ? (0, import_react_dom4.createPortal)(
17560
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17561
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
17562
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
17563
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18711
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
18712
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
18713
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
18714
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17564
18715
  MediaOverlay,
17565
18716
  {
17566
18717
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -17571,7 +18722,7 @@ function OhhwellsBridge() {
17571
18722
  },
17572
18723
  `uploading-${key}`
17573
18724
  )),
17574
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18725
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17575
18726
  MediaOverlay,
17576
18727
  {
17577
18728
  hover: mediaHover,
@@ -17580,11 +18731,11 @@ function OhhwellsBridge() {
17580
18731
  onVideoSettingsChange: handleVideoSettingsChange
17581
18732
  }
17582
18733
  ),
17583
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
17584
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
17585
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
17586
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
17587
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18734
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
18735
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
18736
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
18737
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
18738
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17588
18739
  "div",
17589
18740
  {
17590
18741
  className: "pointer-events-none fixed z-2147483646",
@@ -17594,7 +18745,7 @@ function OhhwellsBridge() {
17594
18745
  width: slot.width,
17595
18746
  height: slot.height
17596
18747
  },
17597
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18748
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17598
18749
  DropIndicator,
17599
18750
  {
17600
18751
  direction: slot.direction,
@@ -17605,7 +18756,7 @@ function OhhwellsBridge() {
17605
18756
  },
17606
18757
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
17607
18758
  )),
17608
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18759
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17609
18760
  "div",
17610
18761
  {
17611
18762
  className: "pointer-events-none fixed z-2147483646",
@@ -17615,7 +18766,7 @@ function OhhwellsBridge() {
17615
18766
  width: slot.width,
17616
18767
  height: slot.height
17617
18768
  },
17618
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18769
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17619
18770
  DropIndicator,
17620
18771
  {
17621
18772
  direction: slot.direction,
@@ -17626,10 +18777,11 @@ function OhhwellsBridge() {
17626
18777
  },
17627
18778
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
17628
18779
  )),
17629
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17630
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17631
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17632
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18780
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
18781
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
18782
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
18783
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
18784
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17633
18785
  FooterContainerChrome,
17634
18786
  {
17635
18787
  rect: toolbarRect,
@@ -17637,7 +18789,7 @@ function OhhwellsBridge() {
17637
18789
  addDisabled: !canAddFooterColumn()
17638
18790
  }
17639
18791
  ),
17640
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18792
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17641
18793
  ItemInteractionLayer,
17642
18794
  {
17643
18795
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -17649,10 +18801,10 @@ function OhhwellsBridge() {
17649
18801
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
17650
18802
  onDragHandleDragStart: handleItemDragStart,
17651
18803
  onDragHandleDragEnd: handleItemDragEnd,
17652
- onItemPointerDown: handleItemChromePointerDown,
17653
- onItemClick: handleItemChromeClick,
17654
- itemDragSurface: !isFooterFrameSelection,
17655
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18804
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
18805
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
18806
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
18807
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17656
18808
  ItemActionToolbar,
17657
18809
  {
17658
18810
  onEditLink: openLinkPopoverForSelected,
@@ -17668,7 +18820,10 @@ function OhhwellsBridge() {
17668
18820
  onSelectParent: handleSelectParent,
17669
18821
  onDuplicate: handleDuplicateSelected,
17670
18822
  onDelete: handleDeleteSelected,
17671
- addItemDisabled: false,
18823
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
18824
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
18825
+ return row ? !canAddSocialItem(row) : false;
18826
+ })(),
17672
18827
  editLinkDisabled: false,
17673
18828
  moreDisabled: false,
17674
18829
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
@@ -17685,8 +18840,8 @@ function OhhwellsBridge() {
17685
18840
  ) : void 0
17686
18841
  }
17687
18842
  ),
17688
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17689
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18843
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
18844
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17690
18845
  EditGlowChrome,
17691
18846
  {
17692
18847
  rect: toolbarRect,
@@ -17696,7 +18851,7 @@ function OhhwellsBridge() {
17696
18851
  hideHandle: isItemDragging
17697
18852
  }
17698
18853
  ),
17699
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18854
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17700
18855
  FloatingToolbar,
17701
18856
  {
17702
18857
  rect: toolbarRect,
@@ -17709,7 +18864,7 @@ function OhhwellsBridge() {
17709
18864
  }
17710
18865
  )
17711
18866
  ] }),
17712
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
18867
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17713
18868
  "div",
17714
18869
  {
17715
18870
  "data-ohw-max-badge": "",
@@ -17735,7 +18890,7 @@ function OhhwellsBridge() {
17735
18890
  ]
17736
18891
  }
17737
18892
  ),
17738
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18893
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17739
18894
  StateToggle,
17740
18895
  {
17741
18896
  rect: toggleState.rect,
@@ -17744,15 +18899,15 @@ function OhhwellsBridge() {
17744
18899
  onStateChange: handleStateChange
17745
18900
  }
17746
18901
  ),
17747
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
18902
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17748
18903
  "div",
17749
18904
  {
17750
18905
  "data-ohw-section-insert-line": "",
17751
18906
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
17752
18907
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
17753
18908
  children: [
17754
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
17755
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18909
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
18910
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17756
18911
  Badge,
17757
18912
  {
17758
18913
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -17769,11 +18924,11 @@ function OhhwellsBridge() {
17769
18924
  children: "Add Section"
17770
18925
  }
17771
18926
  ),
17772
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
18927
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
17773
18928
  ]
17774
18929
  }
17775
18930
  ),
17776
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18931
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17777
18932
  LinkPopover,
17778
18933
  {
17779
18934
  panelRef: linkPopoverPanelRef,
@@ -17790,7 +18945,7 @@ function OhhwellsBridge() {
17790
18945
  },
17791
18946
  linkPopover.key
17792
18947
  ) : null,
17793
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18948
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17794
18949
  FloatingPanel,
17795
18950
  {
17796
18951
  open: true,
@@ -17800,7 +18955,7 @@ function OhhwellsBridge() {
17800
18955
  onPositionChange: setFloatingPanelPos,
17801
18956
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
17802
18957
  onClose: closeFloatingPanelOnly,
17803
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18958
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17804
18959
  SocialsDisplayPanel,
17805
18960
  {
17806
18961
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -17811,11 +18966,115 @@ function OhhwellsBridge() {
17811
18966
  }
17812
18967
  )
17813
18968
  }
18969
+ ) : null,
18970
+ floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
18971
+ FloatingPanel,
18972
+ {
18973
+ open: true,
18974
+ title: floatingPanel.title,
18975
+ context: floatingPanel.context,
18976
+ position: floatingPanelPos,
18977
+ onPositionChange: setFloatingPanelPos,
18978
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
18979
+ onClose: closeFloatingPanelAndDeselect,
18980
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
18981
+ LogoSizePanel,
18982
+ {
18983
+ viewport: editorViewport,
18984
+ sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
18985
+ mobileFollowing: logoSizeDraft.mobileFollowing,
18986
+ onSizeChange: (px) => {
18987
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
18988
+ ...logoSizeDraft,
18989
+ desktopPx: px,
18990
+ mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
18991
+ };
18992
+ setLogoSizeDraft(next);
18993
+ persistLogoSizeDraft(floatingPanel.placement, next);
18994
+ },
18995
+ onCustomizeMobile: () => {
18996
+ const next = {
18997
+ ...logoSizeDraft,
18998
+ mobileFollowing: false,
18999
+ mobilePx: logoSizeDraft.desktopPx
19000
+ };
19001
+ setLogoSizeDraft(next);
19002
+ persistLogoSizeDraft(floatingPanel.placement, next);
19003
+ },
19004
+ onResetMobile: () => {
19005
+ const next = {
19006
+ ...logoSizeDraft,
19007
+ mobileFollowing: true,
19008
+ mobilePx: logoSizeDraft.desktopPx
19009
+ };
19010
+ setLogoSizeDraft(next);
19011
+ persistLogoSizeDraft(floatingPanel.placement, next);
19012
+ },
19013
+ onUpdateEverywhere: () => {
19014
+ const identity = readLogoIdentityFromDom();
19015
+ postToParent2({ type: "ow:open-logo-settings", ...identity });
19016
+ }
19017
+ }
19018
+ )
19019
+ }
17814
19020
  ) : null
17815
19021
  ] }),
17816
19022
  bridgeRoot
17817
19023
  ) : null;
17818
19024
  }
19025
+
19026
+ // src/ui/EmptySection.tsx
19027
+ var import_link = __toESM(require("next/link"), 1);
19028
+ var import_jsx_runtime34 = require("react/jsx-runtime");
19029
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
19030
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
19031
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19032
+ "p",
19033
+ {
19034
+ style: {
19035
+ fontFamily: "var(--brand-font-body)",
19036
+ fontSize: "0.75rem",
19037
+ fontWeight: 500,
19038
+ letterSpacing: "0.15em",
19039
+ textTransform: "uppercase",
19040
+ color: "var(--brand-accent)",
19041
+ marginBottom: "1.5rem"
19042
+ },
19043
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
19044
+ }
19045
+ ),
19046
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19047
+ "h1",
19048
+ {
19049
+ style: {
19050
+ fontFamily: "var(--brand-font-heading)",
19051
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
19052
+ lineHeight: 1.1,
19053
+ letterSpacing: "-0.025em",
19054
+ color: "var(--brand-text)",
19055
+ marginBottom: "1rem"
19056
+ },
19057
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
19058
+ children: title
19059
+ }
19060
+ ),
19061
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19062
+ "p",
19063
+ {
19064
+ style: {
19065
+ fontFamily: "var(--brand-font-body)",
19066
+ fontSize: "1rem",
19067
+ lineHeight: 1.7,
19068
+ fontWeight: 300,
19069
+ color: "var(--brand-text-muted)",
19070
+ maxWidth: "340px"
19071
+ },
19072
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19073
+ children: "This page doesn't have any content yet."
19074
+ }
19075
+ )
19076
+ ] });
19077
+ }
17819
19078
  // Annotate the CommonJS export names for ESM import in node:
17820
19079
  0 && (module.exports = {
17821
19080
  AI_DEFAULT_BRAND,
@@ -17833,6 +19092,7 @@ function OhhwellsBridge() {
17833
19092
  DropdownMenuItem,
17834
19093
  DropdownMenuSeparator,
17835
19094
  DropdownMenuTrigger,
19095
+ EmptySection,
17836
19096
  ItemActionToolbar,
17837
19097
  ItemInteractionLayer,
17838
19098
  LinkEditorPanel,