@ohhwells/bridge 0.1.58 → 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,17 +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 floatingPanelOpenRef = (0, import_react16.useRef)(false);
13627
- floatingPanelOpenRef.current = floatingPanel !== null;
13628
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
13629
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13630
14339
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
13631
14340
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
13632
14341
  const footerDragRef = (0, import_react16.useRef)(null);
@@ -13641,7 +14350,15 @@ function OhhwellsBridge() {
13641
14350
  const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
13642
14351
  const editContentRef = (0, import_react16.useRef)({});
13643
14352
  const aiSectionsRef = (0, import_react16.useRef)("");
14353
+ const brandKitRef = (0, import_react16.useRef)("");
13644
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);
13645
14362
  const [sitePages, setSitePages] = (0, import_react16.useState)([]);
13646
14363
  const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
13647
14364
  const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
@@ -13650,7 +14367,18 @@ function OhhwellsBridge() {
13650
14367
  const linkPopoverOpenRef = (0, import_react16.useRef)(false);
13651
14368
  const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
13652
14369
  setLinkPopoverRef.current = setLinkPopover;
14370
+ setFloatingPanelRef.current = setFloatingPanel;
13653
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
+ }, []);
13654
14382
  const {
13655
14383
  navDragRef,
13656
14384
  navDropSlots,
@@ -13873,6 +14601,10 @@ function OhhwellsBridge() {
13873
14601
  setIsItemDragging(false);
13874
14602
  hoveredNavContainerRef.current = null;
13875
14603
  setHoveredNavContainerRect(null);
14604
+ hoveredItemElRef.current = null;
14605
+ setHoveredItemRect(null);
14606
+ setFloatingPanel(null);
14607
+ setLogoSizeDraft(null);
13876
14608
  if (!activeElRef.current) {
13877
14609
  setNavGroupForceOpen(null, false);
13878
14610
  setToolbarRect(null);
@@ -14069,6 +14801,14 @@ function OhhwellsBridge() {
14069
14801
  if (!selected) return;
14070
14802
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14071
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
+ }
14072
14812
  const after = getSocialItem(selected);
14073
14813
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14074
14814
  if (!result2) return;
@@ -14090,10 +14830,16 @@ function OhhwellsBridge() {
14090
14830
  return;
14091
14831
  }
14092
14832
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14093
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14094
- }
14095
- 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);
14096
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
+ }
14097
14843
  const result2 = insertFooterItem(column, "/", "New link", null);
14098
14844
  applyLinkByKey(result2.hrefKey, result2.href);
14099
14845
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -14564,6 +15310,8 @@ function OhhwellsBridge() {
14564
15310
  setToolbarRect(anchor.getBoundingClientRect());
14565
15311
  setToolbarShowEditLink(false);
14566
15312
  setActiveCommands(/* @__PURE__ */ new Set());
15313
+ setFloatingPanel(null);
15314
+ setLogoSizeDraft(null);
14567
15315
  }, [deactivate, markSelected]);
