@ohhwells/bridge 0.1.60 → 0.1.61-next.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -169,6 +170,7 @@ function applyTreeToState(state, payload) {
169
170
  const entry = {
170
171
  id: payload.id,
171
172
  label: payload.label ?? "Generated section",
173
+ ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
172
174
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
173
175
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
174
176
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -191,6 +193,272 @@ function deleteSectionFromState(state, sectionId) {
191
193
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
192
194
  }
193
195
 
196
+ // src/lib/brand-chrome.ts
197
+ var BRAND_NAME_KEY = "__ohw_brand_name";
198
+ var BRAND_TITLE_KEY = "__ohw_site_title";
199
+ var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
200
+ var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
201
+ BRAND_NAME_KEY,
202
+ BRAND_TITLE_KEY,
203
+ BRAND_FAVICON_LETTER_KEY
204
+ ]);
205
+ function upsertMeta(selector, attr, token, value) {
206
+ let el = document.head.querySelector(selector);
207
+ if (!el) {
208
+ el = document.createElement("meta");
209
+ el.setAttribute(attr, token);
210
+ document.head.appendChild(el);
211
+ }
212
+ if (el.getAttribute("content") !== value) el.setAttribute("content", value);
213
+ }
214
+ function escapeXml(value) {
215
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
216
+ }
217
+ function applyLetterFavicon(letter) {
218
+ 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>`;
219
+ const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
220
+ let link = document.head.querySelector('link[rel="icon"]');
221
+ if (!link) {
222
+ link = document.createElement("link");
223
+ link.rel = "icon";
224
+ document.head.appendChild(link);
225
+ }
226
+ link.type = "image/svg+xml";
227
+ if (link.href !== href) link.href = href;
228
+ }
229
+ function applyBrandChrome(content) {
230
+ const name = content[BRAND_NAME_KEY];
231
+ if (typeof name === "string" && name.length > 0) {
232
+ document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
233
+ if (el.textContent !== name) el.textContent = name;
234
+ if (el.getAttribute("title") !== name) el.setAttribute("title", name);
235
+ });
236
+ }
237
+ const title = content[BRAND_TITLE_KEY];
238
+ if (typeof title === "string" && title.length > 0) {
239
+ if (document.title !== title) document.title = title;
240
+ upsertMeta('meta[property="og:title"]', "property", "og:title", title);
241
+ upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
242
+ }
243
+ const letter = content[BRAND_FAVICON_LETTER_KEY];
244
+ if (typeof letter === "string" && letter.length > 0) {
245
+ applyLetterFavicon(letter);
246
+ }
247
+ }
248
+
249
+ // src/lib/brand-kit.ts
250
+ var BRAND_KIT_KEY = "__ohw_brand";
251
+ var BRAND_VAR_PREFIX = "--ohw-brand-";
252
+ var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
253
+ (role) => `${BRAND_VAR_PREFIX}${role}`
254
+ );
255
+ var FONT_VARS = { heading: ["--font-heading", "--font-display"], body: ["--font-body"] };
256
+ var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
257
+ function brandColorVars(kit) {
258
+ const { dark, primary, accent, light } = kit.palette;
259
+ const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
260
+ return {
261
+ [`${BRAND_VAR_PREFIX}primary`]: primary,
262
+ [`${BRAND_VAR_PREFIX}accent`]: accent,
263
+ [`${BRAND_VAR_PREFIX}light`]: light,
264
+ [`${BRAND_VAR_PREFIX}dark`]: dark,
265
+ [`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
266
+ [`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
267
+ [`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
268
+ };
269
+ }
270
+ function parseBrandKit(raw) {
271
+ if (!raw) return null;
272
+ try {
273
+ const parsed = JSON.parse(raw);
274
+ const p = parsed?.palette;
275
+ const f = parsed?.fonts;
276
+ 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") {
277
+ return null;
278
+ }
279
+ return {
280
+ palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
281
+ fonts: { heading: f.heading, body: f.body }
282
+ };
283
+ } catch {
284
+ return null;
285
+ }
286
+ }
287
+ function familyOf(stack) {
288
+ const first = stack.split(",")[0]?.trim() ?? "";
289
+ return first.replace(/^['"]|['"]$/g, "");
290
+ }
291
+ function loadBrandFonts(families) {
292
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
293
+ if (unique.length === 0) return;
294
+ const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
295
+ const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
296
+ let link = document.getElementById(BRAND_FONT_LINK_ID);
297
+ if (!link) {
298
+ link = document.createElement("link");
299
+ link.id = BRAND_FONT_LINK_ID;
300
+ link.rel = "stylesheet";
301
+ document.head.appendChild(link);
302
+ }
303
+ if (link.href !== href) link.href = href;
304
+ }
305
+ function applyBrandToDom(kit) {
306
+ const root = document.documentElement;
307
+ if (!kit) {
308
+ for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
309
+ for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
310
+ document.getElementById(BRAND_FONT_LINK_ID)?.remove();
311
+ return;
312
+ }
313
+ for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
314
+ for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
315
+ for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
316
+ loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
317
+ }
318
+
319
+ // src/lib/section-styles.ts
320
+ var STYLE_STORE_KEY = "__ohw_styles";
321
+ var STYLE_SHEET_ID = "ohw-section-styles";
322
+ function parseStyleStore(raw) {
323
+ if (!raw) return null;
324
+ try {
325
+ const parsed = JSON.parse(raw);
326
+ if (parsed?.v !== 1) return null;
327
+ return {
328
+ v: 1,
329
+ sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
330
+ nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
331
+ };
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+ var BG_VALUES = {
337
+ surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
338
+ accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
339
+ "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
340
+ };
341
+ var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
342
+ function styleSheetCss() {
343
+ const rules = [];
344
+ for (const [tone, value] of Object.entries(BG_VALUES)) {
345
+ rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
346
+ }
347
+ rules.push(
348
+ `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
349
+ );
350
+ rules.push(
351
+ `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
352
+ `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
353
+ );
354
+ for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
355
+ rules.push(
356
+ `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
357
+ );
358
+ }
359
+ rules.push(
360
+ `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
361
+ );
362
+ for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
363
+ rules.push(
364
+ `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
365
+ );
366
+ }
367
+ const pad = { tight: 40, balanced: 64, airy: 96 };
368
+ for (const [spacing, px] of Object.entries(pad)) {
369
+ rules.push(
370
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
371
+ );
372
+ }
373
+ return rules.join("\n");
374
+ }
375
+ var SECTION_ATTRS = {
376
+ sectionBackground: "data-ohw-style-bg",
377
+ textDistribution: "data-ohw-style-distribution",
378
+ headlineScale: "data-ohw-style-headline",
379
+ imageAspect: "data-ohw-style-aspect",
380
+ spacing: "data-ohw-style-spacing"
381
+ };
382
+ var NODE_WROTE_ATTR = "data-ohw-style-node";
383
+ function ensureStyleSheet() {
384
+ let el = document.getElementById(STYLE_SHEET_ID);
385
+ if (!el) {
386
+ el = document.createElement("style");
387
+ el.id = STYLE_SHEET_ID;
388
+ document.head.appendChild(el);
389
+ }
390
+ const css = styleSheetCss();
391
+ if (el.textContent !== css) el.textContent = css;
392
+ }
393
+ function clearSectionAttrs(root) {
394
+ for (const attr of Object.values(SECTION_ATTRS)) {
395
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
396
+ }
397
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
398
+ ;
399
+ el.style.removeProperty("background");
400
+ el.removeAttribute("data-ohw-style-bgcolor");
401
+ }
402
+ }
403
+ function clearNodeProps(root) {
404
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
405
+ const h = el;
406
+ h.style.removeProperty("color");
407
+ h.style.removeProperty("font-size");
408
+ h.style.removeProperty("background");
409
+ h.removeAttribute(NODE_WROTE_ATTR);
410
+ }
411
+ }
412
+ function buttonSurfaceOf(el) {
413
+ return el.closest("a, button") ?? el;
414
+ }
415
+ function applyStylesToDom(store) {
416
+ ensureStyleSheet();
417
+ clearSectionAttrs(document);
418
+ clearNodeProps(document);
419
+ if (!store) return;
420
+ for (const [sectionId, override] of Object.entries(store.sections)) {
421
+ const sections = document.querySelectorAll(
422
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
423
+ );
424
+ for (const section of Array.from(sections)) {
425
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
426
+ const value = override[prop];
427
+ if (value === void 0) continue;
428
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
429
+ section.setAttribute(attr, String(value).replace(":", "-"));
430
+ }
431
+ if (override.sectionBackgroundColor !== void 0) {
432
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
433
+ section.setAttribute("data-ohw-style-bgcolor", "");
434
+ }
435
+ }
436
+ }
437
+ for (const [key, override] of Object.entries(store.nodes)) {
438
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
439
+ for (const el of Array.from(nodes)) {
440
+ if (override.color !== void 0) {
441
+ el.style.setProperty("color", override.color, "important");
442
+ el.setAttribute(NODE_WROTE_ATTR, "");
443
+ }
444
+ if (override.fontSize !== void 0) {
445
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
446
+ el.setAttribute(NODE_WROTE_ATTR, "");
447
+ }
448
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
449
+ const surface = buttonSurfaceOf(el);
450
+ if (override.buttonBackground !== void 0) {
451
+ surface.style.setProperty("background", override.buttonBackground, "important");
452
+ }
453
+ if (override.buttonText !== void 0) {
454
+ surface.style.setProperty("color", override.buttonText, "important");
455
+ }
456
+ surface.setAttribute(NODE_WROTE_ATTR, "");
457
+ }
458
+ }
459
+ }
460
+ }
461
+
194
462
  // src/ui/ai-tree/aiSectionsManager.tsx
195
463
  var import_react_dom = require("react-dom");
196
464
  var import_client = require("react-dom/client");
@@ -205,7 +473,8 @@ function lucideByName(name) {
205
473
  }
206
474
  var typeStyle = (spec, font) => ({
207
475
  fontFamily: font,
208
- fontSize: spec.size,
476
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
477
+ fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
209
478
  lineHeight: spec.line,
210
479
  fontWeight: spec.weight
211
480
  });
@@ -232,6 +501,8 @@ var AI_RESPONSIVE_CSS = [
232
501
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
233
502
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
234
503
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
504
+ " [data-ai-responsive] { overflow-x: hidden; }",
505
+ " [data-ai-responsive] img { max-width: 100%; }",
235
506
  "}"
236
507
  ].join("\n");
237
508
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -1239,6 +1510,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1239
1510
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1240
1511
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1241
1512
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1513
+ const toneBackground = (() => {
1514
+ const { dark, primary, light } = resolvedBrand.palette;
1515
+ switch (settings.sectionBackground) {
1516
+ case "surface":
1517
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1518
+ case "accent":
1519
+ return primary;
1520
+ case "accent-soft":
1521
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1522
+ default:
1523
+ return void 0;
1524
+ }
1525
+ })();
1526
+ const distributed = !isOverlay && settings.textDistribution;
1242
1527
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1243
1528
  "section",
1244
1529
  {
@@ -1248,10 +1533,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1248
1533
  style: {
1249
1534
  position: "relative",
1250
1535
  padding: `${pad}px 0`,
1251
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1536
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1252
1537
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1253
1538
  backgroundSize: "cover",
1254
- backgroundPosition: "center"
1539
+ backgroundPosition: "center",
1540
+ color: settings.sectionBackground === "accent" ? resolvedBrand.palette.light : void 0
1255
1541
  },
1256
1542
  children: [
1257
1543
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
@@ -1275,10 +1561,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1275
1561
  display: "grid",
1276
1562
  gridTemplateColumns: "repeat(12, 1fr)",
1277
1563
  gap: AI_TREE_TOKENS.spacing6,
1278
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1564
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1279
1565
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1280
1566
  },
1281
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1567
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1568
+ "div",
1569
+ {
1570
+ "data-ai-cell": "",
1571
+ style: {
1572
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1573
+ minWidth: 0,
1574
+ // space-between: each column becomes a flex column whose content spreads over
1575
+ // the full row height instead of clumping at the top.
1576
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1577
+ },
1578
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1579
+ },
1580
+ b
1581
+ ))
1282
1582
  },
1283
1583
  r2
1284
1584
  ))
@@ -1294,17 +1594,34 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1294
1594
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1295
1595
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1296
1596
  var REMOVED_ATTR = "data-ohw-ai-removed";
