@ohhwells/bridge 0.1.57 → 0.1.59-next.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -118,6 +118,129 @@ function deleteSectionFromState(state, sectionId) {
118
118
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
119
119
  }
120
120
 
121
+ // src/lib/brand-chrome.ts
122
+ var BRAND_NAME_KEY = "__ohw_brand_name";
123
+ var BRAND_TITLE_KEY = "__ohw_site_title";
124
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
125
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
126
+ BRAND_NAME_KEY,
127
+ BRAND_TITLE_KEY,
128
+ BRAND_FAVICON_LETTER_KEY
129
+ ]);
130
+ function upsertMeta(selector, attr, token, value) {
131
+ let el = document.head.querySelector(selector);
132
+ if (!el) {
133
+ el = document.createElement("meta");
134
+ el.setAttribute(attr, token);
135
+ document.head.appendChild(el);
136
+ }
137
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
138
+ }
139
+ function escapeXml(value) {
140
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
141
+ }
142
+ function applyLetterFavicon(letter) {
143
+ 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>`;
144
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
145
+ let link = document.head.querySelector('link[rel="icon"]');
146
+ if (!link) {
147
+ link = document.createElement("link");
148
+ link.rel = "icon";
149
+ document.head.appendChild(link);
150
+ }
151
+ link.type = "image/svg+xml";
152
+ if (link.href !== href) link.href = href;
153
+ }
154
+ function applyBrandChrome(content) {
155
+ const name = content[BRAND_NAME_KEY];
156
+ if (typeof name === "string" && name.length > 0) {
157
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
158
+ if (el.textContent !== name) el.textContent = name;
159
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
160
+ });
161
+ }
162
+ const title = content[BRAND_TITLE_KEY];
163
+ if (typeof title === "string" && title.length > 0) {
164
+ if (document.title !== title) document.title = title;
165
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
166
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
167
+ }
168
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
169
+ if (typeof letter === "string" && letter.length > 0) {
170
+ applyLetterFavicon(letter);
171
+ }
172
+ }
173
+
174
+ // src/lib/brand-kit.ts
175
+ var BRAND_KIT_KEY = "__ohw_brand";
176
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
177
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
178
+ (role) => `${BRAND_VAR_PREFIX}${role}`
179
+ );
180
+ var FONT_VARS = { heading: ["--font-heading", "--font-display"], body: ["--font-body"] };
181
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
182
+ function brandColorVars(kit) {
183
+ const { dark, primary, accent, light } = kit.palette;
184
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
185
+ return {
186
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
187
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
188
+ [`${BRAND_VAR_PREFIX}light`]: light,
189
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
190
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
191
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
192
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
193
+ };
194
+ }
195
+ function parseBrandKit(raw) {
196
+ if (!raw) return null;
197
+ try {
198
+ const parsed = JSON.parse(raw);
199
+ const p = parsed?.palette;
200
+ const f = parsed?.fonts;
201
+ 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") {
202
+ return null;
203
+ }
204
+ return {
205
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
206
+ fonts: { heading: f.heading, body: f.body }
207
+ };
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ function familyOf(stack) {
213
+ const first = stack.split(",")[0]?.trim() ?? "";
214
+ return first.replace(/^['"]|['"]$/g, "");
215
+ }
216
+ function loadBrandFonts(families) {
217
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
218
+ if (unique.length === 0) return;
219
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
220
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
221
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
222
+ if (!link) {
223
+ link = document.createElement("link");
224
+ link.id = BRAND_FONT_LINK_ID;
225
+ link.rel = "stylesheet";
226
+ document.head.appendChild(link);
227
+ }
228
+ if (link.href !== href) link.href = href;
229
+ }
230
+ function applyBrandToDom(kit) {
231
+ const root = document.documentElement;
232
+ if (!kit) {
233
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
234
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
235
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
236
+ return;
237
+ }
238
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
239
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
240
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
241
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
242
+ }
243
+
121
244
  // src/ui/ai-tree/aiSectionsManager.tsx
122
245
  import { flushSync } from "react-dom";
123
246
  import { createRoot } from "react-dom/client";
@@ -150,6 +273,17 @@ var FEATURE_LINE_CSS = [
150
273
  function textAttrs(ctx, path) {
151
274
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
152
275
  }
276
+ var AI_RESPONSIVE_CSS = [
277
+ "@media (max-width: 960px) {",
278
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
279
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
280
+ "}",
281
+ "@media (max-width: 640px) {",
282
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
283
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
284
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
285
+ "}"
286
+ ].join("\n");
153
287
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
154
288
  function MediaBox({
155
289
  refValue,
@@ -892,6 +1026,7 @@ function Carousel({ items, itemsPerRow, ctx }) {
892
1026
  children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
893
1027
  "div",
894
1028
  {
1029
+ "data-ai-grid": String(itemsPerRow),
895
1030
  style: {
896
1031
  flex: "0 0 100%",
897
1032
  display: "grid",
@@ -995,6 +1130,7 @@ function CollectionBlock({ node, ctx, path }) {
995
1130
  return /* @__PURE__ */ jsx(
996
1131
  "div",
997
1132
  {
1133
+ "data-ai-grid": String(itemsPerRow),
998
1134
  style: {
999
1135
  display: "grid",
1000
1136
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1158,6 +1294,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1158
1294
  {
1159
1295
  "data-ai-section": tree.tag ?? "",
1160
1296
  ...bgAttrs,
1297
+ "data-ai-responsive": "",
1161
1298
  style: {
1162
1299
  position: "relative",
1163
1300
  padding: `${pad}px 0`,
@@ -1167,10 +1304,12 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1167
1304
  backgroundPosition: "center"
1168
1305
  },
1169
1306
  children: [
1307
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1170
1308
  isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1171
1309
  /* @__PURE__ */ jsx(
1172
1310
  "div",
1173
1311
  {
1312
+ "data-ai-section-inner": "",
1174
1313
  style: {
1175
1314
  position: "relative",
1176
1315
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1181,6 +1320,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1181
1320
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1182
1321
  "div",
1183
1322
  {
1323
+ "data-ai-columns": "",
1184
1324
  style: {
1185
1325
  display: "grid",
1186
1326
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -1204,17 +1344,34 @@ import { jsx as jsx2 } from "react/jsx-runtime";
1204
1344
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1205
1345
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1206
1346
  var REMOVED_ATTR = "data-ohw-ai-removed";
1347
+ function readRootVar(name) {
1348
+ if (typeof document === "undefined") return "";
1349
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1350
+ }
1351
+ function deriveBrandOverride() {
1352
+ const dark = readRootVar("--ohw-brand-dark");
1353
+ const primary = readRootVar("--ohw-brand-primary");
1354
+ const light = readRootVar("--ohw-brand-light");
1355
+ if (!dark || !primary || !light) return null;
1356
+ const accent = readRootVar("--ohw-brand-accent");
1357
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1358
+ const body = readRootVar("--font-body");
1359
+ return {
1360
+ palette: { dark, primary, accent: accent || dark, light },
1361
+ fonts: {
1362
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1363
+ body: body || AI_DEFAULT_BRAND.fonts.body
1364
+ }
1365
+ };
1366
+ }
1207
1367
  function deriveTemplateBrand() {
1208
- if (typeof document === "undefined") return null;
1209
- const cs = getComputedStyle(document.documentElement);
1210
- const read = (name) => cs.getPropertyValue(name).trim();
1211
- const dark = read("--color-dark");
1212
- const primary = read("--color-primary");
1213
- const light = read("--color-light");
1368
+ const dark = readRootVar("--color-dark");
1369
+ const primary = readRootVar("--color-primary");
1370
+ const light = readRootVar("--color-light");
1214
1371
  if (!dark || !primary || !light) return null;
1215
- const accent = read("--color-accent");
1216
- const heading = read("--font-heading") || read("--font-display");
1217
- const body = read("--font-body");
1372
+ const accent = readRootVar("--color-accent");
1373
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1374
+ const body = readRootVar("--font-body");
1218
1375
  return {
1219
1376
  palette: { dark, primary, accent: accent || dark, light },
1220
1377
  fonts: {
@@ -1306,7 +1463,9 @@ function syncReplacedOriginals(state) {
1306
1463
  }
1307
1464
  function applyAiSectionsToDom(state, options) {
1308
1465
  if (typeof document === "undefined") return;
1466
+ const brandOverride = deriveBrandOverride();
1309
1467
  const templateBrand = deriveTemplateBrand();
1468
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1310
1469
  const activeIds = new Set(state.sections.map((entry) => entry.id));
1311
1470
  for (const [id, section] of mounted) {
1312
1471
  if (!activeIds.has(id)) {
@@ -1316,7 +1475,7 @@ function applyAiSectionsToDom(state, options) {
1316
1475
  }
1317
1476
  }
1318
1477
  for (const entry of state.sections) {
1319
- const serialized = JSON.stringify(entry);
1478
+ const serialized = JSON.stringify(entry) + brandKey;
1320
1479
  const existing = mounted.get(entry.id);
1321
1480
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1322
1481
  continue;
@@ -1330,6 +1489,7 @@ function applyAiSectionsToDom(state, options) {
1330
1489
  mounted.delete(entry.id);
1331
1490
  }
1332
1491
  container.setAttribute("data-ohw-section", entry.id);
1492
+ container.setAttribute("data-ohw-instance", entry.id);
1333
1493
  container.setAttribute("data-ohw-section-label", entry.label);
1334
1494
  placeContainer(container, entry);
1335
1495
  const root = mounted.get(entry.id)?.root ?? createRoot(container);
@@ -1340,7 +1500,7 @@ function applyAiSectionsToDom(state, options) {
1340
1500
  AiTreeRenderer,
1341
1501
  {
1342
1502
  tree: entry.tree,
1343
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1503
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1344
1504
  resolveMedia,
1345
1505
  editKeyPrefix: `ai.${entry.id}`
1346
1506
  }
@@ -1957,7 +2117,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
1957
2117
  const autoId = useId();
1958
2118
  const insertAfter = insertAfterProp ?? autoId;
1959
2119
  const [schedule, setSchedule] = useState2(null);
1960
- const [loading, setLoading] = useState2(true);
2120
+ const [loading, setLoading] = useState2(initialScheduleId !== null);
1961
2121
  const [inEditor, setInEditor] = useState2(false);
1962
2122
  const [isHovered, setIsHovered] = useState2(false);
1963
2123
  const [modalState, setModalState] = useState2(null);
@@ -2131,8 +2291,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2131
2291
  "*"
2132
2292
  );
2133
2293
  };
2134
- if (!inEditor && !loading && !schedule) return null;
2135
2294
  const sectionId = `scheduling-${insertAfter}`;
2295
+ if (!inEditor && !loading && !schedule) {
2296
+ return /* @__PURE__ */ jsx4("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2297
+ }
2136
2298
  return /* @__PURE__ */ jsxs3(
2137
2299
  "section",
2138
2300
  {
@@ -6086,6 +6248,7 @@ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
6086
6248
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
6087
6249
  var TOOLBAR_EDGE_MARGIN = 4;
6088
6250
  var SELECTION_CHROME_GAP = 4;
6251
+ var HOVER_CHROME_GAP = 2;
6089
6252
  var TOOLBAR_STROKE_GAP = 4;
6090
6253
  function getChromeZIndex(state) {
6091
6254
  switch (state) {
@@ -6241,10 +6404,11 @@ function ItemInteractionLayer({
6241
6404
  onItemPointerDown,
6242
6405
  onItemClick,
6243
6406
  itemDragSurface = true,
6244
- chromeGap = SELECTION_CHROME_GAP,
6407
+ chromeGap,
6245
6408
  className
6246
6409
  }) {
6247
6410
  if (state === "default") return null;
6411
+ const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
6248
6412
  const isActive = state === "active-top" || state === "active-bottom";
6249
6413
  const isDragging = state === "dragging";
6250
6414
  const showToolbar = isActive && toolbar;
@@ -6260,10 +6424,10 @@ function ItemInteractionLayer({
6260
6424
  className: cn("pointer-events-none", className),
6261
6425
  style: {
6262
6426
  position: "fixed",
6263
- top: rect.top - chromeGap,
6264
- left: rect.left - chromeGap,
6265
- width: rect.width + chromeGap * 2,
6266
- height: rect.height + chromeGap * 2,
6427
+ top: rect.top - gap,
6428
+ left: rect.left - gap,
6429
+ width: rect.width + gap * 2,
6430
+ height: rect.height + gap * 2,
6267
6431
  zIndex: getChromeZIndex(state)
6268
6432
  },
6269
6433
  children: [
@@ -6649,8 +6813,12 @@ function parseSectionsFromHtml(html) {
6649
6813
 
6650
6814
  // src/ui/ai-section/AiSectionOverlay.tsx
6651
6815
  import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
6652
- function readRect(sectionId) {
6653
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
6816
+ function findSectionElement(instanceId) {
6817
+ const escaped = CSS.escape(instanceId);
6818
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
6819
+ }
6820
+ function readRect(instanceId) {
6821
+ const el = findSectionElement(instanceId);
6654
6822
  if (!el) return null;
6655
6823
  const r2 = el.getBoundingClientRect();
6656
6824
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -6673,7 +6841,7 @@ function useLiveSectionRect(sectionId) {
6673
6841
  const opts = { capture: true, passive: true };
6674
6842
  window.addEventListener("scroll", update, opts);
6675
6843
  window.addEventListener("resize", update);
6676
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
6844
+ const el = findSectionElement(sectionId);
6677
6845
  const ro = el ? new ResizeObserver(update) : null;
6678
6846
  if (el && ro) ro.observe(el);
6679
6847
  const interval = setInterval(update, 500);
@@ -6686,6 +6854,14 @@ function useLiveSectionRect(sectionId) {
6686
6854
  }, [sectionId]);
6687
6855
  return rect;
6688
6856
  }
6857
+ function computeSectionBoundaryFlags(instanceId) {
6858
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
6859
+ (el) => !el.parentElement?.closest("[data-ohw-section]")
6860
+ );
6861
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
6862
+ if (index === -1) return { isFirst: true, isLast: true };
6863
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
6864
+ }
6689
6865
  var PRIMARY2 = "#0885FE";
6690
6866
  function edgeAwareRadius(rect) {
6691
6867
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -6767,7 +6943,7 @@ function AiSectionOverlay({
6767
6943
  (el) => {
6768
6944
  postToParent2({
6769
6945
  type: "ow:section-selected",
6770
- sectionId: el?.dataset.ohwSection ?? null,
6946
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
6771
6947
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
6772
6948
  });
6773
6949
  },
@@ -6776,7 +6952,7 @@ function AiSectionOverlay({
6776
6952
  const selectFromElement = useCallback2(
6777
6953
  (el, options) => {
6778
6954
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
6779
- const id = sectionEl?.dataset.ohwSection ?? null;
6955
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
6780
6956
  if (id === selectedIdRef.current) return;
6781
6957
  setSelectedId(id);
6782
6958
  if (options?.report !== false) report(sectionEl);
@@ -6819,7 +6995,7 @@ function AiSectionOverlay({
6819
6995
  setReviewId(found ? sectionId : null);
6820
6996
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
6821
6997
  if (found) {
6822
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
6998
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
6823
6999
  }
6824
7000
  }
6825
7001
  };
@@ -6838,7 +7014,7 @@ function AiSectionOverlay({
6838
7014
  return;
6839
7015
  }
6840
7016
  const sec = t.closest("[data-ohw-section]");
6841
- setHoveredId(sec?.dataset.ohwSection ?? null);
7017
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
6842
7018
  };
6843
7019
  const onLeave = () => setHoveredId(null);
6844
7020
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -6870,9 +7046,29 @@ function AiSectionOverlay({
6870
7046
  },
6871
7047
  [postToParent2]
6872
7048
  );
6873
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7049
+ const activeSelectionId = reviewId ? null : selectedId;
7050
+ const selectionRect = useLiveSectionRect(activeSelectionId);
6874
7051
  const reviewRect = useLiveSectionRect(reviewId);
6875
7052
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7053
+ useEffect4(() => {
7054
+ if (!activeSelectionId || !selectionRect) {
7055
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7056
+ return;
7057
+ }
7058
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7059
+ postToParent2({
7060
+ type: "ow:section-rect",
7061
+ instanceId: activeSelectionId,
7062
+ rect: {
7063
+ top: selectionRect.top + window.scrollY,
7064
+ left: selectionRect.left + window.scrollX,
7065
+ width: selectionRect.width,
7066
+ height: selectionRect.height
7067
+ },
7068
+ isFirst,
7069
+ isLast
7070
+ });
7071
+ }, [activeSelectionId, selectionRect, postToParent2]);
6876
7072
  return /* @__PURE__ */ jsxs8(Fragment5, { children: [
6877
7073
  hoverRect && /* @__PURE__ */ jsx16(
6878
7074
  "div",
@@ -6923,7 +7119,10 @@ function AiSectionOverlay({
6923
7119
  border: `2px solid ${PRIMARY2}`,
6924
7120
  borderRadius: edgeAwareRadius(reviewRect),
6925
7121
  zIndex: 2147483200,
6926
- // The veil itself: swallows clicks so the section stays locked until decided.
7122
+ // The veil itself: swallows clicks so the section stays locked until decided. This
7123
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7124
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7125
+ // Accept/Discard resolves to the media beneath and opens the file picker.
6927
7126
  background: "rgba(8, 133, 254, 0.04)",
6928
7127
  pointerEvents: "auto",
6929
7128
  cursor: "default"
@@ -7035,7 +7234,14 @@ function findClosestButtonLike(target) {
7035
7234
  function findButtonAtPoint(x, y, media) {
7036
7235
  const hits = Array.from(
7037
7236
  document.querySelectorAll(BUTTON_LIKE_SELECTOR)
7038
- ).filter((el) => containsPoint(el, x, y) && !(media && el.contains(media)));
7237
+ ).filter(
7238
+ (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
7239
+ // site button. Without this, once the media overlay renders its button lands right
7240
+ // under the cursor, gets detected as "a button on the media", and dismisses the very
7241
+ // overlay it belongs to — which removes the button, so the next hit-test finds nothing
7242
+ // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
7243
+ !el.closest("[data-ohw-bridge]")
7244
+ );
7039
7245
  if (hits.length === 0) return null;
7040
7246
  return hits.reduce((best, el) => best.contains(el) ? el : best);
7041
7247
  }
@@ -9610,6 +9816,10 @@ function isSocialsRow(el) {
9610
9816
  const anchors = Array.from(el.querySelectorAll("a"));
9611
9817
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9612
9818
  }
9819
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9820
+ function canAddSocialItem(row) {
9821
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9822
+ }
9613
9823
  function listSocialItems(row) {
9614
9824
  return Array.from(row.children).map((child) => {
9615
9825
  if (!(child instanceof HTMLElement)) return null;
@@ -9969,6 +10179,7 @@ function ensureIconSlot(item) {
9969
10179
  // src/lib/footer-items.ts
9970
10180
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
9971
10181
  var MAX_FOOTER_COLUMNS = 18;
10182
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
9972
10183
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
9973
10184
  function parseFooterHrefKey(key) {
9974
10185
  if (!key) return null;
@@ -10186,6 +10397,13 @@ function getNextFooterColumnIndex() {
10186
10397
  function canAddFooterColumn() {
10187
10398
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10188
10399
  }
10400
+ function canAddFooterItem(column) {
10401
+ return listFooterLinksInColumn(column).length < MAX_FOOTER_ITEMS_PER_COLUMN;
10402
+ }
10403
+ function resolveFooterColumnForAdd(selected) {
10404
+ if (selected.hasAttribute("data-ohw-footer-col")) return selected;
10405
+ return selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
10406
+ }
10189
10407
  function buildFooterHeading(colIndex, text) {
10190
10408
  const heading = document.createElement("p");
10191
10409
  heading.setAttribute("data-ohw-editable", "text");
@@ -10810,6 +11028,329 @@ function deleteFooterColumn(column) {
10810
11028
  };
10811
11029
  }
10812
11030
 
11031
+ // src/lib/logo-identity.ts
11032
+ var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11033
+ var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11034
+ var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11035
+ var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11036
+ var LOGO_ALT_KEY = "logo-alt";
11037
+ var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11038
+ var PLACEHOLDER_BUSINESS_NAME = "Business name";
11039
+ function resolveLogoDisplayText(text) {
11040
+ const trimmed = (text ?? "").trim();
11041
+ return trimmed || PLACEHOLDER_BUSINESS_NAME;
11042
+ }
11043
+ function isFooterLogoRoot(root) {
11044
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11045
+ }
11046
+ function imageKeyForRoot(root) {
11047
+ return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11048
+ }
11049
+ function textKeyForRoot(root) {
11050
+ return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11051
+ }
11052
+ function ensureLogoHrefKey(root) {
11053
+ if (!(root instanceof HTMLAnchorElement)) return;
11054
+ if (root.hasAttribute("data-ohw-href-key")) return;
11055
+ root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11056
+ }
11057
+ function applyLogoIdentity(text, isPlaceholder) {
11058
+ const display = resolveLogoDisplayText(text);
11059
+ const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11060
+ for (const key of LOGO_TEXT_KEYS) {
11061
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11062
+ if (el.textContent !== display) el.textContent = display;
11063
+ });
11064
+ }
11065
+ for (const key of LOGO_IMAGE_KEYS) {
11066
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11067
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11068
+ if (img) img.alt = display;
11069
+ });
11070
+ }
11071
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11072
+ if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11073
+ else el.removeAttribute("data-ohw-placeholder");
11074
+ });
11075
+ return display;
11076
+ }
11077
+ function applyLogoImage(url, alt) {
11078
+ const displayAlt = resolveLogoDisplayText(alt);
11079
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11080
+ ensureLogoHrefKey(root);
11081
+ const imageKey = imageKeyForRoot(root);
11082
+ const textKey = textKeyForRoot(root);
11083
+ 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");
11084
+ let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11085
+ if (url) {
11086
+ if (!img) {
11087
+ img = document.createElement("img");
11088
+ img.setAttribute("data-ohw-editable", "image");
11089
+ img.setAttribute("data-ohw-key", imageKey);
11090
+ img.alt = displayAlt;
11091
+ img.style.height = "";
11092
+ img.style.maxHeight = "none";
11093
+ img.style.width = "auto";
11094
+ img.style.display = "block";
11095
+ img.style.objectFit = "contain";
11096
+ root.insertBefore(img, root.firstChild);
11097
+ } else {
11098
+ img.setAttribute("data-ohw-editable", "image");
11099
+ img.setAttribute("data-ohw-key", imageKey);
11100
+ }
11101
+ img.removeAttribute("srcset");
11102
+ img.removeAttribute("sizes");
11103
+ img.src = url;
11104
+ img.alt = displayAlt;
11105
+ img.style.display = "block";
11106
+ if (textEl) textEl.style.display = "none";
11107
+ root.removeAttribute("data-ohw-placeholder");
11108
+ return;
11109
+ }
11110
+ if (img) {
11111
+ img.removeAttribute("src");
11112
+ img.removeAttribute("srcset");
11113
+ img.removeAttribute("sizes");
11114
+ img.alt = displayAlt;
11115
+ img.style.display = "none";
11116
+ }
11117
+ if (!textEl) {
11118
+ textEl = document.createElement("span");
11119
+ textEl.setAttribute("data-ohw-editable", "plain");
11120
+ textEl.setAttribute("data-ohw-key", textKey);
11121
+ root.appendChild(textEl);
11122
+ }
11123
+ textEl.style.display = "";
11124
+ if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11125
+ if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11126
+ root.setAttribute("data-ohw-placeholder", "");
11127
+ } else {
11128
+ root.removeAttribute("data-ohw-placeholder");
11129
+ }
11130
+ });
11131
+ }
11132
+ function applyLogoHref(href) {
11133
+ const target = href.trim() || "/";
11134
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11135
+ ensureLogoHrefKey(root);
11136
+ if (root instanceof HTMLAnchorElement) {
11137
+ root.setAttribute("href", target);
11138
+ }
11139
+ });
11140
+ for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11141
+ }
11142
+ function readLogoIdentityFromDom() {
11143
+ let imageUrl = null;
11144
+ for (const key of LOGO_IMAGE_KEYS) {
11145
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11146
+ const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11147
+ const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11148
+ if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11149
+ imageUrl = img.currentSrc || img.src;
11150
+ break;
11151
+ }
11152
+ }
11153
+ let text = PLACEHOLDER_BUSINESS_NAME;
11154
+ let isPlaceholder = true;
11155
+ for (const key of LOGO_TEXT_KEYS) {
11156
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11157
+ if (el?.textContent?.trim()) {
11158
+ text = el.textContent.trim();
11159
+ const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11160
+ isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11161
+ break;
11162
+ }
11163
+ }
11164
+ if (imageUrl) {
11165
+ const logoImg = document.querySelector(
11166
+ '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11167
+ );
11168
+ const alt = logoImg?.alt?.trim() || text;
11169
+ isPlaceholder = false;
11170
+ const hrefEl = document.querySelector(
11171
+ 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11172
+ );
11173
+ const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11174
+ return { text, isPlaceholder, imageUrl, href: href2, alt };
11175
+ }
11176
+ const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11177
+ const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11178
+ return { text, isPlaceholder, imageUrl: null, href, alt: text };
11179
+ }
11180
+ function applyLogoFromContent(content) {
11181
+ 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);
11182
+ if (!hasLogoIdentity) return false;
11183
+ const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11184
+ const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11185
+ const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11186
+ const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11187
+ const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11188
+ const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11189
+ if (logoImageUrl) {
11190
+ applyLogoImage(logoImageUrl, logoAlt);
11191
+ } else {
11192
+ if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11193
+ applyLogoIdentity(logoText, logoIsPlaceholder);
11194
+ }
11195
+ const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11196
+ if (typeof logoHref === "string" && logoHref.trim()) {
11197
+ applyLogoHref(logoHref);
11198
+ }
11199
+ return true;
11200
+ }
11201
+
11202
+ // src/lib/logo-size.ts
11203
+ var LOGO_SIZE_DEFAULTS = {
11204
+ navbar: 28,
11205
+ footer: 32
11206
+ };
11207
+ var LOGO_SIZE_MIN = 16;
11208
+ var LOGO_SIZE_MAX = 80;
11209
+ var LOGO_SIZE_DESKTOP_KEYS = {
11210
+ navbar: "nav-logo-size",
11211
+ footer: "footer-logo-size"
11212
+ };
11213
+ var LOGO_SIZE_MOBILE_KEYS = {
11214
+ navbar: "nav-logo-size-mobile",
11215
+ footer: "footer-logo-size-mobile"
11216
+ };
11217
+ var LOGO_SIZE_KEYS = [
11218
+ LOGO_SIZE_DESKTOP_KEYS.navbar,
11219
+ LOGO_SIZE_DESKTOP_KEYS.footer,
11220
+ LOGO_SIZE_MOBILE_KEYS.navbar,
11221
+ LOGO_SIZE_MOBILE_KEYS.footer
11222
+ ];
11223
+ function isFooterLogoRoot2(root) {
11224
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11225
+ }
11226
+ function getLogoPlacement(root) {
11227
+ return isFooterLogoRoot2(root) ? "footer" : "navbar";
11228
+ }
11229
+ function parseLogoSizePx(raw, fallback) {
11230
+ if (raw == null || raw === "") return fallback;
11231
+ const n = Number.parseFloat(raw);
11232
+ if (!Number.isFinite(n)) return fallback;
11233
+ return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11234
+ }
11235
+ function isMobileLogoSizeFollowing(content, placement) {
11236
+ const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11237
+ return raw == null || raw.trim() === "";
11238
+ }
11239
+ function resolveDesktopLogoSize(content, placement) {
11240
+ return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11241
+ }
11242
+ function resolveMobileLogoSize(content, placement) {
11243
+ if (isMobileLogoSizeFollowing(content, placement)) {
11244
+ return resolveDesktopLogoSize(content, placement);
11245
+ }
11246
+ return parseLogoSizePx(
11247
+ content[LOGO_SIZE_MOBILE_KEYS[placement]],
11248
+ resolveDesktopLogoSize(content, placement)
11249
+ );
11250
+ }
11251
+ function setRootSizeVars(root, desktopPx, mobilePx, following) {
11252
+ root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11253
+ if (following) {
11254
+ root.style.removeProperty("--ohw-logo-size-mobile");
11255
+ } else {
11256
+ root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11257
+ }
11258
+ root.querySelectorAll("img").forEach((img) => {
11259
+ img.style.height = "";
11260
+ img.style.maxHeight = "none";
11261
+ img.style.width = "auto";
11262
+ img.style.objectFit = "contain";
11263
+ });
11264
+ }
11265
+ function applyLogoSizes(content) {
11266
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11267
+ const placement = getLogoPlacement(root);
11268
+ const desktop = resolveDesktopLogoSize(content, placement);
11269
+ const following = isMobileLogoSizeFollowing(content, placement);
11270
+ const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11271
+ setRootSizeVars(root, desktop, mobile, following);
11272
+ });
11273
+ }
11274
+ function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11275
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11276
+ if (getLogoPlacement(root) !== placement) return;
11277
+ setRootSizeVars(root, desktopPx, mobilePx, following);
11278
+ });
11279
+ }
11280
+ function logoHasUploadedImage(logoEl) {
11281
+ if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11282
+ 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");
11283
+ if (!img) return false;
11284
+ const src = img.getAttribute("src")?.trim() ?? "";
11285
+ if (!src || src.startsWith("data:")) return false;
11286
+ if (img.style.display === "none") return false;
11287
+ return true;
11288
+ }
11289
+ function getLogoInteractionRect(logoEl) {
11290
+ if (logoHasUploadedImage(logoEl)) {
11291
+ 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");
11292
+ if (img) {
11293
+ const r2 = img.getBoundingClientRect();
11294
+ if (r2.width > 0 && r2.height > 0) return r2;
11295
+ }
11296
+ }
11297
+ const text = logoEl.querySelector(
11298
+ '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11299
+ );
11300
+ if (text) {
11301
+ const style = window.getComputedStyle(text);
11302
+ if (style.display !== "none" && style.visibility !== "hidden") {
11303
+ const r2 = text.getBoundingClientRect();
11304
+ if (r2.width > 0 && r2.height > 0) return r2;
11305
+ }
11306
+ }
11307
+ return logoEl.getBoundingClientRect();
11308
+ }
11309
+ function readLogoSizeState(content, placement) {
11310
+ const desktopPx = resolveDesktopLogoSize(content, placement);
11311
+ const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11312
+ const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11313
+ return { desktopPx, mobilePx, mobileFollowing };
11314
+ }
11315
+
11316
+ // src/lib/site-wide-scope.ts
11317
+ function getLogoElement(el) {
11318
+ const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11319
+ if (marked) return marked;
11320
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11321
+ const root = el.closest("nav, [data-ohw-nav-root], footer");
11322
+ if (!root) return null;
11323
+ const anchor = el.closest("a");
11324
+ if (anchor && root.contains(anchor) && !anchor.hasAttribute("data-ohw-href-key") && !anchor.closest("[data-ohw-nav-container]") && Boolean(anchor.querySelector("img") || anchor.matches("img"))) {
11325
+ return anchor;
11326
+ }
11327
+ const img = el.matches("img") ? el : null;
11328
+ if (img && !img.closest("[data-ohw-href-key]") && !img.closest("[data-ohw-nav-container]") && (img.closest("footer") || img.closest("nav, [data-ohw-nav-root]"))) {
11329
+ return img;
11330
+ }
11331
+ return null;
11332
+ }
11333
+ function isInFooter(el) {
11334
+ if (!el) return false;
11335
+ return Boolean(el.closest("footer") || el.closest('[data-ohw-section="footer"]'));
11336
+ }
11337
+ function isSiteWideElement(el) {
11338
+ if (!el) return false;
11339
+ if (getLogoElement(el)) return true;
11340
+ if (isInFooter(el)) return true;
11341
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-root")) {
11342
+ return true;
11343
+ }
11344
+ if (el.hasAttribute("data-ohw-href-key") && el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer], aside")) {
11345
+ return true;
11346
+ }
11347
+ if (el.closest('[data-ohw-role="navbar-button"]')) return true;
11348
+ return false;
11349
+ }
11350
+ function isSiteWideScopeActive(args) {
11351
+ return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11352
+ }
11353
+
10813
11354
  // src/lib/add-footer-column.ts
10814
11355
  function buildFooterColumnEditContentPatch(result) {
10815
11356
  return {
@@ -10921,6 +11462,7 @@ function FloatingPanel({
10921
11462
  e.stopPropagation();
10922
11463
  const el = e.currentTarget;
10923
11464
  el.setPointerCapture(e.pointerId);
11465
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10924
11466
  dragRef.current = {
10925
11467
  pointerId: e.pointerId,
10926
11468
  startX: e.clientX,
@@ -10953,11 +11495,17 @@ function FloatingPanel({
10953
11495
  const drag = dragRef.current;
10954
11496
  if (!drag || drag.pointerId !== e.pointerId) return;
10955
11497
  dragRef.current = null;
11498
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
10956
11499
  try {
10957
11500
  e.currentTarget.releasePointerCapture(e.pointerId);
10958
11501
  } catch {
10959
11502
  }
10960
11503
  }, []);
11504
+ useEffect9(() => {
11505
+ if (open) return;
11506
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11507
+ }, [open]);
11508
+ useEffect9(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
10961
11509
  if (!open) return null;
10962
11510
  return /* @__PURE__ */ jsxs16(
10963
11511
  "div",
@@ -11024,16 +11572,127 @@ function FloatingPanel({
11024
11572
  );
11025
11573
  }
11026
11574
 
11027
- // src/ui/socials-display-panel.tsx
11575
+ // src/ui/logo-size-panel.tsx
11576
+ import { ArrowUpRight, Link as Link2 } from "lucide-react";
11028
11577
  import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
11578
+ function SizeSlider({
11579
+ value,
11580
+ onChange
11581
+ }) {
11582
+ const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11583
+ return /* @__PURE__ */ jsxs17("div", { className: "flex w-full flex-col gap-3", children: [
11584
+ /* @__PURE__ */ jsxs17("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11585
+ /* @__PURE__ */ jsx27("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11586
+ /* @__PURE__ */ jsxs17("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11587
+ value,
11588
+ " px"
11589
+ ] })
11590
+ ] }),
11591
+ /* @__PURE__ */ jsxs17("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11592
+ /* @__PURE__ */ jsx27(
11593
+ "div",
11594
+ {
11595
+ className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11596
+ style: { width: `${pct}%` }
11597
+ }
11598
+ ),
11599
+ /* @__PURE__ */ jsx27(
11600
+ "input",
11601
+ {
11602
+ type: "range",
11603
+ min: LOGO_SIZE_MIN,
11604
+ max: LOGO_SIZE_MAX,
11605
+ step: 1,
11606
+ value,
11607
+ "aria-label": "Logo size",
11608
+ className: cn(
11609
+ "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11610
+ "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11611
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11612
+ "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11613
+ "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11614
+ "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11615
+ "[&::-moz-range-thumb]:bg-background"
11616
+ ),
11617
+ onChange: (e) => onChange(Number(e.target.value))
11618
+ }
11619
+ )
11620
+ ] })
11621
+ ] });
11622
+ }
11623
+ function LogoSizePanel({
11624
+ viewport,
11625
+ sizePx,
11626
+ mobileFollowing = true,
11627
+ onSizeChange,
11628
+ onCustomizeMobile,
11629
+ onResetMobile,
11630
+ onUpdateEverywhere,
11631
+ className
11632
+ }) {
11633
+ const showFollowing = viewport === "mobile" && mobileFollowing;
11634
+ const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11635
+ const showDesktopSlider = viewport === "desktop";
11636
+ return /* @__PURE__ */ jsxs17("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11637
+ showFollowing ? /* @__PURE__ */ jsxs17("div", { className: "flex w-full flex-col gap-2", children: [
11638
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-1", children: [
11639
+ /* @__PURE__ */ jsx27(Link2, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11640
+ /* @__PURE__ */ jsx27("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11641
+ ] }),
11642
+ /* @__PURE__ */ jsx27("p", { className: "text-sm leading-5 text-muted-foreground", children: "Mobile uses the desktop size until you customize it. Change the desktop size and it follows automatically." }),
11643
+ /* @__PURE__ */ jsx27(
11644
+ Button,
11645
+ {
11646
+ type: "button",
11647
+ variant: "outline",
11648
+ size: "sm",
11649
+ className: "h-9 w-full min-w-0 cursor-pointer",
11650
+ onClick: onCustomizeMobile,
11651
+ children: "Customize for mobile"
11652
+ }
11653
+ )
11654
+ ] }) : null,
11655
+ showDesktopSlider || showMobileSlider ? /* @__PURE__ */ jsx27(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11656
+ showMobileSlider ? /* @__PURE__ */ jsx27(
11657
+ Button,
11658
+ {
11659
+ type: "button",
11660
+ variant: "outline",
11661
+ size: "sm",
11662
+ className: "h-9 w-full min-w-0 cursor-pointer",
11663
+ onClick: onResetMobile,
11664
+ children: "Reset to desktop size"
11665
+ }
11666
+ ) : null,
11667
+ /* @__PURE__ */ jsx27("div", { className: "h-px w-full bg-border", role: "separator" }),
11668
+ /* @__PURE__ */ jsxs17(
11669
+ Button,
11670
+ {
11671
+ type: "button",
11672
+ variant: "outline",
11673
+ size: "sm",
11674
+ className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11675
+ onClick: onUpdateEverywhere,
11676
+ children: [
11677
+ "Update logo everywhere",
11678
+ /* @__PURE__ */ jsx27(ArrowUpRight, { size: 16, "aria-hidden": true })
11679
+ ]
11680
+ }
11681
+ ),
11682
+ /* @__PURE__ */ jsx27("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11683
+ ] });
11684
+ }
11685
+
11686
+ // src/ui/socials-display-panel.tsx
11687
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
11029
11688
  function DisplaySwitch({
11030
11689
  label,
11031
11690
  checked,
11032
11691
  disabled,
11033
11692
  onChange
11034
11693
  }) {
11035
- return /* @__PURE__ */ jsxs17("div", { className: "flex w-full items-center gap-2", children: [
11036
- /* @__PURE__ */ jsx27(
11694
+ return /* @__PURE__ */ jsxs18("div", { className: "flex w-full items-center gap-2", children: [
11695
+ /* @__PURE__ */ jsx28(
11037
11696
  "span",
11038
11697
  {
11039
11698
  className: cn(
@@ -11043,7 +11702,7 @@ function DisplaySwitch({
11043
11702
  children: label
11044
11703
  }
11045
11704
  ),
11046
- /* @__PURE__ */ jsx27(
11705
+ /* @__PURE__ */ jsx28(
11047
11706
  "button",
11048
11707
  {
11049
11708
  type: "button",
@@ -11057,7 +11716,7 @@ function DisplaySwitch({
11057
11716
  checked ? "bg-primary" : "bg-primary-50",
11058
11717
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
11059
11718
  ),
11060
- children: /* @__PURE__ */ jsx27(
11719
+ children: /* @__PURE__ */ jsx28(
11061
11720
  "span",
11062
11721
  {
11063
11722
  className: cn(
@@ -11071,8 +11730,8 @@ function DisplaySwitch({
11071
11730
  ] });
11072
11731
  }
11073
11732
  function SocialsDisplayPanel({ display, onChange, className }) {
11074
- return /* @__PURE__ */ jsxs17("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11075
- /* @__PURE__ */ jsx27(
11733
+ return /* @__PURE__ */ jsxs18("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11734
+ /* @__PURE__ */ jsx28(
11076
11735
  DisplaySwitch,
11077
11736
  {
11078
11737
  label: "Text",
@@ -11081,7 +11740,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
11081
11740
  onChange: (text) => onChange({ ...display, text })
11082
11741
  }
11083
11742
  ),
11084
- /* @__PURE__ */ jsx27(
11743
+ /* @__PURE__ */ jsx28(
11085
11744
  DisplaySwitch,
11086
11745
  {
11087
11746
  label: "Icon",
@@ -11642,7 +12301,7 @@ function useNavItemDrag({
11642
12301
 
11643
12302
  // src/ui/footer-container-chrome.tsx
11644
12303
  import { Plus as Plus2 } from "lucide-react";
11645
- import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
12304
+ import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
11646
12305
  function FooterContainerChrome({
11647
12306
  rect,
11648
12307
  onAdd,
@@ -11650,7 +12309,7 @@ function FooterContainerChrome({
11650
12309
  }) {
11651
12310
  const chromeGap = 6;
11652
12311
  const buttonMargin = 7;
11653
- return /* @__PURE__ */ jsx28(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx28(
12312
+ return /* @__PURE__ */ jsx29(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx29(
11654
12313
  "div",
11655
12314
  {
11656
12315
  "data-ohw-footer-container-chrome": "",
@@ -11662,8 +12321,8 @@ function FooterContainerChrome({
11662
12321
  width: rect.width + chromeGap * 2,
11663
12322
  height: rect.height + chromeGap * 2
11664
12323
  },
11665
- children: /* @__PURE__ */ jsxs18(Tooltip, { children: [
11666
- /* @__PURE__ */ jsx28(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx28(
12324
+ children: /* @__PURE__ */ jsxs19(Tooltip, { children: [
12325
+ /* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(
11667
12326
  "button",
11668
12327
  {
11669
12328
  type: "button",
@@ -11682,10 +12341,10 @@ function FooterContainerChrome({
11682
12341
  if (addDisabled) return;
11683
12342
  onAdd();
11684
12343
  },
11685
- children: /* @__PURE__ */ jsx28(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12344
+ children: /* @__PURE__ */ jsx29(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
11686
12345
  }
11687
12346
  ) }),
11688
- /* @__PURE__ */ jsx28(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12347
+ /* @__PURE__ */ jsx29(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
11689
12348
  ] })
11690
12349
  }
11691
12350
  ) });
@@ -11868,6 +12527,18 @@ function collectEditableNodes(extraContent, root = document) {
11868
12527
  }
11869
12528
  if (extraContent && !isScoped) {
11870
12529
  applyNavFooterDeleteOverrides(byKey, extraContent);
12530
+ for (const key of LOGO_IMAGE_KEYS) {
12531
+ if (!(key in extraContent)) continue;
12532
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12533
+ }
12534
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12535
+ if (!(key in extraContent)) continue;
12536
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12537
+ }
12538
+ for (const key of LOGO_SIZE_KEYS) {
12539
+ if (!(key in extraContent)) continue;
12540
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12541
+ }
11871
12542
  }
11872
12543
  return Array.from(byKey.values());
11873
12544
  }
@@ -12134,13 +12805,13 @@ function deleteSelectedNavFooterItem(deps) {
12134
12805
 
12135
12806
  // src/ui/navbar-container-chrome.tsx
12136
12807
  import { Plus as Plus3 } from "lucide-react";
12137
- import { jsx as jsx29 } from "react/jsx-runtime";
12808
+ import { jsx as jsx30 } from "react/jsx-runtime";
12138
12809
  function NavbarContainerChrome({
12139
12810
  rect,
12140
12811
  onAdd
12141
12812
  }) {
12142
12813
  const chromeGap = 6;
12143
- return /* @__PURE__ */ jsx29(
12814
+ return /* @__PURE__ */ jsx30(
12144
12815
  "div",
12145
12816
  {
12146
12817
  "data-ohw-navbar-container-chrome": "",
@@ -12152,7 +12823,7 @@ function NavbarContainerChrome({
12152
12823
  width: rect.width + chromeGap * 2,
12153
12824
  height: rect.height + chromeGap * 2
12154
12825
  },
12155
- children: /* @__PURE__ */ jsx29(
12826
+ children: /* @__PURE__ */ jsx30(
12156
12827
  "button",
12157
12828
  {
12158
12829
  type: "button",
@@ -12169,7 +12840,7 @@ function NavbarContainerChrome({
12169
12840
  e.stopPropagation();
12170
12841
  onAdd();
12171
12842
  },
12172
- children: /* @__PURE__ */ jsx29(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12843
+ children: /* @__PURE__ */ jsx30(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12173
12844
  }
12174
12845
  )
12175
12846
  }
@@ -12178,7 +12849,7 @@ function NavbarContainerChrome({
12178
12849
 
12179
12850
  // src/ui/drop-indicator.tsx
12180
12851
  import * as React10 from "react";
12181
- import { jsx as jsx30 } from "react/jsx-runtime";
12852
+ import { jsx as jsx31 } from "react/jsx-runtime";
12182
12853
  var dropIndicatorVariants = cva(
12183
12854
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
12184
12855
  {
@@ -12202,7 +12873,7 @@ var dropIndicatorVariants = cva(
12202
12873
  );
12203
12874
  var DropIndicator = React10.forwardRef(
12204
12875
  ({ className, direction, state, ...props }, ref) => {
12205
- return /* @__PURE__ */ jsx30(
12876
+ return /* @__PURE__ */ jsx31(
12206
12877
  "div",
12207
12878
  {
12208
12879
  ref,
@@ -12219,7 +12890,7 @@ var DropIndicator = React10.forwardRef(
12219
12890
  DropIndicator.displayName = "DropIndicator";
12220
12891
 
12221
12892
  // src/ui/badge.tsx
12222
- import { jsx as jsx31 } from "react/jsx-runtime";
12893
+ import { jsx as jsx32 } from "react/jsx-runtime";
12223
12894
  var badgeVariants = cva(
12224
12895
  "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",
12225
12896
  {
@@ -12237,12 +12908,12 @@ var badgeVariants = cva(
12237
12908
  }
12238
12909
  );
12239
12910
  function Badge({ className, variant, ...props }) {
12240
- return /* @__PURE__ */ jsx31("div", { className: cn(badgeVariants({ variant }), className), ...props });
12911
+ return /* @__PURE__ */ jsx32("div", { className: cn(badgeVariants({ variant }), className), ...props });
12241
12912
  }
12242
12913
 
12243
12914
  // src/OhhwellsBridge.tsx
12244
- import { Link as Link2 } from "lucide-react";
12245
- import { Fragment as Fragment8, jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
12915
+ import { Link as Link3 } from "lucide-react";
12916
+ import { Fragment as Fragment8, jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
12246
12917
  var PRIMARY3 = "#0885FE";
12247
12918
  var IMAGE_FADE_MS = 300;
12248
12919
  function runOpacityFade(el, onDone) {
@@ -12336,21 +13007,10 @@ function parseSchedulingInsertAfter(insertAfter) {
12336
13007
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
12337
13008
  };
12338
13009
  }
12339
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
12340
- const parsed = parseSchedulingInsertAfter(insertAfter);
12341
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
12342
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
12343
- return { effectiveInsertAfter, insertBefore };
12344
- }
12345
- function getSchedulingMountPoint(insertAfter) {
12346
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
12347
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
12348
- if (!anchorEl && anchor === "scheduling") {
12349
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
12350
- anchorEl = widgets.at(-1) ?? null;
12351
- }
12352
- if (!anchorEl) return null;
12353
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13010
+ function resolveEntryAnchor(entry) {
13011
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13012
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13013
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
12354
13014
  }
12355
13015
  function schedulingMountDepth(insertAfter) {
12356
13016
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -12367,8 +13027,7 @@ function getPageSchedulingEntries(raw) {
12367
13027
  }
12368
13028
  }
12369
13029
  function isSchedulingWidgetMissing(entry) {
12370
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
12371
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13030
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
12372
13031
  }
12373
13032
  function hasMissingSchedulingWidgets(entries) {
12374
13033
  return entries.some(isSchedulingWidgetMissing);
@@ -12398,16 +13057,17 @@ function initSectionsFromContent(content, removeExisting = false) {
12398
13057
  } catch {
12399
13058
  }
12400
13059
  }
12401
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
12402
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
12403
- const sectionId = schedulingSectionId(effectiveInsertAfter);
13060
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13061
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13062
+ const sectionId = schedulingSectionId(widgetId);
12404
13063
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
12405
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
12406
- if (!mountPoint) return false;
13064
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13065
+ if (!anchorEl) return false;
13066
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12407
13067
  const container = document.createElement("div");
12408
13068
  container.dataset.ohwSectionContainer = "scheduling";
12409
- if (insertBefore) {
12410
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13069
+ if (beforeId) {
13070
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
12411
13071
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
12412
13072
  if (!beforePoint) return false;
12413
13073
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -12418,19 +13078,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12418
13078
  }
12419
13079
  tail.insertAdjacentElement("afterend", container);
12420
13080
  }
12421
- const root = createRoot2(container);
12422
- flushSync2(() => {
12423
- root.render(
12424
- /* @__PURE__ */ jsx32(
12425
- SchedulingWidget,
12426
- {
12427
- notifyOnConnect,
12428
- initialScheduleId: scheduleId,
12429
- insertAfter: effectiveInsertAfter
12430
- }
12431
- )
12432
- );
12433
- });
13081
+ try {
13082
+ const root = createRoot2(container);
13083
+ flushSync2(() => {
13084
+ root.render(
13085
+ /* @__PURE__ */ jsx33(
13086
+ SchedulingWidget,
13087
+ {
13088
+ notifyOnConnect,
13089
+ initialScheduleId: scheduleId,
13090
+ insertAfter: widgetId
13091
+ }
13092
+ )
13093
+ );
13094
+ });
13095
+ } catch (err) {
13096
+ console.error("[ow:scheduling] render threw", err);
13097
+ container.remove();
13098
+ return false;
13099
+ }
12434
13100
  const tracker = getSectionsTracker();
12435
13101
  let sections = [];
12436
13102
  try {
@@ -12438,10 +13104,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12438
13104
  } catch {
12439
13105
  }
12440
13106
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
12441
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13107
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
12442
13108
  sections.push({
12443
13109
  type: "scheduling",
12444
- insertAfter: effectiveInsertAfter,
13110
+ insertAfter: widgetId,
13111
+ anchorId,
13112
+ beforeId: beforeId ?? null,
12445
13113
  pagePath: window.location.pathname,
12446
13114
  ...scheduleId ? { scheduleId } : {}
12447
13115
  });
@@ -12455,7 +13123,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
12455
13123
  for (let i = pending.length - 1; i >= 0; i--) {
12456
13124
  const entry = pending[i];
12457
13125
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
12458
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13126
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
13127
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
12459
13128
  pending.splice(i, 1);
12460
13129
  }
12461
13130
  }
@@ -12599,6 +13268,13 @@ function isInsideLinkEditor(target) {
12599
13268
  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"]')
12600
13269
  );
12601
13270
  }
13271
+ function isInsideFloatingPanel(target) {
13272
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
13273
+ }
13274
+ function isPointOverFloatingPanel(clientX, clientY) {
13275
+ const el = document.elementFromPoint(clientX, clientY);
13276
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13277
+ }
12602
13278
  function getHrefKeyFromElement(el) {
12603
13279
  if (!el) return null;
12604
13280
  const anchor = el.closest("[data-ohw-href-key]");
@@ -12736,6 +13412,11 @@ function isNavbarLinksContainer2(el) {
12736
13412
  function getFooterColumn(el) {
12737
13413
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12738
13414
  }
13415
+ function isFooterAddItemDisabled(selected) {
13416
+ if (!selected) return false;
13417
+ const column = resolveFooterColumnForAdd(selected);
13418
+ return column ? !canAddFooterItem(column) : false;
13419
+ }
12739
13420
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12740
13421
  if (getNavigationItemAnchor(target)) return null;
12741
13422
  const column = getFooterColumn(target);
@@ -12831,7 +13512,7 @@ function getNavigationSelectionParent(el) {
12831
13512
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
12832
13513
  return getFooterLinksContainer();
12833
13514
  }
12834
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13515
+ 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)) {
12835
13516
  return getNavigationRoot(el);
12836
13517
  }
12837
13518
  return null;
@@ -13049,6 +13730,9 @@ var ICONS = {
13049
13730
  var SELECTION_CHROME_GAP2 = 4;
13050
13731
  var TOOLBAR_STROKE_GAP2 = 4;
13051
13732
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
13733
+ var HOVER_STROKE_WIDTH = 1.5;
13734
+ var TEXT_HOVER_SIDE_PAD = 4;
13735
+ var CHROME_PRIMARY = `var(--ohw-primary, ${PRIMARY3})`;
13052
13736
  var TOOLBAR_GROUPS = [
13053
13737
  [
13054
13738
  { cmd: "bold", title: "Bold" },
@@ -13074,7 +13758,7 @@ function EditGlowChrome({
13074
13758
  hideHandle = false
13075
13759
  }) {
13076
13760
  const GAP = SELECTION_CHROME_GAP2;
13077
- return /* @__PURE__ */ jsxs19(
13761
+ return /* @__PURE__ */ jsxs20(
13078
13762
  "div",
13079
13763
  {
13080
13764
  ref: elRef,
@@ -13089,7 +13773,7 @@ function EditGlowChrome({
13089
13773
  zIndex: 2147483646
13090
13774
  },
13091
13775
  children: [
13092
- /* @__PURE__ */ jsx32(
13776
+ /* @__PURE__ */ jsx33(
13093
13777
  "div",
13094
13778
  {
13095
13779
  style: {
@@ -13102,7 +13786,7 @@ function EditGlowChrome({
13102
13786
  }
13103
13787
  }
13104
13788
  ),
13105
- reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx32(
13789
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx33(
13106
13790
  "div",
13107
13791
  {
13108
13792
  "data-ohw-drag-handle-container": "",
@@ -13114,7 +13798,7 @@ function EditGlowChrome({
13114
13798
  transform: "translate(calc(-100% - 7px), -50%)",
13115
13799
  pointerEvents: dragDisabled ? "none" : "auto"
13116
13800
  },
13117
- children: /* @__PURE__ */ jsx32(
13801
+ children: /* @__PURE__ */ jsx33(
13118
13802
  DragHandle,
13119
13803
  {
13120
13804
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -13324,7 +14008,7 @@ function FloatingToolbar({
13324
14008
  return () => ro.disconnect();
13325
14009
  }, [showEditLink, activeCommands]);
13326
14010
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
13327
- return /* @__PURE__ */ jsx32(
14011
+ return /* @__PURE__ */ jsx33(
13328
14012
  "div",
13329
14013
  {
13330
14014
  ref: setRefs,
@@ -13336,12 +14020,12 @@ function FloatingToolbar({
13336
14020
  zIndex: 2147483647,
13337
14021
  pointerEvents: "auto"
13338
14022
  },
13339
- children: /* @__PURE__ */ jsxs19(CustomToolbar, { children: [
13340
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs19(React11.Fragment, { children: [
13341
- gi > 0 && /* @__PURE__ */ jsx32(CustomToolbarDivider, {}),
14023
+ children: /* @__PURE__ */ jsxs20(CustomToolbar, { children: [
14024
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs20(React11.Fragment, { children: [
14025
+ gi > 0 && /* @__PURE__ */ jsx33(CustomToolbarDivider, {}),
13342
14026
  btns.map((btn) => {
13343
14027
  const isActive = activeCommands.has(btn.cmd);
13344
- return /* @__PURE__ */ jsx32(
14028
+ return /* @__PURE__ */ jsx33(
13345
14029
  CustomToolbarButton,
13346
14030
  {
13347
14031
  title: btn.title,
@@ -13350,7 +14034,7 @@ function FloatingToolbar({
13350
14034
  e.preventDefault();
13351
14035
  onCommand(btn.cmd);
13352
14036
  },
13353
- children: /* @__PURE__ */ jsx32(
14037
+ children: /* @__PURE__ */ jsx33(
13354
14038
  "svg",
13355
14039
  {
13356
14040
  width: "16",
@@ -13371,7 +14055,7 @@ function FloatingToolbar({
13371
14055
  );
13372
14056
  })
13373
14057
  ] }, gi)),
13374
- showEditLink ? /* @__PURE__ */ jsx32(
14058
+ showEditLink ? /* @__PURE__ */ jsx33(
13375
14059
  CustomToolbarButton,
13376
14060
  {
13377
14061
  type: "button",
@@ -13385,7 +14069,7 @@ function FloatingToolbar({
13385
14069
  e.preventDefault();
13386
14070
  e.stopPropagation();
13387
14071
  },
13388
- children: /* @__PURE__ */ jsx32(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
14072
+ children: /* @__PURE__ */ jsx33(Link3, { className: "size-4 shrink-0", "aria-hidden": true })
13389
14073
  }
13390
14074
  ) : null
13391
14075
  ] })
@@ -13402,7 +14086,7 @@ function StateToggle({
13402
14086
  states,
13403
14087
  onStateChange
13404
14088
  }) {
13405
- return /* @__PURE__ */ jsx32(
14089
+ return /* @__PURE__ */ jsx33(
13406
14090
  ToggleGroup,
13407
14091
  {
13408
14092
  "data-ohw-state-toggle": "",
@@ -13416,11 +14100,12 @@ function StateToggle({
13416
14100
  left: rect.right - 8,
13417
14101
  transform: "translateX(-100%)"
13418
14102
  },
13419
- children: states.map((state) => /* @__PURE__ */ jsx32(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
14103
+ children: states.map((state) => /* @__PURE__ */ jsx33(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
13420
14104
  }
13421
14105
  );
13422
14106
  }
13423
14107
  var contentCache = /* @__PURE__ */ new Map();
14108
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
13424
14109
  function resolveSubdomain(subdomainFromQuery) {
13425
14110
  if (subdomainFromQuery) return subdomainFromQuery;
13426
14111
  if (typeof window !== "undefined") {
@@ -13515,8 +14200,14 @@ function OhhwellsBridge() {
13515
14200
  });
13516
14201
  const selectFrameRef = useRef9(() => {
13517
14202
  });
14203
+ const selectLogoRef = useRef9(() => {
14204
+ });
14205
+ const openLogoSizePanelRef = useRef9(() => {
14206
+ });
13518
14207
  const deselectRef = useRef9(() => {
13519
14208
  });
14209
+ const closeFloatingPanelOnlyRef = useRef9(() => {
14210
+ });
13520
14211
  const reselectNavigationItemRef = useRef9(() => {
13521
14212
  });
13522
14213
  const commitNavigationTextEditRef = useRef9(() => {
@@ -13549,15 +14240,34 @@ function OhhwellsBridge() {
13549
14240
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState11(null);
13550
14241
  const hoveredItemElRef = useRef9(null);
13551
14242
  const [hoveredItemRect, setHoveredItemRect] = useState11(null);
14243
+ const [hoveredTextRect, setHoveredTextRect] = useState11(null);
14244
+ useEffect12(() => {
14245
+ const sync = () => {
14246
+ const el = document.querySelector(
14247
+ "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
14248
+ );
14249
+ const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
14250
+ if (!target) {
14251
+ setHoveredTextRect(null);
14252
+ return;
14253
+ }
14254
+ const r2 = target.getBoundingClientRect();
14255
+ setHoveredTextRect(new DOMRect(r2.x - TEXT_HOVER_SIDE_PAD, r2.y, r2.width + TEXT_HOVER_SIDE_PAD * 2, r2.height));
14256
+ };
14257
+ const observer = new MutationObserver(sync);
14258
+ observer.observe(document.documentElement, {
14259
+ attributes: true,
14260
+ attributeFilter: ["data-ohw-hovered"],
14261
+ subtree: true
14262
+ });
14263
+ return () => observer.disconnect();
14264
+ }, []);
13552
14265
  const siblingHintElRef = useRef9(null);
13553
14266
  const [siblingHintRect, setSiblingHintRect] = useState11(null);
13554
14267
  const [siblingHintRects, setSiblingHintRects] = useState11([]);
13555
14268
  const [isItemDragging, setIsItemDragging] = useState11(false);
13556
14269
  const [isFooterFrameSelection, setIsFooterFrameSelection] = useState11(false);
13557
14270
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
13558
- const [floatingPanel, setFloatingPanel] = useState11(null);
13559
- const [floatingPanelPos, setFloatingPanelPos] = useState11(null);
13560
- const [parentScrollSnap, setParentScrollSnap] = useState11(null);
13561
14271
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = useState11(null);
13562
14272
  const [footerHeadingVisible, setFooterHeadingVisible] = useState11(null);
13563
14273
  const footerDragRef = useRef9(null);
@@ -13572,7 +14282,15 @@ function OhhwellsBridge() {
13572
14282
  const addNavAfterAnchorRef = useRef9(null);
13573
14283
  const editContentRef = useRef9({});
13574
14284
  const aiSectionsRef = useRef9("");
14285
+ const brandKitRef = useRef9("");
13575
14286
  const pendingDeleteUndoRef = useRef9(null);
14287
+ const [floatingPanel, setFloatingPanel] = useState11(null);
14288
+ const floatingPanelOpenRef = useRef9(false);
14289
+ const setFloatingPanelRef = useRef9(setFloatingPanel);
14290
+ const [floatingPanelPos, setFloatingPanelPos] = useState11(null);
14291
+ const [logoSizeDraft, setLogoSizeDraft] = useState11(null);
14292
+ const [editorViewport, setEditorViewport] = useState11("desktop");
14293
+ const [parentScrollSnap, setParentScrollSnap] = useState11(null);
13576
14294
  const [sitePages, setSitePages] = useState11([]);
13577
14295
  const [sectionsByPath, setSectionsByPath] = useState11({});
13578
14296
  const sectionsPrefetchGenRef = useRef9(0);
@@ -13581,7 +14299,18 @@ function OhhwellsBridge() {
13581
14299
  const linkPopoverOpenRef = useRef9(false);
13582
14300
  const linkPopoverGraceUntilRef = useRef9(0);
13583
14301
  setLinkPopoverRef.current = setLinkPopover;
14302
+ setFloatingPanelRef.current = setFloatingPanel;
13584
14303
  linkPopoverSessionRef.current = linkPopover;
14304
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
14305
+ useEffect12(() => {
14306
+ const syncViewport = () => {
14307
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14308
+ setEditorViewport((prev) => prev === next ? prev : next);
14309
+ };
14310
+ syncViewport();
14311
+ window.addEventListener("resize", syncViewport);
14312
+ return () => window.removeEventListener("resize", syncViewport);
14313
+ }, []);
13585
14314
  const {
13586
14315
  navDragRef,
13587
14316
  navDropSlots,
@@ -13804,6 +14533,10 @@ function OhhwellsBridge() {
13804
14533
  setIsItemDragging(false);
13805
14534
  hoveredNavContainerRef.current = null;
13806
14535
  setHoveredNavContainerRect(null);
14536
+ hoveredItemElRef.current = null;
14537
+ setHoveredItemRect(null);
14538
+ setFloatingPanel(null);
14539
+ setLogoSizeDraft(null);
13807
14540
  if (!activeElRef.current) {
13808
14541
  setNavGroupForceOpen(null, false);
13809
14542
  setToolbarRect(null);
@@ -14000,6 +14733,14 @@ function OhhwellsBridge() {
14000
14733
  if (!selected) return;
14001
14734
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14002
14735
  if (socialsRow) {
14736
+ if (!canAddSocialItem(socialsRow)) {
14737
+ postToParent2({
14738
+ type: "ow:toast",
14739
+ title: `Maximum ${MAX_SOCIAL_ITEMS_PER_ROW} social icons`,
14740
+ toastType: "error"
14741
+ });
14742
+ return;
14743
+ }
14003
14744
  const after = getSocialItem(selected);
14004
14745
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14005
14746
  if (!result2) return;
@@ -14021,10 +14762,16 @@ function OhhwellsBridge() {
14021
14762
  return;
14022
14763
  }
14023
14764
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14024
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14025
- }
14026
- const column = (selected.hasAttribute("data-ohw-footer-col") ? selected : null) ?? selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
14765
+ const column = resolveFooterColumnForAdd(selected);
14027
14766
  if (!column) return;
14767
+ if (!canAddFooterItem(column)) {
14768
+ postToParent2({
14769
+ type: "ow:toast",
14770
+ title: `Maximum ${MAX_FOOTER_ITEMS_PER_COLUMN} items per column`,
14771
+ toastType: "error"
14772
+ });
14773
+ return;
14774
+ }
14028
14775
  const result2 = insertFooterItem(column, "/", "New link", null);
14029
14776
  applyLinkByKey(result2.hrefKey, result2.href);
14030
14777
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -14495,6 +15242,8 @@ function OhhwellsBridge() {
14495
15242
  setToolbarRect(anchor.getBoundingClientRect());
14496
15243
  setToolbarShowEditLink(false);
14497
15244
  setActiveCommands(/* @__PURE__ */ new Set());
15245
+ setFloatingPanel(null);
15246
+ setLogoSizeDraft(null);
14498
15247
  }, [deactivate, markSelected]);
14499
15248
  const selectFrame = useCallback7((el) => {
14500
15249
  if (!isNavigationContainer(el)) return;
@@ -14544,7 +15293,51 @@ function OhhwellsBridge() {
14544
15293
  setToolbarRect(el.getBoundingClientRect());
14545
15294
  setToolbarShowEditLink(false);
14546
15295
  setActiveCommands(/* @__PURE__ */ new Set());
15296
+ setFloatingPanel(null);
15297
+ setLogoSizeDraft(null);
14547
15298
  }, [deactivate, markSelected, postToParent2]);
15299
+ const selectLogo = useCallback7(
15300
+ (logoEl) => {
15301
+ if (activeElRef.current) deactivate();
15302
+ selectedElRef.current = logoEl;
15303
+ selectedHrefKeyRef.current = null;
15304
+ selectedFooterColAttrRef.current = null;
15305
+ markSelected(logoEl);
15306
+ setSelectedIsCta(false);
15307
+ setSelectedIsSocial(false);
15308
+ setSelectedIsSocialsRow(false);
15309
+ clearHrefKeyHover(logoEl);
15310
+ hoveredNavContainerRef.current = null;
15311
+ setHoveredNavContainerRect(null);
15312
+ setHoveredItemRect(null);
15313
+ hoveredItemElRef.current = null;
15314
+ siblingHintElRef.current = null;
15315
+ setSiblingHintRect(null);
15316
+ setSiblingHintRects([]);
15317
+ setIsItemDragging(false);
15318
+ setReorderHrefKey(null);
15319
+ setReorderDragDisabled(false);
15320
+ setIsFooterFrameSelection(false);
15321
+ setToolbarVariant("logo");
15322
+ setToolbarRect(getLogoInteractionRect(logoEl));
15323
+ setToolbarShowEditLink(false);
15324
+ setActiveCommands(/* @__PURE__ */ new Set());
15325
+ },
15326
+ [deactivate, markSelected]
15327
+ );
15328
+ const openLogoSizePanel = useCallback7((logoEl) => {
15329
+ const placement = getLogoPlacement(logoEl);
15330
+ const draft = readLogoSizeState(editContentRef.current, placement);
15331
+ setLogoSizeDraft(draft);
15332
+ setParentScrollSnap(parentScrollRef.current);
15333
+ setFloatingPanel({
15334
+ key: `logo-size:${placement}`,
15335
+ title: "Logo",
15336
+ context: placement === "navbar" ? "Navbar" : "Footer",
15337
+ kind: "logo-size",
15338
+ placement
15339
+ });
15340
+ }, []);
14548
15341
  const openSocialsDisplayPanel = useCallback7((row) => {
14549
15342
  setParentScrollSnap(parentScrollRef.current);
14550
15343
  setFloatingPanel({
@@ -14580,11 +15373,53 @@ function OhhwellsBridge() {
14580
15373
  );
14581
15374
  const closeFloatingPanelOnly = useCallback7(() => {
14582
15375
  setFloatingPanel(null);
15376
+ setLogoSizeDraft(null);
14583
15377
  }, []);
14584
15378
  const closeFloatingPanelAndDeselect = useCallback7(() => {
14585
15379
  setFloatingPanel(null);
15380
+ setLogoSizeDraft(null);
14586
15381
  deselectRef.current();
14587
15382
  }, []);
15383
+ const persistLogoSizeDraft = useCallback7(
15384
+ (placement, draft) => {
15385
+ const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15386
+ const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15387
+ const nodes = [
15388
+ { key: desktopKey, text: String(draft.desktopPx) }
15389
+ ];
15390
+ if (draft.mobileFollowing) {
15391
+ nodes.push({ key: mobileKey, text: "" });
15392
+ } else {
15393
+ nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15394
+ }
15395
+ editContentRef.current = {
15396
+ ...editContentRef.current,
15397
+ [desktopKey]: String(draft.desktopPx),
15398
+ [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15399
+ };
15400
+ applyLogoSizeToPlacement(
15401
+ placement,
15402
+ draft.desktopPx,
15403
+ draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15404
+ draft.mobileFollowing
15405
+ );
15406
+ postToParent2({ type: "ow:change", nodes });
15407
+ requestAnimationFrame(() => {
15408
+ const selected = selectedElRef.current;
15409
+ if (!selected || toolbarVariantRef.current !== "logo") return;
15410
+ const rect = getLogoInteractionRect(selected);
15411
+ setToolbarRect(rect);
15412
+ if (glowElRef.current) {
15413
+ const GAP = SELECTION_CHROME_GAP2;
15414
+ glowElRef.current.style.top = `${rect.top - GAP}px`;
15415
+ glowElRef.current.style.left = `${rect.left - GAP}px`;
15416
+ glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15417
+ glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15418
+ }
15419
+ });
15420
+ },
15421
+ [postToParent2]
15422
+ );
14588
15423
  const activate = useCallback7((el, options) => {
14589
15424
  if (activeElRef.current === el) return;
14590
15425
  if (isIconEditable(el)) return;
@@ -14665,7 +15500,37 @@ function OhhwellsBridge() {
14665
15500
  deactivateRef.current = deactivate;
14666
15501
  selectRef.current = select;
14667
15502
  selectFrameRef.current = selectFrame;
15503
+ selectLogoRef.current = selectLogo;
15504
+ openLogoSizePanelRef.current = openLogoSizePanel;
14668
15505
  deselectRef.current = deselect;
15506
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15507
+ const lastSiteWideScopeRef = useRef9(null);
15508
+ useEffect12(() => {
15509
+ if (!isEditMode) {
15510
+ if (lastSiteWideScopeRef.current !== false) {
15511
+ lastSiteWideScopeRef.current = false;
15512
+ postToParent2({ type: "ow:site-wide-scope", active: false });
15513
+ }
15514
+ return;
15515
+ }
15516
+ const active = isSiteWideScopeActive({
15517
+ selected: selectedElRef.current,
15518
+ hoveredItem: hoveredItemElRef.current,
15519
+ hoveredNavContainer: hoveredNavContainerRef.current,
15520
+ active: activeElRef.current
15521
+ });
15522
+ if (lastSiteWideScopeRef.current === active) return;
15523
+ lastSiteWideScopeRef.current = active;
15524
+ postToParent2({ type: "ow:site-wide-scope", active });
15525
+ }, [
15526
+ isEditMode,
15527
+ hoveredItemRect,
15528
+ hoveredNavContainerRect,
15529
+ toolbarVariant,
15530
+ toolbarRect,
15531
+ isFooterFrameSelection,
15532
+ postToParent2
15533
+ ]);
14669
15534
  useLayoutEffect4(() => {
14670
15535
  if (!subdomain || isEditMode) {
14671
15536
  setFetchState("done");
@@ -14677,9 +15542,18 @@ function OhhwellsBridge() {
14677
15542
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
14678
15543
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
14679
15544
  }
15545
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15546
+ brandKitRef.current = content[BRAND_KIT_KEY];
15547
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15548
+ }
15549
+ applyBrandChrome(content);
14680
15550
  for (const [key, val] of Object.entries(content)) {
14681
15551
  if (key === "__ohw_sections") continue;
14682
15552
  if (key === AI_SECTIONS_KEY) continue;
15553
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15554
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15555
+ if (key === BRAND_KIT_KEY) continue;
15556
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14683
15557
  if (applyVideoSettingNode(key, val)) continue;
14684
15558
  if (applyCarouselNode(key, val)) continue;
14685
15559
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14714,6 +15588,8 @@ function OhhwellsBridge() {
14714
15588
  });
14715
15589
  applyLinkByKey(key, val);
14716
15590
  }
15591
+ applyLogoFromContent(content);
15592
+ applyLogoSizes(content);
14717
15593
  reconcileNavbarItemsFromContent(content);
14718
15594
  reconcileFooterOrderFromContent(content);
14719
15595
  reconcileSocialsFromContent(content);
@@ -14734,7 +15610,9 @@ function OhhwellsBridge() {
14734
15610
  let cancelled = false;
14735
15611
  setFetchState("loading");
14736
15612
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14737
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15613
+ const initialPath = pathname;
15614
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15615
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14738
15616
  if (cancelled) return;
14739
15617
  const content = data?.content ?? {};
14740
15618
  contentCache.set(subdomain, content);
@@ -14758,8 +15636,16 @@ function OhhwellsBridge() {
14758
15636
  initSectionInstancesFromContent(content, window.location.pathname);
14759
15637
  observer?.disconnect();
14760
15638
  try {
15639
+ applyBrandChrome(content);
15640
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15641
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15642
+ }
14761
15643
  for (const [key, val] of Object.entries(content)) {
14762
15644
  if (key === "__ohw_sections") continue;
15645
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15646
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15647
+ if (key === BRAND_KIT_KEY) continue;
15648
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14763
15649
  if (applyVideoSettingNode(key, val)) continue;
14764
15650
  if (applyCarouselNode(key, val)) continue;
14765
15651
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14780,6 +15666,7 @@ function OhhwellsBridge() {
14780
15666
  });
14781
15667
  applyLinkByKey(key, val);
14782
15668
  }
15669
+ applyLogoFromContent(content);
14783
15670
  reconcileNavbarItemsFromContent(content);
14784
15671
  reconcileFooterOrderFromContent(content);
14785
15672
  reconcileSocialsFromContent(content);
@@ -14794,6 +15681,17 @@ function OhhwellsBridge() {
14794
15681
  debounceTimer = setTimeout(applyFromCache, 150);
14795
15682
  };
14796
15683
  applyFromCache();
15684
+ const pathCacheKey = `${subdomain}::${pathname}`;
15685
+ if (!fetchedContentPaths.has(pathCacheKey)) {
15686
+ fetchedContentPaths.add(pathCacheKey);
15687
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15688
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15689
+ if (!data?.content) return;
15690
+ contentCache.set(subdomain, data.content);
15691
+ applyFromCache();
15692
+ }).catch(() => {
15693
+ });
15694
+ }
14797
15695
  observer = new MutationObserver(scheduleApply);
14798
15696
  observer.observe(document.body, { childList: true, subtree: true });
14799
15697
  return () => {
@@ -14887,26 +15785,31 @@ function OhhwellsBridge() {
14887
15785
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
14888
15786
  useEffect12(() => {
14889
15787
  if (!isEditMode) return;
15788
+ let lastPosted = 0;
14890
15789
  const measure = () => {
14891
15790
  const h = document.body.scrollHeight;
14892
- if (h > 50) postToParent2({ type: "ow:height", height: h });
15791
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
15792
+ lastPosted = h;
15793
+ postToParent2({ type: "ow:height", height: h });
15794
+ }
15795
+ };
15796
+ let raf = null;
15797
+ const schedule = () => {
15798
+ if (raf != null) return;
15799
+ raf = requestAnimationFrame(() => {
15800
+ raf = null;
15801
+ measure();
15802
+ });
14893
15803
  };
14894
15804
  const t1 = setTimeout(measure, 50);
14895
15805
  const t2 = setTimeout(measure, 500);
14896
- let lastWidth = window.innerWidth;
14897
- let resizeTimer = null;
14898
- const handleResize = () => {
14899
- if (window.innerWidth === lastWidth) return;
14900
- lastWidth = window.innerWidth;
14901
- if (resizeTimer) clearTimeout(resizeTimer);
14902
- resizeTimer = setTimeout(measure, 150);
14903
- };
14904
- window.addEventListener("resize", handleResize);
15806
+ const ro = new ResizeObserver(schedule);
15807
+ ro.observe(document.body);
14905
15808
  return () => {
14906
15809
  clearTimeout(t1);
14907
15810
  clearTimeout(t2);
14908
- if (resizeTimer) clearTimeout(resizeTimer);
14909
- window.removeEventListener("resize", handleResize);
15811
+ if (raf != null) cancelAnimationFrame(raf);
15812
+ ro.disconnect();
14910
15813
  };
14911
15814
  }, [pathname, isEditMode, postToParent2]);
14912
15815
  useEffect12(() => {
@@ -14976,9 +15879,12 @@ function OhhwellsBridge() {
14976
15879
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
14977
15880
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
14978
15881
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
15882
+ /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
15883
+ that used to draw it dashes denser than the overlay border, so identical specs
15884
+ still read as two different frames (OHH-695). The attribute stays: hover paths
15885
+ and suppression rules key off it. */
14979
15886
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
14980
- outline: 2px dashed ${PRIMARY3} !important;
14981
- outline-offset: 4px;
15887
+ outline: none !important;
14982
15888
  }
14983
15889
  [data-ohw-href-key] [data-ohw-hovered],
14984
15890
  [data-ohw-href-key][data-ohw-hovered],
@@ -15031,8 +15937,11 @@ function OhhwellsBridge() {
15031
15937
  stateViews.textContent = `
15032
15938
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15033
15939
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15034
- [data-ohw-state-hovered] { outline: 2px dashed ${PRIMARY3} !important; outline-offset: 4px; }
15940
+ [data-ohw-state-hovered] { outline: ${HOVER_STROKE_WIDTH}px dashed ${CHROME_PRIMARY} !important; outline-offset: ${HOVER_CHROME_GAP}px; }
15035
15941
  [data-ohw-state-hovered]:has([data-ohw-hovered]) { outline: none !important; }
15942
+ /* :has() only sees descendants \u2014 when the card itself is the hovered text, the overlay
15943
+ already frames it, and the card outline doubled it (OHH-695). */
15944
+ [data-ohw-state-hovered][data-ohw-hovered] { outline: none !important; }
15036
15945
  `;
15037
15946
  document.head.appendChild(base);
15038
15947
  document.head.appendChild(forceHover);
@@ -15050,10 +15959,12 @@ function OhhwellsBridge() {
15050
15959
  return;
15051
15960
  }
15052
15961
  const target = e.target;
15962
+ if (target.closest("[data-ohw-ai-review]")) return;
15053
15963
  if (target.closest("[data-ohw-toolbar]")) return;
15054
15964
  if (target.closest("[data-ohw-state-toggle]")) return;
15055
15965
  if (target.closest("[data-ohw-max-badge]")) return;
15056
15966
  if (isInsideLinkEditor(target)) return;
15967
+ if (isInsideFloatingPanel(target)) return;
15057
15968
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15058
15969
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
15059
15970
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -15117,6 +16028,21 @@ function OhhwellsBridge() {
15117
16028
  return;
15118
16029
  }
15119
16030
  }
16031
+ const logoEl = getLogoElement(target);
16032
+ if (logoEl) {
16033
+ e.preventDefault();
16034
+ e.stopPropagation();
16035
+ if (!logoHasUploadedImage(logoEl)) {
16036
+ deselectRef.current();
16037
+ deactivateRef.current();
16038
+ const identity = readLogoIdentityFromDom();
16039
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16040
+ return;
16041
+ }
16042
+ selectLogoRef.current(logoEl);
16043
+ openLogoSizePanelRef.current(logoEl);
16044
+ return;
16045
+ }
15120
16046
  const editable = target.closest("[data-ohw-editable]");
15121
16047
  if (editable) {
15122
16048
  if (editable.dataset.ohwEditable === "link") {
@@ -15269,10 +16195,12 @@ function OhhwellsBridge() {
15269
16195
  };
15270
16196
  const handleDblClick = (e) => {
15271
16197
  const target = e.target;
16198
+ if (target.closest("[data-ohw-ai-review]")) return;
15272
16199
  if (target.closest("[data-ohw-toolbar]")) return;
15273
16200
  if (target.closest("[data-ohw-state-toggle]")) return;
15274
16201
  if (target.closest("[data-ohw-max-badge]")) return;
15275
16202
  if (isInsideLinkEditor(target)) return;
16203
+ if (isInsideFloatingPanel(target)) return;
15276
16204
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15277
16205
  return;
15278
16206
  }
@@ -15300,6 +16228,16 @@ function OhhwellsBridge() {
15300
16228
  setHoveredNavContainerRect(null);
15301
16229
  return;
15302
16230
  }
16231
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
16232
+ hoveredItemElRef.current = null;
16233
+ setHoveredItemRect(null);
16234
+ hoveredNavContainerRef.current = null;
16235
+ setHoveredNavContainerRect(null);
16236
+ siblingHintElRef.current = null;
16237
+ setSiblingHintRect(null);
16238
+ setSiblingHintRects([]);
16239
+ return;
16240
+ }
15303
16241
  {
15304
16242
  const selected2 = selectedElRef.current;
15305
16243
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -15307,7 +16245,7 @@ function OhhwellsBridge() {
15307
16245
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
15308
16246
  if (allowNavContainerHover) {
15309
16247
  const navContainer = target.closest("[data-ohw-nav-container]");
15310
- if (navContainer && !getNavigationItemAnchor(target)) {
16248
+ if (navContainer && !getNavigationItemAnchor(target) && !getLogoElement(target)) {
15311
16249
  hoveredNavContainerRef.current = navContainer;
15312
16250
  setHoveredNavContainerRect(navContainer.getBoundingClientRect());
15313
16251
  hoveredItemElRef.current = null;
@@ -15336,6 +16274,15 @@ function OhhwellsBridge() {
15336
16274
  setHoveredNavContainerRect(null);
15337
16275
  }
15338
16276
  }
16277
+ const logoEl = getLogoElement(target);
16278
+ if (logoEl) {
16279
+ hoveredNavContainerRef.current = null;
16280
+ setHoveredNavContainerRect(null);
16281
+ if (selectedElRef.current === logoEl) return;
16282
+ hoveredItemElRef.current = logoEl;
16283
+ setHoveredItemRect(getLogoInteractionRect(logoEl));
16284
+ return;
16285
+ }
15339
16286
  const navAnchor = getNavigationItemAnchor(target);
15340
16287
  if (navAnchor) {
15341
16288
  hoveredNavContainerRef.current = null;
@@ -15373,6 +16320,11 @@ function OhhwellsBridge() {
15373
16320
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
15374
16321
  } else if (!isInsideNavigationItem(editable)) {
15375
16322
  hoverTarget.setAttribute("data-ohw-hovered", "");
16323
+ if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
16324
+ hoveredNavContainerRef.current = null;
16325
+ setHoveredNavContainerRect(null);
16326
+ hoveredItemElRef.current = editable;
16327
+ }
15376
16328
  }
15377
16329
  }
15378
16330
  };
@@ -15408,6 +16360,18 @@ function OhhwellsBridge() {
15408
16360
  }
15409
16361
  return;
15410
16362
  }
16363
+ const logoEl = getLogoElement(target);
16364
+ if (logoEl) {
16365
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
16366
+ if (related2 && (logoEl === related2 || logoEl.contains(related2) || related2.closest?.('[data-ohw-role="logo"], [data-ohw-logo]'))) {
16367
+ return;
16368
+ }
16369
+ if (hoveredItemElRef.current === logoEl) {
16370
+ hoveredItemElRef.current = null;
16371
+ setHoveredItemRect(null);
16372
+ }
16373
+ return;
16374
+ }
15411
16375
  const editable = target.closest("[data-ohw-editable]");
15412
16376
  if (!editable) return;
15413
16377
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -15428,6 +16392,13 @@ function OhhwellsBridge() {
15428
16392
  }
15429
16393
  } else {
15430
16394
  hoverTarget.removeAttribute("data-ohw-hovered");
16395
+ if (hoveredItemElRef.current === editable) {
16396
+ const stillOnEditable = related instanceof Element && related.closest("[data-ohw-editable]") === editable;
16397
+ if (!stillOnEditable) {
16398
+ hoveredItemElRef.current = null;
16399
+ setHoveredItemRect(null);
16400
+ }
16401
+ }
15431
16402
  }
15432
16403
  }
15433
16404
  };
@@ -15544,6 +16515,26 @@ function OhhwellsBridge() {
15544
16515
  hoveredNavContainerRef.current = null;
15545
16516
  setHoveredNavContainerRect(null);
15546
16517
  }
16518
+ const logoCandidates = [
16519
+ ...document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]'),
16520
+ ...document.querySelectorAll("nav a:not([data-ohw-href-key]), [data-ohw-nav-root] a:not([data-ohw-href-key])"),
16521
+ ...document.querySelectorAll("footer img")
16522
+ ];
16523
+ const seenLogos = /* @__PURE__ */ new Set();
16524
+ for (const candidate of logoCandidates) {
16525
+ const logo = getLogoElement(candidate);
16526
+ if (!logo || seenLogos.has(logo)) continue;
16527
+ seenLogos.add(logo);
16528
+ const r2 = logo.getBoundingClientRect();
16529
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
16530
+ hoveredNavContainerRef.current = null;
16531
+ setHoveredNavContainerRect(null);
16532
+ if (selectedElRef.current !== logo) {
16533
+ hoveredItemElRef.current = logo;
16534
+ setHoveredItemRect(getLogoInteractionRect(logo));
16535
+ }
16536
+ return;
16537
+ }
15547
16538
  const navContainers = Array.from(
15548
16539
  document.querySelectorAll("[data-ohw-nav-container]")
15549
16540
  );
@@ -15629,7 +16620,7 @@ function OhhwellsBridge() {
15629
16620
  }
15630
16621
  };
15631
16622
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
15632
- if (linkPopoverOpenRef.current) {
16623
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
15633
16624
  if (hoveredImageRef.current) {
15634
16625
  hoveredImageRef.current = null;
15635
16626
  hoveredImageHasTextOverlapRef.current = false;
@@ -15883,7 +16874,7 @@ function OhhwellsBridge() {
15883
16874
  }
15884
16875
  };
15885
16876
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
15886
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
16877
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
15887
16878
  if (activeStateElRef.current) {
15888
16879
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
15889
16880
  activeStateElRef.current = null;
@@ -15951,6 +16942,19 @@ function OhhwellsBridge() {
15951
16942
  };
15952
16943
  const handleMouseMove = (e) => {
15953
16944
  const { clientX, clientY } = e;
16945
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
16946
+ hoveredItemElRef.current = null;
16947
+ setHoveredItemRect(null);
16948
+ hoveredNavContainerRef.current = null;
16949
+ setHoveredNavContainerRect(null);
16950
+ siblingHintElRef.current = null;
16951
+ setSiblingHintRect(null);
16952
+ setSiblingHintRects([]);
16953
+ dismissImageHover();
16954
+ clearImageHover();
16955
+ setSectionGap(null);
16956
+ return;
16957
+ }
15954
16958
  probeSectionGapAt(clientX, clientY);
15955
16959
  probeImageAt(clientX, clientY);
15956
16960
  probeHoverCardsAt(clientX, clientY);
@@ -15959,6 +16963,11 @@ function OhhwellsBridge() {
15959
16963
  if (e.data?.type !== "ow:pointer-sync") return;
15960
16964
  const { clientX, clientY } = e.data;
15961
16965
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16966
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
16967
+ dismissImageHover();
16968
+ clearImageHover();
16969
+ return;
16970
+ }
15962
16971
  probeSectionGapAt(clientX, clientY);
15963
16972
  probeImageAt(clientX, clientY);
15964
16973
  probeHoverCardsAt(clientX, clientY);
@@ -16208,6 +17217,11 @@ function OhhwellsBridge() {
16208
17217
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16209
17218
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16210
17219
  }
17220
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17221
+ brandKitRef.current = content[BRAND_KIT_KEY];
17222
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17223
+ }
17224
+ applyBrandChrome(content);
16211
17225
  let sectionsJson = null;
16212
17226
  for (const [key, val] of Object.entries(content)) {
16213
17227
  if (key === "__ohw_sections") {
@@ -16215,6 +17229,10 @@ function OhhwellsBridge() {
16215
17229
  continue;
16216
17230
  }
16217
17231
  if (key === AI_SECTIONS_KEY) continue;
17232
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
17233
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17234
+ if (key === BRAND_KIT_KEY) continue;
17235
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16218
17236
  if (applyVideoSettingNode(key, val)) continue;
16219
17237
  if (applyCarouselNode(key, val)) continue;
16220
17238
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16234,6 +17252,8 @@ function OhhwellsBridge() {
16234
17252
  });
16235
17253
  applyLinkByKey(key, val);
16236
17254
  }
17255
+ applyLogoFromContent(content);
17256
+ applyLogoSizes(content);
16237
17257
  if (sectionsJson) {
16238
17258
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
16239
17259
  sectionsLoadedRef.current = true;
@@ -16249,6 +17269,58 @@ function OhhwellsBridge() {
16249
17269
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
16250
17270
  postToParentRef.current({ type: "ow:hydrate-done" });
16251
17271
  };
17272
+ const handleUpdateLogoIdentity = (e) => {
17273
+ if (e.data?.type !== "ow:update-logo-identity") return;
17274
+ const rawText = typeof e.data.text === "string" ? e.data.text : "";
17275
+ const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17276
+ const href = typeof e.data.href === "string" ? e.data.href : void 0;
17277
+ const imageProvided = "image" in e.data;
17278
+ const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17279
+ let isPlaceholder = e.data.isPlaceholder !== false;
17280
+ if (imageUrl) isPlaceholder = false;
17281
+ else if (imageProvided && imageUrl === null) {
17282
+ isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17283
+ }
17284
+ const display = applyLogoIdentity(rawText, isPlaceholder);
17285
+ const displayAlt = resolveLogoDisplayText(alt || display);
17286
+ if (imageUrl !== void 0) {
17287
+ applyLogoImage(imageUrl, displayAlt);
17288
+ } else {
17289
+ for (const key of LOGO_IMAGE_KEYS) {
17290
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17291
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17292
+ if (img) img.alt = displayAlt;
17293
+ });
17294
+ }
17295
+ }
17296
+ if (href !== void 0) {
17297
+ applyLogoHref(href);
17298
+ applyLinkByKey("nav-logo-href", href);
17299
+ applyLinkByKey("footer-logo-href", href);
17300
+ applyLinkByKey("logo-href", href);
17301
+ }
17302
+ const nodes = [
17303
+ ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17304
+ { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17305
+ { key: LOGO_ALT_KEY, text: displayAlt }
17306
+ ];
17307
+ if (imageUrl !== void 0) {
17308
+ if (imageUrl) {
17309
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17310
+ } else {
17311
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17312
+ }
17313
+ }
17314
+ if (href !== void 0) {
17315
+ for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17316
+ }
17317
+ editContentRef.current = {
17318
+ ...editContentRef.current,
17319
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17320
+ };
17321
+ applyLogoSizes(editContentRef.current);
17322
+ postToParentRef.current({ type: "ow:change", nodes });
17323
+ };
16252
17324
  window.addEventListener("message", handleHydrate);
16253
17325
  const postAiSectionsChanged = () => {
16254
17326
  postToParentRef.current({
@@ -16305,6 +17377,17 @@ function OhhwellsBridge() {
16305
17377
  postAiSectionsChanged();
16306
17378
  };
16307
17379
  window.addEventListener("message", handleAiSetSections);
17380
+ const handleAiSetBrand = (e) => {
17381
+ if (e.data?.type !== "ow:ai-set-brand") return;
17382
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17383
+ const previous = brandKitRef.current;
17384
+ brandKitRef.current = value;
17385
+ applyBrandToDom(parseBrandKit(value));
17386
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17387
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17388
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17389
+ };
17390
+ window.addEventListener("message", handleAiSetBrand);
16308
17391
  const handleDeactivate = (e) => {
16309
17392
  if (e.data?.type !== "ow:deactivate") return;
16310
17393
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -16313,6 +17396,12 @@ function OhhwellsBridge() {
16313
17396
  closeLinkPopoverRef.current();
16314
17397
  return;
16315
17398
  }
17399
+ if (floatingPanelOpenRef.current) {
17400
+ setFloatingPanelRef.current(null);
17401
+ deselectRef.current();
17402
+ deactivateRef.current();
17403
+ return;
17404
+ }
16316
17405
  deselectRef.current();
16317
17406
  deactivateRef.current();
16318
17407
  };
@@ -16332,6 +17421,10 @@ function OhhwellsBridge() {
16332
17421
  closeLinkPopoverRef.current();
16333
17422
  return;
16334
17423
  }
17424
+ if (floatingPanelOpenRef.current) {
17425
+ closeFloatingPanelOnlyRef.current();
17426
+ return;
17427
+ }
16335
17428
  if (activeElRef.current) {
16336
17429
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
16337
17430
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
@@ -16362,6 +17455,10 @@ function OhhwellsBridge() {
16362
17455
  return;
16363
17456
  }
16364
17457
  if (selectedElRef.current) {
17458
+ if (toolbarVariantRef.current === "logo") {
17459
+ deselectRef.current();
17460
+ return;
17461
+ }
16365
17462
  if (toolbarVariantRef.current === "select-frame") {
16366
17463
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16367
17464
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16395,7 +17492,16 @@ function OhhwellsBridge() {
16395
17492
  closeLinkPopoverRef.current();
16396
17493
  return;
16397
17494
  }
17495
+ if (e.key === "Escape" && floatingPanelOpenRef.current) {
17496
+ e.preventDefault();
17497
+ closeFloatingPanelOnlyRef.current();
17498
+ return;
17499
+ }
16398
17500
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17501
+ if (toolbarVariantRef.current === "logo") {
17502
+ deselectRef.current();
17503
+ return;
17504
+ }
16399
17505
  if (toolbarVariantRef.current === "select-frame") {
16400
17506
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16401
17507
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16473,7 +17579,8 @@ function OhhwellsBridge() {
16473
17579
  const handleScroll = () => {
16474
17580
  const focusEl = activeElRef.current ?? selectedElRef.current;
16475
17581
  if (focusEl) {
16476
- const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17582
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17583
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
16477
17584
  applyToolbarPos(r2);
16478
17585
  setToolbarRect(r2);
16479
17586
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -16483,7 +17590,9 @@ function OhhwellsBridge() {
16483
17590
  setToggleState((prev) => prev ? { ...prev, rect } : null);
16484
17591
  }
16485
17592
  if (hoveredItemElRef.current) {
16486
- setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17593
+ const hoverEl = hoveredItemElRef.current;
17594
+ const logo = getLogoElement(hoverEl);
17595
+ setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
16487
17596
  }
16488
17597
  if (hoveredNavContainerRef.current) {
16489
17598
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -16527,6 +17636,9 @@ function OhhwellsBridge() {
16527
17636
  if (aiSectionsRef.current) {
16528
17637
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
16529
17638
  }
17639
+ if (brandKitRef.current) {
17640
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17641
+ }
16530
17642
  postToParentRef.current({ type: "ow:save-result", nodes });
16531
17643
  };
16532
17644
  const handleInsertSection = (e) => {
@@ -16537,8 +17649,12 @@ function OhhwellsBridge() {
16537
17649
  if (inserted) {
16538
17650
  const tracker = getSectionsTracker();
16539
17651
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
16540
- const h = document.documentElement.scrollHeight;
16541
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17652
+ const reportHeight = () => {
17653
+ const h = document.body.scrollHeight;
17654
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17655
+ };
17656
+ reportHeight();
17657
+ setTimeout(reportHeight, 500);
16542
17658
  }
16543
17659
  };
16544
17660
  const handleSwitchSchedule = (e) => {
@@ -16731,13 +17847,17 @@ function OhhwellsBridge() {
16731
17847
  if (e.data?.type !== "ow:parent-scroll") return;
16732
17848
  const { iframeOffsetTop, headerH, canvasH } = e.data;
16733
17849
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
17850
+ if (floatingPanelOpenRef.current) {
17851
+ setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
17852
+ }
16734
17853
  if (visibleViewportRef.current) {
16735
17854
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
16736
17855
  }
16737
17856
  const focusEl = activeElRef.current ?? selectedElRef.current;
16738
17857
  if (focusEl) {
16739
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
16740
- applyToolbarPos(measureEl.getBoundingClientRect());
17858
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17859
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
17860
+ applyToolbarPos(r2);
16741
17861
  }
16742
17862
  };
16743
17863
  const handleClickAt = (e) => {
@@ -16762,6 +17882,25 @@ function OhhwellsBridge() {
16762
17882
  postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
16763
17883
  return;
16764
17884
  }
17885
+ const logoAtPoint = Array.from(
17886
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]')
17887
+ ).map((el) => getLogoElement(el)).find((logo) => {
17888
+ if (!logo) return false;
17889
+ const r2 = logo.getBoundingClientRect();
17890
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
17891
+ });
17892
+ if (logoAtPoint) {
17893
+ if (!logoHasUploadedImage(logoAtPoint)) {
17894
+ deselectRef.current();
17895
+ deactivateRef.current();
17896
+ const identity = readLogoIdentityFromDom();
17897
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
17898
+ return;
17899
+ }
17900
+ selectLogoRef.current(logoAtPoint);
17901
+ openLogoSizePanelRef.current(logoAtPoint);
17902
+ return;
17903
+ }
16765
17904
  const textEditable = Array.from(
16766
17905
  document.querySelectorAll(NON_MEDIA_SELECTOR)
16767
17906
  ).find((el) => {
@@ -16833,6 +17972,14 @@ function OhhwellsBridge() {
16833
17972
  window.addEventListener("message", handleParentScroll);
16834
17973
  window.addEventListener("message", handlePointerSync);
16835
17974
  window.addEventListener("message", handleClickAt);
17975
+ window.addEventListener("message", handleUpdateLogoIdentity);
17976
+ const handleViewMode = (e) => {
17977
+ if (e.data?.type !== "ow:view-mode") return;
17978
+ const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
17979
+ setEditorViewport(mode);
17980
+ applyLogoSizes(editContentRef.current);
17981
+ };
17982
+ window.addEventListener("message", handleViewMode);
16836
17983
  const handleViewportResize = () => {
16837
17984
  if (visibleViewportRef.current) {
16838
17985
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -16888,10 +18035,13 @@ function OhhwellsBridge() {
16888
18035
  window.removeEventListener("resize", handleViewportResize);
16889
18036
  window.removeEventListener("message", handlePointerSync);
16890
18037
  window.removeEventListener("message", handleClickAt);
18038
+ window.removeEventListener("message", handleUpdateLogoIdentity);
18039
+ window.removeEventListener("message", handleViewMode);
16891
18040
  window.removeEventListener("message", handleHydrate);
16892
18041
  window.removeEventListener("message", handleAiApplyTree);
16893
18042
  window.removeEventListener("message", handleAiDeleteSection);
16894
18043
  window.removeEventListener("message", handleAiSetSections);
18044
+ window.removeEventListener("message", handleAiSetBrand);
16895
18045
  window.removeEventListener("message", handleDeactivate);
16896
18046
  window.removeEventListener("message", handleToastAction);
16897
18047
  window.removeEventListener("message", handleUiEscape);
@@ -16917,7 +18067,7 @@ function OhhwellsBridge() {
16917
18067
  if (footerDragRef.current) return;
16918
18068
  const target = e.target;
16919
18069
  if (!target) return;
16920
- 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]')) {
18070
+ 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]')) {
16921
18071
  return;
16922
18072
  }
16923
18073
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17095,7 +18245,7 @@ function OhhwellsBridge() {
17095
18245
  postToParent2({
17096
18246
  type: "ow:ready",
17097
18247
  version: "1",
17098
- bridgeVersion: "0.1.56",
18248
+ bridgeVersion: "0.1.59",
17099
18249
  path: pathname,
17100
18250
  nodes: collectEditableNodes(editContentRef.current),
17101
18251
  sections
@@ -17490,10 +18640,10 @@ function OhhwellsBridge() {
17490
18640
  [postToParent2]
17491
18641
  );
17492
18642
  return bridgeRoot ? createPortal2(
17493
- /* @__PURE__ */ jsxs19(Fragment8, { children: [
17494
- /* @__PURE__ */ jsx32("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
17495
- isEditMode && /* @__PURE__ */ jsx32(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
17496
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx32(
18643
+ /* @__PURE__ */ jsxs20(Fragment8, { children: [
18644
+ /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
18645
+ isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
18646
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx33(
17497
18647
  MediaOverlay,
17498
18648
  {
17499
18649
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -17504,7 +18654,7 @@ function OhhwellsBridge() {
17504
18654
  },
17505
18655
  `uploading-${key}`
17506
18656
  )),
17507
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx32(
18657
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
17508
18658
  MediaOverlay,
17509
18659
  {
17510
18660
  hover: mediaHover,
@@ -17513,11 +18663,11 @@ function OhhwellsBridge() {
17513
18663
  onVideoSettingsChange: handleVideoSettingsChange
17514
18664
  }
17515
18665
  ),
17516
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx32(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
17517
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
17518
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
17519
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
17520
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx32(
18666
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
18667
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
18668
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
18669
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
18670
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
17521
18671
  "div",
17522
18672
  {
17523
18673
  className: "pointer-events-none fixed z-2147483646",
@@ -17527,7 +18677,7 @@ function OhhwellsBridge() {
17527
18677
  width: slot.width,
17528
18678
  height: slot.height
17529
18679
  },
17530
- children: /* @__PURE__ */ jsx32(
18680
+ children: /* @__PURE__ */ jsx33(
17531
18681
  DropIndicator,
17532
18682
  {
17533
18683
  direction: slot.direction,
@@ -17538,7 +18688,7 @@ function OhhwellsBridge() {
17538
18688
  },
17539
18689
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
17540
18690
  )),
17541
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx32(
18691
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
17542
18692
  "div",
17543
18693
  {
17544
18694
  className: "pointer-events-none fixed z-2147483646",
@@ -17548,7 +18698,7 @@ function OhhwellsBridge() {
17548
18698
  width: slot.width,
17549
18699
  height: slot.height
17550
18700
  },
17551
- children: /* @__PURE__ */ jsx32(
18701
+ children: /* @__PURE__ */ jsx33(
17552
18702
  DropIndicator,
17553
18703
  {
17554
18704
  direction: slot.direction,
@@ -17559,10 +18709,11 @@ function OhhwellsBridge() {
17559
18709
  },
17560
18710
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
17561
18711
  )),
17562
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17563
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17564
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx32(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17565
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx32(
18712
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
18713
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
18714
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
18715
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx33(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
18716
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx33(
17566
18717
  FooterContainerChrome,
17567
18718
  {
17568
18719
  rect: toolbarRect,
@@ -17570,7 +18721,7 @@ function OhhwellsBridge() {
17570
18721
  addDisabled: !canAddFooterColumn()
17571
18722
  }
17572
18723
  ),
17573
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx32(
18724
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx33(
17574
18725
  ItemInteractionLayer,
17575
18726
  {
17576
18727
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -17582,10 +18733,10 @@ function OhhwellsBridge() {
17582
18733
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
17583
18734
  onDragHandleDragStart: handleItemDragStart,
17584
18735
  onDragHandleDragEnd: handleItemDragEnd,
17585
- onItemPointerDown: handleItemChromePointerDown,
17586
- onItemClick: handleItemChromeClick,
17587
- itemDragSurface: !isFooterFrameSelection,
17588
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx32(
18736
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
18737
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
18738
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
18739
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx33(
17589
18740
  ItemActionToolbar,
17590
18741
  {
17591
18742
  onEditLink: openLinkPopoverForSelected,
@@ -17601,7 +18752,10 @@ function OhhwellsBridge() {
17601
18752
  onSelectParent: handleSelectParent,
17602
18753
  onDuplicate: handleDuplicateSelected,
17603
18754
  onDelete: handleDeleteSelected,
17604
- addItemDisabled: false,
18755
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
18756
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
18757
+ return row ? !canAddSocialItem(row) : false;
18758
+ })(),
17605
18759
  editLinkDisabled: false,
17606
18760
  moreDisabled: false,
17607
18761
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,
@@ -17618,8 +18772,8 @@ function OhhwellsBridge() {
17618
18772
  ) : void 0
17619
18773
  }
17620
18774
  ),
17621
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs19(Fragment8, { children: [
17622
- /* @__PURE__ */ jsx32(
18775
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs20(Fragment8, { children: [
18776
+ /* @__PURE__ */ jsx33(
17623
18777
  EditGlowChrome,
17624
18778
  {
17625
18779
  rect: toolbarRect,
@@ -17629,7 +18783,7 @@ function OhhwellsBridge() {
17629
18783
  hideHandle: isItemDragging
17630
18784
  }
17631
18785
  ),
17632
- /* @__PURE__ */ jsx32(
18786
+ /* @__PURE__ */ jsx33(
17633
18787
  FloatingToolbar,
17634
18788
  {
17635
18789
  rect: toolbarRect,
@@ -17642,7 +18796,7 @@ function OhhwellsBridge() {
17642
18796
  }
17643
18797
  )
17644
18798
  ] }),
17645
- maxBadge && /* @__PURE__ */ jsxs19(
18799
+ maxBadge && /* @__PURE__ */ jsxs20(
17646
18800
  "div",
17647
18801
  {
17648
18802
  "data-ohw-max-badge": "",
@@ -17668,7 +18822,7 @@ function OhhwellsBridge() {
17668
18822
  ]
17669
18823
  }
17670
18824
  ),
17671
- toggleState && !linkPopover && /* @__PURE__ */ jsx32(
18825
+ toggleState && !linkPopover && /* @__PURE__ */ jsx33(
17672
18826
  StateToggle,
17673
18827
  {
17674
18828
  rect: toggleState.rect,
@@ -17677,15 +18831,15 @@ function OhhwellsBridge() {
17677
18831
  onStateChange: handleStateChange
17678
18832
  }
17679
18833
  ),
17680
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs19(
18834
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs20(
17681
18835
  "div",
17682
18836
  {
17683
18837
  "data-ohw-section-insert-line": "",
17684
18838
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
17685
18839
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
17686
18840
  children: [
17687
- /* @__PURE__ */ jsx32("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
17688
- /* @__PURE__ */ jsx32(
18841
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
18842
+ /* @__PURE__ */ jsx33(
17689
18843
  Badge,
17690
18844
  {
17691
18845
  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",
@@ -17702,11 +18856,11 @@ function OhhwellsBridge() {
17702
18856
  children: "Add Section"
17703
18857
  }
17704
18858
  ),
17705
- /* @__PURE__ */ jsx32("div", { className: "flex-1 bg-primary", style: { height: 3 } })
18859
+ /* @__PURE__ */ jsx33("div", { className: "flex-1 bg-primary", style: { height: 3 } })
17706
18860
  ]
17707
18861
  }
17708
18862
  ),
17709
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx32(
18863
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx33(
17710
18864
  LinkPopover,
17711
18865
  {
17712
18866
  panelRef: linkPopoverPanelRef,
@@ -17723,7 +18877,7 @@ function OhhwellsBridge() {
17723
18877
  },
17724
18878
  linkPopover.key
17725
18879
  ) : null,
17726
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx32(
18880
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx33(
17727
18881
  FloatingPanel,
17728
18882
  {
17729
18883
  open: true,
@@ -17733,7 +18887,7 @@ function OhhwellsBridge() {
17733
18887
  onPositionChange: setFloatingPanelPos,
17734
18888
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
17735
18889
  onClose: closeFloatingPanelOnly,
17736
- children: /* @__PURE__ */ jsx32(
18890
+ children: /* @__PURE__ */ jsx33(
17737
18891
  SocialsDisplayPanel,
17738
18892
  {
17739
18893
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -17744,11 +18898,115 @@ function OhhwellsBridge() {
17744
18898
  }
17745
18899
  )
17746
18900
  }
18901
+ ) : null,
18902
+ floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ jsx33(
18903
+ FloatingPanel,
18904
+ {
18905
+ open: true,
18906
+ title: floatingPanel.title,
18907
+ context: floatingPanel.context,
18908
+ position: floatingPanelPos,
18909
+ onPositionChange: setFloatingPanelPos,
18910
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
18911
+ onClose: closeFloatingPanelAndDeselect,
18912
+ children: /* @__PURE__ */ jsx33(
18913
+ LogoSizePanel,
18914
+ {
18915
+ viewport: editorViewport,
18916
+ sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
18917
+ mobileFollowing: logoSizeDraft.mobileFollowing,
18918
+ onSizeChange: (px) => {
18919
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
18920
+ ...logoSizeDraft,
18921
+ desktopPx: px,
18922
+ mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
18923
+ };
18924
+ setLogoSizeDraft(next);
18925
+ persistLogoSizeDraft(floatingPanel.placement, next);
18926
+ },
18927
+ onCustomizeMobile: () => {
18928
+ const next = {
18929
+ ...logoSizeDraft,
18930
+ mobileFollowing: false,
18931
+ mobilePx: logoSizeDraft.desktopPx
18932
+ };
18933
+ setLogoSizeDraft(next);
18934
+ persistLogoSizeDraft(floatingPanel.placement, next);
18935
+ },
18936
+ onResetMobile: () => {
18937
+ const next = {
18938
+ ...logoSizeDraft,
18939
+ mobileFollowing: true,
18940
+ mobilePx: logoSizeDraft.desktopPx
18941
+ };
18942
+ setLogoSizeDraft(next);
18943
+ persistLogoSizeDraft(floatingPanel.placement, next);
18944
+ },
18945
+ onUpdateEverywhere: () => {
18946
+ const identity = readLogoIdentityFromDom();
18947
+ postToParent2({ type: "ow:open-logo-settings", ...identity });
18948
+ }
18949
+ }
18950
+ )
18951
+ }
17747
18952
  ) : null
17748
18953
  ] }),
17749
18954
  bridgeRoot
17750
18955
  ) : null;
17751
18956
  }
18957
+
18958
+ // src/ui/EmptySection.tsx
18959
+ import Link4 from "next/link";
18960
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
18961
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
18962
+ return /* @__PURE__ */ jsxs21(Fragment9, { children: [
18963
+ /* @__PURE__ */ jsx34(
18964
+ "p",
18965
+ {
18966
+ style: {
18967
+ fontFamily: "var(--brand-font-body)",
18968
+ fontSize: "0.75rem",
18969
+ fontWeight: 500,
18970
+ letterSpacing: "0.15em",
18971
+ textTransform: "uppercase",
18972
+ color: "var(--brand-accent)",
18973
+ marginBottom: "1.5rem"
18974
+ },
18975
+ children: /* @__PURE__ */ jsx34(Link4, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ jsx34("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
18976
+ }
18977
+ ),
18978
+ /* @__PURE__ */ jsx34(
18979
+ "h1",
18980
+ {
18981
+ style: {
18982
+ fontFamily: "var(--brand-font-heading)",
18983
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
18984
+ lineHeight: 1.1,
18985
+ letterSpacing: "-0.025em",
18986
+ color: "var(--brand-text)",
18987
+ marginBottom: "1rem"
18988
+ },
18989
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
18990
+ children: title
18991
+ }
18992
+ ),
18993
+ /* @__PURE__ */ jsx34(
18994
+ "p",
18995
+ {
18996
+ style: {
18997
+ fontFamily: "var(--brand-font-body)",
18998
+ fontSize: "1rem",
18999
+ lineHeight: 1.7,
19000
+ fontWeight: 300,
19001
+ color: "var(--brand-text-muted)",
19002
+ maxWidth: "340px"
19003
+ },
19004
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19005
+ children: "This page doesn't have any content yet."
19006
+ }
19007
+ )
19008
+ ] });
19009
+ }
17752
19010
  export {
17753
19011
  AI_DEFAULT_BRAND,
17754
19012
  AI_TREE_SCHEMA_VERSIONS,
@@ -17765,6 +19023,7 @@ export {
17765
19023
  DropdownMenuItem,
17766
19024
  DropdownMenuSeparator,
17767
19025
  DropdownMenuTrigger,
19026
+ EmptySection,
17768
19027
  ItemActionToolbar,
17769
19028
  ItemInteractionLayer,
17770
19029
  LinkEditorPanel,