14568
15316
  const selectFrame = (0, import_react16.useCallback)((el) => {
14569
15317
  if (!isNavigationContainer(el)) return;
@@ -14613,7 +15361,51 @@ function OhhwellsBridge() {
14613
15361
  setToolbarRect(el.getBoundingClientRect());
14614
15362
  setToolbarShowEditLink(false);
14615
15363
  setActiveCommands(/* @__PURE__ */ new Set());
15364
+ setFloatingPanel(null);
15365
+ setLogoSizeDraft(null);
14616
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
+ }, []);
14617
15409
  const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
14618
15410
  setParentScrollSnap(parentScrollRef.current);
14619
15411
  setFloatingPanel({
@@ -14649,13 +15441,53 @@ function OhhwellsBridge() {
14649
15441
  );
14650
15442
  const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
14651
15443
  setFloatingPanel(null);
15444
+ setLogoSizeDraft(null);
14652
15445
  }, []);
14653
- const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(closeFloatingPanelOnly);
14654
- closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
14655
15446
  const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
14656
15447
  setFloatingPanel(null);
15448
+ setLogoSizeDraft(null);
14657
15449
  deselectRef.current();
14658
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
+ );
14659
15491
  const activate = (0, import_react16.useCallback)((el, options) => {
14660
15492
  if (activeElRef.current === el) return;
14661
15493
  if (isIconEditable(el)) return;
@@ -14736,7 +15568,37 @@ function OhhwellsBridge() {
14736
15568
  deactivateRef.current = deactivate;
14737
15569
  selectRef.current = select;
14738
15570
  selectFrameRef.current = selectFrame;
15571
+ selectLogoRef.current = selectLogo;
15572
+ openLogoSizePanelRef.current = openLogoSizePanel;
14739
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
+ ]);
14740
15602
  (0, import_react16.useLayoutEffect)(() => {
14741
15603
  if (!subdomain || isEditMode) {
14742
15604
  setFetchState("done");
@@ -14748,9 +15610,18 @@ function OhhwellsBridge() {
14748
15610
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
14749
15611
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
14750
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);
14751
15618
  for (const [key, val] of Object.entries(content)) {
14752
15619
  if (key === "__ohw_sections") continue;
14753
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;
14754
15625
  if (applyVideoSettingNode(key, val)) continue;
14755
15626
  if (applyCarouselNode(key, val)) continue;
14756
15627
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14785,6 +15656,8 @@ function OhhwellsBridge() {
14785
15656
  });
14786
15657
  applyLinkByKey(key, val);
14787
15658
  }
15659
+ applyLogoFromContent(content);
15660
+ applyLogoSizes(content);
14788
15661
  reconcileNavbarItemsFromContent(content);
14789
15662
  reconcileFooterOrderFromContent(content);
14790
15663
  reconcileSocialsFromContent(content);
@@ -14805,7 +15678,9 @@ function OhhwellsBridge() {
14805
15678
  let cancelled = false;
14806
15679
  setFetchState("loading");
14807
15680
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14808
- 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) => {
14809
15684
  if (cancelled) return;
14810
15685
  const content = data?.content ?? {};
14811
15686
  contentCache.set(subdomain, content);
@@ -14829,8 +15704,16 @@ function OhhwellsBridge() {
14829
15704
  initSectionInstancesFromContent(content, window.location.pathname);
14830
15705
  observer?.disconnect();
14831
15706
  try {
15707
+ applyBrandChrome(content);
15708
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15709
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15710
+ }
14832
15711
  for (const [key, val] of Object.entries(content)) {
14833
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;
14834
15717
  if (applyVideoSettingNode(key, val)) continue;
14835
15718
  if (applyCarouselNode(key, val)) continue;
14836
15719
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14851,6 +15734,7 @@ function OhhwellsBridge() {
14851
15734
  });
14852
15735
  applyLinkByKey(key, val);
14853
15736
  }
15737
+ applyLogoFromContent(content);
14854
15738
  reconcileNavbarItemsFromContent(content);
14855
15739
  reconcileFooterOrderFromContent(content);
14856
15740
  reconcileSocialsFromContent(content);
@@ -14865,6 +15749,17 @@ function OhhwellsBridge() {
14865
15749
  debounceTimer = setTimeout(applyFromCache, 150);
14866
15750
  };
14867
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
+ }
14868
15763
  observer = new MutationObserver(scheduleApply);
14869
15764
  observer.observe(document.body, { childList: true, subtree: true });
14870
15765
  return () => {
@@ -14958,26 +15853,31 @@ function OhhwellsBridge() {
14958
15853
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
14959
15854
  (0, import_react16.useEffect)(() => {
14960
15855
  if (!isEditMode) return;
15856
+ let lastPosted = 0;
14961
15857
  const measure = () => {
14962
15858
  const h = document.body.scrollHeight;
14963
- 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
+ });
14964
15871
  };
14965
15872
  const t1 = setTimeout(measure, 50);
14966
15873
  const t2 = setTimeout(measure, 500);
14967
- let lastWidth = window.innerWidth;
14968
- let resizeTimer = null;
14969
- const handleResize = () => {
14970
- if (window.innerWidth === lastWidth) return;
14971
- lastWidth = window.innerWidth;
14972
- if (resizeTimer) clearTimeout(resizeTimer);
14973
- resizeTimer = setTimeout(measure, 150);
14974
- };
14975
- window.addEventListener("resize", handleResize);
15874
+ const ro = new ResizeObserver(schedule);
15875
+ ro.observe(document.body);
14976
15876
  return () => {
14977
15877
  clearTimeout(t1);
14978
15878
  clearTimeout(t2);
14979
- if (resizeTimer) clearTimeout(resizeTimer);
14980
- window.removeEventListener("resize", handleResize);
15879
+ if (raf != null) cancelAnimationFrame(raf);
15880
+ ro.disconnect();
14981
15881
  };
14982
15882
  }, [pathname, isEditMode, postToParent2]);
14983
15883
  (0, import_react16.useEffect)(() => {
@@ -15047,9 +15947,12 @@ function OhhwellsBridge() {
15047
15947
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
15048
15948
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
15049
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. */
15050
15954
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
15051
- outline: 2px dashed ${PRIMARY3} !important;
15052
- outline-offset: 4px;
15955
+ outline: none !important;
15053
15956
  }
15054
15957
  [data-ohw-href-key] [data-ohw-hovered],
15055
15958
  [data-ohw-href-key][data-ohw-hovered],
@@ -15102,8 +16005,11 @@ function OhhwellsBridge() {
15102
16005
  stateViews.textContent = `
15103
16006
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15104
16007
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15105
- [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; }
15106
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; }
15107
16013
  `;
15108
16014
  document.head.appendChild(base);
15109
16015
  document.head.appendChild(forceHover);
@@ -15121,10 +16027,12 @@ function OhhwellsBridge() {
15121
16027
  return;
15122
16028
  }
15123
16029
  const target = e.target;
16030
+ if (target.closest("[data-ohw-ai-review]")) return;
15124
16031
  if (target.closest("[data-ohw-toolbar]")) return;
15125
16032
  if (target.closest("[data-ohw-state-toggle]")) return;
15126
16033
  if (target.closest("[data-ohw-max-badge]")) return;
15127
16034
  if (isInsideLinkEditor(target)) return;
16035
+ if (isInsideFloatingPanel(target)) return;
15128
16036
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15129
16037
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
15130
16038
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -15188,6 +16096,21 @@ function OhhwellsBridge() {
15188
16096
  return;
15189
16097
  }
15190
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
+ }
15191
16114
  const editable = target.closest("[data-ohw-editable]");
15192
16115
  if (editable) {
15193
16116
  if (editable.dataset.ohwEditable === "link") {
@@ -15340,10 +16263,12 @@ function OhhwellsBridge() {
15340
16263
  };
15341
16264
  const handleDblClick = (e) => {
15342
16265
  const target = e.target;
16266
+ if (target.closest("[data-ohw-ai-review]")) return;
15343
16267
  if (target.closest("[data-ohw-toolbar]")) return;
15344
16268
  if (target.closest("[data-ohw-state-toggle]")) return;
15345
16269
  if (target.closest("[data-ohw-max-badge]")) return;
15346
16270
  if (isInsideLinkEditor(target)) return;
16271
+ if (isInsideFloatingPanel(target)) return;
15347
16272
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15348
16273
  return;
15349
16274
  }
@@ -15371,6 +16296,16 @@ function OhhwellsBridge() {
15371
16296
  setHoveredNavContainerRect(null);
15372
16297
  return;
15373
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
+ }
15374
16309
  {
15375
16310
  const selected2 = selectedElRef.current;
15376
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)));
@@ -15378,7 +16313,7 @@ function OhhwellsBridge() {
15378
16313
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
15379
16314
  if (allowNavContainerHover) {
15380
16315
  const navContainer = target.closest("[data-ohw-nav-container]");
15381
- if (navContainer && !getNavigationItemAnchor(target)) {
16316
+ if (navContainer && !getNavigationItemAnchor(target) && !getLogoElement(target)) {
15382
16317
  hoveredNavContainerRef.current = navContainer;
15383
16318
  setHoveredNavContainerRect(navContainer.getBoundingClientRect());
15384
16319
  hoveredItemElRef.current = null;
@@ -15407,6 +16342,15 @@ function OhhwellsBridge() {
15407
16342
  setHoveredNavContainerRect(null);
15408
16343
  }
15409
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
+ }
15410
16354
  const navAnchor = getNavigationItemAnchor(target);
15411
16355
  if (navAnchor) {
15412
16356
  hoveredNavContainerRef.current = null;
@@ -15444,6 +16388,11 @@ function OhhwellsBridge() {
15444
16388
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
15445
16389
  } else if (!isInsideNavigationItem(editable)) {
15446
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
+ }
15447
16396
  }
15448
16397
  }
15449
16398
  };
@@ -15479,6 +16428,18 @@ function OhhwellsBridge() {
15479
16428
  }
15480
16429
  return;
15481
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
+ }
15482
16443
  const editable = target.closest("[data-ohw-editable]");
15483
16444
  if (!editable) return;
15484
16445
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -15499,6 +16460,13 @@ function OhhwellsBridge() {
15499
16460
  }
15500
16461
  } else {
15501
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
+ }
15502
16470
  }
15503
16471
  }
15504
16472
  };
@@ -15615,6 +16583,26 @@ function OhhwellsBridge() {
15615
16583
  hoveredNavContainerRef.current = null;
15616
16584
  setHoveredNavContainerRect(null);
15617
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
+ }
15618
16606
  const navContainers = Array.from(
15619
16607
  document.querySelectorAll("[data-ohw-nav-container]")
15620
16608
  );
@@ -15700,7 +16688,7 @@ function OhhwellsBridge() {
15700
16688
  }
15701
16689
  };
