@ohhwells/bridge 0.1.60 → 0.1.61-next.173

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,287 @@ 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
+ var NODE_PROPS = ["color", "font-size", "background"];
384
+ function saveInline(el, prop) {
385
+ const attr = `data-ohw-style-prev-${prop}`;
386
+ if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
387
+ }
388
+ function restoreInline(el, prop) {
389
+ const attr = `data-ohw-style-prev-${prop}`;
390
+ if (!el.hasAttribute(attr)) return;
391
+ const prev = el.getAttribute(attr) ?? "";
392
+ if (prev) el.style.setProperty(prop, prev);
393
+ else el.style.removeProperty(prop);
394
+ el.removeAttribute(attr);
395
+ }
396
+ function ensureStyleSheet() {
397
+ let el = document.getElementById(STYLE_SHEET_ID);
398
+ if (!el) {
399
+ el = document.createElement("style");
400
+ el.id = STYLE_SHEET_ID;
401
+ document.head.appendChild(el);
402
+ }
403
+ const css = styleSheetCss();
404
+ if (el.textContent !== css) el.textContent = css;
405
+ }
406
+ function clearSectionAttrs(root) {
407
+ for (const attr of Object.values(SECTION_ATTRS)) {
408
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
409
+ }
410
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
411
+ restoreInline(el, "background");
412
+ el.removeAttribute("data-ohw-style-bgcolor");
413
+ }
414
+ }
415
+ function clearNodeProps(root) {
416
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
417
+ const h = el;
418
+ for (const prop of NODE_PROPS) restoreInline(h, prop);
419
+ h.removeAttribute(NODE_WROTE_ATTR);
420
+ }
421
+ }
422
+ function buttonSurfaceOf(el) {
423
+ return el.closest("a, button") ?? el;
424
+ }
425
+ function applyStylesToDom(store) {
426
+ ensureStyleSheet();
427
+ clearSectionAttrs(document);
428
+ clearNodeProps(document);
429
+ if (!store) return;
430
+ for (const [sectionId, override] of Object.entries(store.sections)) {
431
+ const sections = document.querySelectorAll(
432
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
433
+ );
434
+ for (const section of Array.from(sections)) {
435
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
436
+ const value = override[prop];
437
+ if (value === void 0) continue;
438
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
439
+ section.setAttribute(attr, String(value).replace(":", "-"));
440
+ }
441
+ if (override.sectionBackgroundColor !== void 0) {
442
+ saveInline(section, "background");
443
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
444
+ section.setAttribute("data-ohw-style-bgcolor", "");
445
+ }
446
+ }
447
+ }
448
+ for (const [key, override] of Object.entries(store.nodes)) {
449
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
450
+ for (const el of Array.from(nodes)) {
451
+ if (override.color !== void 0) {
452
+ saveInline(el, "color");
453
+ el.style.setProperty("color", override.color, "important");
454
+ el.setAttribute(NODE_WROTE_ATTR, "");
455
+ }
456
+ if (override.fontSize !== void 0) {
457
+ saveInline(el, "font-size");
458
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
459
+ el.setAttribute(NODE_WROTE_ATTR, "");
460
+ }
461
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
462
+ const surface = buttonSurfaceOf(el);
463
+ if (override.buttonBackground !== void 0) {
464
+ saveInline(surface, "background");
465
+ surface.style.setProperty("background", override.buttonBackground, "important");
466
+ }
467
+ if (override.buttonText !== void 0) {
468
+ saveInline(surface, "color");
469
+ surface.style.setProperty("color", override.buttonText, "important");
470
+ }
471
+ surface.setAttribute(NODE_WROTE_ATTR, "");
472
+ }
473
+ }
474
+ }
475
+ }
476
+
194
477
  // src/ui/ai-tree/aiSectionsManager.tsx
195
478
  var import_react_dom = require("react-dom");
196
479
  var import_client = require("react-dom/client");
@@ -205,7 +488,8 @@ function lucideByName(name) {
205
488
  }
206
489
  var typeStyle = (spec, font) => ({
207
490
  fontFamily: font,
208
- fontSize: spec.size,
491
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
492
+ 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
493
  lineHeight: spec.line,
210
494
  fontWeight: spec.weight
211
495
  });
@@ -232,6 +516,8 @@ var AI_RESPONSIVE_CSS = [
232
516
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
233
517
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
234
518
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
519
+ " [data-ai-responsive] { overflow-x: hidden; }",
520
+ " [data-ai-responsive] img { max-width: 100%; }",
235
521
  "}"
236
522
  ].join("\n");
237
523
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -1239,6 +1525,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1239
1525
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1240
1526
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1241
1527
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1528
+ const toneBackground = (() => {
1529
+ const { dark, primary, light } = resolvedBrand.palette;
1530
+ switch (settings.sectionBackground) {
1531
+ case "surface":
1532
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1533
+ case "accent":
1534
+ return primary;
1535
+ case "accent-soft":
1536
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1537
+ default:
1538
+ return void 0;
1539
+ }
1540
+ })();
1541
+ const distributed = !isOverlay && settings.textDistribution;
1242
1542
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1243
1543
  "section",
1244
1544
  {
@@ -1248,10 +1548,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1248
1548
  style: {
1249
1549
  position: "relative",
1250
1550
  padding: `${pad}px 0`,
1251
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1551
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1252
1552
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1253
1553
  backgroundSize: "cover",
1254
- backgroundPosition: "center"
1554
+ backgroundPosition: "center",
1555
+ color: settings.sectionBackground === "accent" ? resolvedBrand.palette.light : void 0
1255
1556
  },
1256
1557
  children: [
1257
1558
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
@@ -1275,10 +1576,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1275
1576
  display: "grid",
1276
1577
  gridTemplateColumns: "repeat(12, 1fr)",
1277
1578
  gap: AI_TREE_TOKENS.spacing6,
1278
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1579
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1279
1580
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1280
1581
  },
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))
1582
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1583
+ "div",
1584
+ {
1585
+ "data-ai-cell": "",
1586
+ style: {
1587
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1588
+ minWidth: 0,
1589
+ // space-between: each column becomes a flex column whose content spreads over
1590
+ // the full row height instead of clumping at the top.
1591
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1592
+ },
1593
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1594
+ },
1595
+ b
1596
+ ))
1282
1597
  },
1283
1598
  r2
1284
1599
  ))
@@ -1294,17 +1609,34 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
1294
1609
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1295
1610
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1296
1611
  var REMOVED_ATTR = "data-ohw-ai-removed";
1612
+ function readRootVar(name) {
1613
+ if (typeof document === "undefined") return "";
1614
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
1615
+ }
1616
+ function deriveBrandOverride() {
1617
+ const dark = readRootVar("--ohw-brand-dark");
1618
+ const primary = readRootVar("--ohw-brand-primary");
1619
+ const light = readRootVar("--ohw-brand-light");
1620
+ if (!dark || !primary || !light) return null;
1621
+ const accent = readRootVar("--ohw-brand-accent");
1622
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1623
+ const body = readRootVar("--font-body");
1624
+ return {
1625
+ palette: { dark, primary, accent: accent || dark, light },
1626
+ fonts: {
1627
+ heading: heading || AI_DEFAULT_BRAND.fonts.heading,
1628
+ body: body || AI_DEFAULT_BRAND.fonts.body
1629
+ }
1630
+ };
1631
+ }
1297
1632
  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");
1633
+ const dark = readRootVar("--color-dark");
1634
+ const primary = readRootVar("--color-primary");
1635
+ const light = readRootVar("--color-light");
1304
1636
  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");