1597
+ function readRootVar(name) {
1598
+ if (typeof document === "undefined") return "";
1599
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1600
+ }
1601
+ function deriveBrandOverride() {
1602
+ const dark = readRootVar("--ohw-brand-dark");
1603
+ const primary = readRootVar("--ohw-brand-primary");
1604
+ const light = readRootVar("--ohw-brand-light");
1605
+ if (!dark || !primary || !light) return null;
1606
+ const accent = readRootVar("--ohw-brand-accent");
1607
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1608
+ const body = readRootVar("--font-body");
1609
+ return {
1610
+ palette: { dark, primary, accent: accent || dark, light },
1611
+ fonts: {
1612
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1613
+ body: body || AI_DEFAULT_BRAND.fonts.body
1614
+ }
1615
+ };
1616
+ }
1297
1617
  function deriveTemplateBrand() {
1298
- if (typeof document === "undefined") return null;
1299
- const cs = getComputedStyle(document.documentElement);
1300
- const read = (name) => cs.getPropertyValue(name).trim();
1301
- const dark = read("--color-dark");
1302
- const primary = read("--color-primary");
1303
- const light = read("--color-light");
1618
+ const dark = readRootVar("--color-dark");
1619
+ const primary = readRootVar("--color-primary");
1620
+ const light = readRootVar("--color-light");
1304
1621
  if (!dark || !primary || !light) return null;
1305
- const accent = read("--color-accent");
1306
- const heading = read("--font-heading") || read("--font-display");
1307
- const body = read("--font-body");
1622
+ const accent = readRootVar("--color-accent");
1623
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1624
+ const body = readRootVar("--font-body");
1308
1625
  return {
1309
1626
  palette: { dark, primary, accent: accent || dark, light },
1310
1627
  fonts: {
@@ -1396,8 +1713,12 @@ function syncReplacedOriginals(state) {
1396
1713
  }
1397
1714
  function applyAiSectionsToDom(state, options) {
1398
1715
  if (typeof document === "undefined") return;
1716
+ const brandOverride = deriveBrandOverride();
1399
1717
  const templateBrand = deriveTemplateBrand();
1400
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1718
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1719
+ const pagePath = window.location.pathname;
1720
+ const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1721
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1401
1722
  for (const [id, section] of mounted) {
1402
1723
  if (!activeIds.has(id)) {
1403
1724
  section.root.unmount();
@@ -1405,8 +1726,8 @@ function applyAiSectionsToDom(state, options) {
1405
1726
  mounted.delete(id);
1406
1727
  }
1407
1728
  }
1408
- for (const entry of state.sections) {
1409
- const serialized = JSON.stringify(entry);
1729
+ for (const entry of pageSections) {
1730
+ const serialized = JSON.stringify(entry) + brandKey;
1410
1731
  const existing = mounted.get(entry.id);
1411
1732
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1412
1733
  continue;
@@ -1420,6 +1741,7 @@ function applyAiSectionsToDom(state, options) {
1420
1741
  mounted.delete(entry.id);
1421
1742
  }
1422
1743
  container.setAttribute("data-ohw-section", entry.id);
1744
+ container.setAttribute("data-ohw-instance", entry.id);
1423
1745
  container.setAttribute("data-ohw-section-label", entry.label);
1424
1746
  placeContainer(container, entry);
1425
1747
  const root = mounted.get(entry.id)?.root ?? (0, import_client.createRoot)(container);
@@ -1430,7 +1752,7 @@ function applyAiSectionsToDom(state, options) {
1430
1752
  AiTreeRenderer,
1431
1753
  {
1432
1754
  tree: entry.tree,
1433
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1755
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1434
1756
  resolveMedia,
1435
1757
  editKeyPrefix: `ai.${entry.id}`
1436
1758
  }
@@ -2047,7 +2369,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2047
2369
  const autoId = (0, import_react5.useId)();
2048
2370
  const insertAfter = insertAfterProp ?? autoId;
2049
2371
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2050
- const [loading, setLoading] = (0, import_react5.useState)(true);
2372
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2051
2373
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2052
2374
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2053
2375
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2221,8 +2543,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2221
2543
  "*"
2222
2544
  );
2223
2545
  };
2224
- if (!inEditor && !loading && !schedule) return null;
2225
2546
  const sectionId = `scheduling-${insertAfter}`;
2547
+ if (!inEditor && !loading && !schedule) {
2548
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2549
+ }
2226
2550
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2227
2551
  "section",
2228
2552
  {
@@ -6741,8 +7065,12 @@ function parseSectionsFromHtml(html) {
6741
7065
 
6742
7066
  // src/ui/ai-section/AiSectionOverlay.tsx
6743
7067
  var import_jsx_runtime16 = require("react/jsx-runtime");
6744
- function readRect(sectionId) {
6745
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7068
+ function findSectionElement(instanceId) {
7069
+ const escaped = CSS.escape(instanceId);
7070
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7071
+ }
7072
+ function readRect(instanceId) {
7073
+ const el = findSectionElement(instanceId);
6746
7074
  if (!el) return null;
6747
7075
  const r2 = el.getBoundingClientRect();
6748
7076
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -6765,7 +7093,7 @@ function useLiveSectionRect(sectionId) {
6765
7093
  const opts = { capture: true, passive: true };
6766
7094
  window.addEventListener("scroll", update, opts);
6767
7095
  window.addEventListener("resize", update);
6768
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7096
+ const el = findSectionElement(sectionId);
6769
7097
  const ro = el ? new ResizeObserver(update) : null;
6770
7098
  if (el && ro) ro.observe(el);
6771
7099
  const interval = setInterval(update, 500);
@@ -6778,6 +7106,14 @@ function useLiveSectionRect(sectionId) {
6778
7106
  }, [sectionId]);
6779
7107
  return rect;
6780
7108
  }
7109
+ function computeSectionBoundaryFlags(instanceId) {
7110
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7111
+ (el) => !el.parentElement?.closest("[data-ohw-section]")
7112
+ );
7113
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7114
+ if (index === -1) return { isFirst: true, isLast: true };
7115
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7116
+ }
6781
7117
  var PRIMARY2 = "#0885FE";
6782
7118
  function edgeAwareRadius(rect) {
6783
7119
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -6851,6 +7187,7 @@ function AiSectionOverlay({
6851
7187
  }) {
6852
7188
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
6853
7189
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
7190
+ const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
6854
7191
  const reviewIdRef = (0, import_react8.useRef)(null);
6855
7192
  reviewIdRef.current = reviewId;
6856
7193
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -6859,7 +7196,7 @@ function AiSectionOverlay({
6859
7196
  (el) => {
6860
7197
  postToParent2({
6861
7198
  type: "ow:section-selected",
6862
- sectionId: el?.dataset.ohwSection ?? null,
7199
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
6863
7200
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
6864
7201
  });
6865
7202
  },
@@ -6868,7 +7205,7 @@ function AiSectionOverlay({
6868
7205
  const selectFromElement = (0, import_react8.useCallback)(
6869
7206
  (el, options) => {
6870
7207
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
6871
- const id = sectionEl?.dataset.ohwSection ?? null;
7208
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
6872
7209
  if (id === selectedIdRef.current) return;
6873
7210
  setSelectedId(id);
6874
7211
  if (options?.report !== false) report(sectionEl);
@@ -6909,9 +7246,10 @@ function AiSectionOverlay({
6909
7246
  }
6910
7247
  const found = readRect(sectionId) != null;
6911
7248
  setReviewId(found ? sectionId : null);
7249
+ setReviewButtonsHidden(e.data.hideButtons === true);
6912
7250
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
6913
7251
  if (found) {
6914
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7252
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
6915
7253
  }
6916
7254
  }
6917
7255
  };
@@ -6930,7 +7268,7 @@ function AiSectionOverlay({
6930
7268
  return;
6931
7269
  }
6932
7270
  const sec = t.closest("[data-ohw-section]");
6933
- setHoveredId(sec?.dataset.ohwSection ?? null);
7271
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
6934
7272
  };
6935
7273
  const onLeave = () => setHoveredId(null);
6936
7274
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -6962,9 +7300,29 @@ function AiSectionOverlay({
6962
7300
  },
6963
7301
  [postToParent2]
6964
7302
  );
6965
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7303
+ const activeSelectionId = reviewId ? null : selectedId;
7304
+ const selectionRect = useLiveSectionRect(activeSelectionId);
6966
7305
  const reviewRect = useLiveSectionRect(reviewId);
6967
7306
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7307
+ (0, import_react8.useEffect)(() => {
7308
+ if (!activeSelectionId || !selectionRect) {
7309
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7310
+ return;
7311
+ }
7312
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7313
+ postToParent2({
7314
+ type: "ow:section-rect",
7315
+ instanceId: activeSelectionId,
7316
+ rect: {
7317
+ top: selectionRect.top + window.scrollY,
7318
+ left: selectionRect.left + window.scrollX,
7319
+ width: selectionRect.width,
7320
+ height: selectionRect.height
7321
+ },
7322
+ isFirst,
7323
+ isLast
7324
+ });
7325
+ }, [activeSelectionId, selectionRect, postToParent2]);
6968
7326
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
6969
7327
  hoverRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6970
7328
  "div",
@@ -7015,13 +7373,16 @@ function AiSectionOverlay({
7015
7373
  border: `2px solid ${PRIMARY2}`,
7016
7374
  borderRadius: edgeAwareRadius(reviewRect),
7017
7375
  zIndex: 2147483200,
7018
- // The veil itself: swallows clicks so the section stays locked until decided.
7376
+ // The veil itself: swallows clicks so the section stays locked until decided. This
7377
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7378
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7379
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7019
7380
  background: "rgba(8, 133, 254, 0.04)",
7020
7381
  pointerEvents: "auto",
7021
7382
  cursor: "default"
7022
7383
  },
7023
7384
  onClick: (e) => e.stopPropagation(),
7024
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7385
+ children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7025
7386
  "div",
7026
7387
  {
7027
7388
  style: {
@@ -10921,6 +11282,329 @@ function deleteFooterColumn(column) {
10921
11282
  };
10922
11283
  }
10923
11284
 
11285
+ // src/lib/logo-identity.ts
11286
+ var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11287
+ var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11288
+ var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11289
+ var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11290
+ var LOGO_ALT_KEY = "logo-alt";
11291
+ var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11292
+ var PLACEHOLDER_BUSINESS_NAME = "Business name";
11293
+ function resolveLogoDisplayText(text) {
11294
+ const trimmed = (text ?? "").trim();
11295
+ return trimmed || PLACEHOLDER_BUSINESS_NAME;
11296
+ }
11297
+ function isFooterLogoRoot(root) {
11298
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11299
+ }
11300
+ function imageKeyForRoot(root) {
11301
+ return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11302
+ }
11303
+ function textKeyForRoot(root) {
11304
+ return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11305
+ }
11306
+ function ensureLogoHrefKey(root) {
11307
+ if (!(root instanceof HTMLAnchorElement)) return;
11308
+ if (root.hasAttribute("data-ohw-href-key")) return;
11309
+ root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11310
+ }
11311
+ function applyLogoIdentity(text, isPlaceholder) {
11312
+ const display = resolveLogoDisplayText(text);
11313
+ const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11314
+ for (const key of LOGO_TEXT_KEYS) {
11315
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11316
+ if (el.textContent !== display) el.textContent = display;
11317
+ });
11318
+ }
11319
+ for (const key of LOGO_IMAGE_KEYS) {
11320
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11321
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11322
+ if (img) img.alt = display;
11323
+ });
11324
+ }
11325
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11326
+ if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11327
+ else el.removeAttribute("data-ohw-placeholder");
11328
+ });
11329
+ return display;
11330
+ }
11331
+ function applyLogoImage(url, alt) {
11332
+ const displayAlt = resolveLogoDisplayText(alt);
11333
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11334
+ ensureLogoHrefKey(root);
11335
+ const imageKey = imageKeyForRoot(root);
11336
+ const textKey = textKeyForRoot(root);
11337
+ 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");
11338
+ let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11339
+ if (url) {
11340
+ if (!img) {
11341
+ img = document.createElement("img");
11342
+ img.setAttribute("data-ohw-editable", "image");
11343
+ img.setAttribute("data-ohw-key", imageKey);
11344
+ img.alt = displayAlt;
11345
+ img.style.height = "";
11346
+ img.style.maxHeight = "none";
11347
+ img.style.width = "auto";
11348
+ img.style.display = "block";
11349
+ img.style.objectFit = "contain";
11350
+ root.insertBefore(img, root.firstChild);
11351
+ } else {
11352
+ img.setAttribute("data-ohw-editable", "image");
11353
+ img.setAttribute("data-ohw-key", imageKey);
11354
+ }
11355
+ img.removeAttribute("srcset");
11356
+ img.removeAttribute("sizes");
11357
+ img.src = url;
11358
+ img.alt = displayAlt;
11359
+ img.style.display = "block";
11360
+ if (textEl) textEl.style.display = "none";
11361
+ root.removeAttribute("data-ohw-placeholder");
11362
+ return;
11363
+ }
11364
+ if (img) {
11365
+ img.removeAttribute("src");
11366
+ img.removeAttribute("srcset");
11367
+ img.removeAttribute("sizes");
11368
+ img.alt = displayAlt;
11369
+ img.style.display = "none";
11370
+ }
11371
+ if (!textEl) {
11372
+ textEl = document.createElement("span");
11373
+ textEl.setAttribute("data-ohw-editable", "plain");
11374
+ textEl.setAttribute("data-ohw-key", textKey);
11375
+ root.appendChild(textEl);
11376
+ }
11377
+ textEl.style.display = "";
11378
+ if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11379
+ if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11380
+ root.setAttribute("data-ohw-placeholder", "");
11381
+ } else {
11382
+ root.removeAttribute("data-ohw-placeholder");
11383
+ }
11384
+ });
11385
+ }
11386
+ function applyLogoHref(href) {
11387
+ const target = href.trim() || "/";
11388
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11389
+ ensureLogoHrefKey(root);
11390
+ if (root instanceof HTMLAnchorElement) {
11391
+ root.setAttribute("href", target);
11392
+ }
11393
+ });
11394
+ for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11395
+ }
11396
+ function readLogoIdentityFromDom() {
11397
+ let imageUrl = null;
11398
+ for (const key of LOGO_IMAGE_KEYS) {
11399
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11400
+ const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11401
+ const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11402
+ if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11403
+ imageUrl = img.currentSrc || img.src;
11404
+ break;
11405
+ }
11406
+ }
11407
+ let text = PLACEHOLDER_BUSINESS_NAME;
11408
+ let isPlaceholder = true;
11409
+ for (const key of LOGO_TEXT_KEYS) {
11410
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11411
+ if (el?.textContent?.trim()) {
11412
+ text = el.textContent.trim();
11413
+ const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11414
+ isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11415
+ break;
11416
+ }
11417
+ }
11418
+ if (imageUrl) {
11419
+ const logoImg = document.querySelector(
11420
+ '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11421
+ );
11422
+ const alt = logoImg?.alt?.trim() || text;
11423
+ isPlaceholder = false;
11424
+ const hrefEl = document.querySelector(
11425
+ 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11426
+ );
11427
+ const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11428
+ return { text, isPlaceholder, imageUrl, href: href2, alt };
11429
+ }
11430
+ const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11431
+ const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11432
+ return { text, isPlaceholder, imageUrl: null, href, alt: text };
11433
+ }
11434
+ function applyLogoFromContent(content) {
11435
+ 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);
11436
+ if (!hasLogoIdentity) return false;
11437
+ const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11438
+ const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11439
+ const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11440
+ const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11441
+ const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11442
+ const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11443
+ if (logoImageUrl) {
11444
+ applyLogoImage(logoImageUrl, logoAlt);
11445
+ } else {
11446
+ if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11447
+ applyLogoIdentity(logoText, logoIsPlaceholder);
11448
+ }
11449
+ const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11450
+ if (typeof logoHref === "string" && logoHref.trim()) {
11451
+ applyLogoHref(logoHref);
11452
+ }
11453
+ return true;
11454
+ }
11455
+
11456
+ // src/lib/logo-size.ts
11457
+ var LOGO_SIZE_DEFAULTS = {
11458
+ navbar: 28,
11459
+ footer: 32
11460
+ };
11461
+ var LOGO_SIZE_MIN = 16;
11462
+ var LOGO_SIZE_MAX = 80;
11463
+ var LOGO_SIZE_DESKTOP_KEYS = {
11464
+ navbar: "nav-logo-size",
11465
+ footer: "footer-logo-size"
11466
+ };
11467
+ var LOGO_SIZE_MOBILE_KEYS = {
11468
+ navbar: "nav-logo-size-mobile",
11469
+ footer: "footer-logo-size-mobile"
11470
+ };
11471
+ var LOGO_SIZE_KEYS = [
11472
+ LOGO_SIZE_DESKTOP_KEYS.navbar,
11473
+ LOGO_SIZE_DESKTOP_KEYS.footer,
11474
+ LOGO_SIZE_MOBILE_KEYS.navbar,
11475
+ LOGO_SIZE_MOBILE_KEYS.footer
11476
+ ];
11477
+ function isFooterLogoRoot2(root) {
11478
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11479
+ }
11480
+ function getLogoPlacement(root) {
11481
+ return isFooterLogoRoot2(root) ? "footer" : "navbar";
11482
+ }
11483
+ function parseLogoSizePx(raw, fallback) {
11484
+ if (raw == null || raw === "") return fallback;
11485
+ const n = Number.parseFloat(raw);
11486
+ if (!Number.isFinite(n)) return fallback;
11487
+ return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11488
+ }
11489
+ function isMobileLogoSizeFollowing(content, placement) {
11490
+ const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11491
+ return raw == null || raw.trim() === "";
11492
+ }
11493
+ function resolveDesktopLogoSize(content, placement) {
11494
+ return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11495
+ }
11496
+ function resolveMobileLogoSize(content, placement) {
11497
+ if (isMobileLogoSizeFollowing(content, placement)) {
11498
+ return resolveDesktopLogoSize(content, placement);
11499
+ }
11500
+ return parseLogoSizePx(
11501
+ content[LOGO_SIZE_MOBILE_KEYS[placement]],
11502
+ resolveDesktopLogoSize(content, placement)
11503
+ );
11504
+ }
11505
+ function setRootSizeVars(root, desktopPx, mobilePx, following) {
11506
+ root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11507
+ if (following) {
11508
+ root.style.removeProperty("--ohw-logo-size-mobile");
11509
+ } else {
11510
+ root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11511
+ }
11512
+ root.querySelectorAll("img").forEach((img) => {
11513
+ img.style.height = "";
11514
+ img.style.maxHeight = "none";
11515
+ img.style.width = "auto";
11516
+ img.style.objectFit = "contain";
11517
+ });
11518
+ }
11519
+ function applyLogoSizes(content) {
11520
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11521
+ const placement = getLogoPlacement(root);
11522
+ const desktop = resolveDesktopLogoSize(content, placement);
11523
+ const following = isMobileLogoSizeFollowing(content, placement);
11524
+ const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11525
+ setRootSizeVars(root, desktop, mobile, following);
11526
+ });
11527
+ }
11528
+ function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11529
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11530
+ if (getLogoPlacement(root) !== placement) return;
11531
+ setRootSizeVars(root, desktopPx, mobilePx, following);
11532
+ });
11533
+ }
11534
+ function logoHasUploadedImage(logoEl) {
11535
+ if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11536
+ 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");
11537
+ if (!img) return false;
11538
+ const src = img.getAttribute("src")?.trim() ?? "";
11539
+ if (!src || src.startsWith("data:")) return false;
11540
+ if (img.style.display === "none") return false;
11541
+ return true;
11542
+ }
11543
+ function getLogoInteractionRect(logoEl) {
11544
+ if (logoHasUploadedImage(logoEl)) {
11545
+ 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");
11546
+ if (img) {
11547
+ const r2 = img.getBoundingClientRect();
11548
+ if (r2.width > 0 && r2.height > 0) return r2;
11549
+ }
11550
+ }
11551
+ const text = logoEl.querySelector(
11552
+ '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11553
+ );
11554
+ if (text) {
11555
+ const style = window.getComputedStyle(text);
11556
+ if (style.display !== "none" && style.visibility !== "hidden") {
11557
+ const r2 = text.getBoundingClientRect();
11558
+ if (r2.width > 0 && r2.height > 0) return r2;
11559
+ }
11560
+ }
11561
+ return logoEl.getBoundingClientRect();
11562
+ }
11563
+ function readLogoSizeState(content, placement) {
11564
+ const desktopPx = resolveDesktopLogoSize(content, placement);
11565
+ const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11566
+ const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11567
+ return { desktopPx, mobilePx, mobileFollowing };
11568
+ }
11569
+
11570
+ // src/lib/site-wide-scope.ts
11571
+ function getLogoElement(el) {
11572
+ const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11573
+ if (marked) return marked;
11574
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11575
+ const root = el.closest("nav, [data-ohw-nav-root], footer");
11576
+ if (!root) return null;
11577
+ const anchor = el.closest("a");
11578
+ if (anchor && root.contains(anchor) && !anchor.hasAttribute("data-ohw-href-key") && !anchor.closest("[data-ohw-nav-container]") && Boolean(anchor.querySelector("img") || anchor.matches("img"))) {
11579
+ return anchor;
11580
+ }
11581
+ const img = el.matches("img") ? el : null;
11582
+ if (img && !img.closest("[data-ohw-href-key]") && !img.closest("[data-ohw-nav-container]") && (img.closest("footer") || img.closest("nav, [data-ohw-nav-root]"))) {
11583
+ return img;
11584
+ }
11585
+ return null;
11586
+ }
11587
+ function isInFooter(el) {
11588
+ if (!el) return false;
11589
+ return Boolean(el.closest("footer") || el.closest('[data-ohw-section="footer"]'));
11590
+ }
11591
+ function isSiteWideElement(el) {
11592
+ if (!el) return false;
11593
+ if (getLogoElement(el)) return true;
11594
+ if (isInFooter(el)) return true;
11595
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-root")) {
11596
+ return true;
11597
+ }
11598
+ if (el.hasAttribute("data-ohw-href-key") && el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer], aside")) {
11599
+ return true;
11600
+ }
11601
+ if (el.closest('[data-ohw-role="navbar-button"]')) return true;
11602
+ return false;
11603
+ }
11604
+ function isSiteWideScopeActive(args) {
11605
+ return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11606
+ }
11607
+
10924
11608
  // src/lib/add-footer-column.ts
