@ohhwells/bridge 0.1.78 → 0.1.80
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 +1059 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1059 -138
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -156,7 +156,12 @@ function parseAiSectionsState(raw) {
|
|
|
156
156
|
media: entry.media && typeof entry.media === "object" ? entry.media : {}
|
|
157
157
|
}));
|
|
158
158
|
const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
159
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
v: 1,
|
|
161
|
+
sections,
|
|
162
|
+
...removed.length ? { removed } : {},
|
|
163
|
+
...parsed.hideTemplate === true ? { hideTemplate: true } : {}
|
|
164
|
+
};
|
|
160
165
|
} catch {
|
|
161
166
|
return EMPTY_AI_SECTIONS;
|
|
162
167
|
}
|
|
@@ -169,6 +174,7 @@ function applyTreeToState(state, payload) {
|
|
|
169
174
|
const entry = {
|
|
170
175
|
id: payload.id,
|
|
171
176
|
label: payload.label ?? "Generated section",
|
|
177
|
+
...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
|
|
172
178
|
afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
|
|
173
179
|
...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
|
|
174
180
|
...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
|
|
@@ -191,6 +197,317 @@ function deleteSectionFromState(state, sectionId) {
|
|
|
191
197
|
return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
// src/lib/brand-chrome.ts
|
|
201
|
+
var BRAND_NAME_KEY = "__ohw_brand_name";
|
|
202
|
+
var BRAND_TITLE_KEY = "__ohw_site_title";
|
|
203
|
+
var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
|
|
204
|
+
var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
|
|
205
|
+
BRAND_NAME_KEY,
|
|
206
|
+
BRAND_TITLE_KEY,
|
|
207
|
+
BRAND_FAVICON_LETTER_KEY
|
|
208
|
+
]);
|
|
209
|
+
function upsertMeta(selector, attr, token, value) {
|
|
210
|
+
let el = document.head.querySelector(selector);
|
|
211
|
+
if (!el) {
|
|
212
|
+
el = document.createElement("meta");
|
|
213
|
+
el.setAttribute(attr, token);
|
|
214
|
+
document.head.appendChild(el);
|
|
215
|
+
}
|
|
216
|
+
if (el.getAttribute("content") !== value) el.setAttribute("content", value);
|
|
217
|
+
}
|
|
218
|
+
function escapeXml(value) {
|
|
219
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
220
|
+
}
|
|
221
|
+
function applyLetterFavicon(letter) {
|
|
222
|
+
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>`;
|
|
223
|
+
const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
224
|
+
let link = document.head.querySelector('link[rel="icon"]');
|
|
225
|
+
if (!link) {
|
|
226
|
+
link = document.createElement("link");
|
|
227
|
+
link.rel = "icon";
|
|
228
|
+
document.head.appendChild(link);
|
|
229
|
+
}
|
|
230
|
+
link.type = "image/svg+xml";
|
|
231
|
+
if (link.href !== href) link.href = href;
|
|
232
|
+
}
|
|
233
|
+
function applyBrandChrome(content) {
|
|
234
|
+
const name = content[BRAND_NAME_KEY];
|
|
235
|
+
if (typeof name === "string" && name.length > 0) {
|
|
236
|
+
document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
|
|
237
|
+
if (el.textContent !== name) el.textContent = name;
|
|
238
|
+
if (el.getAttribute("title") !== name) el.setAttribute("title", name);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const title = content[BRAND_TITLE_KEY];
|
|
242
|
+
if (typeof title === "string" && title.length > 0) {
|
|
243
|
+
if (document.title !== title) document.title = title;
|
|
244
|
+
upsertMeta('meta[property="og:title"]', "property", "og:title", title);
|
|
245
|
+
upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
|
|
246
|
+
}
|
|
247
|
+
const letter = content[BRAND_FAVICON_LETTER_KEY];
|
|
248
|
+
if (typeof letter === "string" && letter.length > 0) {
|
|
249
|
+
applyLetterFavicon(letter);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/lib/brand-kit.ts
|
|
254
|
+
var BRAND_KIT_KEY = "__ohw_brand";
|
|
255
|
+
var BRAND_VAR_PREFIX = "--ohw-brand-";
|
|
256
|
+
var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
|
|
257
|
+
(role) => `${BRAND_VAR_PREFIX}${role}`
|
|
258
|
+
);
|
|
259
|
+
var FONT_VARS = {
|
|
260
|
+
heading: ["--font-heading", "--font-display", "--brand-font-heading"],
|
|
261
|
+
body: ["--font-body", "--brand-font-body"]
|
|
262
|
+
};
|
|
263
|
+
var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
|
|
264
|
+
function brandColorVars(kit) {
|
|
265
|
+
const { dark, primary, accent, light } = kit.palette;
|
|
266
|
+
const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
|
|
267
|
+
return {
|
|
268
|
+
[`${BRAND_VAR_PREFIX}primary`]: primary,
|
|
269
|
+
[`${BRAND_VAR_PREFIX}accent`]: accent,
|
|
270
|
+
[`${BRAND_VAR_PREFIX}light`]: light,
|
|
271
|
+
[`${BRAND_VAR_PREFIX}dark`]: dark,
|
|
272
|
+
[`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
|
|
273
|
+
[`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
|
|
274
|
+
[`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function parseBrandKit(raw) {
|
|
278
|
+
if (!raw) return null;
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(raw);
|
|
281
|
+
const p = parsed?.palette;
|
|
282
|
+
const f = parsed?.fonts;
|
|
283
|
+
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") {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
|
|
288
|
+
fonts: { heading: f.heading, body: f.body }
|
|
289
|
+
};
|
|
290
|
+
} catch {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function familyOf(stack) {
|
|
295
|
+
const first = stack.split(",")[0]?.trim() ?? "";
|
|
296
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
297
|
+
}
|
|
298
|
+
function loadBrandFonts(families) {
|
|
299
|
+
const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
|
|
300
|
+
if (unique.length === 0) return;
|
|
301
|
+
const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
|
|
302
|
+
const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
|
|
303
|
+
let link = document.getElementById(BRAND_FONT_LINK_ID);
|
|
304
|
+
if (!link) {
|
|
305
|
+
link = document.createElement("link");
|
|
306
|
+
link.id = BRAND_FONT_LINK_ID;
|
|
307
|
+
link.rel = "stylesheet";
|
|
308
|
+
document.head.appendChild(link);
|
|
309
|
+
}
|
|
310
|
+
if (link.href !== href) link.href = href;
|
|
311
|
+
}
|
|
312
|
+
function applyBrandToDom(kit) {
|
|
313
|
+
const root = document.documentElement;
|
|
314
|
+
if (!kit) {
|
|
315
|
+
for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
|
|
316
|
+
for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
|
|
317
|
+
document.getElementById(BRAND_FONT_LINK_ID)?.remove();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
|
|
321
|
+
for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
|
|
322
|
+
for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
|
|
323
|
+
loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/lib/section-styles.ts
|
|
327
|
+
var STYLE_STORE_KEY = "__ohw_styles";
|
|
328
|
+
var STYLE_SHEET_ID = "ohw-section-styles";
|
|
329
|
+
function parseStyleStore(raw) {
|
|
330
|
+
if (!raw) return null;
|
|
331
|
+
try {
|
|
332
|
+
const parsed = JSON.parse(raw);
|
|
333
|
+
if (parsed?.v !== 1) return null;
|
|
334
|
+
return {
|
|
335
|
+
v: 1,
|
|
336
|
+
sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
|
|
337
|
+
nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
|
|
338
|
+
};
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
var BG_VALUES = {
|
|
344
|
+
surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
|
|
345
|
+
accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
|
|
346
|
+
"accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
|
|
347
|
+
};
|
|
348
|
+
var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
|
|
349
|
+
function styleSheetCss() {
|
|
350
|
+
const rules = [];
|
|
351
|
+
for (const [tone, value] of Object.entries(BG_VALUES)) {
|
|
352
|
+
rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
|
|
353
|
+
}
|
|
354
|
+
rules.push(
|
|
355
|
+
`[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
|
|
356
|
+
);
|
|
357
|
+
rules.push(
|
|
358
|
+
`[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
|
|
359
|
+
`[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
|
|
360
|
+
);
|
|
361
|
+
for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
|
|
362
|
+
rules.push(
|
|
363
|
+
`[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
rules.push(
|
|
367
|
+
`[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
|
|
368
|
+
);
|
|
369
|
+
for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
|
|
370
|
+
rules.push(
|
|
371
|
+
`[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const pad = { tight: 40, balanced: 64, airy: 96 };
|
|
375
|
+
for (const [spacing, px] of Object.entries(pad)) {
|
|
376
|
+
rules.push(
|
|
377
|
+
`[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return rules.join("\n");
|
|
381
|
+
}
|
|
382
|
+
var STYLE_FONT_LINK_ID = "ohw-style-fonts";
|
|
383
|
+
function loadStyleFonts(families) {
|
|
384
|
+
const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
|
|
385
|
+
const existing = document.getElementById(STYLE_FONT_LINK_ID);
|
|
386
|
+
if (unique.length === 0) {
|
|
387
|
+
existing?.remove();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
|
|
391
|
+
const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
|
|
392
|
+
let link = existing;
|
|
393
|
+
if (!link) {
|
|
394
|
+
link = document.createElement("link");
|
|
395
|
+
link.id = STYLE_FONT_LINK_ID;
|
|
396
|
+
link.rel = "stylesheet";
|
|
397
|
+
document.head.appendChild(link);
|
|
398
|
+
}
|
|
399
|
+
if (link.href !== href) link.href = href;
|
|
400
|
+
}
|
|
401
|
+
var SECTION_ATTRS = {
|
|
402
|
+
sectionBackground: "data-ohw-style-bg",
|
|
403
|
+
textDistribution: "data-ohw-style-distribution",
|
|
404
|
+
headlineScale: "data-ohw-style-headline",
|
|
405
|
+
imageAspect: "data-ohw-style-aspect",
|
|
406
|
+
spacing: "data-ohw-style-spacing"
|
|
407
|
+
};
|
|
408
|
+
var NODE_WROTE_ATTR = "data-ohw-style-node";
|
|
409
|
+
var NODE_PROPS = ["color", "font-family", "font-size", "background"];
|
|
410
|
+
function saveInline(el, prop) {
|
|
411
|
+
const attr = `data-ohw-style-prev-${prop}`;
|
|
412
|
+
if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
|
|
413
|
+
}
|
|
414
|
+
function restoreInline(el, prop) {
|
|
415
|
+
const attr = `data-ohw-style-prev-${prop}`;
|
|
416
|
+
if (!el.hasAttribute(attr)) return;
|
|
417
|
+
const prev = el.getAttribute(attr) ?? "";
|
|
418
|
+
if (prev) el.style.setProperty(prop, prev);
|
|
419
|
+
else el.style.removeProperty(prop);
|
|
420
|
+
el.removeAttribute(attr);
|
|
421
|
+
}
|
|
422
|
+
function ensureStyleSheet() {
|
|
423
|
+
let el = document.getElementById(STYLE_SHEET_ID);
|
|
424
|
+
if (!el) {
|
|
425
|
+
el = document.createElement("style");
|
|
426
|
+
el.id = STYLE_SHEET_ID;
|
|
427
|
+
document.head.appendChild(el);
|
|
428
|
+
}
|
|
429
|
+
const css = styleSheetCss();
|
|
430
|
+
if (el.textContent !== css) el.textContent = css;
|
|
431
|
+
}
|
|
432
|
+
function clearSectionAttrs(root) {
|
|
433
|
+
for (const attr of Object.values(SECTION_ATTRS)) {
|
|
434
|
+
for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
|
|
435
|
+
}
|
|
436
|
+
for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
|
|
437
|
+
restoreInline(el, "background");
|
|
438
|
+
el.removeAttribute("data-ohw-style-bgcolor");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function clearNodeProps(root) {
|
|
442
|
+
for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
|
|
443
|
+
const h = el;
|
|
444
|
+
for (const prop of NODE_PROPS) restoreInline(h, prop);
|
|
445
|
+
h.removeAttribute(NODE_WROTE_ATTR);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function buttonSurfaceOf(el) {
|
|
449
|
+
return el.closest("a, button") ?? el;
|
|
450
|
+
}
|
|
451
|
+
function applyStylesToDom(store) {
|
|
452
|
+
ensureStyleSheet();
|
|
453
|
+
clearSectionAttrs(document);
|
|
454
|
+
clearNodeProps(document);
|
|
455
|
+
loadStyleFonts(
|
|
456
|
+
store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
|
|
457
|
+
);
|
|
458
|
+
if (!store) return;
|
|
459
|
+
for (const [sectionId, override] of Object.entries(store.sections)) {
|
|
460
|
+
const sections = document.querySelectorAll(
|
|
461
|
+
`[data-ohw-section="${CSS.escape(sectionId)}"]`
|
|
462
|
+
);
|
|
463
|
+
for (const section of Array.from(sections)) {
|
|
464
|
+
for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
|
|
465
|
+
const value = override[prop];
|
|
466
|
+
if (value === void 0) continue;
|
|
467
|
+
if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
|
|
468
|
+
section.setAttribute(attr, String(value).replace(":", "-"));
|
|
469
|
+
}
|
|
470
|
+
if (override.sectionBackgroundColor !== void 0) {
|
|
471
|
+
saveInline(section, "background");
|
|
472
|
+
section.style.setProperty("background", override.sectionBackgroundColor, "important");
|
|
473
|
+
section.setAttribute("data-ohw-style-bgcolor", "");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
for (const [key, override] of Object.entries(store.nodes)) {
|
|
478
|
+
const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
|
|
479
|
+
for (const el of Array.from(nodes)) {
|
|
480
|
+
if (override.color !== void 0) {
|
|
481
|
+
saveInline(el, "color");
|
|
482
|
+
el.style.setProperty("color", override.color, "important");
|
|
483
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
484
|
+
}
|
|
485
|
+
if (override.fontFamily !== void 0) {
|
|
486
|
+
saveInline(el, "font-family");
|
|
487
|
+
el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
|
|
488
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
489
|
+
}
|
|
490
|
+
if (override.fontSize !== void 0) {
|
|
491
|
+
saveInline(el, "font-size");
|
|
492
|
+
el.style.setProperty("font-size", `${override.fontSize}px`, "important");
|
|
493
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
494
|
+
}
|
|
495
|
+
if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
|
|
496
|
+
const surface = buttonSurfaceOf(el);
|
|
497
|
+
if (override.buttonBackground !== void 0) {
|
|
498
|
+
saveInline(surface, "background");
|
|
499
|
+
surface.style.setProperty("background", override.buttonBackground, "important");
|
|
500
|
+
}
|
|
501
|
+
if (override.buttonText !== void 0) {
|
|
502
|
+
saveInline(surface, "color");
|
|
503
|
+
surface.style.setProperty("color", override.buttonText, "important");
|
|
504
|
+
}
|
|
505
|
+
surface.setAttribute(NODE_WROTE_ATTR, "");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
194
511
|
// src/ui/ai-tree/aiSectionsManager.tsx
|
|
195
512
|
var import_react_dom = require("react-dom");
|
|
196
513
|
var import_client = require("react-dom/client");
|
|
@@ -205,7 +522,8 @@ function lucideByName(name) {
|
|
|
205
522
|
}
|
|
206
523
|
var typeStyle = (spec, font) => ({
|
|
207
524
|
fontFamily: font,
|
|
208
|
-
|
|
525
|
+
// Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
|
|
526
|
+
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
527
|
lineHeight: spec.line,
|
|
210
528
|
fontWeight: spec.weight
|
|
211
529
|
});
|
|
@@ -214,26 +532,61 @@ var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.t
|
|
|
214
532
|
var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
|
|
215
533
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
|
|
216
534
|
)}`;
|
|
535
|
+
var AI_MOBILE_CSS = [
|
|
536
|
+
"@media (max-width: 768px){",
|
|
537
|
+
"[data-ai-section]{overflow-x:hidden}",
|
|
538
|
+
"[data-ai-container]{padding:0 20px !important}",
|
|
539
|
+
"[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
|
|
540
|
+
"[data-ai-cell]{width:100%;min-width:0}",
|
|
541
|
+
"[data-ai-grid]{grid-template-columns:1fr !important}",
|
|
542
|
+
// Group containers flatten to a column on phones; span placements come along for free.
|
|
543
|
+
"[data-ai-group]{display:flex !important;flex-direction:column !important}",
|
|
544
|
+
"[data-ai-group] > *{grid-column:auto !important}",
|
|
545
|
+
"[data-ai-section] img{max-width:100%}",
|
|
546
|
+
"}",
|
|
547
|
+
"@media (min-width: 769px) and (max-width: 1024px){",
|
|
548
|
+
"[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
|
|
549
|
+
"}"
|
|
550
|
+
].join("");
|
|
217
551
|
var FEATURE_LINE_CSS = [
|
|
218
552
|
"[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
|
|
219
553
|
'[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
|
|
220
554
|
`background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
|
|
221
555
|
`mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
|
|
222
556
|
].join("");
|
|
557
|
+
function hexLuminance(color) {
|
|
558
|
+
const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
|
|
559
|
+
if (!m) return null;
|
|
560
|
+
const [r2, g, b] = [0, 2, 4].map((i) => {
|
|
561
|
+
const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
|
|
562
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
563
|
+
});
|
|
564
|
+
return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
565
|
+
}
|
|
566
|
+
function hexContrast(a, b) {
|
|
567
|
+
const la = hexLuminance(a);
|
|
568
|
+
const lb = hexLuminance(b);
|
|
569
|
+
if (la === null || lb === null) return null;
|
|
570
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
571
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
572
|
+
}
|
|
573
|
+
function accentBandContext(brand) {
|
|
574
|
+
const p = brand.palette;
|
|
575
|
+
const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
|
|
576
|
+
if (lightWins) {
|
|
577
|
+
return {
|
|
578
|
+
brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
|
|
579
|
+
buttonLabel: p.primary
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
|
|
584
|
+
buttonLabel: p.light
|
|
585
|
+
};
|
|
586
|
+
}
|
|
223
587
|
function textAttrs(ctx, path) {
|
|
224
588
|
return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
|
|
225
589
|
}
|
|
226
|
-
var AI_RESPONSIVE_CSS = [
|
|
227
|
-
"@media (max-width: 960px) {",
|
|
228
|
-
" [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
|
|
229
|
-
' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
|
|
230
|
-
"}",
|
|
231
|
-
"@media (max-width: 640px) {",
|
|
232
|
-
" [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
|
|
233
|
-
" [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
|
|
234
|
-
" [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
|
|
235
|
-
"}"
|
|
236
|
-
].join("\n");
|
|
237
590
|
var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
238
591
|
function MediaBox({
|
|
239
592
|
refValue,
|
|
@@ -246,17 +599,13 @@ function MediaBox({
|
|
|
246
599
|
const url = refValue ? ctx.resolveMedia(refValue) : null;
|
|
247
600
|
const isIcon = /^(lucide|simple):/.test(refValue);
|
|
248
601
|
const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
|
|
249
|
-
const editAttrs = ctx.keyFor && editPath ? {
|
|
250
|
-
"data-ohw-key": ctx.keyFor(editPath),
|
|
251
|
-
"data-ohw-editable": isIcon ? "icon" : "image"
|
|
252
|
-
} : {};
|
|
602
|
+
const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
|
|
253
603
|
if (isIcon) {
|
|
254
604
|
const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
|
|
255
605
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
256
606
|
"span",
|
|
257
607
|
{
|
|
258
608
|
"data-ai-icon": refValue,
|
|
259
|
-
...editAttrs,
|
|
260
609
|
style: {
|
|
261
610
|
display: "inline-flex",
|
|
262
611
|
width: 48,
|
|
@@ -309,7 +658,7 @@ function ButtonEl({
|
|
|
309
658
|
}) {
|
|
310
659
|
const secondary = slots.variant === "secondary";
|
|
311
660
|
const href = str(slots.href);
|
|
312
|
-
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
|
|
661
|
+
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
|
|
313
662
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
314
663
|
"a",
|
|
315
664
|
{
|
|
@@ -325,7 +674,7 @@ function ButtonEl({
|
|
|
325
674
|
textDecoration: "none",
|
|
326
675
|
cursor: "pointer",
|
|
327
676
|
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
|
|
328
|
-
...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
|
|
677
|
+
...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
|
|
329
678
|
},
|
|
330
679
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
331
680
|
}
|
|
@@ -831,7 +1180,24 @@ function CardBlock({ node, ctx, path }) {
|
|
|
831
1180
|
minWidth: 0
|
|
832
1181
|
},
|
|
833
1182
|
children: [
|
|
834
|
-
media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1183
|
+
media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1184
|
+
"div",
|
|
1185
|
+
{
|
|
1186
|
+
style: (
|
|
1187
|
+
// An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
|
|
1188
|
+
// text to the far side. Photos keep the half-and-half split. The inset has no
|
|
1189
|
+
// inner padding (the photo split absorbed that), so the icon carries its own gap.
|
|
1190
|
+
/^(lucide|simple):/.test(mediaRef) ? {
|
|
1191
|
+
flexShrink: 0,
|
|
1192
|
+
display: "flex",
|
|
1193
|
+
alignItems: "center",
|
|
1194
|
+
padding: mediaInset,
|
|
1195
|
+
[mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
|
|
1196
|
+
} : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
|
|
1197
|
+
),
|
|
1198
|
+
children: media
|
|
1199
|
+
}
|
|
1200
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
835
1201
|
"div",
|
|
836
1202
|
{
|
|
837
1203
|
style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
|
|
@@ -922,13 +1288,44 @@ function AccordionBlock({ node, ctx, path }) {
|
|
|
922
1288
|
) })
|
|
923
1289
|
] }, i)) });
|
|
924
1290
|
}
|
|
1291
|
+
function useIsMobile() {
|
|
1292
|
+
const [mobile, setMobile] = import_react.default.useState(
|
|
1293
|
+
() => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
|
|
1294
|
+
);
|
|
1295
|
+
import_react.default.useEffect(() => {
|
|
1296
|
+
const mq = window.matchMedia("(max-width: 768px)");
|
|
1297
|
+
const update = () => setMobile(mq.matches);
|
|
1298
|
+
update();
|
|
1299
|
+
mq.addEventListener("change", update);
|
|
1300
|
+
return () => mq.removeEventListener("change", update);
|
|
1301
|
+
}, []);
|
|
1302
|
+
return mobile;
|
|
1303
|
+
}
|
|
925
1304
|
function Carousel({ items, itemsPerRow, ctx }) {
|
|
1305
|
+
const isMobile = useIsMobile();
|
|
1306
|
+
const perPage = isMobile ? 1 : itemsPerRow;
|
|
1307
|
+
const pages = Math.max(1, Math.ceil(items.length / perPage));
|
|
926
1308
|
const [page, setPage] = import_react.default.useState(0);
|
|
927
|
-
const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
|
|
928
1309
|
const current = Math.min(page, pages - 1);
|
|
1310
|
+
if (pages <= 1) {
|
|
1311
|
+
const cols = Math.max(1, Math.min(items.length, itemsPerRow));
|
|
1312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1313
|
+
"div",
|
|
1314
|
+
{
|
|
1315
|
+
"data-ai-grid": String(cols),
|
|
1316
|
+
style: {
|
|
1317
|
+
display: "grid",
|
|
1318
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
1319
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1320
|
+
alignItems: "start"
|
|
1321
|
+
},
|
|
1322
|
+
children: items
|
|
1323
|
+
}
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
929
1326
|
const pageGroups = Array.from(
|
|
930
1327
|
{ length: pages },
|
|
931
|
-
(_, p) => items.slice(p *
|
|
1328
|
+
(_, p) => items.slice(p * perPage, (p + 1) * perPage)
|
|
932
1329
|
);
|
|
933
1330
|
const chrome = (enabled) => ({
|
|
934
1331
|
border: `1px solid ${ctx.brand.palette.dark}`,
|
|
@@ -953,55 +1350,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
|
|
|
953
1350
|
cursor: "pointer",
|
|
954
1351
|
padding: 0
|
|
955
1352
|
});
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
966
|
-
}
|
|
967
|
-
),
|
|
968
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1353
|
+
const viewport = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1354
|
+
"div",
|
|
1355
|
+
{
|
|
1356
|
+
style: {
|
|
1357
|
+
display: "flex",
|
|
1358
|
+
transform: `translateX(-${current * 100}%)`,
|
|
1359
|
+
transition: "transform 0.4s ease"
|
|
1360
|
+
},
|
|
1361
|
+
children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
969
1362
|
"div",
|
|
970
1363
|
{
|
|
1364
|
+
"data-ai-grid": String(perPage),
|
|
971
1365
|
style: {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1366
|
+
flex: "0 0 100%",
|
|
1367
|
+
display: "grid",
|
|
1368
|
+
gridTemplateColumns: `repeat(${perPage}, 1fr)`,
|
|
1369
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1370
|
+
alignItems: "start"
|
|
975
1371
|
},
|
|
976
|
-
children:
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1372
|
+
children: group
|
|
1373
|
+
},
|
|
1374
|
+
p
|
|
1375
|
+
))
|
|
1376
|
+
}
|
|
1377
|
+
) });
|
|
1378
|
+
const prevBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1379
|
+
"button",
|
|
1380
|
+
{
|
|
1381
|
+
type: "button",
|
|
1382
|
+
"aria-label": "Previous",
|
|
1383
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
1384
|
+
style: chrome(current > 0),
|
|
1385
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1386
|
+
}
|
|
1387
|
+
);
|
|
1388
|
+
const nextBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1389
|
+
"button",
|
|
1390
|
+
{
|
|
1391
|
+
type: "button",
|
|
1392
|
+
"aria-label": "Next",
|
|
1393
|
+
onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
|
|
1394
|
+
style: chrome(current < pages - 1),
|
|
1395
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1396
|
+
}
|
|
1397
|
+
);
|
|
1398
|
+
const dots = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) });
|
|
1399
|
+
if (isMobile) {
|
|
1400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
|
|
1401
|
+
viewport,
|
|
1402
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
|
|
1403
|
+
prevBtn,
|
|
1404
|
+
nextBtn
|
|
1405
|
+
] }),
|
|
1406
|
+
dots
|
|
1407
|
+
] });
|
|
1408
|
+
}
|
|
1409
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
|
|
1410
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
|
|
1411
|
+
prevBtn,
|
|
1412
|
+
viewport,
|
|
1413
|
+
nextBtn
|
|
1003
1414
|
] }),
|
|
1004
|
-
|
|
1415
|
+
dots
|
|
1005
1416
|
] });
|
|
1006
1417
|
}
|
|
1007
1418
|
function CollectionBlock({ node, ctx, path }) {
|
|
@@ -1080,7 +1491,7 @@ function CollectionBlock({ node, ctx, path }) {
|
|
|
1080
1491
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1081
1492
|
"div",
|
|
1082
1493
|
{
|
|
1083
|
-
"data-ai-grid":
|
|
1494
|
+
"data-ai-grid": "",
|
|
1084
1495
|
style: {
|
|
1085
1496
|
display: "grid",
|
|
1086
1497
|
gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
|
|
@@ -1095,6 +1506,49 @@ function renderNode(node, ctx, path) {
|
|
|
1095
1506
|
switch (node.type) {
|
|
1096
1507
|
case "text":
|
|
1097
1508
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
|
|
1509
|
+
// Layout container: arranges child blocks, contributes no content of its own. `grid` is a
|
|
1510
|
+
// nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
|
|
1511
|
+
// mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
|
|
1512
|
+
// is a column. Children render through this same dispatcher, so edit markers, media
|
|
1513
|
+
// resolution, and copy paths all work unchanged inside a group.
|
|
1514
|
+
case "group": {
|
|
1515
|
+
const layout = str(slots.layout);
|
|
1516
|
+
const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
|
|
1517
|
+
const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1518
|
+
"div",
|
|
1519
|
+
{
|
|
1520
|
+
style: layout === "grid" ? {
|
|
1521
|
+
gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
|
|
1522
|
+
minWidth: 0
|
|
1523
|
+
} : { minWidth: 0 },
|
|
1524
|
+
children: renderNode(child, ctx, `${path}.c${i}`)
|
|
1525
|
+
},
|
|
1526
|
+
i
|
|
1527
|
+
));
|
|
1528
|
+
if (layout === "grid") {
|
|
1529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1530
|
+
"div",
|
|
1531
|
+
{
|
|
1532
|
+
"data-ai-group": "grid",
|
|
1533
|
+
style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
|
|
1534
|
+
children: kids
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
if (layout === "split") {
|
|
1539
|
+
const ratio = str(slots.ratio);
|
|
1540
|
+
const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
|
|
1541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1542
|
+
"div",
|
|
1543
|
+
{
|
|
1544
|
+
"data-ai-group": "split",
|
|
1545
|
+
style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
|
|
1546
|
+
children: kids
|
|
1547
|
+
}
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
|
|
1551
|
+
}
|
|
1098
1552
|
case "button":
|
|
1099
1553
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
|
|
1100
1554
|
case "button-row":
|
|
@@ -1175,33 +1629,111 @@ function renderNode(node, ctx, path) {
|
|
|
1175
1629
|
}
|
|
1176
1630
|
);
|
|
1177
1631
|
}
|
|
1178
|
-
case "form":
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1632
|
+
case "form": {
|
|
1633
|
+
const formAttrs = ctx.keyFor ? {
|
|
1634
|
+
"data-ohw-editable": "form",
|
|
1635
|
+
"data-ohw-key": ctx.keyFor(`${path}.form`),
|
|
1636
|
+
"data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
|
|
1637
|
+
} : {};
|
|
1638
|
+
const fieldStyle = {
|
|
1639
|
+
width: "100%",
|
|
1640
|
+
boxSizing: "border-box",
|
|
1641
|
+
border: `1px solid color-mix(in srgb, ${ctx.brand.palette.dark} 45%, #ffffff)`,
|
|
1642
|
+
borderRadius: 0,
|
|
1643
|
+
padding: 12,
|
|
1644
|
+
background: "#fff",
|
|
1645
|
+
color: ctx.brand.palette.dark,
|
|
1646
|
+
outline: "none",
|
|
1647
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
|
|
1648
|
+
};
|
|
1649
|
+
const labelStyle = {
|
|
1650
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
|
|
1651
|
+
color: ctx.brand.palette.dark,
|
|
1652
|
+
textAlign: "left",
|
|
1653
|
+
width: "100%"
|
|
1654
|
+
};
|
|
1655
|
+
const centered = ctx.sectionAlignment === "center";
|
|
1656
|
+
const submitAlign = centered ? "center" : "flex-start";
|
|
1657
|
+
const children = node.children ?? [];
|
|
1658
|
+
return (
|
|
1659
|
+
// 32px between the field group and the submit. In a stacked (centered) section the form is
|
|
1660
|
+
// capped at 780px and centered — the section's 12-col grid would otherwise leave it hugging
|
|
1661
|
+
// the left edge; a split section lets it fill its own column.
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1663
|
+
"form",
|
|
1664
|
+
{
|
|
1665
|
+
...formAttrs,
|
|
1666
|
+
"data-ai-form": "",
|
|
1667
|
+
style: {
|
|
1668
|
+
display: "flex",
|
|
1669
|
+
flexDirection: "column",
|
|
1670
|
+
gap: 32,
|
|
1671
|
+
width: "100%",
|
|
1672
|
+
...centered ? { maxWidth: 780, marginLeft: "auto", marginRight: "auto" } : {}
|
|
1673
|
+
},
|
|
1674
|
+
children: [
|
|
1675
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 24, width: "100%", alignItems: "flex-start" }, children: children.map((child, i) => {
|
|
1676
|
+
if (child.type !== "input") return null;
|
|
1677
|
+
const cs = child.slots ?? {};
|
|
1678
|
+
const kind = str(cs.kind);
|
|
1679
|
+
const label = str(cs.label);
|
|
1680
|
+
const placeholder = str(cs.placeholder);
|
|
1681
|
+
const required = cs.required === true;
|
|
1682
|
+
const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
|
|
1683
|
+
const isTextarea = kind === "textarea";
|
|
1684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8, width: "100%" }, children: [
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
|
|
1686
|
+
isTextarea ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1687
|
+
"textarea",
|
|
1688
|
+
{
|
|
1689
|
+
name,
|
|
1690
|
+
placeholder,
|
|
1691
|
+
required,
|
|
1692
|
+
style: { ...fieldStyle, height: 180, resize: "vertical" }
|
|
1693
|
+
}
|
|
1694
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1695
|
+
"input",
|
|
1696
|
+
{
|
|
1697
|
+
name,
|
|
1698
|
+
type: kind === "email" ? "email" : "text",
|
|
1699
|
+
placeholder,
|
|
1700
|
+
required,
|
|
1701
|
+
style: { ...fieldStyle, height: 48 }
|
|
1702
|
+
}
|
|
1703
|
+
)
|
|
1704
|
+
] }, i);
|
|
1705
|
+
}) }),
|
|
1706
|
+
children.map((child, i) => {
|
|
1707
|
+
if (child.type === "input") return null;
|
|
1708
|
+
const cs = child.slots ?? {};
|
|
1709
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1710
|
+
"button",
|
|
1711
|
+
{
|
|
1712
|
+
type: "submit",
|
|
1713
|
+
style: {
|
|
1714
|
+
alignSelf: submitAlign,
|
|
1715
|
+
border: "none",
|
|
1716
|
+
cursor: "pointer",
|
|
1717
|
+
padding: "12px 24px",
|
|
1718
|
+
// Corner radius follows the host template's own buttons (measured from a template
|
|
1719
|
+
// CTA); 8px only when the page has no template button to match.
|
|
1720
|
+
borderRadius: ctx.buttonRadius ?? 8,
|
|
1721
|
+
// Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
|
|
1722
|
+
// reads correctly on custom palettes.
|
|
1723
|
+
background: ctx.brand.palette.primary,
|
|
1724
|
+
color: ctx.buttonLabel ?? ctx.brand.palette.light,
|
|
1725
|
+
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
1726
|
+
},
|
|
1727
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
1728
|
+
},
|
|
1729
|
+
i
|
|
1730
|
+
);
|
|
1731
|
+
})
|
|
1732
|
+
]
|
|
1733
|
+
}
|
|
1734
|
+
)
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1205
1737
|
case "schedule-widget":
|
|
1206
1738
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1207
1739
|
"div",
|
|
@@ -1222,16 +1754,27 @@ function renderNode(node, ctx, path) {
|
|
|
1222
1754
|
return null;
|
|
1223
1755
|
}
|
|
1224
1756
|
}
|
|
1225
|
-
function AiTreeRenderer({
|
|
1757
|
+
function AiTreeRenderer({
|
|
1758
|
+
tree,
|
|
1759
|
+
brand,
|
|
1760
|
+
buttonRadius,
|
|
1761
|
+
resolveMedia,
|
|
1762
|
+
editKeyPrefix
|
|
1763
|
+
}) {
|
|
1226
1764
|
if (!isRenderableTree(tree)) {
|
|
1227
1765
|
return null;
|
|
1228
1766
|
}
|
|
1229
1767
|
const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
|
|
1768
|
+
const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
|
|
1769
|
+
const blockBrand = band?.brand ?? resolvedBrand;
|
|
1230
1770
|
const ctx = {
|
|
1231
|
-
brand:
|
|
1771
|
+
brand: blockBrand,
|
|
1232
1772
|
resolveMedia: resolveMedia ?? (() => null),
|
|
1233
|
-
cardSurface:
|
|
1234
|
-
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
|
|
1773
|
+
cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
|
|
1774
|
+
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
|
|
1775
|
+
sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
|
|
1776
|
+
buttonRadius,
|
|
1777
|
+
...band ? { buttonLabel: band.buttonLabel } : {}
|
|
1235
1778
|
};
|
|
1236
1779
|
const settings = tree.settings ?? {};
|
|
1237
1780
|
const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
|
|
@@ -1239,27 +1782,41 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1239
1782
|
const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
|
|
1240
1783
|
const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
|
|
1241
1784
|
const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
|
|
1785
|
+
const toneBackground = (() => {
|
|
1786
|
+
const { dark, primary, light } = resolvedBrand.palette;
|
|
1787
|
+
switch (settings.sectionBackground) {
|
|
1788
|
+
case "surface":
|
|
1789
|
+
return `color-mix(in srgb, ${light} 94%, ${dark})`;
|
|
1790
|
+
case "accent":
|
|
1791
|
+
return primary;
|
|
1792
|
+
case "accent-soft":
|
|
1793
|
+
return `color-mix(in srgb, ${primary} 12%, ${light})`;
|
|
1794
|
+
default:
|
|
1795
|
+
return void 0;
|
|
1796
|
+
}
|
|
1797
|
+
})();
|
|
1798
|
+
const distributed = !isOverlay && settings.textDistribution;
|
|
1242
1799
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1243
1800
|
"section",
|
|
1244
1801
|
{
|
|
1245
1802
|
"data-ai-section": tree.tag ?? "",
|
|
1246
1803
|
...bgAttrs,
|
|
1247
|
-
"data-ai-responsive": "",
|
|
1248
1804
|
style: {
|
|
1249
1805
|
position: "relative",
|
|
1250
1806
|
padding: `${pad}px 0`,
|
|
1251
|
-
background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
|
|
1807
|
+
background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
|
|
1252
1808
|
backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
|
|
1253
1809
|
backgroundSize: "cover",
|
|
1254
|
-
backgroundPosition: "center"
|
|
1810
|
+
backgroundPosition: "center",
|
|
1811
|
+
color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
|
|
1255
1812
|
},
|
|
1256
1813
|
children: [
|
|
1257
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
|
|
1258
1814
|
isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
|
|
1815
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
|
|
1259
1816
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1260
1817
|
"div",
|
|
1261
1818
|
{
|
|
1262
|
-
"data-ai-
|
|
1819
|
+
"data-ai-container": "",
|
|
1263
1820
|
style: {
|
|
1264
1821
|
position: "relative",
|
|
1265
1822
|
maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
|
|
@@ -1270,15 +1827,29 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1270
1827
|
children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1271
1828
|
"div",
|
|
1272
1829
|
{
|
|
1273
|
-
"data-ai-
|
|
1830
|
+
"data-ai-row": "",
|
|
1274
1831
|
style: {
|
|
1275
1832
|
display: "grid",
|
|
1276
1833
|
gridTemplateColumns: "repeat(12, 1fr)",
|
|
1277
1834
|
gap: AI_TREE_TOKENS.spacing6,
|
|
1278
|
-
alignItems: settings.verticalPosition === "top" ? "start" : "center",
|
|
1835
|
+
alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
|
|
1279
1836
|
marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
|
|
1280
1837
|
},
|
|
1281
|
-
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1838
|
+
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1839
|
+
"div",
|
|
1840
|
+
{
|
|
1841
|
+
"data-ai-cell": "",
|
|
1842
|
+
style: {
|
|
1843
|
+
gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
|
|
1844
|
+
minWidth: 0,
|
|
1845
|
+
// space-between: each column becomes a flex column whose content spreads over
|
|
1846
|
+
// the full row height instead of clumping at the top.
|
|
1847
|
+
...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
|
|
1848
|
+
},
|
|
1849
|
+
children: renderNode(block, ctx, `r${r2}.b${b}`)
|
|
1850
|
+
},
|
|
1851
|
+
b
|
|
1852
|
+
))
|
|
1282
1853
|
},
|
|
1283
1854
|
r2
|
|
1284
1855
|
))
|
|
@@ -1294,17 +1865,36 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
1294
1865
|
var CONTAINER_ATTR = "data-ohw-ai-generated";
|
|
1295
1866
|
var REPLACED_ATTR = "data-ohw-ai-replaced-by";
|
|
1296
1867
|
var REMOVED_ATTR = "data-ohw-ai-removed";
|
|
1868
|
+
var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
|
|
1869
|
+
var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
1870
|
+
function readRootVar(name) {
|
|
1871
|
+
if (typeof document === "undefined") return "";
|
|
1872
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
1873
|
+
}
|
|
1874
|
+
function deriveBrandOverride() {
|
|
1875
|
+
const dark = readRootVar("--ohw-brand-dark");
|
|
1876
|
+
const primary = readRootVar("--ohw-brand-primary");
|
|
1877
|
+
const light = readRootVar("--ohw-brand-light");
|
|
1878
|
+
if (!dark || !primary || !light) return null;
|
|
1879
|
+
const accent = readRootVar("--ohw-brand-accent");
|
|
1880
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1881
|
+
const body = readRootVar("--font-body");
|
|
1882
|
+
return {
|
|
1883
|
+
palette: { dark, primary, accent: accent || dark, light },
|
|
1884
|
+
fonts: {
|
|
1885
|
+
heading: heading || AI_DEFAULT_BRAND.fonts.heading,
|
|
1886
|
+
body: body || AI_DEFAULT_BRAND.fonts.body
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1297
1890
|
function deriveTemplateBrand() {
|
|
1298
|
-
|
|
1299
|
-
const
|
|
1300
|
-
const
|
|
1301
|
-
const dark = read("--color-dark");
|
|
1302
|
-
const primary = read("--color-primary");
|
|
1303
|
-
const light = read("--color-light");
|
|
1891
|
+
const dark = readRootVar("--color-dark");
|
|
1892
|
+
const primary = readRootVar("--color-primary");
|
|
1893
|
+
const light = readRootVar("--color-light");
|
|
1304
1894
|
if (!dark || !primary || !light) return null;
|
|
1305
|
-
const accent =
|
|
1306
|
-
const heading =
|
|
1307
|
-
const body =
|
|
1895
|
+
const accent = readRootVar("--color-accent");
|
|
1896
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1897
|
+
const body = readRootVar("--font-body");
|
|
1308
1898
|
return {
|
|
1309
1899
|
palette: { dark, primary, accent: accent || dark, light },
|
|
1310
1900
|
fonts: {
|
|
@@ -1313,6 +1903,13 @@ function deriveTemplateBrand() {
|
|
|
1313
1903
|
}
|
|
1314
1904
|
};
|
|
1315
1905
|
}
|
|
1906
|
+
function deriveTemplateButtonRadius() {
|
|
1907
|
+
if (typeof document === "undefined") return null;
|
|
1908
|
+
const btn = document.querySelector('[data-ohw-role="button"]');
|
|
1909
|
+
if (!btn) return null;
|
|
1910
|
+
const radius = getComputedStyle(btn).borderTopLeftRadius;
|
|
1911
|
+
return radius || null;
|
|
1912
|
+
}
|
|
1316
1913
|
var mounted = /* @__PURE__ */ new Map();
|
|
1317
1914
|
function findTemplateSection(id) {
|
|
1318
1915
|
for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
|
|
@@ -1376,6 +1973,24 @@ function syncRemovedSections(state) {
|
|
|
1376
1973
|
}
|
|
1377
1974
|
}
|
|
1378
1975
|
}
|
|
1976
|
+
function syncTemplateHidden(state, pageHasSections) {
|
|
1977
|
+
const hide = state.hideTemplate === true && pageHasSections;
|
|
1978
|
+
for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
|
|
1979
|
+
if (!hide) {
|
|
1980
|
+
el.style.removeProperty("display");
|
|
1981
|
+
el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
if (!hide) return;
|
|
1985
|
+
for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
|
|
1986
|
+
if (el.hasAttribute(CONTAINER_ATTR)) continue;
|
|
1987
|
+
if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
|
|
1988
|
+
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
1989
|
+
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
|
|
1990
|
+
el.style.display = "none";
|
|
1991
|
+
el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1379
1994
|
function syncReplacedOriginals(state) {
|
|
1380
1995
|
for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
|
|
1381
1996
|
const byId = el.getAttribute(REPLACED_ATTR) ?? "";
|
|
@@ -1394,10 +2009,64 @@ function syncReplacedOriginals(state) {
|
|
|
1394
2009
|
}
|
|
1395
2010
|
}
|
|
1396
2011
|
}
|
|
2012
|
+
var sectionOrderIndex = /* @__PURE__ */ new Map();
|
|
2013
|
+
function setAiSectionOrder(raw, currentPath) {
|
|
2014
|
+
const next = /* @__PURE__ */ new Map();
|
|
2015
|
+
if (raw) {
|
|
2016
|
+
try {
|
|
2017
|
+
const entries = JSON.parse(raw);
|
|
2018
|
+
for (const entry of entries) {
|
|
2019
|
+
if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
|
|
2020
|
+
}
|
|
2021
|
+
} catch {
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
sectionOrderIndex = next;
|
|
2025
|
+
}
|
|
2026
|
+
function applyExplicitOrder(entries) {
|
|
2027
|
+
if (sectionOrderIndex.size === 0) return entries;
|
|
2028
|
+
return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
|
|
2029
|
+
if (a.order === void 0 && b.order === void 0) return a.index - b.index;
|
|
2030
|
+
if (a.order === void 0) return 1;
|
|
2031
|
+
if (b.order === void 0) return -1;
|
|
2032
|
+
return a.order - b.order;
|
|
2033
|
+
}).map((item) => item.entry);
|
|
2034
|
+
}
|
|
2035
|
+
function orderByChain(sections) {
|
|
2036
|
+
const ids = new Set(sections.map((entry) => entry.id));
|
|
2037
|
+
const after = /* @__PURE__ */ new Map();
|
|
2038
|
+
const roots = [];
|
|
2039
|
+
for (const entry of sections) {
|
|
2040
|
+
const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
|
|
2041
|
+
if (anchor && ids.has(anchor)) {
|
|
2042
|
+
const bucket = after.get(anchor);
|
|
2043
|
+
if (bucket) bucket.push(entry);
|
|
2044
|
+
else after.set(anchor, [entry]);
|
|
2045
|
+
} else {
|
|
2046
|
+
roots.push(entry);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const out = [];
|
|
2050
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2051
|
+
const visit = (entry) => {
|
|
2052
|
+
if (seen.has(entry.id)) return;
|
|
2053
|
+
seen.add(entry.id);
|
|
2054
|
+
out.push(entry);
|
|
2055
|
+
for (const child of after.get(entry.id) ?? []) visit(child);
|
|
2056
|
+
};
|
|
2057
|
+
for (const root of roots) visit(root);
|
|
2058
|
+
return out.length === sections.length ? out : sections;
|
|
2059
|
+
}
|
|
1397
2060
|
function applyAiSectionsToDom(state, options) {
|
|
1398
2061
|
if (typeof document === "undefined") return;
|
|
2062
|
+
const brandOverride = deriveBrandOverride();
|
|
1399
2063
|
const templateBrand = deriveTemplateBrand();
|
|
1400
|
-
const
|
|
2064
|
+
const templateButtonRadius = deriveTemplateButtonRadius();
|
|
2065
|
+
const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
|
|
2066
|
+
const pagePath = window.location.pathname;
|
|
2067
|
+
const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
|
|
2068
|
+
const activeIds = new Set(pageSections.map((entry) => entry.id));
|
|
2069
|
+
const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
|
|
1401
2070
|
for (const [id, section] of mounted) {
|
|
1402
2071
|
if (!activeIds.has(id)) {
|
|
1403
2072
|
section.root.unmount();
|
|
@@ -1405,8 +2074,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1405
2074
|
mounted.delete(id);
|
|
1406
2075
|
}
|
|
1407
2076
|
}
|
|
1408
|
-
for (const entry of
|
|
1409
|
-
const serialized = JSON.stringify(entry);
|
|
2077
|
+
for (const entry of ordered) {
|
|
2078
|
+
const serialized = JSON.stringify(entry) + brandKey;
|
|
1410
2079
|
const existing = mounted.get(entry.id);
|
|
1411
2080
|
if (existing && existing.serialized === serialized && existing.container.isConnected) {
|
|
1412
2081
|
continue;
|
|
@@ -1431,7 +2100,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1431
2100
|
AiTreeRenderer,
|
|
1432
2101
|
{
|
|
1433
2102
|
tree: entry.tree,
|
|
1434
|
-
brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2103
|
+
brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2104
|
+
buttonRadius: templateButtonRadius,
|
|
1435
2105
|
resolveMedia,
|
|
1436
2106
|
editKeyPrefix: `ai.${entry.id}`
|
|
1437
2107
|
}
|
|
@@ -1440,8 +2110,20 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1440
2110
|
});
|
|
1441
2111
|
mounted.set(entry.id, { root, container, serialized });
|
|
1442
2112
|
}
|
|
2113
|
+
if (state.hideTemplate === true) {
|
|
2114
|
+
let prev = null;
|
|
2115
|
+
for (const entry of ordered) {
|
|
2116
|
+
const el = mounted.get(entry.id)?.container;
|
|
2117
|
+
if (!el) continue;
|
|
2118
|
+
if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
|
|
2119
|
+
prev.insertAdjacentElement("afterend", el);
|
|
2120
|
+
}
|
|
2121
|
+
prev = el;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
1443
2124
|
syncReplacedOriginals(state);
|
|
1444
2125
|
syncRemovedSections(state);
|
|
2126
|
+
syncTemplateHidden(state, pageSections.length > 0);
|
|
1445
2127
|
}
|
|
1446
2128
|
|
|
1447
2129
|
// src/useLinkHrefGuardian.ts
|
|
@@ -7140,13 +7822,17 @@ function MediaOverlay({
|
|
|
7140
7822
|
hover,
|
|
7141
7823
|
isUploading,
|
|
7142
7824
|
fadingOut = false,
|
|
7825
|
+
selected = false,
|
|
7826
|
+
hovered = false,
|
|
7143
7827
|
onFadeOutComplete,
|
|
7144
7828
|
onReplace,
|
|
7829
|
+
onSelect,
|
|
7145
7830
|
onVideoSettingsChange
|
|
7146
7831
|
}) {
|
|
7147
7832
|
const { rect } = hover;
|
|
7148
7833
|
const skeletonRef = React8.useRef(null);
|
|
7149
7834
|
const isVideo = hover.elementType === "video";
|
|
7835
|
+
const showChrome = !selected || hovered;
|
|
7150
7836
|
const autoplay = hover.videoAutoplay ?? true;
|
|
7151
7837
|
const muted = hover.videoMuted ?? true;
|
|
7152
7838
|
const probeRef = React8.useRef(null);
|
|
@@ -7193,7 +7879,7 @@ function MediaOverlay({
|
|
|
7193
7879
|
}
|
|
7194
7880
|
);
|
|
7195
7881
|
}
|
|
7196
|
-
const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7882
|
+
const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7197
7883
|
"div",
|
|
7198
7884
|
{
|
|
7199
7885
|
"data-ohw-bridge": "",
|
|
@@ -7263,10 +7949,12 @@ function MediaOverlay({
|
|
|
7263
7949
|
// in-document, pointer-events does it natively. The button below opts back in, so
|
|
7264
7950
|
// Replace still works.
|
|
7265
7951
|
pointerEvents: hover.hasTextOverlap ? "none" : "auto",
|
|
7266
|
-
|
|
7267
|
-
|
|
7952
|
+
// Selected: a firm component ring with no wash, so the image reads as chosen rather
|
|
7953
|
+
// than hovered. Hover keeps the existing tinted preview.
|
|
7954
|
+
boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
|
|
7955
|
+
background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
|
|
7268
7956
|
},
|
|
7269
|
-
onClick: () => onReplace(hover.key),
|
|
7957
|
+
onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
|
|
7270
7958
|
children: [
|
|
7271
7959
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7272
7960
|
Button,
|
|
@@ -7320,7 +8008,7 @@ function MediaOverlay({
|
|
|
7320
8008
|
},
|
|
7321
8009
|
children: [
|
|
7322
8010
|
isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
|
|
7323
|
-
replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
|
|
8011
|
+
replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
|
|
7324
8012
|
]
|
|
7325
8013
|
}
|
|
7326
8014
|
)
|
|
@@ -7404,6 +8092,8 @@ function parseSectionsFromRoot(root) {
|
|
|
7404
8092
|
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
7405
8093
|
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
7406
8094
|
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
8095
|
+
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
8096
|
+
continue;
|
|
7407
8097
|
seen.add(id);
|
|
7408
8098
|
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
7409
8099
|
sections.push({ id, label });
|
|
@@ -7689,6 +8379,7 @@ function AiSectionOverlay({
|
|
|
7689
8379
|
}) {
|
|
7690
8380
|
const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
|
|
7691
8381
|
const [reviewId, setReviewId] = (0, import_react8.useState)(null);
|
|
8382
|
+
const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
|
|
7692
8383
|
const reviewIdRef = (0, import_react8.useRef)(null);
|
|
7693
8384
|
reviewIdRef.current = reviewId;
|
|
7694
8385
|
const selectedIdRef = (0, import_react8.useRef)(null);
|
|
@@ -7750,6 +8441,7 @@ function AiSectionOverlay({
|
|
|
7750
8441
|
}
|
|
7751
8442
|
const found = readRect(sectionId) != null;
|
|
7752
8443
|
setReviewId(found ? sectionId : null);
|
|
8444
|
+
setReviewButtonsHidden(e.data.hideButtons === true);
|
|
7753
8445
|
postToParent2({ type: "ow:ai-review-started", sectionId, found });
|
|
7754
8446
|
if (found) {
|
|
7755
8447
|
document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
@@ -7877,13 +8569,16 @@ function AiSectionOverlay({
|
|
|
7877
8569
|
border: `2px solid ${PRIMARY2}`,
|
|
7878
8570
|
borderRadius: edgeAwareRadius(reviewRect),
|
|
7879
8571
|
zIndex: 2147483200,
|
|
7880
|
-
// The veil itself: swallows clicks so the section stays locked until decided.
|
|
8572
|
+
// The veil itself: swallows clicks so the section stays locked until decided. This
|
|
8573
|
+
// stopPropagation only guards the bubble phase; the bridge's capture-phase click
|
|
8574
|
+
// handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
|
|
8575
|
+
// Accept/Discard resolves to the media beneath and opens the file picker.
|
|
7881
8576
|
background: "rgba(8, 133, 254, 0.04)",
|
|
7882
8577
|
pointerEvents: "auto",
|
|
7883
8578
|
cursor: "default"
|
|
7884
8579
|
},
|
|
7885
8580
|
onClick: (e) => e.stopPropagation(),
|
|
7886
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
8581
|
+
children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
7887
8582
|
"div",
|
|
7888
8583
|
{
|
|
7889
8584
|
style: {
|
|
@@ -10450,8 +11145,13 @@ function referenceBox(slot) {
|
|
|
10450
11145
|
const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
|
|
10451
11146
|
(el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
|
|
10452
11147
|
) : null;
|
|
10453
|
-
|
|
10454
|
-
|
|
11148
|
+
if (neighbour) {
|
|
11149
|
+
const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
|
|
11150
|
+
if (box2?.width && box2.height) return box2;
|
|
11151
|
+
}
|
|
11152
|
+
const own = slot.getBoundingClientRect();
|
|
11153
|
+
if (own.width && own.height) return own;
|
|
11154
|
+
const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
|
|
10455
11155
|
return box?.width && box.height ? box : null;
|
|
10456
11156
|
}
|
|
10457
11157
|
function iconMarkupSizedFor(slot, markup) {
|
|
@@ -13320,6 +14020,7 @@ function useSectionDrag({
|
|
|
13320
14020
|
}
|
|
13321
14021
|
const orderJson = JSON.stringify(entries);
|
|
13322
14022
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
14023
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
13323
14024
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
13324
14025
|
applyPersistedOrder(entries);
|
|
13325
14026
|
clearSectionDragVisuals();
|
|
@@ -15439,6 +16140,70 @@ function OhhwellsBridge() {
|
|
|
15439
16140
|
const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
|
|
15440
16141
|
const dragOverElRef = (0, import_react17.useRef)(null);
|
|
15441
16142
|
const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
|
|
16143
|
+
const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
|
|
16144
|
+
const selectedMediaElRef = (0, import_react17.useRef)(null);
|
|
16145
|
+
const clearMediaSelection = (0, import_react17.useCallback)(() => {
|
|
16146
|
+
const prev = selectedMediaElRef.current;
|
|
16147
|
+
selectedMediaElRef.current = null;
|
|
16148
|
+
setSelectedMedia(null);
|
|
16149
|
+
const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
|
|
16150
|
+
if (sectionEl) {
|
|
16151
|
+
postToParentRef.current({
|
|
16152
|
+
type: "ow:section-selected",
|
|
16153
|
+
sectionId: sectionEl.dataset.ohwSection ?? null,
|
|
16154
|
+
sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
|
|
16155
|
+
key: null
|
|
16156
|
+
});
|
|
16157
|
+
}
|
|
16158
|
+
}, []);
|
|
16159
|
+
const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
|
|
16160
|
+
clearMediaSelectionRef.current = clearMediaSelection;
|
|
16161
|
+
const selectMediaElement = (0, import_react17.useCallback)((el) => {
|
|
16162
|
+
const r2 = el.getBoundingClientRect();
|
|
16163
|
+
const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
|
|
16164
|
+
selectedMediaElRef.current = el;
|
|
16165
|
+
setSelectedMedia({
|
|
16166
|
+
key: el.dataset.ohwKey ?? "",
|
|
16167
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
16168
|
+
elementType: el.dataset.ohwEditable ?? "image",
|
|
16169
|
+
hasTextOverlap: false,
|
|
16170
|
+
isDragOver: false,
|
|
16171
|
+
...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
|
|
16172
|
+
});
|
|
16173
|
+
const sectionEl = el.closest("[data-ohw-section]");
|
|
16174
|
+
aiSectionApiRef.current?.selectFromElement(el, { report: false });
|
|
16175
|
+
postToParentRef.current({
|
|
16176
|
+
type: "ow:section-selected",
|
|
16177
|
+
sectionId: sectionEl?.dataset.ohwSection ?? null,
|
|
16178
|
+
sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
|
|
16179
|
+
key: el.dataset.ohwKey ?? null,
|
|
16180
|
+
// Display name for the pill — the raw key prettifies into fragments ("Img"); the
|
|
16181
|
+
// bridge knows what the node IS, so it names it.
|
|
16182
|
+
keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
|
|
16183
|
+
});
|
|
16184
|
+
}, []);
|
|
16185
|
+
const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
|
|
16186
|
+
selectMediaElementRef.current = selectMediaElement;
|
|
16187
|
+
(0, import_react17.useEffect)(() => {
|
|
16188
|
+
if (!selectedMedia) return;
|
|
16189
|
+
const update = () => {
|
|
16190
|
+
const el = selectedMediaElRef.current;
|
|
16191
|
+
if (!el || !el.isConnected) {
|
|
16192
|
+
clearMediaSelection();
|
|
16193
|
+
return;
|
|
16194
|
+
}
|
|
16195
|
+
const r2 = el.getBoundingClientRect();
|
|
16196
|
+
setSelectedMedia(
|
|
16197
|
+
(prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
|
|
16198
|
+
);
|
|
16199
|
+
};
|
|
16200
|
+
window.addEventListener("scroll", update, true);
|
|
16201
|
+
window.addEventListener("resize", update);
|
|
16202
|
+
return () => {
|
|
16203
|
+
window.removeEventListener("scroll", update, true);
|
|
16204
|
+
window.removeEventListener("resize", update);
|
|
16205
|
+
};
|
|
16206
|
+
}, [selectedMedia !== null]);
|
|
15442
16207
|
const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
|
|
15443
16208
|
const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
|
|
15444
16209
|
const hoveredGapRef = (0, import_react17.useRef)(null);
|
|
@@ -15722,6 +16487,8 @@ function OhhwellsBridge() {
|
|
|
15722
16487
|
const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
|
|
15723
16488
|
const editContentRef = (0, import_react17.useRef)({});
|
|
15724
16489
|
const aiSectionsRef = (0, import_react17.useRef)("");
|
|
16490
|
+
const brandKitRef = (0, import_react17.useRef)("");
|
|
16491
|
+
const stylesRef = (0, import_react17.useRef)("");
|
|
15725
16492
|
const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
|
|
15726
16493
|
const [sitePages, setSitePages] = (0, import_react17.useState)([]);
|
|
15727
16494
|
const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
|
|
@@ -17042,13 +17809,29 @@ function OhhwellsBridge() {
|
|
|
17042
17809
|
}
|
|
17043
17810
|
const applyContent = (content) => {
|
|
17044
17811
|
const imageLoads = [];
|
|
17812
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17813
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
17814
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17815
|
+
} else {
|
|
17816
|
+
brandKitRef.current = "";
|
|
17817
|
+
applyBrandToDom(null);
|
|
17818
|
+
}
|
|
17045
17819
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
17046
17820
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
17821
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
17047
17822
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
17048
17823
|
}
|
|
17824
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17825
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
17826
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17827
|
+
}
|
|
17828
|
+
applyBrandChrome(content);
|
|
17049
17829
|
for (const [key, val] of Object.entries(content)) {
|
|
17050
17830
|
if (key === "__ohw_sections") continue;
|
|
17051
17831
|
if (key === AI_SECTIONS_KEY) continue;
|
|
17832
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17833
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17834
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17052
17835
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17053
17836
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17054
17837
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17236,8 +18019,25 @@ function OhhwellsBridge() {
|
|
|
17236
18019
|
initSectionInstancesFromContent(content, window.location.pathname);
|
|
17237
18020
|
observer?.disconnect();
|
|
17238
18021
|
try {
|
|
18022
|
+
applyBrandChrome(content);
|
|
18023
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
18024
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
18025
|
+
} else {
|
|
18026
|
+
applyBrandToDom(null);
|
|
18027
|
+
}
|
|
18028
|
+
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
18029
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
18030
|
+
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
18031
|
+
}
|
|
18032
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
18033
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
18034
|
+
}
|
|
17239
18035
|
for (const [key, val] of Object.entries(content)) {
|
|
17240
18036
|
if (key === "__ohw_sections") continue;
|
|
18037
|
+
if (key === AI_SECTIONS_KEY) continue;
|
|
18038
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
18039
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
18040
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17241
18041
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17242
18042
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17243
18043
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17380,9 +18180,21 @@ function OhhwellsBridge() {
|
|
|
17380
18180
|
}, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
|
|
17381
18181
|
(0, import_react17.useEffect)(() => {
|
|
17382
18182
|
if (!isEditMode) return;
|
|
18183
|
+
let lastPosted = 0;
|
|
17383
18184
|
const measure = () => {
|
|
17384
18185
|
const h = document.body.scrollHeight;
|
|
17385
|
-
if (h > 50
|
|
18186
|
+
if (h > 50 && Math.abs(h - lastPosted) > 1) {
|
|
18187
|
+
lastPosted = h;
|
|
18188
|
+
postToParent2({ type: "ow:height", height: h });
|
|
18189
|
+
}
|
|
18190
|
+
};
|
|
18191
|
+
let raf = null;
|
|
18192
|
+
const schedule = () => {
|
|
18193
|
+
if (raf != null) return;
|
|
18194
|
+
raf = requestAnimationFrame(() => {
|
|
18195
|
+
raf = null;
|
|
18196
|
+
measure();
|
|
18197
|
+
});
|
|
17386
18198
|
};
|
|
17387
18199
|
const t1 = setTimeout(measure, 50);
|
|
17388
18200
|
const t2 = setTimeout(measure, 500);
|
|
@@ -17402,6 +18214,7 @@ function OhhwellsBridge() {
|
|
|
17402
18214
|
return () => {
|
|
17403
18215
|
clearTimeout(t1);
|
|
17404
18216
|
clearTimeout(t2);
|
|
18217
|
+
if (raf != null) cancelAnimationFrame(raf);
|
|
17405
18218
|
clearResizeTimers();
|
|
17406
18219
|
window.removeEventListener("resize", handleResize);
|
|
17407
18220
|
};
|
|
@@ -17644,10 +18457,14 @@ function OhhwellsBridge() {
|
|
|
17644
18457
|
return;
|
|
17645
18458
|
}
|
|
17646
18459
|
const target = e.target;
|
|
18460
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17647
18461
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17648
18462
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17649
18463
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
17650
18464
|
if (isInsideLinkEditor(target)) return;
|
|
18465
|
+
if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
|
|
18466
|
+
clearMediaSelectionRef.current();
|
|
18467
|
+
}
|
|
17651
18468
|
if (isInsideFloatingPanel(target)) return;
|
|
17652
18469
|
if (target.closest("[data-ohw-form-toolbar]")) return;
|
|
17653
18470
|
if (target.closest(
|
|
@@ -17817,8 +18634,11 @@ function OhhwellsBridge() {
|
|
|
17817
18634
|
if (isMediaEditable(editable) && !buttonOnMedia) {
|
|
17818
18635
|
e.preventDefault();
|
|
17819
18636
|
e.stopPropagation();
|
|
17820
|
-
|
|
17821
|
-
|
|
18637
|
+
if (selectedMediaElRef.current === editable) {
|
|
18638
|
+
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
|
|
18639
|
+
} else {
|
|
18640
|
+
selectMediaElementRef.current(editable);
|
|
18641
|
+
}
|
|
17822
18642
|
return;
|
|
17823
18643
|
}
|
|
17824
18644
|
const socialItem = getSocialItem(editable);
|
|
@@ -17959,6 +18779,7 @@ function OhhwellsBridge() {
|
|
|
17959
18779
|
};
|
|
17960
18780
|
const handleDblClick = (e) => {
|
|
17961
18781
|
const target = e.target;
|
|
18782
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17962
18783
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17963
18784
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17964
18785
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
@@ -18759,7 +19580,9 @@ function OhhwellsBridge() {
|
|
|
18759
19580
|
return;
|
|
18760
19581
|
}
|
|
18761
19582
|
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
18762
|
-
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).
|
|
19583
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
|
|
19584
|
+
(el) => !el.hasAttribute("data-ohw-ai-template-hidden") && !el.hasAttribute("data-ohw-ai-removed") && !el.hasAttribute("data-ohw-ai-replaced-by") && el.getBoundingClientRect().height > 0
|
|
19585
|
+
).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
18763
19586
|
const ZONE = 20;
|
|
18764
19587
|
for (let i = 0; i < sections.length; i++) {
|
|
18765
19588
|
const a = sections[i];
|
|
@@ -19091,10 +19914,23 @@ function OhhwellsBridge() {
|
|
|
19091
19914
|
if (e.data?.type !== "ow:hydrate") return;
|
|
19092
19915
|
const content = e.data.content;
|
|
19093
19916
|
if (!content) return;
|
|
19917
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
19918
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
19919
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
19920
|
+
} else {
|
|
19921
|
+
brandKitRef.current = "";
|
|
19922
|
+
applyBrandToDom(null);
|
|
19923
|
+
}
|
|
19094
19924
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
19095
19925
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
19926
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
19096
19927
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
19097
19928
|
}
|
|
19929
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
19930
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
19931
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
19932
|
+
}
|
|
19933
|
+
applyBrandChrome(content);
|
|
19098
19934
|
let sectionsJson = null;
|
|
19099
19935
|
for (const [key, val] of Object.entries(content)) {
|
|
19100
19936
|
if (key === "__ohw_sections") {
|
|
@@ -19102,6 +19938,9 @@ function OhhwellsBridge() {
|
|
|
19102
19938
|
continue;
|
|
19103
19939
|
}
|
|
19104
19940
|
if (key === AI_SECTIONS_KEY) continue;
|
|
19941
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
19942
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
19943
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
19105
19944
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
19106
19945
|
if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
|
|
19107
19946
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -19117,6 +19956,8 @@ function OhhwellsBridge() {
|
|
|
19117
19956
|
if (video && video.src !== val) applyVideoSrc(video, val);
|
|
19118
19957
|
} else if (el.dataset.ohwEditable === "link") {
|
|
19119
19958
|
applyLinkHref(el, val);
|
|
19959
|
+
} else if (el.dataset.ohwEditable === "icon") {
|
|
19960
|
+
applyIconMarkup(el, val);
|
|
19120
19961
|
} else if (isIconMarkupValue(val)) {
|
|
19121
19962
|
} else {
|
|
19122
19963
|
el.innerHTML = val;
|
|
@@ -19201,12 +20042,21 @@ function OhhwellsBridge() {
|
|
|
19201
20042
|
nodes: collectEditableNodes(editContentRef.current)
|
|
19202
20043
|
});
|
|
19203
20044
|
};
|
|
20045
|
+
const clearInteractionChrome = () => {
|
|
20046
|
+
deactivateRef.current();
|
|
20047
|
+
deselectRef.current();
|
|
20048
|
+
clearMediaSelectionRef.current();
|
|
20049
|
+
};
|
|
19204
20050
|
const handleAiApplyTree = (e) => {
|
|
19205
20051
|
if (e.data?.type !== "ow:ai-apply-tree") return;
|
|
19206
20052
|
const payload = e.data.payload;
|
|
19207
20053
|
if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
|
|
20054
|
+
clearInteractionChrome();
|
|
19208
20055
|
const previous = aiSectionsRef.current;
|
|
19209
|
-
const nextState = applyTreeToState(parseAiSectionsState(previous),
|
|
20056
|
+
const nextState = applyTreeToState(parseAiSectionsState(previous), {
|
|
20057
|
+
...payload,
|
|
20058
|
+
path: payload.path ?? window.location.pathname
|
|
20059
|
+
});
|
|
19210
20060
|
const nextValue = serializeAiSectionsState(nextState);
|
|
19211
20061
|
aiSectionsRef.current = nextValue;
|
|
19212
20062
|
applyAiSectionsToDom(nextState);
|
|
@@ -19227,6 +20077,7 @@ function OhhwellsBridge() {
|
|
|
19227
20077
|
const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
19228
20078
|
if (!exists) return;
|
|
19229
20079
|
if (isPageFrameSection(exists)) return;
|
|
20080
|
+
clearInteractionChrome();
|
|
19230
20081
|
const previous = aiSectionsRef.current;
|
|
19231
20082
|
const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
|
|
19232
20083
|
const nextValue = serializeAiSectionsState(nextState);
|
|
@@ -19242,14 +20093,45 @@ function OhhwellsBridge() {
|
|
|
19242
20093
|
const handleAiSetSections = (e) => {
|
|
19243
20094
|
if (e.data?.type !== "ow:ai-set-sections") return;
|
|
19244
20095
|
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20096
|
+
clearInteractionChrome();
|
|
19245
20097
|
aiSectionsRef.current = value;
|
|
19246
20098
|
applyAiSectionsToDom(parseAiSectionsState(value));
|
|
20099
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
19247
20100
|
const restoredHeight = document.body.scrollHeight;
|
|
19248
20101
|
if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
|
|
19249
20102
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
|
|
19250
20103
|
postAiSectionsChanged();
|
|
19251
20104
|
};
|
|
19252
20105
|
window.addEventListener("message", handleAiSetSections);
|
|
20106
|
+
const handleAiSetBrand = (e) => {
|
|
20107
|
+
if (e.data?.type !== "ow:ai-set-brand") return;
|
|
20108
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20109
|
+
const previous = brandKitRef.current;
|
|
20110
|
+
brandKitRef.current = value;
|
|
20111
|
+
applyBrandToDom(parseBrandKit(value));
|
|
20112
|
+
if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
|
|
20113
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
20114
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
|
|
20115
|
+
postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
|
|
20116
|
+
};
|
|
20117
|
+
window.addEventListener("message", handleAiSetBrand);
|
|
20118
|
+
const handleAiSetStyles = (e) => {
|
|
20119
|
+
if (e.data?.type !== "ow:ai-set-styles") return;
|
|
20120
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20121
|
+
const previous = stylesRef.current;
|
|
20122
|
+
stylesRef.current = value;
|
|
20123
|
+
applyStylesToDom(parseStyleStore(value));
|
|
20124
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
|
|
20125
|
+
postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
|
|
20126
|
+
};
|
|
20127
|
+
window.addEventListener("message", handleAiSetStyles);
|
|
20128
|
+
const handleGetBrand = (e) => {
|
|
20129
|
+
if (e.data?.type !== "ow:get-brand") return;
|
|
20130
|
+
const template = deriveTemplateBrand();
|
|
20131
|
+
const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
|
|
20132
|
+
postToParentRef.current({ type: "ow:brand-value", value });
|
|
20133
|
+
};
|
|
20134
|
+
window.addEventListener("message", handleGetBrand);
|
|
19253
20135
|
const handleMoveSection = (e) => {
|
|
19254
20136
|
if (e.data?.type !== "ow:move-section") return;
|
|
19255
20137
|
const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
|
|
@@ -19259,6 +20141,7 @@ function OhhwellsBridge() {
|
|
|
19259
20141
|
if (!entries) return;
|
|
19260
20142
|
const orderJson = JSON.stringify(entries);
|
|
19261
20143
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20144
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
19262
20145
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
19263
20146
|
window.dispatchEvent(new Event("resize"));
|
|
19264
20147
|
};
|
|
@@ -19322,6 +20205,7 @@ function OhhwellsBridge() {
|
|
|
19322
20205
|
}
|
|
19323
20206
|
deselectRef.current();
|
|
19324
20207
|
deactivateRef.current();
|
|
20208
|
+
clearMediaSelectionRef.current();
|
|
19325
20209
|
};
|
|
19326
20210
|
window.addEventListener("message", handleDeactivate);
|
|
19327
20211
|
const handleToastAction = (e) => {
|
|
@@ -19407,6 +20291,10 @@ function OhhwellsBridge() {
|
|
|
19407
20291
|
const handleKeyDown = (e) => {
|
|
19408
20292
|
if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
|
|
19409
20293
|
if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
|
|
20294
|
+
if (e.key === "Escape" && selectedMediaElRef.current) {
|
|
20295
|
+
clearMediaSelectionRef.current();
|
|
20296
|
+
return;
|
|
20297
|
+
}
|
|
19410
20298
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
|
|
19411
20299
|
e.preventDefault();
|
|
19412
20300
|
selectAllTextInEditable(activeElRef.current);
|
|
@@ -19566,6 +20454,12 @@ function OhhwellsBridge() {
|
|
|
19566
20454
|
if (aiSectionsRef.current) {
|
|
19567
20455
|
nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
|
|
19568
20456
|
}
|
|
20457
|
+
if (stylesRef.current) {
|
|
20458
|
+
nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
|
|
20459
|
+
}
|
|
20460
|
+
if (brandKitRef.current) {
|
|
20461
|
+
nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
|
|
20462
|
+
}
|
|
19569
20463
|
document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
|
|
19570
20464
|
const formKey = formKeyOf(form);
|
|
19571
20465
|
if (!formKey) return;
|
|
@@ -19980,6 +20874,9 @@ function OhhwellsBridge() {
|
|
|
19980
20874
|
window.removeEventListener("message", handleAiApplyTree);
|
|
19981
20875
|
window.removeEventListener("message", handleAiDeleteSection);
|
|
19982
20876
|
window.removeEventListener("message", handleAiSetSections);
|
|
20877
|
+
window.removeEventListener("message", handleAiSetBrand);
|
|
20878
|
+
window.removeEventListener("message", handleAiSetStyles);
|
|
20879
|
+
window.removeEventListener("message", handleGetBrand);
|
|
19983
20880
|
window.removeEventListener("message", handleMoveSection);
|
|
19984
20881
|
window.removeEventListener("message", handlePanelDragging);
|
|
19985
20882
|
window.removeEventListener("message", handleDeleteSection);
|
|
@@ -20190,7 +21087,7 @@ function OhhwellsBridge() {
|
|
|
20190
21087
|
postToParent2({
|
|
20191
21088
|
type: "ow:ready",
|
|
20192
21089
|
version: "1",
|
|
20193
|
-
bridgeVersion: "0.1.
|
|
21090
|
+
bridgeVersion: "0.1.79",
|
|
20194
21091
|
path: pathname,
|
|
20195
21092
|
nodes: collectEditableNodes(editContentRef.current),
|
|
20196
21093
|
sections
|
|
@@ -20597,11 +21494,22 @@ function OhhwellsBridge() {
|
|
|
20597
21494
|
const showEditLink = toolbarShowEditLink;
|
|
20598
21495
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
20599
21496
|
linkPopoverOpenRef.current = linkPopover !== null;
|
|
21497
|
+
const handleMediaSelect = (0, import_react17.useCallback)((key) => {
|
|
21498
|
+
const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
|
|
21499
|
+
(m) => (m.dataset.ohwKey ?? "") === key
|
|
21500
|
+
) ?? null;
|
|
21501
|
+
if (!el) return;
|
|
21502
|
+
selectMediaElementRef.current(el);
|
|
21503
|
+
}, []);
|
|
20600
21504
|
const handleMediaReplace = (0, import_react17.useCallback)(
|
|
20601
21505
|
(key) => {
|
|
20602
|
-
postToParent2({
|
|
21506
|
+
postToParent2({
|
|
21507
|
+
type: "ow:image-pick",
|
|
21508
|
+
key,
|
|
21509
|
+
elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
|
|
21510
|
+
});
|
|
20603
21511
|
},
|
|
20604
|
-
[postToParent2, mediaHover?.elementType]
|
|
21512
|
+
[postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
|
|
20605
21513
|
);
|
|
20606
21514
|
const handleEditCarousel = (0, import_react17.useCallback)(
|
|
20607
21515
|
(key) => {
|
|
@@ -20673,12 +21581,25 @@ function OhhwellsBridge() {
|
|
|
20673
21581
|
},
|
|
20674
21582
|
`uploading-${key}`
|
|
20675
21583
|
)),
|
|
20676
|
-
mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
21584
|
+
mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
20677
21585
|
MediaOverlay,
|
|
20678
21586
|
{
|
|
20679
21587
|
hover: mediaHover,
|
|
20680
21588
|
isUploading: false,
|
|
20681
21589
|
onReplace: handleMediaReplace,
|
|
21590
|
+
onSelect: handleMediaSelect,
|
|
21591
|
+
onVideoSettingsChange: handleVideoSettingsChange
|
|
21592
|
+
}
|
|
21593
|
+
),
|
|
21594
|
+
selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
21595
|
+
MediaOverlay,
|
|
21596
|
+
{
|
|
21597
|
+
hover: selectedMedia,
|
|
21598
|
+
selected: true,
|
|
21599
|
+
hovered: mediaHover?.key === selectedMedia.key,
|
|
21600
|
+
isUploading: false,
|
|
21601
|
+
onReplace: handleMediaReplace,
|
|
21602
|
+
onSelect: handleMediaSelect,
|
|
20682
21603
|
onVideoSettingsChange: handleVideoSettingsChange
|
|
20683
21604
|
}
|
|
20684
21605
|
),
|