1637
+ const accent = readRootVar("--color-accent");
1638
+ const heading = readRootVar("--font-heading") || readRootVar("--font-display");
1639
+ const body = readRootVar("--font-body");
1308
1640
  return {
1309
1641
  palette: { dark, primary, accent: accent || dark, light },
1310
1642
  fonts: {
@@ -1396,8 +1728,12 @@ function syncReplacedOriginals(state) {
1396
1728
  }
1397
1729
  function applyAiSectionsToDom(state, options) {
1398
1730
  if (typeof document === "undefined") return;
1731
+ const brandOverride = deriveBrandOverride();
1399
1732
  const templateBrand = deriveTemplateBrand();
1400
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1733
+ const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1734
+ const pagePath = window.location.pathname;
1735
+ const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1736
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1401
1737
  for (const [id, section] of mounted) {
1402
1738
  if (!activeIds.has(id)) {
1403
1739
  section.root.unmount();
@@ -1405,8 +1741,8 @@ function applyAiSectionsToDom(state, options) {
1405
1741
  mounted.delete(id);
1406
1742
  }
1407
1743
  }
1408
- for (const entry of state.sections) {
1409
- const serialized = JSON.stringify(entry);
1744
+ for (const entry of pageSections) {
1745
+ const serialized = JSON.stringify(entry) + brandKey;
1410
1746
  const existing = mounted.get(entry.id);
1411
1747
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
1412
1748
  continue;
@@ -1420,6 +1756,7 @@ function applyAiSectionsToDom(state, options) {
1420
1756
  mounted.delete(entry.id);
1421
1757
  }
1422
1758
  container.setAttribute("data-ohw-section", entry.id);
1759
+ container.setAttribute("data-ohw-instance", entry.id);
1423
1760
  container.setAttribute("data-ohw-section-label", entry.label);
1424
1761
  placeContainer(container, entry);
1425
1762
  const root = mounted.get(entry.id)?.root ?? (0, import_client.createRoot)(container);
@@ -1430,7 +1767,7 @@ function applyAiSectionsToDom(state, options) {
1430
1767
  AiTreeRenderer,
1431
1768
  {
1432
1769
  tree: entry.tree,
1433
- brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1770
+ brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
1434
1771
  resolveMedia,
1435
1772
  editKeyPrefix: `ai.${entry.id}`
1436
1773
  }
@@ -2047,7 +2384,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2047
2384
  const autoId = (0, import_react5.useId)();
2048
2385
  const insertAfter = insertAfterProp ?? autoId;
2049
2386
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
2050
- const [loading, setLoading] = (0, import_react5.useState)(true);
2387
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
2051
2388
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
2052
2389
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
2053
2390
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2221,8 +2558,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2221
2558
  "*"
2222
2559
  );
2223
2560
  };
2224
- if (!inEditor && !loading && !schedule) return null;
2225
2561
  const sectionId = `scheduling-${insertAfter}`;
2562
+ if (!inEditor && !loading && !schedule) {
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2564
+ }
2226
2565
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2227
2566
  "section",
2228
2567
  {
@@ -6741,8 +7080,12 @@ function parseSectionsFromHtml(html) {
6741
7080
 
6742
7081
  // src/ui/ai-section/AiSectionOverlay.tsx
6743
7082
  var import_jsx_runtime16 = require("react/jsx-runtime");
6744
- function readRect(sectionId) {
6745
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7083
+ function findSectionElement(instanceId) {
7084
+ const escaped = CSS.escape(instanceId);
7085
+ return document.querySelector(`[data-ohw-instance="${escaped}"]`) ?? document.querySelector(`[data-ohw-section="${escaped}"]:not([data-ohw-instance])`);
7086
+ }
7087
+ function readRect(instanceId) {
7088
+ const el = findSectionElement(instanceId);
6746
7089
  if (!el) return null;
6747
7090
  const r2 = el.getBoundingClientRect();
6748
7091
  if (r2.width <= 0 || r2.height <= 0) return null;
@@ -6765,7 +7108,7 @@ function useLiveSectionRect(sectionId) {
6765
7108
  const opts = { capture: true, passive: true };
6766
7109
  window.addEventListener("scroll", update, opts);
6767
7110
  window.addEventListener("resize", update);
6768
- const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
7111
+ const el = findSectionElement(sectionId);
6769
7112
  const ro = el ? new ResizeObserver(update) : null;
6770
7113
  if (el && ro) ro.observe(el);
6771
7114
  const interval = setInterval(update, 500);
@@ -6778,6 +7121,14 @@ function useLiveSectionRect(sectionId) {
6778
7121
  }, [sectionId]);
6779
7122
  return rect;
6780
7123
  }
7124
+ function computeSectionBoundaryFlags(instanceId) {
7125
+ const topLevel = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
7126
+ (el) => !el.parentElement?.closest("[data-ohw-section]")
7127
+ );
7128
+ const index = topLevel.findIndex((el) => (el.dataset.ohwInstance ?? el.dataset.ohwSection) === instanceId);
7129
+ if (index === -1) return { isFirst: true, isLast: true };
7130
+ return { isFirst: index === 0, isLast: index === topLevel.length - 1 };
7131
+ }
6781
7132
  var PRIMARY2 = "#0885FE";
6782
7133
  function edgeAwareRadius(rect) {
6783
7134
  const container = window.innerWidth <= 480 ? 16 : 24;
@@ -6851,6 +7202,7 @@ function AiSectionOverlay({
6851
7202
  }) {
6852
7203
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
6853
7204
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
7205
+ const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
6854
7206
  const reviewIdRef = (0, import_react8.useRef)(null);
6855
7207
  reviewIdRef.current = reviewId;
6856
7208
  const selectedIdRef = (0, import_react8.useRef)(null);
@@ -6859,7 +7211,7 @@ function AiSectionOverlay({
6859
7211
  (el) => {
6860
7212
  postToParent2({
6861
7213
  type: "ow:section-selected",
6862
- sectionId: el?.dataset.ohwSection ?? null,
7214
+ sectionId: el ? el.dataset.ohwInstance ?? el.dataset.ohwSection ?? null : null,
6863
7215
  sectionLabel: el ? el.dataset.ohwSectionLabel ?? titleCaseSectionId(el.dataset.ohwSection ?? "") : null
6864
7216
  });
6865
7217
  },
@@ -6868,7 +7220,7 @@ function AiSectionOverlay({
6868
7220
  const selectFromElement = (0, import_react8.useCallback)(
6869
7221
  (el, options) => {
6870
7222
  const sectionEl = el?.closest("[data-ohw-section]") ?? null;
6871
- const id = sectionEl?.dataset.ohwSection ?? null;
7223
+ const id = sectionEl ? sectionEl.dataset.ohwInstance ?? sectionEl.dataset.ohwSection ?? null : null;
6872
7224
  if (id === selectedIdRef.current) return;
6873
7225
  setSelectedId(id);
6874
7226
  if (options?.report !== false) report(sectionEl);
@@ -6909,9 +7261,10 @@ function AiSectionOverlay({
6909
7261
  }
6910
7262
  const found = readRect(sectionId) != null;
6911
7263
  setReviewId(found ? sectionId : null);
7264
+ setReviewButtonsHidden(e.data.hideButtons === true);
6912
7265
  postToParent2({ type: "ow:ai-review-started", sectionId, found });
6913
7266
  if (found) {
6914
- document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
7267
+ document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
6915
7268
  }
6916
7269
  }
6917
7270
  };
@@ -6930,7 +7283,7 @@ function AiSectionOverlay({
6930
7283
  return;
6931
7284
  }
6932
7285
  const sec = t.closest("[data-ohw-section]");
6933
- setHoveredId(sec?.dataset.ohwSection ?? null);
7286
+ setHoveredId(sec ? sec.dataset.ohwInstance ?? sec.dataset.ohwSection ?? null : null);
6934
7287
  };
6935
7288
  const onLeave = () => setHoveredId(null);
6936
7289
  document.addEventListener("mousemove", onMove, { passive: true });
@@ -6962,9 +7315,29 @@ function AiSectionOverlay({
6962
7315
  },
6963
7316
  [postToParent2]
6964
7317
  );
6965
- const selectionRect = useLiveSectionRect(reviewId ? null : selectedId);
7318
+ const activeSelectionId = reviewId ? null : selectedId;
7319
+ const selectionRect = useLiveSectionRect(activeSelectionId);
6966
7320
  const reviewRect = useLiveSectionRect(reviewId);
6967
7321
  const hoverRect = useLiveSectionRect(reviewId || hoveredId === selectedId ? null : hoveredId);
7322
+ (0, import_react8.useEffect)(() => {
7323
+ if (!activeSelectionId || !selectionRect) {
7324
+ postToParent2({ type: "ow:section-rect", instanceId: null, rect: null });
7325
+ return;
7326
+ }
7327
+ const { isFirst, isLast } = computeSectionBoundaryFlags(activeSelectionId);
7328
+ postToParent2({
7329
+ type: "ow:section-rect",
7330
+ instanceId: activeSelectionId,
7331
+ rect: {
7332
+ top: selectionRect.top + window.scrollY,
7333
+ left: selectionRect.left + window.scrollX,
7334
+ width: selectionRect.width,
7335
+ height: selectionRect.height
7336
+ },
7337
+ isFirst,
7338
+ isLast
7339
+ });
7340
+ }, [activeSelectionId, selectionRect, postToParent2]);
6968
7341
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
6969
7342
  hoverRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6970
7343
  "div",
@@ -7015,13 +7388,16 @@ function AiSectionOverlay({
7015
7388
  border: `2px solid ${PRIMARY2}`,
7016
7389
  borderRadius: edgeAwareRadius(reviewRect),
7017
7390
  zIndex: 2147483200,
7018
- // The veil itself: swallows clicks so the section stays locked until decided.
7391
+ // The veil itself: swallows clicks so the section stays locked until decided. This
7392
+ // stopPropagation only guards the bubble phase; the bridge's capture-phase click
7393
+ // handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
7394
+ // Accept/Discard resolves to the media beneath and opens the file picker.
7019
7395
  background: "rgba(8, 133, 254, 0.04)",
7020
7396
  pointerEvents: "auto",
7021
7397
  cursor: "default"
7022
7398
  },
7023
7399
  onClick: (e) => e.stopPropagation(),
7024
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7400
+ children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7025
7401
  "div",
7026
7402
  {
7027
7403
  style: {
@@ -10921,6 +11297,329 @@ function deleteFooterColumn(column) {
10921
11297
  };
10922
11298
  }
10923
11299
 
11300
+ // src/lib/logo-identity.ts
11301
+ var LOGO_TEXT_KEYS = ["nav-logo-text", "footer-logo-text", "logo-text"];
11302
+ var LOGO_IMAGE_KEYS = ["nav-logo-image", "footer-logo", "footer-logo-image"];
11303
+ var LOGO_HREF_KEYS = ["nav-logo-href", "footer-logo-href", "logo-href"];
11304
+ var LOGO_PLACEHOLDER_KEY = "logo-is-placeholder";
11305
+ var LOGO_ALT_KEY = "logo-alt";
11306
+ var LOGO_IMAGE_URL_KEY = "nav-logo-image";
11307
+ var PLACEHOLDER_BUSINESS_NAME = "Business name";
11308
+ function resolveLogoDisplayText(text) {
11309
+ const trimmed = (text ?? "").trim();
11310
+ return trimmed || PLACEHOLDER_BUSINESS_NAME;
11311
+ }
11312
+ function isFooterLogoRoot(root) {
11313
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11314
+ }
11315
+ function imageKeyForRoot(root) {
11316
+ return isFooterLogoRoot(root) ? "footer-logo" : "nav-logo-image";
11317
+ }
11318
+ function textKeyForRoot(root) {
11319
+ return isFooterLogoRoot(root) ? "footer-logo-text" : "nav-logo-text";
11320
+ }
11321
+ function ensureLogoHrefKey(root) {
11322
+ if (!(root instanceof HTMLAnchorElement)) return;
11323
+ if (root.hasAttribute("data-ohw-href-key")) return;
11324
+ root.setAttribute("data-ohw-href-key", isFooterLogoRoot(root) ? "footer-logo-href" : "nav-logo-href");
11325
+ }
11326
+ function applyLogoIdentity(text, isPlaceholder) {
11327
+ const display = resolveLogoDisplayText(text);
11328
+ const placeholder = isPlaceholder || !text.trim() || display === PLACEHOLDER_BUSINESS_NAME;
11329
+ for (const key of LOGO_TEXT_KEYS) {
11330
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11331
+ if (el.textContent !== display) el.textContent = display;
11332
+ });
11333
+ }
11334
+ for (const key of LOGO_IMAGE_KEYS) {
11335
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11336
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
11337
+ if (img) img.alt = display;
11338
+ });
11339
+ }
11340
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((el) => {
11341
+ if (placeholder) el.setAttribute("data-ohw-placeholder", "");
11342
+ else el.removeAttribute("data-ohw-placeholder");
11343
+ });
11344
+ return display;
11345
+ }
11346
+ function applyLogoImage(url, alt) {
11347
+ const displayAlt = resolveLogoDisplayText(alt);
11348
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11349
+ ensureLogoHrefKey(root);
11350
+ const imageKey = imageKeyForRoot(root);
11351
+ const textKey = textKeyForRoot(root);
11352
+ 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");
11353
+ let textEl = root.querySelector(`[data-ohw-key="${textKey}"]`) ?? root.querySelector('[data-ohw-key="logo-text"]');
11354
+ if (url) {
11355
+ if (!img) {
11356
+ img = document.createElement("img");
11357
+ img.setAttribute("data-ohw-editable", "image");
11358
+ img.setAttribute("data-ohw-key", imageKey);
11359
+ img.alt = displayAlt;
11360
+ img.style.height = "";
11361
+ img.style.maxHeight = "none";
11362
+ img.style.width = "auto";
11363
+ img.style.display = "block";
11364
+ img.style.objectFit = "contain";
11365
+ root.insertBefore(img, root.firstChild);
11366
+ } else {
11367
+ img.setAttribute("data-ohw-editable", "image");
11368
+ img.setAttribute("data-ohw-key", imageKey);
11369
+ }
11370
+ img.removeAttribute("srcset");
11371
+ img.removeAttribute("sizes");
11372
+ img.src = url;
11373
+ img.alt = displayAlt;
11374
+ img.style.display = "block";
11375
+ if (textEl) textEl.style.display = "none";
11376
+ root.removeAttribute("data-ohw-placeholder");
11377
+ return;
11378
+ }
11379
+ if (img) {
11380
+ img.removeAttribute("src");
11381
+ img.removeAttribute("srcset");
11382
+ img.removeAttribute("sizes");
11383
+ img.alt = displayAlt;
11384
+ img.style.display = "none";
11385
+ }
11386
+ if (!textEl) {
11387
+ textEl = document.createElement("span");
11388
+ textEl.setAttribute("data-ohw-editable", "plain");
11389
+ textEl.setAttribute("data-ohw-key", textKey);
11390
+ root.appendChild(textEl);
11391
+ }
11392
+ textEl.style.display = "";
11393
+ if (textEl.textContent !== displayAlt) textEl.textContent = displayAlt;
11394
+ if (!displayAlt.trim() || displayAlt === PLACEHOLDER_BUSINESS_NAME) {
11395
+ root.setAttribute("data-ohw-placeholder", "");
11396
+ } else {
11397
+ root.removeAttribute("data-ohw-placeholder");
11398
+ }
11399
+ });
11400
+ }
11401
+ function applyLogoHref(href) {
11402
+ const target = href.trim() || "/";
11403
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11404
+ ensureLogoHrefKey(root);
11405
+ if (root instanceof HTMLAnchorElement) {
11406
+ root.setAttribute("href", target);
11407
+ }
11408
+ });
11409
+ for (const key of LOGO_HREF_KEYS) setStoredLinkHref(key, target);
11410
+ }
11411
+ function readLogoIdentityFromDom() {
11412
+ let imageUrl = null;
11413
+ for (const key of LOGO_IMAGE_KEYS) {
11414
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11415
+ const img = el instanceof HTMLImageElement ? el : el?.querySelector("img");
11416
+ const attrSrc = img?.getAttribute("src")?.trim() ?? "";
11417
+ if (attrSrc && !attrSrc.startsWith("data:") && img && img.style.display !== "none") {
11418
+ imageUrl = img.currentSrc || img.src;
11419
+ break;
11420
+ }
11421
+ }
11422
+ let text = PLACEHOLDER_BUSINESS_NAME;
11423
+ let isPlaceholder = true;
11424
+ for (const key of LOGO_TEXT_KEYS) {
11425
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
11426
+ if (el?.textContent?.trim()) {
11427
+ text = el.textContent.trim();
11428
+ const logoRoot2 = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11429
+ isPlaceholder = logoRoot2?.hasAttribute("data-ohw-placeholder") === true || text === PLACEHOLDER_BUSINESS_NAME;
11430
+ break;
11431
+ }
11432
+ }
11433
+ if (imageUrl) {
11434
+ const logoImg = document.querySelector(
11435
+ '[data-ohw-key="nav-logo-image"], [data-ohw-key="footer-logo"]'
11436
+ );
11437
+ const alt = logoImg?.alt?.trim() || text;
11438
+ isPlaceholder = false;
11439
+ const hrefEl = document.querySelector(
11440
+ 'a[data-ohw-role="logo"], a[data-ohw-logo], [data-ohw-role="logo"]'
11441
+ );
11442
+ const href2 = (hrefEl instanceof HTMLAnchorElement ? hrefEl.getAttribute("href") : null) || hrefEl?.closest("a")?.getAttribute("href") || "/";
11443
+ return { text, isPlaceholder, imageUrl, href: href2, alt };
11444
+ }
11445
+ const logoRoot = document.querySelector('[data-ohw-role="logo"], [data-ohw-logo]');
11446
+ const href = (logoRoot instanceof HTMLAnchorElement ? logoRoot.getAttribute("href") : null) || logoRoot?.closest("a")?.getAttribute("href") || "/";
11447
+ return { text, isPlaceholder, imageUrl: null, href, alt: text };
11448
+ }
11449
+ function applyLogoFromContent(content) {
11450
+ 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);
11451
+ if (!hasLogoIdentity) return false;
11452
+ const logoText = content[LOGO_TEXT_KEYS[0]] ?? content[LOGO_TEXT_KEYS[1]] ?? readLogoIdentityFromDom().text;
11453
+ const logoAlt = content[LOGO_ALT_KEY] ?? logoText;
11454
+ const rawLogoImage = content[LOGO_IMAGE_URL_KEY] ?? content["footer-logo"] ?? content["footer-logo-image"] ?? null;
11455
+ const logoImageUrl = typeof rawLogoImage === "string" && rawLogoImage.trim() ? rawLogoImage.trim() : null;
11456
+ const imageExplicitlyCleared = LOGO_IMAGE_KEYS.some((key) => key in content) && !logoImageUrl;
11457
+ const logoIsPlaceholder = LOGO_PLACEHOLDER_KEY in content ? content[LOGO_PLACEHOLDER_KEY] !== "false" : !logoImageUrl && (!logoText.trim() || logoText === PLACEHOLDER_BUSINESS_NAME);
11458
+ if (logoImageUrl) {
11459
+ applyLogoImage(logoImageUrl, logoAlt);
11460
+ } else {
11461
+ if (imageExplicitlyCleared) applyLogoImage(null, logoAlt);
11462
+ applyLogoIdentity(logoText, logoIsPlaceholder);
11463
+ }
11464
+ const logoHref = content["nav-logo-href"] ?? content["footer-logo-href"] ?? content["logo-href"];
11465
+ if (typeof logoHref === "string" && logoHref.trim()) {
11466
+ applyLogoHref(logoHref);
11467
+ }
11468
+ return true;
11469
+ }
11470
+
11471
+ // src/lib/logo-size.ts
11472
+ var LOGO_SIZE_DEFAULTS = {
11473
+ navbar: 28,
11474
+ footer: 32
11475
+ };
11476
+ var LOGO_SIZE_MIN = 16;
11477
+ var LOGO_SIZE_MAX = 80;
11478
+ var LOGO_SIZE_DESKTOP_KEYS = {
11479
+ navbar: "nav-logo-size",
11480
+ footer: "footer-logo-size"
11481
+ };
11482
+ var LOGO_SIZE_MOBILE_KEYS = {
11483
+ navbar: "nav-logo-size-mobile",
11484
+ footer: "footer-logo-size-mobile"
11485
+ };
11486
+ var LOGO_SIZE_KEYS = [
11487
+ LOGO_SIZE_DESKTOP_KEYS.navbar,
11488
+ LOGO_SIZE_DESKTOP_KEYS.footer,
11489
+ LOGO_SIZE_MOBILE_KEYS.navbar,
11490
+ LOGO_SIZE_MOBILE_KEYS.footer
11491
+ ];
11492
+ function isFooterLogoRoot2(root) {
11493
+ return Boolean(root.closest("footer") || root.closest('[data-ohw-section="footer"]'));
11494
+ }
11495
+ function getLogoPlacement(root) {
11496
+ return isFooterLogoRoot2(root) ? "footer" : "navbar";
11497
+ }
11498
+ function parseLogoSizePx(raw, fallback) {
11499
+ if (raw == null || raw === "") return fallback;
11500
+ const n = Number.parseFloat(raw);
11501
+ if (!Number.isFinite(n)) return fallback;
11502
+ return Math.min(LOGO_SIZE_MAX, Math.max(LOGO_SIZE_MIN, Math.round(n)));
11503
+ }
11504
+ function isMobileLogoSizeFollowing(content, placement) {
11505
+ const raw = content[LOGO_SIZE_MOBILE_KEYS[placement]];
11506
+ return raw == null || raw.trim() === "";
11507
+ }
11508
+ function resolveDesktopLogoSize(content, placement) {
11509
+ return parseLogoSizePx(content[LOGO_SIZE_DESKTOP_KEYS[placement]], LOGO_SIZE_DEFAULTS[placement]);
11510
+ }
11511
+ function resolveMobileLogoSize(content, placement) {
11512
+ if (isMobileLogoSizeFollowing(content, placement)) {
11513
+ return resolveDesktopLogoSize(content, placement);
11514
+ }
11515
+ return parseLogoSizePx(
11516
+ content[LOGO_SIZE_MOBILE_KEYS[placement]],
11517
+ resolveDesktopLogoSize(content, placement)
11518
+ );
11519
+ }
11520
+ function setRootSizeVars(root, desktopPx, mobilePx, following) {
11521
+ root.style.setProperty("--ohw-logo-size", `${desktopPx}px`);
11522
+ if (following) {
11523
+ root.style.removeProperty("--ohw-logo-size-mobile");
11524
+ } else {
11525
+ root.style.setProperty("--ohw-logo-size-mobile", `${mobilePx}px`);
11526
+ }
11527
+ root.querySelectorAll("img").forEach((img) => {
11528
+ img.style.height = "";
11529
+ img.style.maxHeight = "none";
11530
+ img.style.width = "auto";
11531
+ img.style.objectFit = "contain";
11532
+ });
11533
+ }
11534
+ function applyLogoSizes(content) {
11535
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11536
+ const placement = getLogoPlacement(root);
11537
+ const desktop = resolveDesktopLogoSize(content, placement);
11538
+ const following = isMobileLogoSizeFollowing(content, placement);
11539
+ const mobile = following ? desktop : resolveMobileLogoSize(content, placement);
11540
+ setRootSizeVars(root, desktop, mobile, following);
11541
+ });
11542
+ }
11543
+ function applyLogoSizeToPlacement(placement, desktopPx, mobilePx, following) {
11544
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]').forEach((root) => {
11545
+ if (getLogoPlacement(root) !== placement) return;
11546
+ setRootSizeVars(root, desktopPx, mobilePx, following);
11547
+ });
11548
+ }
11549
+ function logoHasUploadedImage(logoEl) {
11550
+ if (logoEl.hasAttribute("data-ohw-placeholder")) return false;
11551
+ 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");
11552
+ if (!img) return false;
11553
+ const src = img.getAttribute("src")?.trim() ?? "";
11554
+ if (!src || src.startsWith("data:")) return false;
11555
+ if (img.style.display === "none") return false;
11556
+ return true;
11557
+ }
11558
+ function getLogoInteractionRect(logoEl) {
11559
+ if (logoHasUploadedImage(logoEl)) {
11560
+ 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");
11561
+ if (img) {
11562
+ const r2 = img.getBoundingClientRect();
11563
+ if (r2.width > 0 && r2.height > 0) return r2;
11564
+ }
11565
+ }
11566
+ const text = logoEl.querySelector(
11567
+ '[data-ohw-key="nav-logo-text"], [data-ohw-key="footer-logo-text"]'
11568
+ );
11569
+ if (text) {
11570
+ const style = window.getComputedStyle(text);
11571
+ if (style.display !== "none" && style.visibility !== "hidden") {
11572
+ const r2 = text.getBoundingClientRect();
11573
+ if (r2.width > 0 && r2.height > 0) return r2;
11574
+ }
11575
+ }
11576
+ return logoEl.getBoundingClientRect();
11577
+ }
11578
+ function readLogoSizeState(content, placement) {
11579
+ const desktopPx = resolveDesktopLogoSize(content, placement);
11580
+ const mobileFollowing = isMobileLogoSizeFollowing(content, placement);
11581
+ const mobilePx = mobileFollowing ? desktopPx : resolveMobileLogoSize(content, placement);
11582
+ return { desktopPx, mobilePx, mobileFollowing };
11583
+ }
11584
+
11585
+ // src/lib/site-wide-scope.ts
11586
+ function getLogoElement(el) {
11587
+ const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
11588
+ if (marked) return marked;
11589
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
11590
+ const root = el.closest("nav, [data-ohw-nav-root], footer");
11591
+ if (!root) return null;
11592
+ const anchor = el.closest("a");
11593
+ if (anchor && root.contains(anchor) && !anchor.hasAttribute("data-ohw-href-key") && !anchor.closest("[data-ohw-nav-container]") && Boolean(anchor.querySelector("img") || anchor.matches("img"))) {
11594
+ return anchor;
11595
+ }
11596
+ const img = el.matches("img") ? el : null;
11597
+ if (img && !img.closest("[data-ohw-href-key]") && !img.closest("[data-ohw-nav-container]") && (img.closest("footer") || img.closest("nav, [data-ohw-nav-root]"))) {
11598
+ return img;
11599
+ }
11600
+ return null;
11601
+ }
11602
+ function isInFooter(el) {
11603
+ if (!el) return false;
11604
+ return Boolean(el.closest("footer") || el.closest('[data-ohw-section="footer"]'));
11605
+ }
11606
+ function isSiteWideElement(el) {
11607
+ if (!el) return false;
11608
+ if (getLogoElement(el)) return true;
11609
+ if (isInFooter(el)) return true;
11610
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-root")) {
11611
+ return true;
11612
+ }
11613
+ if (el.hasAttribute("data-ohw-href-key") && el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer], aside")) {
11614
+ return true;
11615
+ }
11616
+ if (el.closest('[data-ohw-role="navbar-button"]')) return true;
11617
+ return false;
11618
+ }
11619
+ function isSiteWideScopeActive(args) {
11620
+ return isSiteWideElement(args.selected) || isSiteWideElement(args.hoveredItem) || isSiteWideElement(args.hoveredNavContainer) || isSiteWideElement(args.active);
11621
+ }
11622
+
10924
11623
  // src/lib/add-footer-column.ts