15702
16690
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
15703
- if (linkPopoverOpenRef.current) {
16691
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
15704
16692
  if (hoveredImageRef.current) {
15705
16693
  hoveredImageRef.current = null;
15706
16694
  hoveredImageHasTextOverlapRef.current = false;
@@ -15954,7 +16942,7 @@ function OhhwellsBridge() {
15954
16942
  }
15955
16943
  };
15956
16944
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
15957
- 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")) {
15958
16946
  if (activeStateElRef.current) {
15959
16947
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
15960
16948
  activeStateElRef.current = null;
@@ -16022,6 +17010,19 @@ function OhhwellsBridge() {
16022
17010
  };
16023
17011
  const handleMouseMove = (e) => {
16024
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
+ }
16025
17026
  probeSectionGapAt(clientX, clientY);
16026
17027
  probeImageAt(clientX, clientY);
16027
17028
  probeHoverCardsAt(clientX, clientY);
@@ -16030,6 +17031,11 @@ function OhhwellsBridge() {
16030
17031
  if (e.data?.type !== "ow:pointer-sync") return;
16031
17032
  const { clientX, clientY } = e.data;
16032
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
+ }
16033
17039
  probeSectionGapAt(clientX, clientY);
16034
17040
  probeImageAt(clientX, clientY);
16035
17041
  probeHoverCardsAt(clientX, clientY);
@@ -16279,6 +17285,11 @@ function OhhwellsBridge() {
16279
17285
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16280
17286
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16281
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);
16282
17293
  let sectionsJson = null;
16283
17294
  for (const [key, val] of Object.entries(content)) {
16284
17295
  if (key === "__ohw_sections") {
@@ -16286,6 +17297,10 @@ function OhhwellsBridge() {
16286
17297
  continue;
16287
17298
  }
16288
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;
16289
17304
  if (applyVideoSettingNode(key, val)) continue;
16290
17305
  if (applyCarouselNode(key, val)) continue;
16291
17306
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16305,6 +17320,8 @@ function OhhwellsBridge() {
16305
17320
  });
16306
17321
  applyLinkByKey(key, val);
16307
17322
  }
17323
+ applyLogoFromContent(content);
17324
+ applyLogoSizes(content);
16308
17325
  if (sectionsJson) {
16309
17326
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
16310
17327
  sectionsLoadedRef.current = true;
@@ -16320,6 +17337,58 @@ function OhhwellsBridge() {
16320
17337
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
16321
17338
  postToParentRef.current({ type: "ow:hydrate-done" });
16322
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
+ };
16323
17392
  window.addEventListener("message", handleHydrate);
16324
17393
  const postAiSectionsChanged = () => {
16325
17394
  postToParentRef.current({
@@ -16376,6 +17445,17 @@ function OhhwellsBridge() {
16376
17445
  postAiSectionsChanged();
16377
17446
  };
16378
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);
16379
17459
  const handleDeactivate = (e) => {
16380
17460
  if (e.data?.type !== "ow:deactivate") return;
16381
17461
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -16384,6 +17464,12 @@ function OhhwellsBridge() {
16384
17464
  closeLinkPopoverRef.current();
16385
17465
  return;
16386
17466
  }
17467
+ if (floatingPanelOpenRef.current) {
17468
+ setFloatingPanelRef.current(null);
17469
+ deselectRef.current();
17470
+ deactivateRef.current();
17471
+ return;
17472
+ }
16387
17473
  deselectRef.current();
16388
17474
  deactivateRef.current();
16389
17475
  };
@@ -16437,6 +17523,10 @@ function OhhwellsBridge() {
16437
17523
  return;
16438
17524
  }
16439
17525
  if (selectedElRef.current) {
17526
+ if (toolbarVariantRef.current === "logo") {
17527
+ deselectRef.current();
17528
+ return;
17529
+ }
16440
17530
  if (toolbarVariantRef.current === "select-frame") {
16441
17531
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16442
17532
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16476,6 +17566,10 @@ function OhhwellsBridge() {
16476
17566
  return;
16477
17567
  }
16478
17568
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17569
+ if (toolbarVariantRef.current === "logo") {
17570
+ deselectRef.current();
17571
+ return;
17572
+ }
16479
17573
  if (toolbarVariantRef.current === "select-frame") {
16480
17574
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16481
17575
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16553,7 +17647,8 @@ function OhhwellsBridge() {
16553
17647
  const handleScroll = () => {
16554
17648
  const focusEl = activeElRef.current ?? selectedElRef.current;
16555
17649
  if (focusEl) {
16556
- 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();
16557
17652
  applyToolbarPos(r2);
16558
17653
  setToolbarRect(r2);
16559
17654
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -16563,7 +17658,9 @@ function OhhwellsBridge() {
16563
17658
  setToggleState((prev) => prev ? { ...prev, rect } : null);
16564
17659
  }
16565
17660
  if (hoveredItemElRef.current) {
16566
- setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17661
+ const hoverEl = hoveredItemElRef.current;
17662
+ const logo = getLogoElement(hoverEl);
17663
+ setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
16567
17664
  }
16568
17665
  if (hoveredNavContainerRef.current) {
16569
17666
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -16607,6 +17704,9 @@ function OhhwellsBridge() {
16607
17704
  if (aiSectionsRef.current) {
16608
17705
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
16609
17706
  }
17707
+ if (brandKitRef.current) {
17708
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17709
+ }
16610
17710
  postToParentRef.current({ type: "ow:save-result", nodes });
16611
17711
  };
16612
17712
  const handleInsertSection = (e) => {
@@ -16617,8 +17717,12 @@ function OhhwellsBridge() {
16617
17717
  if (inserted) {
16618
17718
  const tracker = getSectionsTracker();
16619
17719
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
16620
- const h = document.documentElement.scrollHeight;
16621
- 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);
16622
17726
  }
16623
17727
  };
16624
17728
  const handleSwitchSchedule = (e) => {
@@ -16811,13 +17915,17 @@ function OhhwellsBridge() {
16811
17915
  if (e.data?.type !== "ow:parent-scroll") return;
16812
17916
  const { iframeOffsetTop, headerH, canvasH } = e.data;
16813
17917
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
17918
+ if (floatingPanelOpenRef.current) {
17919
+ setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
17920
+ }
16814
17921
  if (visibleViewportRef.current) {
16815
17922
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
16816
17923
  }
16817
17924
  const focusEl = activeElRef.current ?? selectedElRef.current;
16818
17925
  if (focusEl) {
16819
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
16820
- 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);
16821
17929
  }
16822
17930
  };
16823
17931
  const handleClickAt = (e) => {
@@ -16842,6 +17950,25 @@ function OhhwellsBridge() {
16842
17950
  postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
16843
17951
  return;
16844
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
+ }
16845
17972
  const textEditable = Array.from(
16846
17973
  document.querySelectorAll(NON_MEDIA_SELECTOR)
16847
17974
  ).find((el) => {
@@ -16913,6 +18040,14 @@ function OhhwellsBridge() {
16913
18040
  window.addEventListener("message", handleParentScroll);
16914
18041
  window.addEventListener("message", handlePointerSync);
16915
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);
16916
18051
  const handleViewportResize = () => {
16917
18052
  if (visibleViewportRef.current) {
16918
18053
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -16968,10 +18103,13 @@ function OhhwellsBridge() {
16968
18103
  window.removeEventListener("resize", handleViewportResize);
16969
18104
  window.removeEventListener("message", handlePointerSync);
16970
18105
  window.removeEventListener("message", handleClickAt);
18106
+ window.removeEventListener("message", handleUpdateLogoIdentity);
18107
+ window.removeEventListener("message", handleViewMode);
16971
18108
  window.removeEventListener("message", handleHydrate);
16972
18109
  window.removeEventListener("message", handleAiApplyTree);
16973
18110
  window.removeEventListener("message", handleAiDeleteSection);
16974
18111
  window.removeEventListener("message", handleAiSetSections);
18112
+ window.removeEventListener("message", handleAiSetBrand);
16975
18113
  window.removeEventListener("message", handleDeactivate);
16976
18114
  window.removeEventListener("message", handleToastAction);
16977
18115
  window.removeEventListener("message", handleUiEscape);
@@ -16997,7 +18135,7 @@ function OhhwellsBridge() {
16997
18135
  if (footerDragRef.current) return;
16998
18136
  const target = e.target;
16999
18137
  if (!target) return;
17000
- 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]')) {
17001
18139
  return;
17002
18140
  }
17003
18141
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17175,7 +18313,7 @@ function OhhwellsBridge() {
17175
18313
  postToParent2({
17176
18314
  type: "ow:ready",
17177
18315
  version: "1",
17178
- bridgeVersion: "0.1.57",
18316
+ bridgeVersion: "0.1.59",
17179
18317
  path: pathname,
17180
18318
  nodes: collectEditableNodes(editContentRef.current),
17181
18319
  sections
@@ -17570,10 +18708,10 @@ function OhhwellsBridge() {
17570
18708
  [postToParent2]
17571
18709
  );
17572
18710
  return bridgeRoot ? (0, import_react_dom4.createPortal)(
17573
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17574
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
17575
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
17576
- 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)(
17577
18715
  MediaOverlay,
17578
18716
  {
17579
18717
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -17584,7 +18722,7 @@ function OhhwellsBridge() {
17584
18722
  },
17585
18723
  `uploading-${key}`
17586
18724
  )),
17587
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18725
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17588
18726
  MediaOverlay,
17589
18727
  {
17590
18728
  hover: mediaHover,
@@ -17593,11 +18731,11 @@ function OhhwellsBridge() {
17593
18731
  onVideoSettingsChange: handleVideoSettingsChange
17594
18732
  }
17595
18733
  ),
17596
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
17597
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
17598
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
17599
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
17600
- 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)(
17601
18739
  "div",
17602
18740
  {
17603
18741
  className: "pointer-events-none fixed z-2147483646",
@@ -17607,7 +18745,7 @@ function OhhwellsBridge() {
17607
18745
  width: slot.width,
17608
18746
  height: slot.height
17609
18747
  },
17610
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18748
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17611
18749
  DropIndicator,
17612
18750
  {
17613
18751
  direction: slot.direction,
@@ -17618,7 +18756,7 @@ function OhhwellsBridge() {
17618
18756
  },
17619
18757
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
17620
18758
  )),
17621
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18759
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17622
18760
  "div",
17623
18761
  {
17624
18762
  className: "pointer-events-none fixed z-2147483646",
@@ -17628,7 +18766,7 @@ function OhhwellsBridge() {
17628
18766
  width: slot.width,
17629
18767
  height: slot.height
17630
18768
  },
17631
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18769
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17632
18770
  DropIndicator,
17633
18771
  {
17634
18772
  direction: slot.direction,
@@ -17639,10 +18777,11 @@ function OhhwellsBridge() {
17639
18777
  },
17640
18778
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
17641
18779
  )),
17642
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17643
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17644
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17645
- 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)(
17646
18785
  FooterContainerChrome,
17647
18786
  {
17648
18787
  rect: toolbarRect,
@@ -17650,7 +18789,7 @@ function OhhwellsBridge() {
17650
18789
  addDisabled: !canAddFooterColumn()
17651
18790
  }
17652
18791
  ),
17653
- 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)(
17654
18793
  ItemInteractionLayer,
17655
18794
  {
17656
18795
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -17662,10 +18801,10 @@ function OhhwellsBridge() {
17662
18801
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
17663
18802
  onDragHandleDragStart: handleItemDragStart,
17664
18803
  onDragHandleDragEnd: handleItemDragEnd,
17665
- onItemPointerDown: handleItemChromePointerDown,
17666
- onItemClick: handleItemChromeClick,
17667
- itemDragSurface: !isFooterFrameSelection,
17668
- 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)(
17669
18808
  ItemActionToolbar,
17670
18809
  {
17671
18810
  onEditLink: openLinkPopoverForSelected,
@@ -17681,7 +18820,10 @@ function OhhwellsBridge() {
17681
18820
  onSelectParent: handleSelectParent,
17682
18821
  onDuplicate: handleDuplicateSelected,
17683
18822
  onDelete: handleDeleteSelected,
17684
- 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
+ })(),
17685
18827
  editLinkDisabled: false,
17686
18828
  moreDisabled: false,
17687
18829
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
@@ -17698,8 +18840,8 @@ function OhhwellsBridge() {
17698
18840
  ) : void 0
17699
18841
  }
17700
18842
  ),
17701
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17702
- /* @__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)(
17703
18845
  EditGlowChrome,
17704
18846
  {
17705
18847
  rect: toolbarRect,
@@ -17709,7 +18851,7 @@ function OhhwellsBridge() {
17709
18851
  hideHandle: isItemDragging
17710
18852
  }
17711
18853
  ),
17712
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18854
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17713
18855
  FloatingToolbar,
17714
18856
  {
17715
18857
  rect: toolbarRect,
@@ -17722,7 +18864,7 @@ function OhhwellsBridge() {
17722
18864
  }
17723
18865
  )
17724
18866
  ] }),
17725
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
18867
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17726
18868
  "div",
17727
18869
  {
17728
18870
  "data-ohw-max-badge": "",
@@ -17748,7 +18890,7 @@ function OhhwellsBridge() {
17748
18890
  ]
17749
18891
  }
17750
18892
  ),
17751
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18893
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17752
18894
  StateToggle,
17753
18895
  {
17754
18896
  rect: toggleState.rect,
@@ -17757,15 +18899,15 @@ function OhhwellsBridge() {
17757
18899
  onStateChange: handleStateChange
17758
18900
  }
17759
18901
  ),
17760
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
18902
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17761
18903
  "div",
17762
18904
  {
17763
18905
  "data-ohw-section-insert-line": "",
17764
18906
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
17765
18907
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
17766
18908
  children: [
17767
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
17768
- /* @__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)(
17769
18911
  Badge,
17770
18912
  {
17771
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",
@@ -17782,11 +18924,11 @@ function OhhwellsBridge() {
17782
18924
  children: "Add Section"
17783
18925
  }
17784
18926
  ),
17785
- /* @__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 } })
17786
18928
  ]
17787
18929
  }
17788
18930
  ),
17789
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18931
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17790
18932
  LinkPopover,
17791
18933
  {
17792
18934
  panelRef: linkPopoverPanelRef,
@@ -17803,7 +18945,7 @@ function OhhwellsBridge() {
17803
18945
  },
17804
18946
  linkPopover.key
17805
18947
  ) : null,
17806
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18948
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17807
18949
  FloatingPanel,
17808
18950
  {
17809
18951
  open: true,
@@ -17813,7 +18955,7 @@ function OhhwellsBridge() {
17813
18955
  onPositionChange: setFloatingPanelPos,
17814
18956
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
17815
18957
  onClose: closeFloatingPanelOnly,
17816
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18958
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17817
18959
  SocialsDisplayPanel,
17818
18960
  {
17819
18961
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -17824,11 +18966,115 @@ function OhhwellsBridge() {
17824
18966
  }
17825
18967
  )
17826
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
+ }
17827
19020
  ) : null
17828
19021
  ] }),
17829
19022
  bridgeRoot
17830
19023
  ) : null;
17831
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
+ }
17832
19078
  // Annotate the CommonJS export names for ESM import in node:
17833
19079
  0 && (module.exports = {
17834
19080
  AI_DEFAULT_BRAND,
@@ -17846,6 +19092,7 @@ function OhhwellsBridge() {
17846
19092
  DropdownMenuItem,
17847
19093
  DropdownMenuSeparator,
17848
19094
  DropdownMenuTrigger,
19095
+ EmptySection,
17849
19096
  ItemActionToolbar,
17850
19097
  ItemInteractionLayer,
17851
19098
  LinkEditorPanel,