10925
11609
  function buildFooterColumnEditContentPatch(result) {
10926
11610
  return {
@@ -11136,16 +11820,127 @@ function FloatingPanel({
11136
11820
  );
11137
11821
  }
11138
11822
 
11139
- // src/ui/socials-display-panel.tsx
11823
+ // src/ui/logo-size-panel.tsx
11824
+ var import_lucide_react14 = require("lucide-react");
11140
11825
  var import_jsx_runtime27 = require("react/jsx-runtime");
11826
+ function SizeSlider({
11827
+ value,
11828
+ onChange
11829
+ }) {
11830
+ const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11831
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-3", children: [
11832
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11833
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11834
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11835
+ value,
11836
+ " px"
11837
+ ] })
11838
+ ] }),
11839
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11840
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11841
+ "div",
11842
+ {
11843
+ className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11844
+ style: { width: `${pct}%` }
11845
+ }
11846
+ ),
11847
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11848
+ "input",
11849
+ {
11850
+ type: "range",
11851
+ min: LOGO_SIZE_MIN,
11852
+ max: LOGO_SIZE_MAX,
11853
+ step: 1,
11854
+ value,
11855
+ "aria-label": "Logo size",
11856
+ className: cn(
11857
+ "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11858
+ "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11859
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11860
+ "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11861
+ "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11862
+ "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11863
+ "[&::-moz-range-thumb]:bg-background"
11864
+ ),
11865
+ onChange: (e) => onChange(Number(e.target.value))
11866
+ }
11867
+ )
11868
+ ] })
11869
+ ] });
11870
+ }
11871
+ function LogoSizePanel({
11872
+ viewport,
11873
+ sizePx,
11874
+ mobileFollowing = true,
11875
+ onSizeChange,
11876
+ onCustomizeMobile,
11877
+ onResetMobile,
11878
+ onUpdateEverywhere,
11879
+ className
11880
+ }) {
11881
+ const showFollowing = viewport === "mobile" && mobileFollowing;
11882
+ const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11883
+ const showDesktopSlider = viewport === "desktop";
11884
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11885
+ showFollowing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
11886
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start gap-1", children: [
11887
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.Link, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11888
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11889
+ ] }),
11890
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Mobile uses the desktop size until you customize it. Change the desktop size and it follows automatically." }),
11891
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11892
+ Button,
11893
+ {
11894
+ type: "button",
11895
+ variant: "outline",
11896
+ size: "sm",
11897
+ className: "h-9 w-full min-w-0 cursor-pointer",
11898
+ onClick: onCustomizeMobile,
11899
+ children: "Customize for mobile"
11900
+ }
11901
+ )
11902
+ ] }) : null,
11903
+ showDesktopSlider || showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11904
+ showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11905
+ Button,
11906
+ {
11907
+ type: "button",
11908
+ variant: "outline",
11909
+ size: "sm",
11910
+ className: "h-9 w-full min-w-0 cursor-pointer",
11911
+ onClick: onResetMobile,
11912
+ children: "Reset to desktop size"
11913
+ }
11914
+ ) : null,
11915
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-px w-full bg-border", role: "separator" }),
11916
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11917
+ Button,
11918
+ {
11919
+ type: "button",
11920
+ variant: "outline",
11921
+ size: "sm",
11922
+ className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11923
+ onClick: onUpdateEverywhere,
11924
+ children: [
11925
+ "Update logo everywhere",
11926
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.ArrowUpRight, { size: 16, "aria-hidden": true })
11927
+ ]
11928
+ }
11929
+ ),
11930
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11931
+ ] });
11932
+ }
11933
+
11934
+ // src/ui/socials-display-panel.tsx
11935
+ var import_jsx_runtime28 = require("react/jsx-runtime");
11141
11936
  function DisplaySwitch({
11142
11937
  label,
11143
11938
  checked,
11144
11939
  disabled,
11145
11940
  onChange
11146
11941
  }) {
11147
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2", children: [
11148
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11942
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex w-full items-center gap-2", children: [
11943
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11149
11944
  "span",
11150
11945
  {
11151
11946
  className: cn(
@@ -11155,7 +11950,7 @@ function DisplaySwitch({
11155
11950
  children: label
11156
11951
  }
11157
11952
  ),
11158
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11953
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11159
11954
  "button",
11160
11955
  {
11161
11956
  type: "button",
@@ -11169,7 +11964,7 @@ function DisplaySwitch({
11169
11964
  checked ? "bg-primary" : "bg-primary-50",
11170
11965
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
11171
11966
  ),
11172
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11967
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11173
11968
  "span",
11174
11969
  {
11175
11970
  className: cn(
@@ -11183,8 +11978,8 @@ function DisplaySwitch({
11183
11978
  ] });
11184
11979
  }
11185
11980
  function SocialsDisplayPanel({ display, onChange, className }) {
11186
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11187
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11981
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11982
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11188
11983
  DisplaySwitch,
11189
11984
  {
11190
11985
  label: "Text",
@@ -11193,7 +11988,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
11193
11988
  onChange: (text) => onChange({ ...display, text })
11194
11989
  }
11195
11990
  ),
11196
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11991
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11197
11992
  DisplaySwitch,
11198
11993
  {
11199
11994
  label: "Icon",
@@ -11753,8 +12548,8 @@ function useNavItemDrag({
11753
12548
  }
11754
12549
 
11755
12550
  // src/ui/footer-container-chrome.tsx
11756
- var import_lucide_react14 = require("lucide-react");
11757
- var import_jsx_runtime28 = require("react/jsx-runtime");
12551
+ var import_lucide_react15 = require("lucide-react");
12552
+ var import_jsx_runtime29 = require("react/jsx-runtime");
11758
12553
  function FooterContainerChrome({
11759
12554
  rect,
11760
12555
  onAdd,
@@ -11762,7 +12557,7 @@ function FooterContainerChrome({
11762
12557
  }) {
11763
12558
  const chromeGap = 6;
11764
12559
  const buttonMargin = 7;
11765
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
12560
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11766
12561
  "div",
11767
12562
  {
11768
12563
  "data-ohw-footer-container-chrome": "",
@@ -11774,8 +12569,8 @@ function FooterContainerChrome({
11774
12569
  width: rect.width + chromeGap * 2,
11775
12570
  height: rect.height + chromeGap * 2
11776
12571
  },
11777
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(Tooltip, { children: [
11778
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
12572
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Tooltip, { children: [
12573
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11779
12574
  "button",
11780
12575
  {
11781
12576
  type: "button",
@@ -11794,10 +12589,10 @@ function FooterContainerChrome({
11794
12589
  if (addDisabled) return;
11795
12590
  onAdd();
11796
12591
  },
11797
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react14.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12592
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
11798
12593
  }
11799
12594
  ) }),
11800
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12595
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
11801
12596
  ] })
11802
12597
  }
11803
12598
  ) });
@@ -11980,6 +12775,18 @@ function collectEditableNodes(extraContent, root = document) {
11980
12775
  }
11981
12776
  if (extraContent && !isScoped) {
11982
12777
  applyNavFooterDeleteOverrides(byKey, extraContent);
12778
+ for (const key of LOGO_IMAGE_KEYS) {
12779
+ if (!(key in extraContent)) continue;
12780
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12781
+ }
12782
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12783
+ if (!(key in extraContent)) continue;
12784
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12785
+ }
12786
+ for (const key of LOGO_SIZE_KEYS) {
12787
+ if (!(key in extraContent)) continue;
12788
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12789
+ }
11983
12790
  }
11984
12791
  return Array.from(byKey.values());
11985
12792
  }
@@ -12245,14 +13052,14 @@ function deleteSelectedNavFooterItem(deps) {
12245
13052
  }
12246
13053
 
12247
13054
  // src/ui/navbar-container-chrome.tsx
12248
- var import_lucide_react15 = require("lucide-react");
12249
- var import_jsx_runtime29 = require("react/jsx-runtime");
13055
+ var import_lucide_react16 = require("lucide-react");
13056
+ var import_jsx_runtime30 = require("react/jsx-runtime");
12250
13057
  function NavbarContainerChrome({
12251
13058
  rect,
12252
13059
  onAdd
12253
13060
  }) {
12254
13061
  const chromeGap = 6;
12255
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13062
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12256
13063
  "div",
12257
13064
  {
12258
13065
  "data-ohw-navbar-container-chrome": "",
@@ -12264,7 +13071,7 @@ function NavbarContainerChrome({
12264
13071
  width: rect.width + chromeGap * 2,
12265
13072
  height: rect.height + chromeGap * 2
12266
13073
  },
12267
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13074
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12268
13075
  "button",
12269
13076
  {
12270
13077
  type: "button",
@@ -12281,7 +13088,7 @@ function NavbarContainerChrome({
12281
13088
  e.stopPropagation();
12282
13089
  onAdd();
12283
13090
  },
12284
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
13091
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react16.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12285
13092
  }
12286
13093
  )
12287
13094
  }
@@ -12290,7 +13097,7 @@ function NavbarContainerChrome({
12290
13097
 
12291
13098
  // src/ui/drop-indicator.tsx
12292
13099
  var React10 = __toESM(require("react"), 1);
12293
- var import_jsx_runtime30 = require("react/jsx-runtime");
13100
+ var import_jsx_runtime31 = require("react/jsx-runtime");
12294
13101
  var dropIndicatorVariants = cva(
12295
13102
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
12296
13103
  {
@@ -12314,7 +13121,7 @@ var dropIndicatorVariants = cva(
12314
13121
  );
12315
13122
  var DropIndicator = React10.forwardRef(
12316
13123
  ({ className, direction, state, ...props }, ref) => {
12317
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
13124
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12318
13125
  "div",
12319
13126
  {
12320
13127
  ref,
@@ -12331,7 +13138,7 @@ var DropIndicator = React10.forwardRef(
12331
13138
  DropIndicator.displayName = "DropIndicator";
12332
13139
 
12333
13140
  // src/ui/badge.tsx
12334
- var import_jsx_runtime31 = require("react/jsx-runtime");
13141
+ var import_jsx_runtime32 = require("react/jsx-runtime");
12335
13142
  var badgeVariants = cva(
12336
13143
  "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",
12337
13144
  {
@@ -12349,12 +13156,12 @@ var badgeVariants = cva(
12349
13156
  }
12350
13157
  );
12351
13158
  function Badge({ className, variant, ...props }) {
12352
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
13159
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
12353
13160
  }
12354
13161
 
12355
13162
  // src/OhhwellsBridge.tsx
12356
- var import_lucide_react16 = require("lucide-react");
12357
- var import_jsx_runtime32 = require("react/jsx-runtime");
13163
+ var import_lucide_react17 = require("lucide-react");
13164
+ var import_jsx_runtime33 = require("react/jsx-runtime");
12358
13165
  var PRIMARY3 = "#0885FE";
12359
13166
  var IMAGE_FADE_MS = 300;
12360
13167
  function runOpacityFade(el, onDone) {
@@ -12448,21 +13255,10 @@ function parseSchedulingInsertAfter(insertAfter) {
12448
13255
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
12449
13256
  };
12450
13257
  }
12451
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
12452
- const parsed = parseSchedulingInsertAfter(insertAfter);
12453
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
12454
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
12455
- return { effectiveInsertAfter, insertBefore };
12456
- }
12457
- function getSchedulingMountPoint(insertAfter) {
12458
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
12459
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
12460
- if (!anchorEl && anchor === "scheduling") {
12461
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
12462
- anchorEl = widgets.at(-1) ?? null;
12463
- }
12464
- if (!anchorEl) return null;
12465
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
13258
+ function resolveEntryAnchor(entry) {
13259
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13260
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13261
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
12466
13262
  }
12467
13263
  function schedulingMountDepth(insertAfter) {
12468
13264
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -12479,8 +13275,7 @@ function getPageSchedulingEntries(raw) {
12479
13275
  }
12480
13276
  }
12481
13277
  function isSchedulingWidgetMissing(entry) {
12482
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
12483
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13278
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
12484
13279
  }
12485
13280
  function hasMissingSchedulingWidgets(entries) {
12486
13281
  return entries.some(isSchedulingWidgetMissing);
@@ -12510,16 +13305,17 @@ function initSectionsFromContent(content, removeExisting = false) {
12510
13305
  } catch {
12511
13306
  }
12512
13307
  }
12513
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
12514
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
12515
- const sectionId = schedulingSectionId(effectiveInsertAfter);
13308
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13309
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13310
+ const sectionId = schedulingSectionId(widgetId);
12516
13311
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
12517
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
12518
- if (!mountPoint) return false;
13312
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13313
+ if (!anchorEl) return false;
13314
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12519
13315
  const container = document.createElement("div");
12520
13316
  container.dataset.ohwSectionContainer = "scheduling";
12521
- if (insertBefore) {
12522
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13317
+ if (beforeId) {
13318
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
12523
13319
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
12524
13320
  if (!beforePoint) return false;
12525
13321
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -12530,19 +13326,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12530
13326
  }
12531
13327
  tail.insertAdjacentElement("afterend", container);
12532
13328
  }
12533
- const root = (0, import_client2.createRoot)(container);
12534
- (0, import_react_dom3.flushSync)(() => {
12535
- root.render(
12536
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12537
- SchedulingWidget,
12538
- {
12539
- notifyOnConnect,
12540
- initialScheduleId: scheduleId,
12541
- insertAfter: effectiveInsertAfter
12542
- }
12543
- )
12544
- );
12545
- });
13329
+ try {
13330
+ const root = (0, import_client2.createRoot)(container);
13331
+ (0, import_react_dom3.flushSync)(() => {
13332
+ root.render(
13333
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13334
+ SchedulingWidget,
13335
+ {
13336
+ notifyOnConnect,
13337
+ initialScheduleId: scheduleId,
13338
+ insertAfter: widgetId
13339
+ }
13340
+ )
13341
+ );
13342
+ });
13343
+ } catch (err) {
13344
+ console.error("[ow:scheduling] render threw", err);
13345
+ container.remove();
13346
+ return false;
13347
+ }
12546
13348
  const tracker = getSectionsTracker();
12547
13349
  let sections = [];
12548
13350
  try {
@@ -12550,10 +13352,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12550
13352
  } catch {
12551
13353
  }
12552
13354
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
12553
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13355
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
12554
13356
  sections.push({
12555
13357
  type: "scheduling",
12556
- insertAfter: effectiveInsertAfter,
13358
+ insertAfter: widgetId,
13359
+ anchorId,
13360
+ beforeId: beforeId ?? null,
12557
13361
  pagePath: window.location.pathname,
12558
13362
  ...scheduleId ? { scheduleId } : {}
12559
13363
  });
@@ -12567,7 +13371,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
12567
13371
  for (let i = pending.length - 1; i >= 0; i--) {
12568
13372
  const entry = pending[i];
12569
13373
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
12570
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13374
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
13375
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
12571
13376
  pending.splice(i, 1);
12572
13377
  }
12573
13378
  }
@@ -12711,6 +13516,13 @@ function isInsideLinkEditor(target) {
12711
13516
  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"]')
12712
13517
  );
12713
13518
  }
13519
+ function isInsideFloatingPanel(target) {
13520
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
13521
+ }
13522
+ function isPointOverFloatingPanel(clientX, clientY) {
13523
+ const el = document.elementFromPoint(clientX, clientY);
13524
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13525
+ }
12714
13526
  function getHrefKeyFromElement(el) {
12715
13527
  if (!el) return null;
12716
13528
  const anchor = el.closest("[data-ohw-href-key]");
@@ -12948,7 +13760,7 @@ function getNavigationSelectionParent(el) {
12948
13760
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
12949
13761
  return getFooterLinksContainer();
12950
13762
  }
12951
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13763
+ 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)) {
12952
13764
  return getNavigationRoot(el);
12953
13765
  }
12954
13766
  return null;
@@ -13194,7 +14006,7 @@ function EditGlowChrome({
13194
14006
  hideHandle = false
13195
14007
  }) {
13196
14008
  const GAP = SELECTION_CHROME_GAP2;
13197
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
14009
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
13198
14010
  "div",
13199
14011
  {
13200
14012
  ref: elRef,
@@ -13209,7 +14021,7 @@ function EditGlowChrome({
13209
14021
  zIndex: 2147483646
13210
14022
  },
13211
14023
  children: [
13212
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14024
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13213
14025
  "div",
13214
14026
  {
13215
14027
  style: {
@@ -13222,7 +14034,7 @@ function EditGlowChrome({
13222
14034
  }
13223
14035
  }
13224
14036
  ),
13225
- reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14037
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13226
14038
  "div",
13227
14039
  {
13228
14040
  "data-ohw-drag-handle-container": "",
@@ -13234,7 +14046,7 @@ function EditGlowChrome({
13234
14046
  transform: "translate(calc(-100% - 7px), -50%)",
13235
14047
  pointerEvents: dragDisabled ? "none" : "auto"
13236
14048
  },
13237
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14049
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13238
14050
  DragHandle,
13239
14051
  {
13240
14052
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -13444,7 +14256,7 @@ function FloatingToolbar({
13444
14256
  return () => ro.disconnect();
13445
14257
  }, [showEditLink, activeCommands]);
13446
14258
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
13447
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14259
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13448
14260
  "div",
13449
14261
  {
13450
14262
  ref: setRefs,
@@ -13456,12 +14268,12 @@ function FloatingToolbar({
13456
14268
  zIndex: 2147483647,
13457
14269
  pointerEvents: "auto"
13458
14270
  },
13459
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(CustomToolbar, { children: [
13460
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react16.default.Fragment, { children: [
13461
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CustomToolbarDivider, {}),
14271
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(CustomToolbar, { children: [
14272
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react16.default.Fragment, { children: [
14273
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CustomToolbarDivider, {}),
13462
14274
  btns.map((btn) => {
13463
14275
  const isActive = activeCommands.has(btn.cmd);
13464
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14276
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13465
14277
  CustomToolbarButton,
13466
14278
  {
13467
14279
  title: btn.title,
@@ -13470,7 +14282,7 @@ function FloatingToolbar({
13470
14282
  e.preventDefault();
13471
14283
  onCommand(btn.cmd);
13472
14284
  },
13473
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14285
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13474
14286
  "svg",
13475
14287
  {
13476
14288
  width: "16",
@@ -13491,7 +14303,7 @@ function FloatingToolbar({
13491
14303
  );
13492
14304
  })
13493
14305
  ] }, gi)),
13494
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14306
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13495
14307
  CustomToolbarButton,
13496
14308
  {
13497
14309
  type: "button",
@@ -13505,7 +14317,7 @@ function FloatingToolbar({
13505
14317
  e.preventDefault();
13506
14318
  e.stopPropagation();
13507
14319
  },
13508
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react16.Link, { className: "size-4 shrink-0", "aria-hidden": true })
14320
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Link, { className: "size-4 shrink-0", "aria-hidden": true })
13509
14321
  }
13510
14322
  ) : null
13511
14323
  ] })
@@ -13522,7 +14334,7 @@ function StateToggle({
13522
14334
  states,
13523
14335
  onStateChange
13524
14336
  }) {
13525
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14337
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13526
14338
  ToggleGroup,
13527
14339
  {
13528
14340
  "data-ohw-state-toggle": "",
@@ -13536,11 +14348,12 @@ function StateToggle({
13536
14348
  left: rect.right - 8,
13537
14349
  transform: "translateX(-100%)"
13538
14350
  },
13539
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
14351
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
13540
14352
  }
13541
14353
  );
13542
14354
  }
13543
14355
  var contentCache = /* @__PURE__ */ new Map();
14356
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
13544
14357
  function resolveSubdomain(subdomainFromQuery) {
13545
14358
  if (subdomainFromQuery) return subdomainFromQuery;
13546
14359
  if (typeof window !== "undefined") {
@@ -13635,8 +14448,14 @@ function OhhwellsBridge() {
13635
14448
  });
13636
14449
  const selectFrameRef = (0, import_react16.useRef)(() => {
13637
14450
  });
14451
+ const selectLogoRef = (0, import_react16.useRef)(() => {
14452
+ });
14453
+ const openLogoSizePanelRef = (0, import_react16.useRef)(() => {
14454
+ });
13638
14455
  const deselectRef = (0, import_react16.useRef)(() => {
13639
14456
  });
14457
+ const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(() => {
14458
+ });
13640
14459
  const reselectNavigationItemRef = (0, import_react16.useRef)(() => {
13641
14460
  });
13642
14461
  const commitNavigationTextEditRef = (0, import_react16.useRef)(() => {
@@ -13697,11 +14516,6 @@ function OhhwellsBridge() {
13697
14516
  const [isItemDragging, setIsItemDragging] = (0, import_react16.useState)(false);
13698
14517
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react16.useState)(false);
13699
14518
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
13700
- const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
13701
- const floatingPanelOpenRef = (0, import_react16.useRef)(false);
13702
- floatingPanelOpenRef.current = floatingPanel !== null;
13703
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
13704
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13705
14519
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
13706
14520
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
13707
14521
  const footerDragRef = (0, import_react16.useRef)(null);
@@ -13716,7 +14530,16 @@ function OhhwellsBridge() {
13716
14530
  const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
13717
14531
  const editContentRef = (0, import_react16.useRef)({});
13718
14532
  const aiSectionsRef = (0, import_react16.useRef)("");
14533
+ const brandKitRef = (0, import_react16.useRef)("");
14534
+ const stylesRef = (0, import_react16.useRef)("");
13719
14535
  const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14536
+ const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14537
+ const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14538
+ const setFloatingPanelRef = (0, import_react16.useRef)(setFloatingPanel);
14539
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14540
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react16.useState)(null);
14541
+ const [editorViewport, setEditorViewport] = (0, import_react16.useState)("desktop");
14542
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13720
14543
  const [sitePages, setSitePages] = (0, import_react16.useState)([]);
13721
14544
  const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
13722
14545
  const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
@@ -13725,7 +14548,18 @@ function OhhwellsBridge() {
13725
14548
  const linkPopoverOpenRef = (0, import_react16.useRef)(false);
13726
14549
  const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
13727
14550
  setLinkPopoverRef.current = setLinkPopover;
14551
+ setFloatingPanelRef.current = setFloatingPanel;
13728
14552
  linkPopoverSessionRef.current = linkPopover;
14553
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
14554
+ (0, import_react16.useEffect)(() => {
14555
+ const syncViewport = () => {
14556
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14557
+ setEditorViewport((prev) => prev === next ? prev : next);
14558
+ };
14559
+ syncViewport();
14560
+ window.addEventListener("resize", syncViewport);
14561
+ return () => window.removeEventListener("resize", syncViewport);
14562
+ }, []);
13729
14563
  const {
13730
14564
  navDragRef,
13731
14565
  navDropSlots,
@@ -13948,6 +14782,10 @@ function OhhwellsBridge() {
13948
14782
  setIsItemDragging(false);
13949
14783
  hoveredNavContainerRef.current = null;
13950
14784
  setHoveredNavContainerRect(null);
14785
+ hoveredItemElRef.current = null;
14786
+ setHoveredItemRect(null);
14787
+ setFloatingPanel(null);
14788
+ setLogoSizeDraft(null);
13951
14789
  if (!activeElRef.current) {
13952
14790
  setNavGroupForceOpen(null, false);
13953
14791
  setToolbarRect(null);
@@ -14653,6 +15491,8 @@ function OhhwellsBridge() {
14653
15491
  setToolbarRect(anchor.getBoundingClientRect());
14654
15492
  setToolbarShowEditLink(false);
14655
15493
  setActiveCommands(/* @__PURE__ */ new Set());
15494
+ setFloatingPanel(null);
15495
+ setLogoSizeDraft(null);
14656
15496
  }, [deactivate, markSelected]);
14657
15497
  const selectFrame = (0, import_react16.useCallback)((el) => {
14658
15498
  if (!isNavigationContainer(el)) return;
@@ -14702,7 +15542,51 @@ function OhhwellsBridge() {
14702
15542
  setToolbarRect(el.getBoundingClientRect());
14703
15543
  setToolbarShowEditLink(false);
14704
15544
  setActiveCommands(/* @__PURE__ */ new Set());
15545
+ setFloatingPanel(null);
15546
+ setLogoSizeDraft(null);
14705
15547
  }, [deactivate, markSelected, postToParent2]);
15548
+ const selectLogo = (0, import_react16.useCallback)(
15549
+ (logoEl) => {
15550
+ if (activeElRef.current) deactivate();
15551
+ selectedElRef.current = logoEl;
15552
+ selectedHrefKeyRef.current = null;
15553
+ selectedFooterColAttrRef.current = null;
15554
+ markSelected(logoEl);
15555
+ setSelectedIsCta(false);
15556
+ setSelectedIsSocial(false);
15557
+ setSelectedIsSocialsRow(false);
15558
+ clearHrefKeyHover(logoEl);
15559
+ hoveredNavContainerRef.current = null;
15560
+ setHoveredNavContainerRect(null);
15561
+ setHoveredItemRect(null);
15562
+ hoveredItemElRef.current = null;
15563
+ siblingHintElRef.current = null;
15564
+ setSiblingHintRect(null);
15565
+ setSiblingHintRects([]);
15566
+ setIsItemDragging(false);
15567
+ setReorderHrefKey(null);
15568
+ setReorderDragDisabled(false);
15569
+ setIsFooterFrameSelection(false);
15570
+ setToolbarVariant("logo");
15571
+ setToolbarRect(getLogoInteractionRect(logoEl));
15572
+ setToolbarShowEditLink(false);
15573
+ setActiveCommands(/* @__PURE__ */ new Set());
15574
+ },
15575
+ [deactivate, markSelected]
15576
+ );
15577
+ const openLogoSizePanel = (0, import_react16.useCallback)((logoEl) => {
15578
+ const placement = getLogoPlacement(logoEl);
15579
+ const draft = readLogoSizeState(editContentRef.current, placement);
15580
+ setLogoSizeDraft(draft);
15581
+ setParentScrollSnap(parentScrollRef.current);
15582
+ setFloatingPanel({
15583
+ key: `logo-size:${placement}`,
15584
+ title: "Logo",
15585
+ context: placement === "navbar" ? "Navbar" : "Footer",
15586
+ kind: "logo-size",
15587
+ placement
15588
+ });
15589
+ }, []);
14706
15590
  const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
14707
15591
  setParentScrollSnap(parentScrollRef.current);
14708
15592
  setFloatingPanel({
@@ -14738,13 +15622,53 @@ function OhhwellsBridge() {
14738
15622
  );
14739
15623
  const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
14740
15624
  setFloatingPanel(null);
15625
+ setLogoSizeDraft(null);
14741
15626
  }, []);
14742
- const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(closeFloatingPanelOnly);
14743
- closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
14744
15627
  const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
14745
15628
  setFloatingPanel(null);
15629
+ setLogoSizeDraft(null);
14746
15630
  deselectRef.current();
14747
15631
  }, []);
15632
+ const persistLogoSizeDraft = (0, import_react16.useCallback)(
15633
+ (placement, draft) => {
15634
+ const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15635
+ const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15636
+ const nodes = [
15637
+ { key: desktopKey, text: String(draft.desktopPx) }
15638
+ ];
15639
+ if (draft.mobileFollowing) {
15640
+ nodes.push({ key: mobileKey, text: "" });
15641
+ } else {
15642
+ nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15643
+ }
15644
+ editContentRef.current = {
15645
+ ...editContentRef.current,
15646
+ [desktopKey]: String(draft.desktopPx),
15647
+ [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15648
+ };
15649
+ applyLogoSizeToPlacement(
15650
+ placement,
15651
+ draft.desktopPx,
15652
+ draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15653
+ draft.mobileFollowing
15654
+ );
15655
+ postToParent2({ type: "ow:change", nodes });
15656
+ requestAnimationFrame(() => {
15657
+ const selected = selectedElRef.current;
15658
+ if (!selected || toolbarVariantRef.current !== "logo") return;
15659
+ const rect = getLogoInteractionRect(selected);
15660
+ setToolbarRect(rect);
15661
+ if (glowElRef.current) {
15662
+ const GAP = SELECTION_CHROME_GAP2;
15663
+ glowElRef.current.style.top = `${rect.top - GAP}px`;
15664
+ glowElRef.current.style.left = `${rect.left - GAP}px`;
15665
+ glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15666
+ glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15667
+ }
15668
+ });
15669
+ },
15670
+ [postToParent2]
15671
+ );
14748
15672
  const activate = (0, import_react16.useCallback)((el, options) => {
14749
15673
  if (activeElRef.current === el) return;
14750
15674
  if (isIconEditable(el)) return;
@@ -14825,7 +15749,37 @@ function OhhwellsBridge() {
14825
15749
  deactivateRef.current = deactivate;
14826
15750
  selectRef.current = select;
14827
15751
  selectFrameRef.current = selectFrame;
15752
+ selectLogoRef.current = selectLogo;
15753
+ openLogoSizePanelRef.current = openLogoSizePanel;
14828
15754
  deselectRef.current = deselect;
15755
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15756
+ const lastSiteWideScopeRef = (0, import_react16.useRef)(null);
15757
+ (0, import_react16.useEffect)(() => {
15758
+ if (!isEditMode) {
15759
+ if (lastSiteWideScopeRef.current !== false) {
15760
+ lastSiteWideScopeRef.current = false;
15761
+ postToParent2({ type: "ow:site-wide-scope", active: false });
15762
+ }
15763
+ return;
15764
+ }
15765
+ const active = isSiteWideScopeActive({
15766
+ selected: selectedElRef.current,
15767
+ hoveredItem: hoveredItemElRef.current,
15768
+ hoveredNavContainer: hoveredNavContainerRef.current,
15769
+ active: activeElRef.current
15770
+ });
15771
+ if (lastSiteWideScopeRef.current === active) return;
15772
+ lastSiteWideScopeRef.current = active;
15773
+ postToParent2({ type: "ow:site-wide-scope", active });
15774
+ }, [
15775
+ isEditMode,
15776
+ hoveredItemRect,
15777
+ hoveredNavContainerRect,
15778
+ toolbarVariant,
15779
+ toolbarRect,
15780
+ isFooterFrameSelection,
15781
+ postToParent2
15782
+ ]);
14829
15783
  (0, import_react16.useLayoutEffect)(() => {
14830
15784
  if (!subdomain || isEditMode) {
14831
15785
  setFetchState("done");
@@ -14837,9 +15791,23 @@ function OhhwellsBridge() {
14837
15791
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
14838
15792
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
14839
15793
  }
15794
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15795
+ brandKitRef.current = content[BRAND_KIT_KEY];
15796
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15797
+ }
15798
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15799
+ stylesRef.current = content[STYLE_STORE_KEY];
15800
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15801
+ }
15802
+ applyBrandChrome(content);
14840
15803
  for (const [key, val] of Object.entries(content)) {
14841
15804
  if (key === "__ohw_sections") continue;
14842
15805
  if (key === AI_SECTIONS_KEY) continue;
15806
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15807
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15808
+ if (key === BRAND_KIT_KEY) continue;
15809
+ if (key === STYLE_STORE_KEY) continue;
15810
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14843
15811
  if (applyVideoSettingNode(key, val)) continue;
14844
15812
  if (applyCarouselNode(key, val)) continue;
14845
15813
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14874,6 +15842,8 @@ function OhhwellsBridge() {
14874
15842
  });
14875
15843
  applyLinkByKey(key, val);
14876
15844
  }
15845
+ applyLogoFromContent(content);
15846
+ applyLogoSizes(content);
14877
15847
  reconcileNavbarItemsFromContent(content);
14878
15848
  reconcileFooterOrderFromContent(content);
14879
15849
  reconcileSocialsFromContent(content);
@@ -14894,7 +15864,9 @@ function OhhwellsBridge() {
14894
15864
  let cancelled = false;
14895
15865
  setFetchState("loading");
14896
15866
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14897
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15867
+ const initialPath = pathname;
15868
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15869
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14898
15870
  if (cancelled) return;
14899
15871
  const content = data?.content ?? {};
14900
15872
  contentCache.set(subdomain, content);
@@ -14918,8 +15890,21 @@ function OhhwellsBridge() {
14918
15890
  initSectionInstancesFromContent(content, window.location.pathname);
14919
15891
  observer?.disconnect();
14920
15892
  try {
15893
+ applyBrandChrome(content);
15894
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15895
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15896
+ }
15897
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15898
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15899
+ }
14921
15900
  for (const [key, val] of Object.entries(content)) {
14922
15901
  if (key === "__ohw_sections") continue;
15902
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15903
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15904
+ if (key === BRAND_KIT_KEY) continue;
15905
+ if (key === STYLE_STORE_KEY) continue;
15906
+ if (key === STYLE_STORE_KEY) continue;
15907
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14923
15908
  if (applyVideoSettingNode(key, val)) continue;
14924
15909
  if (applyCarouselNode(key, val)) continue;
14925
15910
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14940,6 +15925,7 @@ function OhhwellsBridge() {
14940
15925
  });
14941
15926
  applyLinkByKey(key, val);
14942
15927
  }
15928
+ applyLogoFromContent(content);
14943
15929
  reconcileNavbarItemsFromContent(content);
14944
15930
  reconcileFooterOrderFromContent(content);
14945
15931
  reconcileSocialsFromContent(content);
@@ -14954,6 +15940,17 @@ function OhhwellsBridge() {
14954
15940
  debounceTimer = setTimeout(applyFromCache, 150);
14955
15941
  };
14956
15942
  applyFromCache();
15943
+ const pathCacheKey = `${subdomain}::${pathname}`;
15944
+ if (!fetchedContentPaths.has(pathCacheKey)) {
15945
+ fetchedContentPaths.add(pathCacheKey);
15946
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15947
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15948
+ if (!data?.content) return;
15949
+ contentCache.set(subdomain, data.content);
15950
+ applyFromCache();
15951
+ }).catch(() => {
15952
+ });
15953
+ }
14957
15954
  observer = new MutationObserver(scheduleApply);
14958
15955
  observer.observe(document.body, { childList: true, subtree: true });
14959
15956
  return () => {
@@ -15047,26 +16044,31 @@ function OhhwellsBridge() {
15047
16044
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
15048
16045
  (0, import_react16.useEffect)(() => {
15049
16046
  if (!isEditMode) return;
16047
+ let lastPosted = 0;
15050
16048
  const measure = () => {
15051
16049
  const h = document.body.scrollHeight;
15052
- if (h > 50) postToParent2({ type: "ow:height", height: h });
16050
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
16051
+ lastPosted = h;
16052
+ postToParent2({ type: "ow:height", height: h });
16053
+ }
16054
+ };
16055
+ let raf = null;
16056
+ const schedule = () => {
16057
+ if (raf != null) return;
16058
+ raf = requestAnimationFrame(() => {
16059
+ raf = null;
16060
+ measure();
16061
+ });
15053
16062
  };
15054
16063
  const t1 = setTimeout(measure, 50);
15055
16064
  const t2 = setTimeout(measure, 500);
15056
- let lastWidth = window.innerWidth;
15057
- let resizeTimer = null;
15058
- const handleResize = () => {
15059
- if (window.innerWidth === lastWidth) return;
15060
- lastWidth = window.innerWidth;
15061
- if (resizeTimer) clearTimeout(resizeTimer);
15062
- resizeTimer = setTimeout(measure, 150);
15063
- };
15064
- window.addEventListener("resize", handleResize);
16065
+ const ro = new ResizeObserver(schedule);
16066
+ ro.observe(document.body);
15065
16067
  return () => {
15066
16068
  clearTimeout(t1);
15067
16069
  clearTimeout(t2);
15068
- if (resizeTimer) clearTimeout(resizeTimer);
15069
- window.removeEventListener("resize", handleResize);
16070
+ if (raf != null) cancelAnimationFrame(raf);
16071
+ ro.disconnect();
15070
16072
  };
15071
16073
  }, [pathname, isEditMode, postToParent2]);
15072
16074
  (0, import_react16.useEffect)(() => {
@@ -15216,10 +16218,12 @@ function OhhwellsBridge() {
15216
16218
  return;
15217
16219
  }
15218
16220
  const target = e.target;
16221
+ if (target.closest("[data-ohw-ai-review]")) return;
15219
16222
  if (target.closest("[data-ohw-toolbar]")) return;
15220
16223
  if (target.closest("[data-ohw-state-toggle]")) return;
15221
16224
  if (target.closest("[data-ohw-max-badge]")) return;
15222
16225
  if (isInsideLinkEditor(target)) return;
16226
+ if (isInsideFloatingPanel(target)) return;
15223
16227
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15224
16228
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
15225
16229
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -15283,6 +16287,21 @@ function OhhwellsBridge() {
15283
16287
  return;
15284
16288
  }
15285
16289
  }
16290
+ const logoEl = getLogoElement(target);
16291
+ if (logoEl) {
16292
+ e.preventDefault();
16293
+ e.stopPropagation();
16294
+ if (!logoHasUploadedImage(logoEl)) {
16295
+ deselectRef.current();
16296
+ deactivateRef.current();
16297
+ const identity = readLogoIdentityFromDom();
16298
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16299
+ return;
16300
+ }
16301
+ selectLogoRef.current(logoEl);
16302
+ openLogoSizePanelRef.current(logoEl);
16303
+ return;
16304
+ }
15286
16305
  const editable = target.closest("[data-ohw-editable]");
15287
16306
  if (editable) {
15288
16307
  if (editable.dataset.ohwEditable === "link") {
@@ -15435,10 +16454,12 @@ function OhhwellsBridge() {
15435
16454
  };
15436
16455
  const handleDblClick = (e) => {
15437
16456
  const target = e.target;
16457
+ if (target.closest("[data-ohw-ai-review]")) return;
15438
16458
  if (target.closest("[data-ohw-toolbar]")) return;
15439
16459
  if (target.closest("[data-ohw-state-toggle]")) return;
15440
16460
  if (target.closest("[data-ohw-max-badge]")) return;
15441
16461
  if (isInsideLinkEditor(target)) return;
16462
+ if (isInsideFloatingPanel(target)) return;
15442
16463
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15443
16464
  return;
15444
16465
  }
@@ -15466,11 +16487,14 @@ function OhhwellsBridge() {
15466
16487
  setHoveredNavContainerRect(null);
15467
16488
  return;
15468
16489
  }
15469
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
16490
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
15470
16491
  hoveredItemElRef.current = null;
15471
16492
  setHoveredItemRect(null);
15472
16493
  hoveredNavContainerRef.current = null;
15473
16494
  setHoveredNavContainerRect(null);
16495
+ siblingHintElRef.current = null;
16496
+ setSiblingHintRect(null);
16497
+ setSiblingHintRects([]);
15474
16498
  return;
15475
16499
  }
15476
16500
  {
@@ -15480,7 +16504,7 @@ function OhhwellsBridge() {
15480
16504
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
15481
16505
  if (allowNavContainerHover) {
15482
16506
  const navContainer = target.closest("[data-ohw-nav-container]");
15483
- if (navContainer && !getNavigationItemAnchor(target)) {
16507
+ if (navContainer && !getNavigationItemAnchor(target) && !getLogoElement(target)) {
15484
16508
  hoveredNavContainerRef.current = navContainer;
15485
16509
  setHoveredNavContainerRect(navContainer.getBoundingClientRect());
15486
16510
  hoveredItemElRef.current = null;
@@ -15509,6 +16533,15 @@ function OhhwellsBridge() {
15509
16533
  setHoveredNavContainerRect(null);
15510
16534
  }
15511
16535
  }
16536
+ const logoEl = getLogoElement(target);
16537
+ if (logoEl) {
16538
+ hoveredNavContainerRef.current = null;
16539
+ setHoveredNavContainerRect(null);
16540
+ if (selectedElRef.current === logoEl) return;
16541
+ hoveredItemElRef.current = logoEl;
16542
+ setHoveredItemRect(getLogoInteractionRect(logoEl));
16543
+ return;
16544
+ }
15512
16545
  const navAnchor = getNavigationItemAnchor(target);
15513
16546
  if (navAnchor) {
15514
16547
  hoveredNavContainerRef.current = null;
@@ -15546,6 +16579,11 @@ function OhhwellsBridge() {
15546
16579
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
15547
16580
  } else if (!isInsideNavigationItem(editable)) {
15548
16581
  hoverTarget.setAttribute("data-ohw-hovered", "");
16582
+ if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
16583
+ hoveredNavContainerRef.current = null;
16584
+ setHoveredNavContainerRect(null);
16585
+ hoveredItemElRef.current = editable;
16586
+ }
15549
16587
  }
15550
16588
  }
15551
16589
  };
@@ -15581,6 +16619,18 @@ function OhhwellsBridge() {
15581
16619
  }
15582
16620
  return;
15583
16621
  }
16622
+ const logoEl = getLogoElement(target);
16623
+ if (logoEl) {
16624
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
16625
+ if (related2 && (logoEl === related2 || logoEl.contains(related2) || related2.closest?.('[data-ohw-role="logo"], [data-ohw-logo]'))) {
16626
+ return;
16627
+ }
16628
+ if (hoveredItemElRef.current === logoEl) {
16629
+ hoveredItemElRef.current = null;
16630
+ setHoveredItemRect(null);
16631
+ }
16632
+ return;
16633
+ }
15584
16634
  const editable = target.closest("[data-ohw-editable]");
15585
16635
  if (!editable) return;
15586
16636
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -15601,6 +16651,13 @@ function OhhwellsBridge() {
15601
16651
  }
15602
16652
  } else {
15603
16653
  hoverTarget.removeAttribute("data-ohw-hovered");
16654
+ if (hoveredItemElRef.current === editable) {
16655
+ const stillOnEditable = related instanceof Element && related.closest("[data-ohw-editable]") === editable;
16656
+ if (!stillOnEditable) {
16657
+ hoveredItemElRef.current = null;
16658
+ setHoveredItemRect(null);
16659
+ }
16660
+ }
15604
16661
  }
15605
16662
  }
15606
16663
  };
@@ -15717,6 +16774,26 @@ function OhhwellsBridge() {
15717
16774
  hoveredNavContainerRef.current = null;
15718
16775
  setHoveredNavContainerRect(null);
15719
16776
  }
16777
+ const logoCandidates = [
16778
+ ...document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]'),
16779
+ ...document.querySelectorAll("nav a:not([data-ohw-href-key]), [data-ohw-nav-root] a:not([data-ohw-href-key])"),
16780
+ ...document.querySelectorAll("footer img")
16781
+ ];
16782
+ const seenLogos = /* @__PURE__ */ new Set();
16783
+ for (const candidate of logoCandidates) {
16784
+ const logo = getLogoElement(candidate);
16785
+ if (!logo || seenLogos.has(logo)) continue;
16786
+ seenLogos.add(logo);
16787
+ const r2 = logo.getBoundingClientRect();
16788
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
16789
+ hoveredNavContainerRef.current = null;
16790
+ setHoveredNavContainerRect(null);
16791
+ if (selectedElRef.current !== logo) {
16792
+ hoveredItemElRef.current = logo;
16793
+ setHoveredItemRect(getLogoInteractionRect(logo));
16794
+ }
16795
+ return;
16796
+ }
15720
16797
  const navContainers = Array.from(
15721
16798
  document.querySelectorAll("[data-ohw-nav-container]")
15722
16799
  );
@@ -15802,7 +16879,7 @@ function OhhwellsBridge() {
15802
16879
  }
15803
16880
  };
15804
16881
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
15805
- if (linkPopoverOpenRef.current) {
16882
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
15806
16883
  if (hoveredImageRef.current) {
15807
16884
  hoveredImageRef.current = null;
15808
16885
  hoveredImageHasTextOverlapRef.current = false;
@@ -16056,7 +17133,7 @@ function OhhwellsBridge() {
16056
17133
  }
16057
17134
  };
16058
17135
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
16059
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17136
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
16060
17137
  if (activeStateElRef.current) {
16061
17138
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
16062
17139
  activeStateElRef.current = null;
@@ -16122,16 +17199,21 @@ function OhhwellsBridge() {
16122
17199
  setSectionGap(null);
16123
17200
  }
16124
17201
  };
16125
- const pointOwnedByFloatingPanel = (clientX, clientY) => {
16126
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return true;
16127
- const panel = document.querySelector("[data-ohw-floating-panel]");
16128
- if (!panel) return false;
16129
- const rect = panel.getBoundingClientRect();
16130
- return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
16131
- };
16132
17202
  const handleMouseMove = (e) => {
16133
17203
  const { clientX, clientY } = e;
16134
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17204
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17205
+ hoveredItemElRef.current = null;
17206
+ setHoveredItemRect(null);
17207
+ hoveredNavContainerRef.current = null;
17208
+ setHoveredNavContainerRect(null);
17209
+ siblingHintElRef.current = null;
17210
+ setSiblingHintRect(null);
17211
+ setSiblingHintRects([]);
17212
+ dismissImageHover();
17213
+ clearImageHover();
17214
+ setSectionGap(null);
17215
+ return;
17216
+ }
16135
17217
  probeSectionGapAt(clientX, clientY);
16136
17218
  probeImageAt(clientX, clientY);
16137
17219
  probeHoverCardsAt(clientX, clientY);
@@ -16140,7 +17222,11 @@ function OhhwellsBridge() {
16140
17222
  if (e.data?.type !== "ow:pointer-sync") return;
16141
17223
  const { clientX, clientY } = e.data;
16142
17224
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16143
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17225
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17226
+ dismissImageHover();
17227
+ clearImageHover();
17228
+ return;
17229
+ }
16144
17230
  probeSectionGapAt(clientX, clientY);
16145
17231
  probeImageAt(clientX, clientY);
16146
17232
  probeHoverCardsAt(clientX, clientY);
@@ -16390,6 +17476,15 @@ function OhhwellsBridge() {
16390
17476
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16391
17477
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16392
17478
  }
17479
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17480
+ brandKitRef.current = content[BRAND_KIT_KEY];
17481
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17482
+ }
17483
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17484
+ stylesRef.current = content[STYLE_STORE_KEY];
17485
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17486
+ }
17487
+ applyBrandChrome(content);
16393
17488
  let sectionsJson = null;
16394
17489
  for (const [key, val] of Object.entries(content)) {
16395
17490
  if (key === "__ohw_sections") {
@@ -16397,6 +17492,11 @@ function OhhwellsBridge() {
16397
17492
  continue;
16398
17493
  }
16399
17494
  if (key === AI_SECTIONS_KEY) continue;
17495
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
17496
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17497
+ if (key === BRAND_KIT_KEY) continue;
17498
+ if (key === STYLE_STORE_KEY) continue;
17499
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16400
17500
  if (applyVideoSettingNode(key, val)) continue;
16401
17501
  if (applyCarouselNode(key, val)) continue;
16402
17502
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16416,6 +17516,8 @@ function OhhwellsBridge() {
16416
17516
  });
16417
17517
  applyLinkByKey(key, val);
16418
17518
  }
17519
+ applyLogoFromContent(content);
17520
+ applyLogoSizes(content);
16419
17521
  if (sectionsJson) {
16420
17522
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
16421
17523
  sectionsLoadedRef.current = true;
@@ -16431,6 +17533,58 @@ function OhhwellsBridge() {
16431
17533
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
16432
17534
  postToParentRef.current({ type: "ow:hydrate-done" });
16433
17535
  };
17536
+ const handleUpdateLogoIdentity = (e) => {
17537
+ if (e.data?.type !== "ow:update-logo-identity") return;
17538
+ const rawText = typeof e.data.text === "string" ? e.data.text : "";
17539
+ const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17540
+ const href = typeof e.data.href === "string" ? e.data.href : void 0;
17541
+ const imageProvided = "image" in e.data;
17542
+ const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17543
+ let isPlaceholder = e.data.isPlaceholder !== false;
17544
+ if (imageUrl) isPlaceholder = false;
17545
+ else if (imageProvided && imageUrl === null) {
17546
+ isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17547
+ }
17548
+ const display = applyLogoIdentity(rawText, isPlaceholder);
17549
+ const displayAlt = resolveLogoDisplayText(alt || display);
17550
+ if (imageUrl !== void 0) {
17551
+ applyLogoImage(imageUrl, displayAlt);
17552
+ } else {
17553
+ for (const key of LOGO_IMAGE_KEYS) {
17554
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17555
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17556
+ if (img) img.alt = displayAlt;
17557
+ });
17558
+ }
17559
+ }
17560
+ if (href !== void 0) {
17561
+ applyLogoHref(href);
17562
+ applyLinkByKey("nav-logo-href", href);
17563
+ applyLinkByKey("footer-logo-href", href);
17564
+ applyLinkByKey("logo-href", href);
17565
+ }
17566
+ const nodes = [
17567
+ ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17568
+ { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17569
+ { key: LOGO_ALT_KEY, text: displayAlt }
17570
+ ];
17571
+ if (imageUrl !== void 0) {
17572
+ if (imageUrl) {
17573
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17574
+ } else {
17575
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17576
+ }
17577
+ }
17578
+ if (href !== void 0) {
17579
+ for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17580
+ }
17581
+ editContentRef.current = {
17582
+ ...editContentRef.current,
17583
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17584
+ };
17585
+ applyLogoSizes(editContentRef.current);
17586
+ postToParentRef.current({ type: "ow:change", nodes });
17587
+ };
16434
17588
  window.addEventListener("message", handleHydrate);
16435
17589
  const postAiSectionsChanged = () => {
16436
17590
  postToParentRef.current({
@@ -16444,7 +17598,10 @@ function OhhwellsBridge() {
16444
17598
  const payload = e.data.payload;
16445
17599
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
16446
17600
  const previous = aiSectionsRef.current;
16447
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
17601
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
17602
+ ...payload,
17603
+ path: payload.path ?? window.location.pathname
17604
+ });
16448
17605
  const nextValue = serializeAiSectionsState(nextState);
16449
17606
  aiSectionsRef.current = nextValue;
16450
17607
  applyAiSectionsToDom(nextState);
@@ -16481,12 +17638,42 @@ function OhhwellsBridge() {
16481
17638
  const value = typeof e.data.value === "string" ? e.data.value : "";
16482
17639
  aiSectionsRef.current = value;
16483
17640
  applyAiSectionsToDom(parseAiSectionsState(value));
17641
+ applyStylesToDom(parseStyleStore(stylesRef.current));
16484
17642
  const restoredHeight = document.documentElement.scrollHeight;
16485
17643
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
16486
17644
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
16487
17645
  postAiSectionsChanged();
16488
17646
  };
16489
17647
  window.addEventListener("message", handleAiSetSections);
17648
+ const handleAiSetBrand = (e) => {
17649
+ if (e.data?.type !== "ow:ai-set-brand") return;
17650
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17651
+ const previous = brandKitRef.current;
17652
+ brandKitRef.current = value;
17653
+ applyBrandToDom(parseBrandKit(value));
17654
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17655
+ applyStylesToDom(parseStyleStore(stylesRef.current));
17656
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17657
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17658
+ };
17659
+ window.addEventListener("message", handleAiSetBrand);
17660
+ const handleAiSetStyles = (e) => {
17661
+ if (e.data?.type !== "ow:ai-set-styles") return;
17662
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17663
+ const previous = stylesRef.current;
17664
+ stylesRef.current = value;
17665
+ applyStylesToDom(parseStyleStore(value));
17666
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
17667
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
17668
+ };
17669
+ window.addEventListener("message", handleAiSetStyles);
17670
+ const handleGetBrand = (e) => {
17671
+ if (e.data?.type !== "ow:get-brand") return;
17672
+ const template = deriveTemplateBrand();
17673
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
17674
+ postToParentRef.current({ type: "ow:brand-value", value });
17675
+ };
17676
+ window.addEventListener("message", handleGetBrand);
16490
17677
  const handleDeactivate = (e) => {
16491
17678
  if (e.data?.type !== "ow:deactivate") return;
16492
17679
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -16495,6 +17682,12 @@ function OhhwellsBridge() {
16495
17682
  closeLinkPopoverRef.current();
16496
17683
  return;
16497
17684
  }
17685
+ if (floatingPanelOpenRef.current) {
17686
+ setFloatingPanelRef.current(null);
17687
+ deselectRef.current();
17688
+ deactivateRef.current();
17689
+ return;
17690
+ }
16498
17691
  deselectRef.current();
16499
17692
  deactivateRef.current();
16500
17693
  };
@@ -16548,6 +17741,10 @@ function OhhwellsBridge() {
16548
17741
  return;
16549
17742
  }
16550
17743
  if (selectedElRef.current) {
17744
+ if (toolbarVariantRef.current === "logo") {
17745
+ deselectRef.current();
17746
+ return;
17747
+ }
16551
17748
  if (toolbarVariantRef.current === "select-frame") {
16552
17749
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16553
17750
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16587,6 +17784,10 @@ function OhhwellsBridge() {
16587
17784
  return;
16588
17785
  }
16589
17786
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17787
+ if (toolbarVariantRef.current === "logo") {
17788
+ deselectRef.current();
17789
+ return;
17790
+ }
16590
17791
  if (toolbarVariantRef.current === "select-frame") {
16591
17792
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16592
17793
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16664,7 +17865,8 @@ function OhhwellsBridge() {
16664
17865
  const handleScroll = () => {
16665
17866
  const focusEl = activeElRef.current ?? selectedElRef.current;
16666
17867
  if (focusEl) {
16667
- const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17868
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17869
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
16668
17870
  applyToolbarPos(r2);
16669
17871
  setToolbarRect(r2);
16670
17872
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -16674,7 +17876,9 @@ function OhhwellsBridge() {
16674
17876
  setToggleState((prev) => prev ? { ...prev, rect } : null);
16675
17877
  }
16676
17878
  if (hoveredItemElRef.current) {
16677
- setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17879
+ const hoverEl = hoveredItemElRef.current;
17880
+ const logo = getLogoElement(hoverEl);
17881
+ setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
16678
17882
  }
16679
17883
  if (hoveredNavContainerRef.current) {
16680
17884
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -16718,6 +17922,12 @@ function OhhwellsBridge() {
16718
17922
  if (aiSectionsRef.current) {
16719
17923
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
16720
17924
  }
17925
+ if (stylesRef.current) {
17926
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
17927
+ }
17928
+ if (brandKitRef.current) {
17929
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17930
+ }
16721
17931
  postToParentRef.current({ type: "ow:save-result", nodes });
16722
17932
  };
16723
17933
  const handleInsertSection = (e) => {
@@ -16728,8 +17938,12 @@ function OhhwellsBridge() {
16728
17938
  if (inserted) {
16729
17939
  const tracker = getSectionsTracker();
16730
17940
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
16731
- const h = document.documentElement.scrollHeight;
16732
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17941
+ const reportHeight = () => {
17942
+ const h = document.body.scrollHeight;
17943
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17944
+ };
17945
+ reportHeight();
17946
+ setTimeout(reportHeight, 500);
16733
17947
  }
16734
17948
  };
16735
17949
  const handleSwitchSchedule = (e) => {
@@ -16922,13 +18136,17 @@ function OhhwellsBridge() {
16922
18136
  if (e.data?.type !== "ow:parent-scroll") return;
16923
18137
  const { iframeOffsetTop, headerH, canvasH } = e.data;
16924
18138
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
18139
+ if (floatingPanelOpenRef.current) {
18140
+ setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
18141
+ }
16925
18142
  if (visibleViewportRef.current) {
16926
18143
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
16927
18144
  }
16928
18145
  const focusEl = activeElRef.current ?? selectedElRef.current;
16929
18146
  if (focusEl) {
16930
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
16931
- applyToolbarPos(measureEl.getBoundingClientRect());
18147
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
18148
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
18149
+ applyToolbarPos(r2);
16932
18150
  }
16933
18151
  };
16934
18152
  const handleClickAt = (e) => {
@@ -16953,6 +18171,25 @@ function OhhwellsBridge() {
16953
18171
  postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
16954
18172
  return;
16955
18173
  }
18174
+ const logoAtPoint = Array.from(
18175
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]')
18176
+ ).map((el) => getLogoElement(el)).find((logo) => {
18177
+ if (!logo) return false;
18178
+ const r2 = logo.getBoundingClientRect();
18179
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
18180
+ });
18181
+ if (logoAtPoint) {
18182
+ if (!logoHasUploadedImage(logoAtPoint)) {
18183
+ deselectRef.current();
18184
+ deactivateRef.current();
18185
+ const identity = readLogoIdentityFromDom();
18186
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
18187
+ return;
18188
+ }
18189
+ selectLogoRef.current(logoAtPoint);
18190
+ openLogoSizePanelRef.current(logoAtPoint);
18191
+ return;
18192
+ }
16956
18193
  const textEditable = Array.from(
16957
18194
  document.querySelectorAll(NON_MEDIA_SELECTOR)
16958
18195
  ).find((el) => {
@@ -17024,6 +18261,14 @@ function OhhwellsBridge() {
17024
18261
  window.addEventListener("message", handleParentScroll);
17025
18262
  window.addEventListener("message", handlePointerSync);
17026
18263
  window.addEventListener("message", handleClickAt);
18264
+ window.addEventListener("message", handleUpdateLogoIdentity);
18265
+ const handleViewMode = (e) => {
18266
+ if (e.data?.type !== "ow:view-mode") return;
18267
+ const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
18268
+ setEditorViewport(mode);
18269
+ applyLogoSizes(editContentRef.current);
18270
+ };
18271
+ window.addEventListener("message", handleViewMode);
17027
18272
  const handleViewportResize = () => {
17028
18273
  if (visibleViewportRef.current) {
17029
18274
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -17079,10 +18324,15 @@ function OhhwellsBridge() {
17079
18324
  window.removeEventListener("resize", handleViewportResize);
17080
18325
  window.removeEventListener("message", handlePointerSync);
17081
18326
  window.removeEventListener("message", handleClickAt);
18327
+ window.removeEventListener("message", handleUpdateLogoIdentity);
18328
+ window.removeEventListener("message", handleViewMode);
17082
18329
  window.removeEventListener("message", handleHydrate);
17083
18330
  window.removeEventListener("message", handleAiApplyTree);
17084
18331
  window.removeEventListener("message", handleAiDeleteSection);
17085
18332
  window.removeEventListener("message", handleAiSetSections);
18333
+ window.removeEventListener("message", handleAiSetBrand);
18334
+ window.removeEventListener("message", handleAiSetStyles);
18335
+ window.removeEventListener("message", handleGetBrand);
17086
18336
  window.removeEventListener("message", handleDeactivate);
17087
18337
  window.removeEventListener("message", handleToastAction);
17088
18338
  window.removeEventListener("message", handleUiEscape);
@@ -17286,7 +18536,7 @@ function OhhwellsBridge() {
17286
18536
  postToParent2({
17287
18537
  type: "ow:ready",
17288
18538
  version: "1",
17289
- bridgeVersion: "0.1.59",
18539
+ bridgeVersion: "0.1.61",
17290
18540
  path: pathname,
17291
18541
  nodes: collectEditableNodes(editContentRef.current),
17292
18542
  sections
@@ -17681,10 +18931,10 @@ function OhhwellsBridge() {
17681
18931
  [postToParent2]
17682
18932
  );
17683
18933
  return bridgeRoot ? (0, import_react_dom4.createPortal)(
17684
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17685
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
17686
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
17687
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18934
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
18935
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
18936
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
18937
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17688
18938
  MediaOverlay,
17689
18939
  {
17690
18940
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -17695,7 +18945,7 @@ function OhhwellsBridge() {
17695
18945
  },
17696
18946
  `uploading-${key}`
17697
18947
  )),
17698
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18948
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17699
18949
  MediaOverlay,
17700
18950
  {
17701
18951
  hover: mediaHover,
@@ -17704,11 +18954,11 @@ function OhhwellsBridge() {
17704
18954
  onVideoSettingsChange: handleVideoSettingsChange
17705
18955
  }
17706
18956
  ),
17707
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
17708
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
17709
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
17710
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
17711
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18957
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
18958
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
18959
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
18960
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
18961
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17712
18962
  "div",
17713
18963
  {
17714
18964
  className: "pointer-events-none fixed z-2147483646",
@@ -17718,7 +18968,7 @@ function OhhwellsBridge() {
17718
18968
  width: slot.width,
17719
18969
  height: slot.height
17720
18970
  },
17721
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18971
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17722
18972
  DropIndicator,
17723
18973
  {
17724
18974
  direction: slot.direction,
@@ -17729,7 +18979,7 @@ function OhhwellsBridge() {
17729
18979
  },
17730
18980
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
17731
18981
  )),
17732
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18982
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17733
18983
  "div",
17734
18984
  {
17735
18985
  className: "pointer-events-none fixed z-2147483646",
@@ -17739,7 +18989,7 @@ function OhhwellsBridge() {
17739
18989
  width: slot.width,
17740
18990
  height: slot.height
17741
18991
  },
17742
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18992
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17743
18993
  DropIndicator,
17744
18994
  {
17745
18995
  direction: slot.direction,
@@ -17750,11 +19000,11 @@ function OhhwellsBridge() {
17750
19000
  },
17751
19001
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
17752
19002
  )),
17753
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17754
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17755
- hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
17756
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17757
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19003
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
19004
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
19005
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
19006
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
19007
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17758
19008
  FooterContainerChrome,
17759
19009
  {
17760
19010
  rect: toolbarRect,
@@ -17762,7 +19012,7 @@ function OhhwellsBridge() {
17762
19012
  addDisabled: !canAddFooterColumn()
17763
19013
  }
17764
19014
  ),
17765
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19015
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17766
19016
  ItemInteractionLayer,
17767
19017
  {
17768
19018
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -17774,10 +19024,10 @@ function OhhwellsBridge() {
17774
19024
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
17775
19025
  onDragHandleDragStart: handleItemDragStart,
17776
19026
  onDragHandleDragEnd: handleItemDragEnd,
17777
- onItemPointerDown: handleItemChromePointerDown,
17778
- onItemClick: handleItemChromeClick,
17779
- itemDragSurface: !isFooterFrameSelection,
17780
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19027
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19028
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19029
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19030
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17781
19031
  ItemActionToolbar,
17782
19032
  {
17783
19033
  onEditLink: openLinkPopoverForSelected,
@@ -17813,8 +19063,8 @@ function OhhwellsBridge() {
17813
19063
  ) : void 0
17814
19064
  }
17815
19065
  ),
17816
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17817
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19066
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
19067
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17818
19068
  EditGlowChrome,
17819
19069
  {
17820
19070
  rect: toolbarRect,
@@ -17824,7 +19074,7 @@ function OhhwellsBridge() {
17824
19074
  hideHandle: isItemDragging
17825
19075
  }
17826
19076
  ),
17827
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19077
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17828
19078
  FloatingToolbar,
17829
19079
  {
17830
19080
  rect: toolbarRect,
@@ -17837,7 +19087,7 @@ function OhhwellsBridge() {
17837
19087
  }
17838
19088
  )
17839
19089
  ] }),
17840
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
19090
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17841
19091
  "div",
17842
19092
  {
17843
19093
  "data-ohw-max-badge": "",
@@ -17863,7 +19113,7 @@ function OhhwellsBridge() {
17863
19113
  ]
17864
19114
  }
17865
19115
  ),
17866
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19116
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17867
19117
  StateToggle,
17868
19118
  {
17869
19119
  rect: toggleState.rect,
@@ -17872,15 +19122,15 @@ function OhhwellsBridge() {
17872
19122
  onStateChange: handleStateChange
17873
19123
  }
17874
19124
  ),
17875
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
19125
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17876
19126
  "div",
17877
19127
  {
17878
19128
  "data-ohw-section-insert-line": "",
17879
19129
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
17880
19130
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
17881
19131
  children: [
17882
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
17883
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19132
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
19133
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17884
19134
  Badge,
17885
19135
  {
17886
19136
  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",
@@ -17897,11 +19147,11 @@ function OhhwellsBridge() {
17897
19147
  children: "Add Section"
17898
19148
  }
17899
19149
  ),
17900
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
19150
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
17901
19151
  ]
17902
19152
  }
17903
19153
  ),
17904
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19154
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17905
19155
  LinkPopover,
17906
19156
  {
17907
19157
  panelRef: linkPopoverPanelRef,
@@ -17918,7 +19168,7 @@ function OhhwellsBridge() {
17918
19168
  },
17919
19169
  linkPopover.key
17920
19170
  ) : null,
17921
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19171
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17922
19172
  FloatingPanel,
17923
19173
  {
17924
19174
  open: true,
@@ -17928,7 +19178,7 @@ function OhhwellsBridge() {
17928
19178
  onPositionChange: setFloatingPanelPos,
17929
19179
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
17930
19180
  onClose: closeFloatingPanelOnly,
17931
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19181
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17932
19182
  SocialsDisplayPanel,
17933
19183
  {
17934
19184
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -17939,11 +19189,115 @@ function OhhwellsBridge() {
17939
19189
  }
17940
19190
  )
17941
19191
  }
19192
+ ) : null,
19193
+ floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19194
+ FloatingPanel,
19195
+ {
19196
+ open: true,
19197
+ title: floatingPanel.title,
19198
+ context: floatingPanel.context,
19199
+ position: floatingPanelPos,
19200
+ onPositionChange: setFloatingPanelPos,
19201
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
19202
+ onClose: closeFloatingPanelAndDeselect,
19203
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19204
+ LogoSizePanel,
19205
+ {
19206
+ viewport: editorViewport,
19207
+ sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
19208
+ mobileFollowing: logoSizeDraft.mobileFollowing,
19209
+ onSizeChange: (px) => {
19210
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
19211
+ ...logoSizeDraft,
19212
+ desktopPx: px,
19213
+ mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
19214
+ };
19215
+ setLogoSizeDraft(next);
19216
+ persistLogoSizeDraft(floatingPanel.placement, next);
19217
+ },
19218
+ onCustomizeMobile: () => {
19219
+ const next = {
19220
+ ...logoSizeDraft,
19221
+ mobileFollowing: false,
19222
+ mobilePx: logoSizeDraft.desktopPx
19223
+ };
19224
+ setLogoSizeDraft(next);
19225
+ persistLogoSizeDraft(floatingPanel.placement, next);
19226
+ },
19227
+ onResetMobile: () => {
19228
+ const next = {
19229
+ ...logoSizeDraft,
19230
+ mobileFollowing: true,
19231
+ mobilePx: logoSizeDraft.desktopPx
19232
+ };
19233
+ setLogoSizeDraft(next);
19234
+ persistLogoSizeDraft(floatingPanel.placement, next);
19235
+ },
19236
+ onUpdateEverywhere: () => {
19237
+ const identity = readLogoIdentityFromDom();
19238
+ postToParent2({ type: "ow:open-logo-settings", ...identity });
19239
+ }
19240
+ }
19241
+ )
19242
+ }
17942
19243
  ) : null
17943
19244
  ] }),
17944
19245
  bridgeRoot
17945
19246
  ) : null;
17946
19247
  }
19248
+
19249
+ // src/ui/EmptySection.tsx
19250
+ var import_link = __toESM(require("next/link"), 1);
19251
+ var import_jsx_runtime34 = require("react/jsx-runtime");
19252
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
19253
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
19254
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19255
+ "p",
19256
+ {
19257
+ style: {
19258
+ fontFamily: "var(--brand-font-body)",
19259
+ fontSize: "0.75rem",
19260
+ fontWeight: 500,
19261
+ letterSpacing: "0.15em",
19262
+ textTransform: "uppercase",
19263
+ color: "var(--brand-accent)",
19264
+ marginBottom: "1.5rem"
19265
+ },
19266
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
19267
+ }
19268
+ ),
19269
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19270
+ "h1",
19271
+ {
19272
+ style: {
19273
+ fontFamily: "var(--brand-font-heading)",
19274
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
19275
+ lineHeight: 1.1,
19276
+ letterSpacing: "-0.025em",
19277
+ color: "var(--brand-text)",
19278
+ marginBottom: "1rem"
19279
+ },
19280
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
19281
+ children: title
19282
+ }
19283
+ ),
19284
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19285
+ "p",
19286
+ {
19287
+ style: {
19288
+ fontFamily: "var(--brand-font-body)",
19289
+ fontSize: "1rem",
19290
+ lineHeight: 1.7,
19291
+ fontWeight: 300,
19292
+ color: "var(--brand-text-muted)",
19293
+ maxWidth: "340px"
19294
+ },
19295
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19296
+ children: "This page doesn't have any content yet."
19297
+ }
19298
+ )
19299
+ ] });
19300
+ }
17947
19301
  // Annotate the CommonJS export names for ESM import in node:
17948
19302
  0 && (module.exports = {
17949
19303
  AI_DEFAULT_BRAND,
@@ -17961,6 +19315,7 @@ function OhhwellsBridge() {
17961
19315
  DropdownMenuItem,
17962
19316
  DropdownMenuSeparator,
17963
19317
  DropdownMenuTrigger,
19318
+ EmptySection,
17964
19319
  ItemActionToolbar,
17965
19320
  ItemInteractionLayer,
17966
19321
  LinkEditorPanel,