10925
11624
  function buildFooterColumnEditContentPatch(result) {
10926
11625
  return {
@@ -11136,16 +11835,127 @@ function FloatingPanel({
11136
11835
  );
11137
11836
  }
11138
11837
 
11139
- // src/ui/socials-display-panel.tsx
11838
+ // src/ui/logo-size-panel.tsx
11839
+ var import_lucide_react14 = require("lucide-react");
11140
11840
  var import_jsx_runtime27 = require("react/jsx-runtime");
11841
+ function SizeSlider({
11842
+ value,
11843
+ onChange
11844
+ }) {
11845
+ const pct = (value - LOGO_SIZE_MIN) / (LOGO_SIZE_MAX - LOGO_SIZE_MIN) * 100;
11846
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-3", children: [
11847
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full items-center gap-2 text-sm font-medium leading-5", children: [
11848
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "min-w-0 flex-1 text-foreground", children: "Size" }),
11849
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
11850
+ value,
11851
+ " px"
11852
+ ] })
11853
+ ] }),
11854
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "relative h-2 w-full rounded-full bg-primary-50", children: [
11855
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11856
+ "div",
11857
+ {
11858
+ className: "absolute inset-y-0 left-0 rounded-full bg-primary",
11859
+ style: { width: `${pct}%` }
11860
+ }
11861
+ ),
11862
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11863
+ "input",
11864
+ {
11865
+ type: "range",
11866
+ min: LOGO_SIZE_MIN,
11867
+ max: LOGO_SIZE_MAX,
11868
+ step: 1,
11869
+ value,
11870
+ "aria-label": "Logo size",
11871
+ className: cn(
11872
+ "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent",
11873
+ "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-5",
11874
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2",
11875
+ "[&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background",
11876
+ "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full",
11877
+ "[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary",
11878
+ "[&::-moz-range-thumb]:bg-background"
11879
+ ),
11880
+ onChange: (e) => onChange(Number(e.target.value))
11881
+ }
11882
+ )
11883
+ ] })
11884
+ ] });
11885
+ }
11886
+ function LogoSizePanel({
11887
+ viewport,
11888
+ sizePx,
11889
+ mobileFollowing = true,
11890
+ onSizeChange,
11891
+ onCustomizeMobile,
11892
+ onResetMobile,
11893
+ onUpdateEverywhere,
11894
+ className
11895
+ }) {
11896
+ const showFollowing = viewport === "mobile" && mobileFollowing;
11897
+ const showMobileSlider = viewport === "mobile" && !mobileFollowing;
11898
+ const showDesktopSlider = viewport === "desktop";
11899
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("flex w-full flex-col gap-4", className), children: [
11900
+ showFollowing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
11901
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start gap-1", children: [
11902
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.Link, { size: 16, className: "mt-0.5 shrink-0 text-foreground", "aria-hidden": true }),
11903
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm font-semibold leading-5 text-foreground", children: "Following desktop size" })
11904
+ ] }),
11905
+ /* @__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." }),
11906
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11907
+ Button,
11908
+ {
11909
+ type: "button",
11910
+ variant: "outline",
11911
+ size: "sm",
11912
+ className: "h-9 w-full min-w-0 cursor-pointer",
11913
+ onClick: onCustomizeMobile,
11914
+ children: "Customize for mobile"
11915
+ }
11916
+ )
11917
+ ] }) : null,
11918
+ showDesktopSlider || showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SizeSlider, { value: sizePx, onChange: onSizeChange }) : null,
11919
+ showMobileSlider ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11920
+ Button,
11921
+ {
11922
+ type: "button",
11923
+ variant: "outline",
11924
+ size: "sm",
11925
+ className: "h-9 w-full min-w-0 cursor-pointer",
11926
+ onClick: onResetMobile,
11927
+ children: "Reset to desktop size"
11928
+ }
11929
+ ) : null,
11930
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-px w-full bg-border", role: "separator" }),
11931
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
11932
+ Button,
11933
+ {
11934
+ type: "button",
11935
+ variant: "outline",
11936
+ size: "sm",
11937
+ className: "h-9 w-full min-w-0 cursor-pointer gap-1",
11938
+ onClick: onUpdateEverywhere,
11939
+ children: [
11940
+ "Update logo everywhere",
11941
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react14.ArrowUpRight, { size: 16, "aria-hidden": true })
11942
+ ]
11943
+ }
11944
+ ),
11945
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm leading-5 text-muted-foreground", children: "Matches the right version to your background." })
11946
+ ] });
11947
+ }
11948
+
11949
+ // src/ui/socials-display-panel.tsx
11950
+ var import_jsx_runtime28 = require("react/jsx-runtime");
11141
11951
  function DisplaySwitch({
11142
11952
  label,
11143
11953
  checked,
11144
11954
  disabled,
11145
11955
  onChange
11146
11956
  }) {
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)(
11957
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex w-full items-center gap-2", children: [
11958
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11149
11959
  "span",
11150
11960
  {
11151
11961
  className: cn(
@@ -11155,7 +11965,7 @@ function DisplaySwitch({
11155
11965
  children: label
11156
11966
  }
11157
11967
  ),
11158
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11968
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11159
11969
  "button",
11160
11970
  {
11161
11971
  type: "button",
@@ -11169,7 +11979,7 @@ function DisplaySwitch({
11169
11979
  checked ? "bg-primary" : "bg-primary-50",
11170
11980
  disabled ? "cursor-default opacity-50" : "cursor-pointer"
11171
11981
  ),
11172
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
11982
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11173
11983
  "span",
11174
11984
  {
11175
11985
  className: cn(
@@ -11183,8 +11993,8 @@ function DisplaySwitch({
11183
11993
  ] });
11184
11994
  }
11185
11995
  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)(
11996
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: cn("flex w-full flex-col gap-3", className), children: [
11997
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11188
11998
  DisplaySwitch,
11189
11999
  {
11190
12000
  label: "Text",
@@ -11193,7 +12003,7 @@ function SocialsDisplayPanel({ display, onChange, className }) {
11193
12003
  onChange: (text) => onChange({ ...display, text })
11194
12004
  }
11195
12005
  ),
11196
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
12006
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11197
12007
  DisplaySwitch,
11198
12008
  {
11199
12009
  label: "Icon",
@@ -11753,8 +12563,8 @@ function useNavItemDrag({
11753
12563
  }
11754
12564
 
11755
12565
  // src/ui/footer-container-chrome.tsx
11756
- var import_lucide_react14 = require("lucide-react");
11757
- var import_jsx_runtime28 = require("react/jsx-runtime");
12566
+ var import_lucide_react15 = require("lucide-react");
12567
+ var import_jsx_runtime29 = require("react/jsx-runtime");
11758
12568
  function FooterContainerChrome({
11759
12569
  rect,
11760
12570
  onAdd,
@@ -11762,7 +12572,7 @@ function FooterContainerChrome({
11762
12572
  }) {
11763
12573
  const chromeGap = 6;
11764
12574
  const buttonMargin = 7;
11765
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
12575
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11766
12576
  "div",
11767
12577
  {
11768
12578
  "data-ohw-footer-container-chrome": "",
@@ -11774,8 +12584,8 @@ function FooterContainerChrome({
11774
12584
  width: rect.width + chromeGap * 2,
11775
12585
  height: rect.height + chromeGap * 2
11776
12586
  },
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)(
12587
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Tooltip, { children: [
12588
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11779
12589
  "button",
11780
12590
  {
11781
12591
  type: "button",
@@ -11794,10 +12604,10 @@ function FooterContainerChrome({
11794
12604
  if (addDisabled) return;
11795
12605
  onAdd();
11796
12606
  },
11797
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react14.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12607
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
11798
12608
  }
11799
12609
  ) }),
11800
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
12610
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
11801
12611
  ] })
11802
12612
  }
11803
12613
  ) });
@@ -11980,6 +12790,18 @@ function collectEditableNodes(extraContent, root = document) {
11980
12790
  }
11981
12791
  if (extraContent && !isScoped) {
11982
12792
  applyNavFooterDeleteOverrides(byKey, extraContent);
12793
+ for (const key of LOGO_IMAGE_KEYS) {
12794
+ if (!(key in extraContent)) continue;
12795
+ byKey.set(key, { key, type: "image", text: extraContent[key] ?? "" });
12796
+ }
12797
+ for (const key of [LOGO_PLACEHOLDER_KEY, LOGO_ALT_KEY]) {
12798
+ if (!(key in extraContent)) continue;
12799
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12800
+ }
12801
+ for (const key of LOGO_SIZE_KEYS) {
12802
+ if (!(key in extraContent)) continue;
12803
+ byKey.set(key, { key, type: "meta", text: extraContent[key] ?? "" });
12804
+ }
11983
12805
  }
11984
12806
  return Array.from(byKey.values());
11985
12807
  }
@@ -12245,14 +13067,14 @@ function deleteSelectedNavFooterItem(deps) {
12245
13067
  }
12246
13068
 
12247
13069
  // src/ui/navbar-container-chrome.tsx
12248
- var import_lucide_react15 = require("lucide-react");
12249
- var import_jsx_runtime29 = require("react/jsx-runtime");
13070
+ var import_lucide_react16 = require("lucide-react");
13071
+ var import_jsx_runtime30 = require("react/jsx-runtime");
12250
13072
  function NavbarContainerChrome({
12251
13073
  rect,
12252
13074
  onAdd
12253
13075
  }) {
12254
13076
  const chromeGap = 6;
12255
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13077
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12256
13078
  "div",
12257
13079
  {
12258
13080
  "data-ohw-navbar-container-chrome": "",
@@ -12264,7 +13086,7 @@ function NavbarContainerChrome({
12264
13086
  width: rect.width + chromeGap * 2,
12265
13087
  height: rect.height + chromeGap * 2
12266
13088
  },
12267
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13089
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12268
13090
  "button",
12269
13091
  {
12270
13092
  type: "button",
@@ -12281,7 +13103,7 @@ function NavbarContainerChrome({
12281
13103
  e.stopPropagation();
12282
13104
  onAdd();
12283
13105
  },
12284
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
13106
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_lucide_react16.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
12285
13107
  }
12286
13108
  )
12287
13109
  }
@@ -12290,7 +13112,7 @@ function NavbarContainerChrome({
12290
13112
 
12291
13113
  // src/ui/drop-indicator.tsx
12292
13114
  var React10 = __toESM(require("react"), 1);
12293
- var import_jsx_runtime30 = require("react/jsx-runtime");
13115
+ var import_jsx_runtime31 = require("react/jsx-runtime");
12294
13116
  var dropIndicatorVariants = cva(
12295
13117
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
12296
13118
  {
@@ -12314,7 +13136,7 @@ var dropIndicatorVariants = cva(
12314
13136
  );
12315
13137
  var DropIndicator = React10.forwardRef(
12316
13138
  ({ className, direction, state, ...props }, ref) => {
12317
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
13139
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12318
13140
  "div",
12319
13141
  {
12320
13142
  ref,
@@ -12331,7 +13153,7 @@ var DropIndicator = React10.forwardRef(
12331
13153
  DropIndicator.displayName = "DropIndicator";
12332
13154
 
12333
13155
  // src/ui/badge.tsx
12334
- var import_jsx_runtime31 = require("react/jsx-runtime");
13156
+ var import_jsx_runtime32 = require("react/jsx-runtime");
12335
13157
  var badgeVariants = cva(
12336
13158
  "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
13159
  {
@@ -12349,12 +13171,12 @@ var badgeVariants = cva(
12349
13171
  }
12350
13172
  );
12351
13173
  function Badge({ className, variant, ...props }) {
12352
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
13174
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
12353
13175
  }
12354
13176
 
12355
13177
  // src/OhhwellsBridge.tsx
12356
- var import_lucide_react16 = require("lucide-react");
12357
- var import_jsx_runtime32 = require("react/jsx-runtime");
13178
+ var import_lucide_react17 = require("lucide-react");
13179
+ var import_jsx_runtime33 = require("react/jsx-runtime");
12358
13180
  var PRIMARY3 = "#0885FE";
12359
13181
  var IMAGE_FADE_MS = 300;
12360
13182
  function runOpacityFade(el, onDone) {
@@ -12448,21 +13270,10 @@ function parseSchedulingInsertAfter(insertAfter) {
12448
13270
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
12449
13271
  };
12450
13272
  }
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;
13273
+ function resolveEntryAnchor(entry) {
13274
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
13275
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
13276
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
12466
13277
  }
12467
13278
  function schedulingMountDepth(insertAfter) {
12468
13279
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -12479,8 +13290,7 @@ function getPageSchedulingEntries(raw) {
12479
13290
  }
12480
13291
  }
12481
13292
  function isSchedulingWidgetMissing(entry) {
12482
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
12483
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
13293
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
12484
13294
  }
12485
13295
  function hasMissingSchedulingWidgets(entries) {
12486
13296
  return entries.some(isSchedulingWidgetMissing);
@@ -12510,16 +13320,17 @@ function initSectionsFromContent(content, removeExisting = false) {
12510
13320
  } catch {
12511
13321
  }
12512
13322
  }
12513
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
12514
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
12515
- const sectionId = schedulingSectionId(effectiveInsertAfter);
13323
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
13324
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
13325
+ const sectionId = schedulingSectionId(widgetId);
12516
13326
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
12517
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
12518
- if (!mountPoint) return false;
13327
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
13328
+ if (!anchorEl) return false;
13329
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12519
13330
  const container = document.createElement("div");
12520
13331
  container.dataset.ohwSectionContainer = "scheduling";
12521
- if (insertBefore) {
12522
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
13332
+ if (beforeId) {
13333
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
12523
13334
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
12524
13335
  if (!beforePoint) return false;
12525
13336
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -12530,19 +13341,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12530
13341
  }
12531
13342
  tail.insertAdjacentElement("afterend", container);
12532
13343
  }
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
- });
13344
+ try {
13345
+ const root = (0, import_client2.createRoot)(container);
13346
+ (0, import_react_dom3.flushSync)(() => {
13347
+ root.render(
13348
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13349
+ SchedulingWidget,
13350
+ {
13351
+ notifyOnConnect,
13352
+ initialScheduleId: scheduleId,
13353
+ insertAfter: widgetId
13354
+ }
13355
+ )
13356
+ );
13357
+ });
13358
+ } catch (err) {
13359
+ console.error("[ow:scheduling] render threw", err);
13360
+ container.remove();
13361
+ return false;
13362
+ }
12546
13363
  const tracker = getSectionsTracker();
12547
13364
  let sections = [];
12548
13365
  try {
@@ -12550,10 +13367,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12550
13367
  } catch {
12551
13368
  }
12552
13369
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
12553
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
13370
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
12554
13371
  sections.push({
12555
13372
  type: "scheduling",
12556
- insertAfter: effectiveInsertAfter,
13373
+ insertAfter: widgetId,
13374
+ anchorId,
13375
+ beforeId: beforeId ?? null,
12557
13376
  pagePath: window.location.pathname,
12558
13377
  ...scheduleId ? { scheduleId } : {}
12559
13378
  });
@@ -12567,7 +13386,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
12567
13386
  for (let i = pending.length - 1; i >= 0; i--) {
12568
13387
  const entry = pending[i];
12569
13388
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
12570
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
13389
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
13390
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
12571
13391
  pending.splice(i, 1);
12572
13392
  }
12573
13393
  }
@@ -12711,6 +13531,13 @@ function isInsideLinkEditor(target) {
12711
13531
  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
13532
  );
12713
13533
  }
13534
+ function isInsideFloatingPanel(target) {
13535
+ return Boolean(target.closest("[data-ohw-floating-panel]"));
13536
+ }
13537
+ function isPointOverFloatingPanel(clientX, clientY) {
13538
+ const el = document.elementFromPoint(clientX, clientY);
13539
+ return Boolean(el instanceof Element && el.closest("[data-ohw-floating-panel]"));
13540
+ }
12714
13541
  function getHrefKeyFromElement(el) {
12715
13542
  if (!el) return null;
12716
13543
  const anchor = el.closest("[data-ohw-href-key]");
@@ -12948,7 +13775,7 @@ function getNavigationSelectionParent(el) {
12948
13775
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
12949
13776
  return getFooterLinksContainer();
12950
13777
  }
12951
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
13778
+ 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
13779
  return getNavigationRoot(el);
12953
13780
  }
12954
13781
  return null;
@@ -13194,7 +14021,7 @@ function EditGlowChrome({
13194
14021
  hideHandle = false
13195
14022
  }) {
13196
14023
  const GAP = SELECTION_CHROME_GAP2;
13197
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
14024
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
13198
14025
  "div",
13199
14026
  {
13200
14027
  ref: elRef,
@@ -13209,7 +14036,7 @@ function EditGlowChrome({
13209
14036
  zIndex: 2147483646
13210
14037
  },
13211
14038
  children: [
13212
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14039
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13213
14040
  "div",
13214
14041
  {
13215
14042
  style: {
@@ -13222,7 +14049,7 @@ function EditGlowChrome({
13222
14049
  }
13223
14050
  }
13224
14051
  ),
13225
- reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14052
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13226
14053
  "div",
13227
14054
  {
13228
14055
  "data-ohw-drag-handle-container": "",
@@ -13234,7 +14061,7 @@ function EditGlowChrome({
13234
14061
  transform: "translate(calc(-100% - 7px), -50%)",
13235
14062
  pointerEvents: dragDisabled ? "none" : "auto"
13236
14063
  },
13237
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14064
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13238
14065
  DragHandle,
13239
14066
  {
13240
14067
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -13444,7 +14271,7 @@ function FloatingToolbar({
13444
14271
  return () => ro.disconnect();
13445
14272
  }, [showEditLink, activeCommands]);
13446
14273
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
13447
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14274
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13448
14275
  "div",
13449
14276
  {
13450
14277
  ref: setRefs,
@@ -13456,12 +14283,12 @@ function FloatingToolbar({
13456
14283
  zIndex: 2147483647,
13457
14284
  pointerEvents: "auto"
13458
14285
  },
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, {}),
14286
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(CustomToolbar, { children: [
14287
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react16.default.Fragment, { children: [
14288
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CustomToolbarDivider, {}),
13462
14289
  btns.map((btn) => {
13463
14290
  const isActive = activeCommands.has(btn.cmd);
13464
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14291
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13465
14292
  CustomToolbarButton,
13466
14293
  {
13467
14294
  title: btn.title,
@@ -13470,7 +14297,7 @@ function FloatingToolbar({
13470
14297
  e.preventDefault();
13471
14298
  onCommand(btn.cmd);
13472
14299
  },
13473
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14300
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13474
14301
  "svg",
13475
14302
  {
13476
14303
  width: "16",
@@ -13491,7 +14318,7 @@ function FloatingToolbar({
13491
14318
  );
13492
14319
  })
13493
14320
  ] }, gi)),
13494
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14321
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13495
14322
  CustomToolbarButton,
13496
14323
  {
13497
14324
  type: "button",
@@ -13505,7 +14332,7 @@ function FloatingToolbar({
13505
14332
  e.preventDefault();
13506
14333
  e.stopPropagation();
13507
14334
  },
13508
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react16.Link, { className: "size-4 shrink-0", "aria-hidden": true })
14335
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react17.Link, { className: "size-4 shrink-0", "aria-hidden": true })
13509
14336
  }
13510
14337
  ) : null
13511
14338
  ] })
@@ -13522,7 +14349,7 @@ function StateToggle({
13522
14349
  states,
13523
14350
  onStateChange
13524
14351
  }) {
13525
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
14352
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
13526
14353
  ToggleGroup,
13527
14354
  {
13528
14355
  "data-ohw-state-toggle": "",
@@ -13536,11 +14363,12 @@ function StateToggle({
13536
14363
  left: rect.right - 8,
13537
14364
  transform: "translateX(-100%)"
13538
14365
  },
13539
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
14366
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
13540
14367
  }
13541
14368
  );
13542
14369
  }
13543
14370
  var contentCache = /* @__PURE__ */ new Map();
14371
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
13544
14372
  function resolveSubdomain(subdomainFromQuery) {
13545
14373
  if (subdomainFromQuery) return subdomainFromQuery;
13546
14374
  if (typeof window !== "undefined") {
@@ -13635,8 +14463,14 @@ function OhhwellsBridge() {
13635
14463
  });
13636
14464
  const selectFrameRef = (0, import_react16.useRef)(() => {
13637
14465
  });
14466
+ const selectLogoRef = (0, import_react16.useRef)(() => {
14467
+ });
14468
+ const openLogoSizePanelRef = (0, import_react16.useRef)(() => {
14469
+ });
13638
14470
  const deselectRef = (0, import_react16.useRef)(() => {
13639
14471
  });
14472
+ const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(() => {
14473
+ });
13640
14474
  const reselectNavigationItemRef = (0, import_react16.useRef)(() => {
13641
14475
  });
13642
14476
  const commitNavigationTextEditRef = (0, import_react16.useRef)(() => {
@@ -13697,11 +14531,6 @@ function OhhwellsBridge() {
13697
14531
  const [isItemDragging, setIsItemDragging] = (0, import_react16.useState)(false);
13698
14532
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react16.useState)(false);
13699
14533
  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
14534
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react16.useState)(null);
13706
14535
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react16.useState)(null);
13707
14536
  const footerDragRef = (0, import_react16.useRef)(null);
@@ -13716,7 +14545,16 @@ function OhhwellsBridge() {
13716
14545
  const addNavAfterAnchorRef = (0, import_react16.useRef)(null);
13717
14546
  const editContentRef = (0, import_react16.useRef)({});
13718
14547
  const aiSectionsRef = (0, import_react16.useRef)("");
14548
+ const brandKitRef = (0, import_react16.useRef)("");
14549
+ const stylesRef = (0, import_react16.useRef)("");
13719
14550
  const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14551
+ const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14552
+ const floatingPanelOpenRef = (0, import_react16.useRef)(false);
14553
+ const setFloatingPanelRef = (0, import_react16.useRef)(setFloatingPanel);
14554
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react16.useState)(null);
14555
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react16.useState)(null);
14556
+ const [editorViewport, setEditorViewport] = (0, import_react16.useState)("desktop");
14557
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react16.useState)(null);
13720
14558
  const [sitePages, setSitePages] = (0, import_react16.useState)([]);
13721
14559
  const [sectionsByPath, setSectionsByPath] = (0, import_react16.useState)({});
13722
14560
  const sectionsPrefetchGenRef = (0, import_react16.useRef)(0);
@@ -13725,7 +14563,18 @@ function OhhwellsBridge() {
13725
14563
  const linkPopoverOpenRef = (0, import_react16.useRef)(false);
13726
14564
  const linkPopoverGraceUntilRef = (0, import_react16.useRef)(0);
13727
14565
  setLinkPopoverRef.current = setLinkPopover;
14566
+ setFloatingPanelRef.current = setFloatingPanel;
13728
14567
  linkPopoverSessionRef.current = linkPopover;
14568
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
14569
+ (0, import_react16.useEffect)(() => {
14570
+ const syncViewport = () => {
14571
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
14572
+ setEditorViewport((prev) => prev === next ? prev : next);
14573
+ };
14574
+ syncViewport();
14575
+ window.addEventListener("resize", syncViewport);
14576
+ return () => window.removeEventListener("resize", syncViewport);
14577
+ }, []);
13729
14578
  const {
13730
14579
  navDragRef,
13731
14580
  navDropSlots,
@@ -13948,6 +14797,10 @@ function OhhwellsBridge() {
13948
14797
  setIsItemDragging(false);
13949
14798
  hoveredNavContainerRef.current = null;
13950
14799
  setHoveredNavContainerRect(null);
14800
+ hoveredItemElRef.current = null;
14801
+ setHoveredItemRect(null);
14802
+ setFloatingPanel(null);
14803
+ setLogoSizeDraft(null);
13951
14804
  if (!activeElRef.current) {
13952
14805
  setNavGroupForceOpen(null, false);
13953
14806
  setToolbarRect(null);
@@ -14653,6 +15506,8 @@ function OhhwellsBridge() {
14653
15506
  setToolbarRect(anchor.getBoundingClientRect());
14654
15507
  setToolbarShowEditLink(false);
14655
15508
  setActiveCommands(/* @__PURE__ */ new Set());
15509
+ setFloatingPanel(null);
15510
+ setLogoSizeDraft(null);
14656
15511
  }, [deactivate, markSelected]);
14657
15512
  const selectFrame = (0, import_react16.useCallback)((el) => {
14658
15513
  if (!isNavigationContainer(el)) return;
@@ -14702,7 +15557,51 @@ function OhhwellsBridge() {
14702
15557
  setToolbarRect(el.getBoundingClientRect());
14703
15558
  setToolbarShowEditLink(false);
14704
15559
  setActiveCommands(/* @__PURE__ */ new Set());
15560
+ setFloatingPanel(null);
15561
+ setLogoSizeDraft(null);
14705
15562
  }, [deactivate, markSelected, postToParent2]);
15563
+ const selectLogo = (0, import_react16.useCallback)(
15564
+ (logoEl) => {
15565
+ if (activeElRef.current) deactivate();
15566
+ selectedElRef.current = logoEl;
15567
+ selectedHrefKeyRef.current = null;
15568
+ selectedFooterColAttrRef.current = null;
15569
+ markSelected(logoEl);
15570
+ setSelectedIsCta(false);
15571
+ setSelectedIsSocial(false);
15572
+ setSelectedIsSocialsRow(false);
15573
+ clearHrefKeyHover(logoEl);
15574
+ hoveredNavContainerRef.current = null;
15575
+ setHoveredNavContainerRect(null);
15576
+ setHoveredItemRect(null);
15577
+ hoveredItemElRef.current = null;
15578
+ siblingHintElRef.current = null;
15579
+ setSiblingHintRect(null);
15580
+ setSiblingHintRects([]);
15581
+ setIsItemDragging(false);
15582
+ setReorderHrefKey(null);
15583
+ setReorderDragDisabled(false);
15584
+ setIsFooterFrameSelection(false);
15585
+ setToolbarVariant("logo");
15586
+ setToolbarRect(getLogoInteractionRect(logoEl));
15587
+ setToolbarShowEditLink(false);
15588
+ setActiveCommands(/* @__PURE__ */ new Set());
15589
+ },
15590
+ [deactivate, markSelected]
15591
+ );
15592
+ const openLogoSizePanel = (0, import_react16.useCallback)((logoEl) => {
15593
+ const placement = getLogoPlacement(logoEl);
15594
+ const draft = readLogoSizeState(editContentRef.current, placement);
15595
+ setLogoSizeDraft(draft);
15596
+ setParentScrollSnap(parentScrollRef.current);
15597
+ setFloatingPanel({
15598
+ key: `logo-size:${placement}`,
15599
+ title: "Logo",
15600
+ context: placement === "navbar" ? "Navbar" : "Footer",
15601
+ kind: "logo-size",
15602
+ placement
15603
+ });
15604
+ }, []);
14706
15605
  const openSocialsDisplayPanel = (0, import_react16.useCallback)((row) => {
14707
15606
  setParentScrollSnap(parentScrollRef.current);
14708
15607
  setFloatingPanel({
@@ -14738,13 +15637,53 @@ function OhhwellsBridge() {
14738
15637
  );
14739
15638
  const closeFloatingPanelOnly = (0, import_react16.useCallback)(() => {
14740
15639
  setFloatingPanel(null);
15640
+ setLogoSizeDraft(null);
14741
15641
  }, []);
14742
- const closeFloatingPanelOnlyRef = (0, import_react16.useRef)(closeFloatingPanelOnly);
14743
- closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
14744
15642
  const closeFloatingPanelAndDeselect = (0, import_react16.useCallback)(() => {
14745
15643
  setFloatingPanel(null);
15644
+ setLogoSizeDraft(null);
14746
15645
  deselectRef.current();
14747
15646
  }, []);
15647
+ const persistLogoSizeDraft = (0, import_react16.useCallback)(
15648
+ (placement, draft) => {
15649
+ const desktopKey = LOGO_SIZE_DESKTOP_KEYS[placement];
15650
+ const mobileKey = LOGO_SIZE_MOBILE_KEYS[placement];
15651
+ const nodes = [
15652
+ { key: desktopKey, text: String(draft.desktopPx) }
15653
+ ];
15654
+ if (draft.mobileFollowing) {
15655
+ nodes.push({ key: mobileKey, text: "" });
15656
+ } else {
15657
+ nodes.push({ key: mobileKey, text: String(draft.mobilePx) });
15658
+ }
15659
+ editContentRef.current = {
15660
+ ...editContentRef.current,
15661
+ [desktopKey]: String(draft.desktopPx),
15662
+ [mobileKey]: draft.mobileFollowing ? "" : String(draft.mobilePx)
15663
+ };
15664
+ applyLogoSizeToPlacement(
15665
+ placement,
15666
+ draft.desktopPx,
15667
+ draft.mobileFollowing ? draft.desktopPx : draft.mobilePx,
15668
+ draft.mobileFollowing
15669
+ );
15670
+ postToParent2({ type: "ow:change", nodes });
15671
+ requestAnimationFrame(() => {
15672
+ const selected = selectedElRef.current;
15673
+ if (!selected || toolbarVariantRef.current !== "logo") return;
15674
+ const rect = getLogoInteractionRect(selected);
15675
+ setToolbarRect(rect);
15676
+ if (glowElRef.current) {
15677
+ const GAP = SELECTION_CHROME_GAP2;
15678
+ glowElRef.current.style.top = `${rect.top - GAP}px`;
15679
+ glowElRef.current.style.left = `${rect.left - GAP}px`;
15680
+ glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
15681
+ glowElRef.current.style.height = `${rect.height + GAP * 2}px`;
15682
+ }
15683
+ });
15684
+ },
15685
+ [postToParent2]
15686
+ );
14748
15687
  const activate = (0, import_react16.useCallback)((el, options) => {
14749
15688
  if (activeElRef.current === el) return;
14750
15689
  if (isIconEditable(el)) return;
@@ -14825,7 +15764,37 @@ function OhhwellsBridge() {
14825
15764
  deactivateRef.current = deactivate;
14826
15765
  selectRef.current = select;
14827
15766
  selectFrameRef.current = selectFrame;
15767
+ selectLogoRef.current = selectLogo;
15768
+ openLogoSizePanelRef.current = openLogoSizePanel;
14828
15769
  deselectRef.current = deselect;
15770
+ closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
15771
+ const lastSiteWideScopeRef = (0, import_react16.useRef)(null);
15772
+ (0, import_react16.useEffect)(() => {
15773
+ if (!isEditMode) {
15774
+ if (lastSiteWideScopeRef.current !== false) {
15775
+ lastSiteWideScopeRef.current = false;
15776
+ postToParent2({ type: "ow:site-wide-scope", active: false });
15777
+ }
15778
+ return;
15779
+ }
15780
+ const active = isSiteWideScopeActive({
15781
+ selected: selectedElRef.current,
15782
+ hoveredItem: hoveredItemElRef.current,
15783
+ hoveredNavContainer: hoveredNavContainerRef.current,
15784
+ active: activeElRef.current
15785
+ });
15786
+ if (lastSiteWideScopeRef.current === active) return;
15787
+ lastSiteWideScopeRef.current = active;
15788
+ postToParent2({ type: "ow:site-wide-scope", active });
15789
+ }, [
15790
+ isEditMode,
15791
+ hoveredItemRect,
15792
+ hoveredNavContainerRect,
15793
+ toolbarVariant,
15794
+ toolbarRect,
15795
+ isFooterFrameSelection,
15796
+ postToParent2
15797
+ ]);
14829
15798
  (0, import_react16.useLayoutEffect)(() => {
14830
15799
  if (!subdomain || isEditMode) {
14831
15800
  setFetchState("done");
@@ -14837,9 +15806,23 @@ function OhhwellsBridge() {
14837
15806
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
14838
15807
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
14839
15808
  }
15809
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15810
+ brandKitRef.current = content[BRAND_KIT_KEY];
15811
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15812
+ }
15813
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15814
+ stylesRef.current = content[STYLE_STORE_KEY];
15815
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15816
+ }
15817
+ applyBrandChrome(content);
14840
15818
  for (const [key, val] of Object.entries(content)) {
14841
15819
  if (key === "__ohw_sections") continue;
14842
15820
  if (key === AI_SECTIONS_KEY) continue;
15821
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15822
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15823
+ if (key === BRAND_KIT_KEY) continue;
15824
+ if (key === STYLE_STORE_KEY) continue;
15825
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14843
15826
  if (applyVideoSettingNode(key, val)) continue;
14844
15827
  if (applyCarouselNode(key, val)) continue;
14845
15828
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14874,6 +15857,8 @@ function OhhwellsBridge() {
14874
15857
  });
14875
15858
  applyLinkByKey(key, val);
14876
15859
  }
15860
+ applyLogoFromContent(content);
15861
+ applyLogoSizes(content);
14877
15862
  reconcileNavbarItemsFromContent(content);
14878
15863
  reconcileFooterOrderFromContent(content);
14879
15864
  reconcileSocialsFromContent(content);
@@ -14894,7 +15879,9 @@ function OhhwellsBridge() {
14894
15879
  let cancelled = false;
14895
15880
  setFetchState("loading");
14896
15881
  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) => {
15882
+ const initialPath = pathname;
15883
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
15884
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14898
15885
  if (cancelled) return;
14899
15886
  const content = data?.content ?? {};
14900
15887
  contentCache.set(subdomain, content);
@@ -14918,8 +15905,21 @@ function OhhwellsBridge() {
14918
15905
  initSectionInstancesFromContent(content, window.location.pathname);
14919
15906
  observer?.disconnect();
14920
15907
  try {
15908
+ applyBrandChrome(content);
15909
+ if (typeof content[BRAND_KIT_KEY] === "string") {
15910
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15911
+ }
15912
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15913
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15914
+ }
14921
15915
  for (const [key, val] of Object.entries(content)) {
14922
15916
  if (key === "__ohw_sections") continue;
15917
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
15918
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
15919
+ if (key === BRAND_KIT_KEY) continue;
15920
+ if (key === STYLE_STORE_KEY) continue;
15921
+ if (key === STYLE_STORE_KEY) continue;
15922
+ if (BRAND_CHROME_KEYS.has(key)) continue;
14923
15923
  if (applyVideoSettingNode(key, val)) continue;
14924
15924
  if (applyCarouselNode(key, val)) continue;
14925
15925
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -14940,6 +15940,7 @@ function OhhwellsBridge() {
14940
15940
  });
14941
15941
  applyLinkByKey(key, val);
14942
15942
  }
15943
+ applyLogoFromContent(content);
14943
15944
  reconcileNavbarItemsFromContent(content);
14944
15945
  reconcileFooterOrderFromContent(content);
14945
15946
  reconcileSocialsFromContent(content);
@@ -14954,6 +15955,17 @@ function OhhwellsBridge() {
14954
15955
  debounceTimer = setTimeout(applyFromCache, 150);
14955
15956
  };
14956
15957
  applyFromCache();
15958
+ const pathCacheKey = `${subdomain}::${pathname}`;
15959
+ if (!fetchedContentPaths.has(pathCacheKey)) {
15960
+ fetchedContentPaths.add(pathCacheKey);
15961
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
15962
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
15963
+ if (!data?.content) return;
15964
+ contentCache.set(subdomain, data.content);
15965
+ applyFromCache();
15966
+ }).catch(() => {
15967
+ });
15968
+ }
14957
15969
  observer = new MutationObserver(scheduleApply);
14958
15970
  observer.observe(document.body, { childList: true, subtree: true });
14959
15971
  return () => {
@@ -15047,26 +16059,31 @@ function OhhwellsBridge() {
15047
16059
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
15048
16060
  (0, import_react16.useEffect)(() => {
15049
16061
  if (!isEditMode) return;
16062
+ let lastPosted = 0;
15050
16063
  const measure = () => {
15051
16064
  const h = document.body.scrollHeight;
15052
- if (h > 50) postToParent2({ type: "ow:height", height: h });
16065
+ if (h > 50 && Math.abs(h - lastPosted) > 1) {
16066
+ lastPosted = h;
16067
+ postToParent2({ type: "ow:height", height: h });
16068
+ }
16069
+ };
16070
+ let raf = null;
16071
+ const schedule = () => {
16072
+ if (raf != null) return;
16073
+ raf = requestAnimationFrame(() => {
16074
+ raf = null;
16075
+ measure();
16076
+ });
15053
16077
  };
15054
16078
  const t1 = setTimeout(measure, 50);
15055
16079
  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);
16080
+ const ro = new ResizeObserver(schedule);
16081
+ ro.observe(document.body);
15065
16082
  return () => {
15066
16083
  clearTimeout(t1);
15067
16084
  clearTimeout(t2);
15068
- if (resizeTimer) clearTimeout(resizeTimer);
15069
- window.removeEventListener("resize", handleResize);
16085
+ if (raf != null) cancelAnimationFrame(raf);
16086
+ ro.disconnect();
15070
16087
  };
15071
16088
  }, [pathname, isEditMode, postToParent2]);
15072
16089
  (0, import_react16.useEffect)(() => {
@@ -15216,10 +16233,12 @@ function OhhwellsBridge() {
15216
16233
  return;
15217
16234
  }
15218
16235
  const target = e.target;
16236
+ if (target.closest("[data-ohw-ai-review]")) return;
15219
16237
  if (target.closest("[data-ohw-toolbar]")) return;
15220
16238
  if (target.closest("[data-ohw-state-toggle]")) return;
15221
16239
  if (target.closest("[data-ohw-max-badge]")) return;
15222
16240
  if (isInsideLinkEditor(target)) return;
16241
+ if (isInsideFloatingPanel(target)) return;
15223
16242
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15224
16243
  const beneath = document.elementsFromPoint(e.clientX, e.clientY).find(
15225
16244
  (el) => el instanceof HTMLElement && !el.closest("[data-ohw-bridge-root]") && el.closest("[data-ohw-section]") != null
@@ -15283,6 +16302,21 @@ function OhhwellsBridge() {
15283
16302
  return;
15284
16303
  }
15285
16304
  }
16305
+ const logoEl = getLogoElement(target);
16306
+ if (logoEl) {
16307
+ e.preventDefault();
16308
+ e.stopPropagation();
16309
+ if (!logoHasUploadedImage(logoEl)) {
16310
+ deselectRef.current();
16311
+ deactivateRef.current();
16312
+ const identity = readLogoIdentityFromDom();
16313
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
16314
+ return;
16315
+ }
16316
+ selectLogoRef.current(logoEl);
16317
+ openLogoSizePanelRef.current(logoEl);
16318
+ return;
16319
+ }
15286
16320
  const editable = target.closest("[data-ohw-editable]");
15287
16321
  if (editable) {
15288
16322
  if (editable.dataset.ohwEditable === "link") {
@@ -15435,10 +16469,12 @@ function OhhwellsBridge() {
15435
16469
  };
15436
16470
  const handleDblClick = (e) => {
15437
16471
  const target = e.target;
16472
+ if (target.closest("[data-ohw-ai-review]")) return;
15438
16473
  if (target.closest("[data-ohw-toolbar]")) return;
15439
16474
  if (target.closest("[data-ohw-state-toggle]")) return;
15440
16475
  if (target.closest("[data-ohw-max-badge]")) return;
15441
16476
  if (isInsideLinkEditor(target)) return;
16477
+ if (isInsideFloatingPanel(target)) return;
15442
16478
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
15443
16479
  return;
15444
16480
  }
@@ -15466,11 +16502,14 @@ function OhhwellsBridge() {
15466
16502
  setHoveredNavContainerRect(null);
15467
16503
  return;
15468
16504
  }
15469
- if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
16505
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || isInsideFloatingPanel(target) || isPointOverFloatingPanel(e.clientX, e.clientY)) {
15470
16506
  hoveredItemElRef.current = null;
15471
16507
  setHoveredItemRect(null);
15472
16508
  hoveredNavContainerRef.current = null;
15473
16509
  setHoveredNavContainerRect(null);
16510
+ siblingHintElRef.current = null;
16511
+ setSiblingHintRect(null);
16512
+ setSiblingHintRects([]);
15474
16513
  return;
15475
16514
  }
15476
16515
  {
@@ -15480,7 +16519,7 @@ function OhhwellsBridge() {
15480
16519
  const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
15481
16520
  if (allowNavContainerHover) {
15482
16521
  const navContainer = target.closest("[data-ohw-nav-container]");
15483
- if (navContainer && !getNavigationItemAnchor(target)) {
16522
+ if (navContainer && !getNavigationItemAnchor(target) && !getLogoElement(target)) {
15484
16523
  hoveredNavContainerRef.current = navContainer;
15485
16524
  setHoveredNavContainerRect(navContainer.getBoundingClientRect());
15486
16525
  hoveredItemElRef.current = null;
@@ -15509,6 +16548,15 @@ function OhhwellsBridge() {
15509
16548
  setHoveredNavContainerRect(null);
15510
16549
  }
15511
16550
  }
16551
+ const logoEl = getLogoElement(target);
16552
+ if (logoEl) {
16553
+ hoveredNavContainerRef.current = null;
16554
+ setHoveredNavContainerRect(null);
16555
+ if (selectedElRef.current === logoEl) return;
16556
+ hoveredItemElRef.current = logoEl;
16557
+ setHoveredItemRect(getLogoInteractionRect(logoEl));
16558
+ return;
16559
+ }
15512
16560
  const navAnchor = getNavigationItemAnchor(target);
15513
16561
  if (navAnchor) {
15514
16562
  hoveredNavContainerRef.current = null;
@@ -15546,6 +16594,11 @@ function OhhwellsBridge() {
15546
16594
  setHoveredItemRect(hoverTarget.getBoundingClientRect());
15547
16595
  } else if (!isInsideNavigationItem(editable)) {
15548
16596
  hoverTarget.setAttribute("data-ohw-hovered", "");
16597
+ if (editable.closest("footer") || editable.closest('[data-ohw-section="footer"]')) {
16598
+ hoveredNavContainerRef.current = null;
16599
+ setHoveredNavContainerRect(null);
16600
+ hoveredItemElRef.current = editable;
16601
+ }
15549
16602
  }
15550
16603
  }
15551
16604
  };
@@ -15581,6 +16634,18 @@ function OhhwellsBridge() {
15581
16634
  }
15582
16635
  return;
15583
16636
  }
16637
+ const logoEl = getLogoElement(target);
16638
+ if (logoEl) {
16639
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
16640
+ if (related2 && (logoEl === related2 || logoEl.contains(related2) || related2.closest?.('[data-ohw-role="logo"], [data-ohw-logo]'))) {
16641
+ return;
16642
+ }
16643
+ if (hoveredItemElRef.current === logoEl) {
16644
+ hoveredItemElRef.current = null;
16645
+ setHoveredItemRect(null);
16646
+ }
16647
+ return;
16648
+ }
15584
16649
  const editable = target.closest("[data-ohw-editable]");
15585
16650
  if (!editable) return;
15586
16651
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -15601,6 +16666,13 @@ function OhhwellsBridge() {
15601
16666
  }
15602
16667
  } else {
15603
16668
  hoverTarget.removeAttribute("data-ohw-hovered");
16669
+ if (hoveredItemElRef.current === editable) {
16670
+ const stillOnEditable = related instanceof Element && related.closest("[data-ohw-editable]") === editable;
16671
+ if (!stillOnEditable) {
16672
+ hoveredItemElRef.current = null;
16673
+ setHoveredItemRect(null);
16674
+ }
16675
+ }
15604
16676
  }
15605
16677
  }
15606
16678
  };
@@ -15717,6 +16789,26 @@ function OhhwellsBridge() {
15717
16789
  hoveredNavContainerRef.current = null;
15718
16790
  setHoveredNavContainerRect(null);
15719
16791
  }
16792
+ const logoCandidates = [
16793
+ ...document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]'),
16794
+ ...document.querySelectorAll("nav a:not([data-ohw-href-key]), [data-ohw-nav-root] a:not([data-ohw-href-key])"),
16795
+ ...document.querySelectorAll("footer img")
16796
+ ];
16797
+ const seenLogos = /* @__PURE__ */ new Set();
16798
+ for (const candidate of logoCandidates) {
16799
+ const logo = getLogoElement(candidate);
16800
+ if (!logo || seenLogos.has(logo)) continue;
16801
+ seenLogos.add(logo);
16802
+ const r2 = logo.getBoundingClientRect();
16803
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
16804
+ hoveredNavContainerRef.current = null;
16805
+ setHoveredNavContainerRect(null);
16806
+ if (selectedElRef.current !== logo) {
16807
+ hoveredItemElRef.current = logo;
16808
+ setHoveredItemRect(getLogoInteractionRect(logo));
16809
+ }
16810
+ return;
16811
+ }
15720
16812
  const navContainers = Array.from(
15721
16813
  document.querySelectorAll("[data-ohw-nav-container]")
15722
16814
  );
@@ -15802,7 +16894,7 @@ function OhhwellsBridge() {
15802
16894
  }
15803
16895
  };
15804
16896
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
15805
- if (linkPopoverOpenRef.current) {
16897
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
15806
16898
  if (hoveredImageRef.current) {
15807
16899
  hoveredImageRef.current = null;
15808
16900
  hoveredImageHasTextOverlapRef.current = false;
@@ -16056,7 +17148,7 @@ function OhhwellsBridge() {
16056
17148
  }
16057
17149
  };
16058
17150
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
16059
- if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
17151
+ if (linkPopoverOpenRef.current || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY) || document.documentElement.hasAttribute("data-ohw-section-picking")) {
16060
17152
  if (activeStateElRef.current) {
16061
17153
  activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
16062
17154
  activeStateElRef.current = null;
@@ -16122,16 +17214,21 @@ function OhhwellsBridge() {
16122
17214
  setSectionGap(null);
16123
17215
  }
16124
17216
  };
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
17217
  const handleMouseMove = (e) => {
16133
17218
  const { clientX, clientY } = e;
16134
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17219
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17220
+ hoveredItemElRef.current = null;
17221
+ setHoveredItemRect(null);
17222
+ hoveredNavContainerRef.current = null;
17223
+ setHoveredNavContainerRect(null);
17224
+ siblingHintElRef.current = null;
17225
+ setSiblingHintRect(null);
17226
+ setSiblingHintRects([]);
17227
+ dismissImageHover();
17228
+ clearImageHover();
17229
+ setSectionGap(null);
17230
+ return;
17231
+ }
16135
17232
  probeSectionGapAt(clientX, clientY);
16136
17233
  probeImageAt(clientX, clientY);
16137
17234
  probeHoverCardsAt(clientX, clientY);
@@ -16140,7 +17237,11 @@ function OhhwellsBridge() {
16140
17237
  if (e.data?.type !== "ow:pointer-sync") return;
16141
17238
  const { clientX, clientY } = e.data;
16142
17239
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16143
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
17240
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || floatingPanelOpenRef.current && isPointOverFloatingPanel(clientX, clientY)) {
17241
+ dismissImageHover();
17242
+ clearImageHover();
17243
+ return;
17244
+ }
16144
17245
  probeSectionGapAt(clientX, clientY);
16145
17246
  probeImageAt(clientX, clientY);
16146
17247
  probeHoverCardsAt(clientX, clientY);
@@ -16390,6 +17491,15 @@ function OhhwellsBridge() {
16390
17491
  aiSectionsRef.current = content[AI_SECTIONS_KEY];
16391
17492
  applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
16392
17493
  }
17494
+ if (typeof content[BRAND_KIT_KEY] === "string") {
17495
+ brandKitRef.current = content[BRAND_KIT_KEY];
17496
+ applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17497
+ }
17498
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17499
+ stylesRef.current = content[STYLE_STORE_KEY];
17500
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17501
+ }
17502
+ applyBrandChrome(content);
16393
17503
  let sectionsJson = null;
16394
17504
  for (const [key, val] of Object.entries(content)) {
16395
17505
  if (key === "__ohw_sections") {
@@ -16397,6 +17507,11 @@ function OhhwellsBridge() {
16397
17507
  continue;
16398
17508
  }
16399
17509
  if (key === AI_SECTIONS_KEY) continue;
17510
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
17511
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17512
+ if (key === BRAND_KIT_KEY) continue;
17513
+ if (key === STYLE_STORE_KEY) continue;
17514
+ if (BRAND_CHROME_KEYS.has(key)) continue;
16400
17515
  if (applyVideoSettingNode(key, val)) continue;
16401
17516
  if (applyCarouselNode(key, val)) continue;
16402
17517
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -16416,6 +17531,8 @@ function OhhwellsBridge() {
16416
17531
  });
16417
17532
  applyLinkByKey(key, val);
16418
17533
  }
17534
+ applyLogoFromContent(content);
17535
+ applyLogoSizes(content);
16419
17536
  if (sectionsJson) {
16420
17537
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
16421
17538
  sectionsLoadedRef.current = true;
@@ -16431,6 +17548,58 @@ function OhhwellsBridge() {
16431
17548
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
16432
17549
  postToParentRef.current({ type: "ow:hydrate-done" });
16433
17550
  };
17551
+ const handleUpdateLogoIdentity = (e) => {
17552
+ if (e.data?.type !== "ow:update-logo-identity") return;
17553
+ const rawText = typeof e.data.text === "string" ? e.data.text : "";
17554
+ const alt = typeof e.data.alt === "string" ? e.data.alt : rawText;
17555
+ const href = typeof e.data.href === "string" ? e.data.href : void 0;
17556
+ const imageProvided = "image" in e.data;
17557
+ const imageUrl = imageProvided && typeof e.data.image === "string" && e.data.image.trim() ? e.data.image.trim() : imageProvided ? null : void 0;
17558
+ let isPlaceholder = e.data.isPlaceholder !== false;
17559
+ if (imageUrl) isPlaceholder = false;
17560
+ else if (imageProvided && imageUrl === null) {
17561
+ isPlaceholder = e.data.isPlaceholder === true || !rawText.trim() || resolveLogoDisplayText(rawText) === PLACEHOLDER_BUSINESS_NAME;
17562
+ }
17563
+ const display = applyLogoIdentity(rawText, isPlaceholder);
17564
+ const displayAlt = resolveLogoDisplayText(alt || display);
17565
+ if (imageUrl !== void 0) {
17566
+ applyLogoImage(imageUrl, displayAlt);
17567
+ } else {
17568
+ for (const key of LOGO_IMAGE_KEYS) {
17569
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
17570
+ const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
17571
+ if (img) img.alt = displayAlt;
17572
+ });
17573
+ }
17574
+ }
17575
+ if (href !== void 0) {
17576
+ applyLogoHref(href);
17577
+ applyLinkByKey("nav-logo-href", href);
17578
+ applyLinkByKey("footer-logo-href", href);
17579
+ applyLinkByKey("logo-href", href);
17580
+ }
17581
+ const nodes = [
17582
+ ...LOGO_TEXT_KEYS.map((key) => ({ key, text: display })),
17583
+ { key: LOGO_PLACEHOLDER_KEY, text: isPlaceholder ? "true" : "false" },
17584
+ { key: LOGO_ALT_KEY, text: displayAlt }
17585
+ ];
17586
+ if (imageUrl !== void 0) {
17587
+ if (imageUrl) {
17588
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: imageUrl });
17589
+ } else {
17590
+ for (const key of LOGO_IMAGE_KEYS) nodes.push({ key, text: "" });
17591
+ }
17592
+ }
17593
+ if (href !== void 0) {
17594
+ for (const key of LOGO_HREF_KEYS) nodes.push({ key, text: href.trim() || "/" });
17595
+ }
17596
+ editContentRef.current = {
17597
+ ...editContentRef.current,
17598
+ ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
17599
+ };
17600
+ applyLogoSizes(editContentRef.current);
17601
+ postToParentRef.current({ type: "ow:change", nodes });
17602
+ };
16434
17603
  window.addEventListener("message", handleHydrate);
16435
17604
  const postAiSectionsChanged = () => {
16436
17605
  postToParentRef.current({
@@ -16444,7 +17613,10 @@ function OhhwellsBridge() {
16444
17613
  const payload = e.data.payload;
16445
17614
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
16446
17615
  const previous = aiSectionsRef.current;
16447
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
17616
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
17617
+ ...payload,
17618
+ path: payload.path ?? window.location.pathname
17619
+ });
16448
17620
  const nextValue = serializeAiSectionsState(nextState);
16449
17621
  aiSectionsRef.current = nextValue;
16450
17622
  applyAiSectionsToDom(nextState);
@@ -16481,12 +17653,42 @@ function OhhwellsBridge() {
16481
17653
  const value = typeof e.data.value === "string" ? e.data.value : "";
16482
17654
  aiSectionsRef.current = value;
16483
17655
  applyAiSectionsToDom(parseAiSectionsState(value));
17656
+ applyStylesToDom(parseStyleStore(stylesRef.current));
16484
17657
  const restoredHeight = document.documentElement.scrollHeight;
16485
17658
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
16486
17659
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
16487
17660
  postAiSectionsChanged();
16488
17661
  };
16489
17662
  window.addEventListener("message", handleAiSetSections);
17663
+ const handleAiSetBrand = (e) => {
17664
+ if (e.data?.type !== "ow:ai-set-brand") return;
17665
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17666
+ const previous = brandKitRef.current;
17667
+ brandKitRef.current = value;
17668
+ applyBrandToDom(parseBrandKit(value));
17669
+ if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17670
+ applyStylesToDom(parseStyleStore(stylesRef.current));
17671
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17672
+ postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17673
+ };
17674
+ window.addEventListener("message", handleAiSetBrand);
17675
+ const handleAiSetStyles = (e) => {
17676
+ if (e.data?.type !== "ow:ai-set-styles") return;
17677
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17678
+ const previous = stylesRef.current;
17679
+ stylesRef.current = value;
17680
+ applyStylesToDom(parseStyleStore(value));
17681
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
17682
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
17683
+ };
17684
+ window.addEventListener("message", handleAiSetStyles);
17685
+ const handleGetBrand = (e) => {
17686
+ if (e.data?.type !== "ow:get-brand") return;
17687
+ const template = deriveTemplateBrand();
17688
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
17689
+ postToParentRef.current({ type: "ow:brand-value", value });
17690
+ };
17691
+ window.addEventListener("message", handleGetBrand);
16490
17692
  const handleDeactivate = (e) => {
16491
17693
  if (e.data?.type !== "ow:deactivate") return;
16492
17694
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -16495,6 +17697,12 @@ function OhhwellsBridge() {
16495
17697
  closeLinkPopoverRef.current();
16496
17698
  return;
16497
17699
  }
17700
+ if (floatingPanelOpenRef.current) {
17701
+ setFloatingPanelRef.current(null);
17702
+ deselectRef.current();
17703
+ deactivateRef.current();
17704
+ return;
17705
+ }
16498
17706
  deselectRef.current();
16499
17707
  deactivateRef.current();
16500
17708
  };
@@ -16548,6 +17756,10 @@ function OhhwellsBridge() {
16548
17756
  return;
16549
17757
  }
16550
17758
  if (selectedElRef.current) {
17759
+ if (toolbarVariantRef.current === "logo") {
17760
+ deselectRef.current();
17761
+ return;
17762
+ }
16551
17763
  if (toolbarVariantRef.current === "select-frame") {
16552
17764
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16553
17765
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16587,6 +17799,10 @@ function OhhwellsBridge() {
16587
17799
  return;
16588
17800
  }
16589
17801
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
17802
+ if (toolbarVariantRef.current === "logo") {
17803
+ deselectRef.current();
17804
+ return;
17805
+ }
16590
17806
  if (toolbarVariantRef.current === "select-frame") {
16591
17807
  const parent2 = getNavigationSelectionParent(selectedElRef.current);
16592
17808
  if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
@@ -16664,7 +17880,8 @@ function OhhwellsBridge() {
16664
17880
  const handleScroll = () => {
16665
17881
  const focusEl = activeElRef.current ?? selectedElRef.current;
16666
17882
  if (focusEl) {
16667
- const r2 = activeElRef.current ? getEditMeasureEl(activeElRef.current).getBoundingClientRect() : focusEl.getBoundingClientRect();
17883
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
17884
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
16668
17885
  applyToolbarPos(r2);
16669
17886
  setToolbarRect(r2);
16670
17887
  setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
@@ -16674,7 +17891,9 @@ function OhhwellsBridge() {
16674
17891
  setToggleState((prev) => prev ? { ...prev, rect } : null);
16675
17892
  }
16676
17893
  if (hoveredItemElRef.current) {
16677
- setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
17894
+ const hoverEl = hoveredItemElRef.current;
17895
+ const logo = getLogoElement(hoverEl);
17896
+ setHoveredItemRect(logo ? getLogoInteractionRect(logo) : hoverEl.getBoundingClientRect());
16678
17897
  }
16679
17898
  if (hoveredNavContainerRef.current) {
16680
17899
  setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
@@ -16718,6 +17937,12 @@ function OhhwellsBridge() {
16718
17937
  if (aiSectionsRef.current) {
16719
17938
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
16720
17939
  }
17940
+ if (stylesRef.current) {
17941
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
17942
+ }
17943
+ if (brandKitRef.current) {
17944
+ nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17945
+ }
16721
17946
  postToParentRef.current({ type: "ow:save-result", nodes });
16722
17947
  };
16723
17948
  const handleInsertSection = (e) => {
@@ -16728,8 +17953,12 @@ function OhhwellsBridge() {
16728
17953
  if (inserted) {
16729
17954
  const tracker = getSectionsTracker();
16730
17955
  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 });
17956
+ const reportHeight = () => {
17957
+ const h = document.body.scrollHeight;
17958
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
17959
+ };
17960
+ reportHeight();
17961
+ setTimeout(reportHeight, 500);
16733
17962
  }
16734
17963
  };
16735
17964
  const handleSwitchSchedule = (e) => {
@@ -16922,13 +18151,17 @@ function OhhwellsBridge() {
16922
18151
  if (e.data?.type !== "ow:parent-scroll") return;
16923
18152
  const { iframeOffsetTop, headerH, canvasH } = e.data;
16924
18153
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
18154
+ if (floatingPanelOpenRef.current) {
18155
+ setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
18156
+ }
16925
18157
  if (visibleViewportRef.current) {
16926
18158
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
16927
18159
  }
16928
18160
  const focusEl = activeElRef.current ?? selectedElRef.current;
16929
18161
  if (focusEl) {
16930
- const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : focusEl;
16931
- applyToolbarPos(measureEl.getBoundingClientRect());
18162
+ const measureEl = activeElRef.current ? getEditMeasureEl(activeElRef.current) : toolbarVariantRef.current === "logo" ? null : focusEl;
18163
+ const r2 = measureEl ? measureEl.getBoundingClientRect() : toolbarVariantRef.current === "logo" ? getLogoInteractionRect(focusEl) : focusEl.getBoundingClientRect();
18164
+ applyToolbarPos(r2);
16932
18165
  }
16933
18166
  };
16934
18167
  const handleClickAt = (e) => {
@@ -16953,6 +18186,25 @@ function OhhwellsBridge() {
16953
18186
  postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
16954
18187
  return;
16955
18188
  }
18189
+ const logoAtPoint = Array.from(
18190
+ document.querySelectorAll('[data-ohw-role="logo"], [data-ohw-logo]')
18191
+ ).map((el) => getLogoElement(el)).find((logo) => {
18192
+ if (!logo) return false;
18193
+ const r2 = logo.getBoundingClientRect();
18194
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
18195
+ });
18196
+ if (logoAtPoint) {
18197
+ if (!logoHasUploadedImage(logoAtPoint)) {
18198
+ deselectRef.current();
18199
+ deactivateRef.current();
18200
+ const identity = readLogoIdentityFromDom();
18201
+ postToParentRef.current({ type: "ow:open-logo-settings", ...identity });
18202
+ return;
18203
+ }
18204
+ selectLogoRef.current(logoAtPoint);
18205
+ openLogoSizePanelRef.current(logoAtPoint);
18206
+ return;
18207
+ }
16956
18208
  const textEditable = Array.from(
16957
18209
  document.querySelectorAll(NON_MEDIA_SELECTOR)
16958
18210
  ).find((el) => {
@@ -17024,6 +18276,14 @@ function OhhwellsBridge() {
17024
18276
  window.addEventListener("message", handleParentScroll);
17025
18277
  window.addEventListener("message", handlePointerSync);
17026
18278
  window.addEventListener("message", handleClickAt);
18279
+ window.addEventListener("message", handleUpdateLogoIdentity);
18280
+ const handleViewMode = (e) => {
18281
+ if (e.data?.type !== "ow:view-mode") return;
18282
+ const mode = e.data.mode === "Mobile" || e.data.mode === "mobile" ? "mobile" : "desktop";
18283
+ setEditorViewport(mode);
18284
+ applyLogoSizes(editContentRef.current);
18285
+ };
18286
+ window.addEventListener("message", handleViewMode);
17027
18287
  const handleViewportResize = () => {
17028
18288
  if (visibleViewportRef.current) {
17029
18289
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
@@ -17079,10 +18339,15 @@ function OhhwellsBridge() {
17079
18339
  window.removeEventListener("resize", handleViewportResize);
17080
18340
  window.removeEventListener("message", handlePointerSync);
17081
18341
  window.removeEventListener("message", handleClickAt);
18342
+ window.removeEventListener("message", handleUpdateLogoIdentity);
18343
+ window.removeEventListener("message", handleViewMode);
17082
18344
  window.removeEventListener("message", handleHydrate);
17083
18345
  window.removeEventListener("message", handleAiApplyTree);
17084
18346
  window.removeEventListener("message", handleAiDeleteSection);
17085
18347
  window.removeEventListener("message", handleAiSetSections);
18348
+ window.removeEventListener("message", handleAiSetBrand);
18349
+ window.removeEventListener("message", handleAiSetStyles);
18350
+ window.removeEventListener("message", handleGetBrand);
17086
18351
  window.removeEventListener("message", handleDeactivate);
17087
18352
  window.removeEventListener("message", handleToastAction);
17088
18353
  window.removeEventListener("message", handleUiEscape);
@@ -17286,7 +18551,7 @@ function OhhwellsBridge() {
17286
18551
  postToParent2({
17287
18552
  type: "ow:ready",
17288
18553
  version: "1",
17289
- bridgeVersion: "0.1.59",
18554
+ bridgeVersion: "0.1.61",
17290
18555
  path: pathname,
17291
18556
  nodes: collectEditableNodes(editContentRef.current),
17292
18557
  sections
@@ -17681,10 +18946,10 @@ function OhhwellsBridge() {
17681
18946
  [postToParent2]
17682
18947
  );
17683
18948
  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)(
18949
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
18950
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
18951
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
18952
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17688
18953
  MediaOverlay,
17689
18954
  {
17690
18955
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -17695,7 +18960,7 @@ function OhhwellsBridge() {
17695
18960
  },
17696
18961
  `uploading-${key}`
17697
18962
  )),
17698
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18963
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17699
18964
  MediaOverlay,
17700
18965
  {
17701
18966
  hover: mediaHover,
@@ -17704,11 +18969,11 @@ function OhhwellsBridge() {
17704
18969
  onVideoSettingsChange: handleVideoSettingsChange
17705
18970
  }
17706
18971
  ),
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)(
18972
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
18973
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
18974
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
18975
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
18976
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17712
18977
  "div",
17713
18978
  {
17714
18979
  className: "pointer-events-none fixed z-2147483646",
@@ -17718,7 +18983,7 @@ function OhhwellsBridge() {
17718
18983
  width: slot.width,
17719
18984
  height: slot.height
17720
18985
  },
17721
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18986
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17722
18987
  DropIndicator,
17723
18988
  {
17724
18989
  direction: slot.direction,
@@ -17729,7 +18994,7 @@ function OhhwellsBridge() {
17729
18994
  },
17730
18995
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
17731
18996
  )),
17732
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
18997
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17733
18998
  "div",
17734
18999
  {
17735
19000
  className: "pointer-events-none fixed z-2147483646",
@@ -17739,7 +19004,7 @@ function OhhwellsBridge() {
17739
19004
  width: slot.width,
17740
19005
  height: slot.height
17741
19006
  },
17742
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19007
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17743
19008
  DropIndicator,
17744
19009
  {
17745
19010
  direction: slot.direction,
@@ -17750,11 +19015,11 @@ function OhhwellsBridge() {
17750
19015
  },
17751
19016
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
17752
19017
  )),
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)(
19018
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
19019
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
19020
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
19021
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
19022
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17758
19023
  FooterContainerChrome,
17759
19024
  {
17760
19025
  rect: toolbarRect,
@@ -17762,7 +19027,7 @@ function OhhwellsBridge() {
17762
19027
  addDisabled: !canAddFooterColumn()
17763
19028
  }
17764
19029
  ),
17765
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19030
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17766
19031
  ItemInteractionLayer,
17767
19032
  {
17768
19033
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -17774,10 +19039,10 @@ function OhhwellsBridge() {
17774
19039
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
17775
19040
  onDragHandleDragStart: handleItemDragStart,
17776
19041
  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)(
19042
+ onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
19043
+ onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
19044
+ itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection,
19045
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17781
19046
  ItemActionToolbar,
17782
19047
  {
17783
19048
  onEditLink: openLinkPopoverForSelected,
@@ -17813,8 +19078,8 @@ function OhhwellsBridge() {
17813
19078
  ) : void 0
17814
19079
  }
17815
19080
  ),
17816
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
17817
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19081
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
19082
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17818
19083
  EditGlowChrome,
17819
19084
  {
17820
19085
  rect: toolbarRect,
@@ -17824,7 +19089,7 @@ function OhhwellsBridge() {
17824
19089
  hideHandle: isItemDragging
17825
19090
  }
17826
19091
  ),
17827
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19092
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17828
19093
  FloatingToolbar,
17829
19094
  {
17830
19095
  rect: toolbarRect,
@@ -17837,7 +19102,7 @@ function OhhwellsBridge() {
17837
19102
  }
17838
19103
  )
17839
19104
  ] }),
17840
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
19105
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17841
19106
  "div",
17842
19107
  {
17843
19108
  "data-ohw-max-badge": "",
@@ -17863,7 +19128,7 @@ function OhhwellsBridge() {
17863
19128
  ]
17864
19129
  }
17865
19130
  ),
17866
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19131
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17867
19132
  StateToggle,
17868
19133
  {
17869
19134
  rect: toggleState.rect,
@@ -17872,15 +19137,15 @@ function OhhwellsBridge() {
17872
19137
  onStateChange: handleStateChange
17873
19138
  }
17874
19139
  ),
17875
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
19140
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
17876
19141
  "div",
17877
19142
  {
17878
19143
  "data-ohw-section-insert-line": "",
17879
19144
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
17880
19145
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
17881
19146
  children: [
17882
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
17883
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19147
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
19148
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17884
19149
  Badge,
17885
19150
  {
17886
19151
  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 +19162,11 @@ function OhhwellsBridge() {
17897
19162
  children: "Add Section"
17898
19163
  }
17899
19164
  ),
17900
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
19165
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
17901
19166
  ]
17902
19167
  }
17903
19168
  ),
17904
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19169
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17905
19170
  LinkPopover,
17906
19171
  {
17907
19172
  panelRef: linkPopoverPanelRef,
@@ -17918,7 +19183,7 @@ function OhhwellsBridge() {
17918
19183
  },
17919
19184
  linkPopover.key
17920
19185
  ) : null,
17921
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19186
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17922
19187
  FloatingPanel,
17923
19188
  {
17924
19189
  open: true,
@@ -17928,7 +19193,7 @@ function OhhwellsBridge() {
17928
19193
  onPositionChange: setFloatingPanelPos,
17929
19194
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
17930
19195
  onClose: closeFloatingPanelOnly,
17931
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
19196
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17932
19197
  SocialsDisplayPanel,
17933
19198
  {
17934
19199
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -17939,11 +19204,115 @@ function OhhwellsBridge() {
17939
19204
  }
17940
19205
  )
17941
19206
  }
19207
+ ) : null,
19208
+ floatingPanel && floatingPanel.kind === "logo-size" && logoSizeDraft ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19209
+ FloatingPanel,
19210
+ {
19211
+ open: true,
19212
+ title: floatingPanel.title,
19213
+ context: floatingPanel.context,
19214
+ position: floatingPanelPos,
19215
+ onPositionChange: setFloatingPanelPos,
19216
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
19217
+ onClose: closeFloatingPanelAndDeselect,
19218
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
19219
+ LogoSizePanel,
19220
+ {
19221
+ viewport: editorViewport,
19222
+ sizePx: editorViewport === "mobile" && !logoSizeDraft.mobileFollowing ? logoSizeDraft.mobilePx : logoSizeDraft.desktopPx,
19223
+ mobileFollowing: logoSizeDraft.mobileFollowing,
19224
+ onSizeChange: (px) => {
19225
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
19226
+ ...logoSizeDraft,
19227
+ desktopPx: px,
19228
+ mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
19229
+ };
19230
+ setLogoSizeDraft(next);
19231
+ persistLogoSizeDraft(floatingPanel.placement, next);
19232
+ },
19233
+ onCustomizeMobile: () => {
19234
+ const next = {
19235
+ ...logoSizeDraft,
19236
+ mobileFollowing: false,
19237
+ mobilePx: logoSizeDraft.desktopPx
19238
+ };
19239
+ setLogoSizeDraft(next);
19240
+ persistLogoSizeDraft(floatingPanel.placement, next);
19241
+ },
19242
+ onResetMobile: () => {
19243
+ const next = {
19244
+ ...logoSizeDraft,
19245
+ mobileFollowing: true,
19246
+ mobilePx: logoSizeDraft.desktopPx
19247
+ };
19248
+ setLogoSizeDraft(next);
19249
+ persistLogoSizeDraft(floatingPanel.placement, next);
19250
+ },
19251
+ onUpdateEverywhere: () => {
19252
+ const identity = readLogoIdentityFromDom();
19253
+ postToParent2({ type: "ow:open-logo-settings", ...identity });
19254
+ }
19255
+ }
19256
+ )
19257
+ }
17942
19258
  ) : null
17943
19259
  ] }),
17944
19260
  bridgeRoot
17945
19261
  ) : null;
17946
19262
  }
19263
+
19264
+ // src/ui/EmptySection.tsx
19265
+ var import_link = __toESM(require("next/link"), 1);
19266
+ var import_jsx_runtime34 = require("react/jsx-runtime");
19267
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
19268
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
19269
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19270
+ "p",
19271
+ {
19272
+ style: {
19273
+ fontFamily: "var(--brand-font-body)",
19274
+ fontSize: "0.75rem",
19275
+ fontWeight: 500,
19276
+ letterSpacing: "0.15em",
19277
+ textTransform: "uppercase",
19278
+ color: "var(--brand-accent)",
19279
+ marginBottom: "1.5rem"
19280
+ },
19281
+ 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" }) })
19282
+ }
19283
+ ),
19284
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19285
+ "h1",
19286
+ {
19287
+ style: {
19288
+ fontFamily: "var(--brand-font-heading)",
19289
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
19290
+ lineHeight: 1.1,
19291
+ letterSpacing: "-0.025em",
19292
+ color: "var(--brand-text)",
19293
+ marginBottom: "1rem"
19294
+ },
19295
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
19296
+ children: title
19297
+ }
19298
+ ),
19299
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
19300
+ "p",
19301
+ {
19302
+ style: {
19303
+ fontFamily: "var(--brand-font-body)",
19304
+ fontSize: "1rem",
19305
+ lineHeight: 1.7,
19306
+ fontWeight: 300,
19307
+ color: "var(--brand-text-muted)",
19308
+ maxWidth: "340px"
19309
+ },
19310
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
19311
+ children: "This page doesn't have any content yet."
19312
+ }
19313
+ )
19314
+ ] });
19315
+ }
17947
19316
  // Annotate the CommonJS export names for ESM import in node:
17948
19317
  0 && (module.exports = {
17949
19318
  AI_DEFAULT_BRAND,
@@ -17961,6 +19330,7 @@ function OhhwellsBridge() {
17961
19330
  DropdownMenuItem,
17962
19331
  DropdownMenuSeparator,
17963
19332
  DropdownMenuTrigger,
19333
+ EmptySection,
17964
19334
  ItemActionToolbar,
17965
19335
  ItemInteractionLayer,
17966
19336
  LinkEditorPanel,