@ohhwells/bridge 0.1.78 → 0.1.79
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 +1030 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +1030 -133
- 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,63 @@ 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
|
+
// The 50:50 form collapses to a single stacked column on phones.
|
|
546
|
+
"[data-ai-form]{grid-template-columns:1fr !important}",
|
|
547
|
+
"[data-ai-section] img{max-width:100%}",
|
|
548
|
+
"}",
|
|
549
|
+
"@media (min-width: 769px) and (max-width: 1024px){",
|
|
550
|
+
"[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
|
|
551
|
+
"}"
|
|
552
|
+
].join("");
|
|
217
553
|
var FEATURE_LINE_CSS = [
|
|
218
554
|
"[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
|
|
219
555
|
'[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
|
|
220
556
|
`background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
|
|
221
557
|
`mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
|
|
222
558
|
].join("");
|
|
559
|
+
function hexLuminance(color) {
|
|
560
|
+
const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
|
|
561
|
+
if (!m) return null;
|
|
562
|
+
const [r2, g, b] = [0, 2, 4].map((i) => {
|
|
563
|
+
const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
|
|
564
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
565
|
+
});
|
|
566
|
+
return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
567
|
+
}
|
|
568
|
+
function hexContrast(a, b) {
|
|
569
|
+
const la = hexLuminance(a);
|
|
570
|
+
const lb = hexLuminance(b);
|
|
571
|
+
if (la === null || lb === null) return null;
|
|
572
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
573
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
574
|
+
}
|
|
575
|
+
function accentBandContext(brand) {
|
|
576
|
+
const p = brand.palette;
|
|
577
|
+
const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
|
|
578
|
+
if (lightWins) {
|
|
579
|
+
return {
|
|
580
|
+
brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
|
|
581
|
+
buttonLabel: p.primary
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
|
|
586
|
+
buttonLabel: p.light
|
|
587
|
+
};
|
|
588
|
+
}
|
|
223
589
|
function textAttrs(ctx, path) {
|
|
224
590
|
return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
|
|
225
591
|
}
|
|
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
592
|
var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
238
593
|
function MediaBox({
|
|
239
594
|
refValue,
|
|
@@ -246,17 +601,13 @@ function MediaBox({
|
|
|
246
601
|
const url = refValue ? ctx.resolveMedia(refValue) : null;
|
|
247
602
|
const isIcon = /^(lucide|simple):/.test(refValue);
|
|
248
603
|
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
|
-
} : {};
|
|
604
|
+
const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
|
|
253
605
|
if (isIcon) {
|
|
254
606
|
const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
|
|
255
607
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
256
608
|
"span",
|
|
257
609
|
{
|
|
258
610
|
"data-ai-icon": refValue,
|
|
259
|
-
...editAttrs,
|
|
260
611
|
style: {
|
|
261
612
|
display: "inline-flex",
|
|
262
613
|
width: 48,
|
|
@@ -309,7 +660,7 @@ function ButtonEl({
|
|
|
309
660
|
}) {
|
|
310
661
|
const secondary = slots.variant === "secondary";
|
|
311
662
|
const href = str(slots.href);
|
|
312
|
-
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
|
|
663
|
+
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
|
|
313
664
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
314
665
|
"a",
|
|
315
666
|
{
|
|
@@ -325,7 +676,7 @@ function ButtonEl({
|
|
|
325
676
|
textDecoration: "none",
|
|
326
677
|
cursor: "pointer",
|
|
327
678
|
...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 }
|
|
679
|
+
...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
680
|
},
|
|
330
681
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
331
682
|
}
|
|
@@ -831,7 +1182,24 @@ function CardBlock({ node, ctx, path }) {
|
|
|
831
1182
|
minWidth: 0
|
|
832
1183
|
},
|
|
833
1184
|
children: [
|
|
834
|
-
media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1185
|
+
media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1186
|
+
"div",
|
|
1187
|
+
{
|
|
1188
|
+
style: (
|
|
1189
|
+
// An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
|
|
1190
|
+
// text to the far side. Photos keep the half-and-half split. The inset has no
|
|
1191
|
+
// inner padding (the photo split absorbed that), so the icon carries its own gap.
|
|
1192
|
+
/^(lucide|simple):/.test(mediaRef) ? {
|
|
1193
|
+
flexShrink: 0,
|
|
1194
|
+
display: "flex",
|
|
1195
|
+
alignItems: "center",
|
|
1196
|
+
padding: mediaInset,
|
|
1197
|
+
[mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
|
|
1198
|
+
} : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
|
|
1199
|
+
),
|
|
1200
|
+
children: media
|
|
1201
|
+
}
|
|
1202
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
835
1203
|
"div",
|
|
836
1204
|
{
|
|
837
1205
|
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 +1290,44 @@ function AccordionBlock({ node, ctx, path }) {
|
|
|
922
1290
|
) })
|
|
923
1291
|
] }, i)) });
|
|
924
1292
|
}
|
|
1293
|
+
function useIsMobile() {
|
|
1294
|
+
const [mobile, setMobile] = import_react.default.useState(
|
|
1295
|
+
() => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
|
|
1296
|
+
);
|
|
1297
|
+
import_react.default.useEffect(() => {
|
|
1298
|
+
const mq = window.matchMedia("(max-width: 768px)");
|
|
1299
|
+
const update = () => setMobile(mq.matches);
|
|
1300
|
+
update();
|
|
1301
|
+
mq.addEventListener("change", update);
|
|
1302
|
+
return () => mq.removeEventListener("change", update);
|
|
1303
|
+
}, []);
|
|
1304
|
+
return mobile;
|
|
1305
|
+
}
|
|
925
1306
|
function Carousel({ items, itemsPerRow, ctx }) {
|
|
1307
|
+
const isMobile = useIsMobile();
|
|
1308
|
+
const perPage = isMobile ? 1 : itemsPerRow;
|
|
1309
|
+
const pages = Math.max(1, Math.ceil(items.length / perPage));
|
|
926
1310
|
const [page, setPage] = import_react.default.useState(0);
|
|
927
|
-
const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
|
|
928
1311
|
const current = Math.min(page, pages - 1);
|
|
1312
|
+
if (pages <= 1) {
|
|
1313
|
+
const cols = Math.max(1, Math.min(items.length, itemsPerRow));
|
|
1314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1315
|
+
"div",
|
|
1316
|
+
{
|
|
1317
|
+
"data-ai-grid": String(cols),
|
|
1318
|
+
style: {
|
|
1319
|
+
display: "grid",
|
|
1320
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
1321
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1322
|
+
alignItems: "start"
|
|
1323
|
+
},
|
|
1324
|
+
children: items
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
929
1328
|
const pageGroups = Array.from(
|
|
930
1329
|
{ length: pages },
|
|
931
|
-
(_, p) => items.slice(p *
|
|
1330
|
+
(_, p) => items.slice(p * perPage, (p + 1) * perPage)
|
|
932
1331
|
);
|
|
933
1332
|
const chrome = (enabled) => ({
|
|
934
1333
|
border: `1px solid ${ctx.brand.palette.dark}`,
|
|
@@ -953,55 +1352,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
|
|
|
953
1352
|
cursor: "pointer",
|
|
954
1353
|
padding: 0
|
|
955
1354
|
});
|
|
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)(
|
|
1355
|
+
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)(
|
|
1356
|
+
"div",
|
|
1357
|
+
{
|
|
1358
|
+
style: {
|
|
1359
|
+
display: "flex",
|
|
1360
|
+
transform: `translateX(-${current * 100}%)`,
|
|
1361
|
+
transition: "transform 0.4s ease"
|
|
1362
|
+
},
|
|
1363
|
+
children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
969
1364
|
"div",
|
|
970
1365
|
{
|
|
1366
|
+
"data-ai-grid": String(perPage),
|
|
971
1367
|
style: {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1368
|
+
flex: "0 0 100%",
|
|
1369
|
+
display: "grid",
|
|
1370
|
+
gridTemplateColumns: `repeat(${perPage}, 1fr)`,
|
|
1371
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1372
|
+
alignItems: "start"
|
|
975
1373
|
},
|
|
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
|
-
|
|
1374
|
+
children: group
|
|
1375
|
+
},
|
|
1376
|
+
p
|
|
1377
|
+
))
|
|
1378
|
+
}
|
|
1379
|
+
) });
|
|
1380
|
+
const prevBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1381
|
+
"button",
|
|
1382
|
+
{
|
|
1383
|
+
type: "button",
|
|
1384
|
+
"aria-label": "Previous",
|
|
1385
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
1386
|
+
style: chrome(current > 0),
|
|
1387
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
const nextBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1391
|
+
"button",
|
|
1392
|
+
{
|
|
1393
|
+
type: "button",
|
|
1394
|
+
"aria-label": "Next",
|
|
1395
|
+
onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
|
|
1396
|
+
style: chrome(current < pages - 1),
|
|
1397
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1398
|
+
}
|
|
1399
|
+
);
|
|
1400
|
+
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)) });
|
|
1401
|
+
if (isMobile) {
|
|
1402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
|
|
1403
|
+
viewport,
|
|
1404
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
|
|
1405
|
+
prevBtn,
|
|
1406
|
+
nextBtn
|
|
1407
|
+
] }),
|
|
1408
|
+
dots
|
|
1409
|
+
] });
|
|
1410
|
+
}
|
|
1411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
|
|
1413
|
+
prevBtn,
|
|
1414
|
+
viewport,
|
|
1415
|
+
nextBtn
|
|
1003
1416
|
] }),
|
|
1004
|
-
|
|
1417
|
+
dots
|
|
1005
1418
|
] });
|
|
1006
1419
|
}
|
|
1007
1420
|
function CollectionBlock({ node, ctx, path }) {
|
|
@@ -1080,7 +1493,7 @@ function CollectionBlock({ node, ctx, path }) {
|
|
|
1080
1493
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1081
1494
|
"div",
|
|
1082
1495
|
{
|
|
1083
|
-
"data-ai-grid":
|
|
1496
|
+
"data-ai-grid": "",
|
|
1084
1497
|
style: {
|
|
1085
1498
|
display: "grid",
|
|
1086
1499
|
gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
|
|
@@ -1095,6 +1508,49 @@ function renderNode(node, ctx, path) {
|
|
|
1095
1508
|
switch (node.type) {
|
|
1096
1509
|
case "text":
|
|
1097
1510
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
|
|
1511
|
+
// Layout container: arranges child blocks, contributes no content of its own. `grid` is a
|
|
1512
|
+
// nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
|
|
1513
|
+
// mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
|
|
1514
|
+
// is a column. Children render through this same dispatcher, so edit markers, media
|
|
1515
|
+
// resolution, and copy paths all work unchanged inside a group.
|
|
1516
|
+
case "group": {
|
|
1517
|
+
const layout = str(slots.layout);
|
|
1518
|
+
const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
|
|
1519
|
+
const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1520
|
+
"div",
|
|
1521
|
+
{
|
|
1522
|
+
style: layout === "grid" ? {
|
|
1523
|
+
gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
|
|
1524
|
+
minWidth: 0
|
|
1525
|
+
} : { minWidth: 0 },
|
|
1526
|
+
children: renderNode(child, ctx, `${path}.c${i}`)
|
|
1527
|
+
},
|
|
1528
|
+
i
|
|
1529
|
+
));
|
|
1530
|
+
if (layout === "grid") {
|
|
1531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1532
|
+
"div",
|
|
1533
|
+
{
|
|
1534
|
+
"data-ai-group": "grid",
|
|
1535
|
+
style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
|
|
1536
|
+
children: kids
|
|
1537
|
+
}
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
if (layout === "split") {
|
|
1541
|
+
const ratio = str(slots.ratio);
|
|
1542
|
+
const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
|
|
1543
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1544
|
+
"div",
|
|
1545
|
+
{
|
|
1546
|
+
"data-ai-group": "split",
|
|
1547
|
+
style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
|
|
1548
|
+
children: kids
|
|
1549
|
+
}
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
|
|
1553
|
+
}
|
|
1098
1554
|
case "button":
|
|
1099
1555
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
|
|
1100
1556
|
case "button-row":
|
|
@@ -1175,33 +1631,102 @@ function renderNode(node, ctx, path) {
|
|
|
1175
1631
|
}
|
|
1176
1632
|
);
|
|
1177
1633
|
}
|
|
1178
|
-
case "form":
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1634
|
+
case "form": {
|
|
1635
|
+
const formAttrs = ctx.keyFor ? {
|
|
1636
|
+
"data-ohw-editable": "form",
|
|
1637
|
+
"data-ohw-key": ctx.keyFor(`${path}.form`),
|
|
1638
|
+
"data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
|
|
1639
|
+
} : {};
|
|
1640
|
+
const fieldStyle = {
|
|
1641
|
+
width: "100%",
|
|
1642
|
+
boxSizing: "border-box",
|
|
1643
|
+
border: `1px solid ${ctx.brand.palette.accent}`,
|
|
1644
|
+
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
1645
|
+
padding: "12px 14px",
|
|
1646
|
+
background: "#fff",
|
|
1647
|
+
color: ctx.brand.palette.dark,
|
|
1648
|
+
outline: "none",
|
|
1649
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
|
|
1650
|
+
};
|
|
1651
|
+
const labelStyle = {
|
|
1652
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
|
|
1653
|
+
color: ctx.brand.palette.dark
|
|
1654
|
+
};
|
|
1655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1656
|
+
"form",
|
|
1657
|
+
{
|
|
1658
|
+
...formAttrs,
|
|
1659
|
+
"data-ai-form": "",
|
|
1660
|
+
style: {
|
|
1661
|
+
display: "grid",
|
|
1662
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
1663
|
+
columnGap: 64,
|
|
1664
|
+
rowGap: AI_TREE_TOKENS.spacing4
|
|
1665
|
+
},
|
|
1666
|
+
children: (node.children ?? []).map((child, i) => {
|
|
1667
|
+
if (child.type === "input") {
|
|
1668
|
+
const cs2 = child.slots ?? {};
|
|
1669
|
+
const kind = str(cs2.kind);
|
|
1670
|
+
const label = str(cs2.label);
|
|
1671
|
+
const placeholder = str(cs2.placeholder);
|
|
1672
|
+
const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
|
|
1673
|
+
const isTextarea = kind === "textarea";
|
|
1674
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1675
|
+
"div",
|
|
1676
|
+
{
|
|
1677
|
+
style: {
|
|
1678
|
+
display: "flex",
|
|
1679
|
+
flexDirection: "column",
|
|
1680
|
+
gap: 8,
|
|
1681
|
+
...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
|
|
1682
|
+
},
|
|
1683
|
+
children: [
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
|
|
1685
|
+
isTextarea ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1686
|
+
"textarea",
|
|
1687
|
+
{
|
|
1688
|
+
name,
|
|
1689
|
+
placeholder,
|
|
1690
|
+
style: { ...fieldStyle, height: 140, resize: "vertical" }
|
|
1691
|
+
}
|
|
1692
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1693
|
+
"input",
|
|
1694
|
+
{
|
|
1695
|
+
name,
|
|
1696
|
+
type: kind === "email" ? "email" : "text",
|
|
1697
|
+
placeholder,
|
|
1698
|
+
style: { ...fieldStyle, height: 48 }
|
|
1699
|
+
}
|
|
1700
|
+
)
|
|
1701
|
+
]
|
|
1702
|
+
},
|
|
1703
|
+
i
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const cs = child.slots ?? {};
|
|
1707
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1708
|
+
"button",
|
|
1193
1709
|
{
|
|
1710
|
+
type: "submit",
|
|
1194
1711
|
style: {
|
|
1195
|
-
|
|
1712
|
+
gridColumn: "1 / -1",
|
|
1713
|
+
justifySelf: "start",
|
|
1714
|
+
border: "none",
|
|
1715
|
+
cursor: "pointer",
|
|
1716
|
+
padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
|
|
1196
1717
|
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1718
|
+
background: ctx.brand.palette.primary,
|
|
1719
|
+
color: AI_TREE_TOKENS.textPrimaryForeground,
|
|
1720
|
+
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
1721
|
+
},
|
|
1722
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
1723
|
+
},
|
|
1724
|
+
i
|
|
1725
|
+
);
|
|
1726
|
+
})
|
|
1202
1727
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1205
1730
|
case "schedule-widget":
|
|
1206
1731
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1207
1732
|
"div",
|
|
@@ -1227,11 +1752,14 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1227
1752
|
return null;
|
|
1228
1753
|
}
|
|
1229
1754
|
const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
|
|
1755
|
+
const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
|
|
1756
|
+
const blockBrand = band?.brand ?? resolvedBrand;
|
|
1230
1757
|
const ctx = {
|
|
1231
|
-
brand:
|
|
1758
|
+
brand: blockBrand,
|
|
1232
1759
|
resolveMedia: resolveMedia ?? (() => null),
|
|
1233
|
-
cardSurface:
|
|
1234
|
-
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
|
|
1760
|
+
cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
|
|
1761
|
+
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
|
|
1762
|
+
...band ? { buttonLabel: band.buttonLabel } : {}
|
|
1235
1763
|
};
|
|
1236
1764
|
const settings = tree.settings ?? {};
|
|
1237
1765
|
const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
|
|
@@ -1239,27 +1767,41 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1239
1767
|
const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
|
|
1240
1768
|
const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
|
|
1241
1769
|
const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
|
|
1770
|
+
const toneBackground = (() => {
|
|
1771
|
+
const { dark, primary, light } = resolvedBrand.palette;
|
|
1772
|
+
switch (settings.sectionBackground) {
|
|
1773
|
+
case "surface":
|
|
1774
|
+
return `color-mix(in srgb, ${light} 94%, ${dark})`;
|
|
1775
|
+
case "accent":
|
|
1776
|
+
return primary;
|
|
1777
|
+
case "accent-soft":
|
|
1778
|
+
return `color-mix(in srgb, ${primary} 12%, ${light})`;
|
|
1779
|
+
default:
|
|
1780
|
+
return void 0;
|
|
1781
|
+
}
|
|
1782
|
+
})();
|
|
1783
|
+
const distributed = !isOverlay && settings.textDistribution;
|
|
1242
1784
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1243
1785
|
"section",
|
|
1244
1786
|
{
|
|
1245
1787
|
"data-ai-section": tree.tag ?? "",
|
|
1246
1788
|
...bgAttrs,
|
|
1247
|
-
"data-ai-responsive": "",
|
|
1248
1789
|
style: {
|
|
1249
1790
|
position: "relative",
|
|
1250
1791
|
padding: `${pad}px 0`,
|
|
1251
|
-
background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
|
|
1792
|
+
background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
|
|
1252
1793
|
backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
|
|
1253
1794
|
backgroundSize: "cover",
|
|
1254
|
-
backgroundPosition: "center"
|
|
1795
|
+
backgroundPosition: "center",
|
|
1796
|
+
color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
|
|
1255
1797
|
},
|
|
1256
1798
|
children: [
|
|
1257
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
|
|
1258
1799
|
isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
|
|
1259
1801
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1260
1802
|
"div",
|
|
1261
1803
|
{
|
|
1262
|
-
"data-ai-
|
|
1804
|
+
"data-ai-container": "",
|
|
1263
1805
|
style: {
|
|
1264
1806
|
position: "relative",
|
|
1265
1807
|
maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
|
|
@@ -1270,15 +1812,29 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1270
1812
|
children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1271
1813
|
"div",
|
|
1272
1814
|
{
|
|
1273
|
-
"data-ai-
|
|
1815
|
+
"data-ai-row": "",
|
|
1274
1816
|
style: {
|
|
1275
1817
|
display: "grid",
|
|
1276
1818
|
gridTemplateColumns: "repeat(12, 1fr)",
|
|
1277
1819
|
gap: AI_TREE_TOKENS.spacing6,
|
|
1278
|
-
alignItems: settings.verticalPosition === "top" ? "start" : "center",
|
|
1820
|
+
alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
|
|
1279
1821
|
marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
|
|
1280
1822
|
},
|
|
1281
|
-
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1823
|
+
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1824
|
+
"div",
|
|
1825
|
+
{
|
|
1826
|
+
"data-ai-cell": "",
|
|
1827
|
+
style: {
|
|
1828
|
+
gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
|
|
1829
|
+
minWidth: 0,
|
|
1830
|
+
// space-between: each column becomes a flex column whose content spreads over
|
|
1831
|
+
// the full row height instead of clumping at the top.
|
|
1832
|
+
...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
|
|
1833
|
+
},
|
|
1834
|
+
children: renderNode(block, ctx, `r${r2}.b${b}`)
|
|
1835
|
+
},
|
|
1836
|
+
b
|
|
1837
|
+
))
|
|
1282
1838
|
},
|
|
1283
1839
|
r2
|
|
1284
1840
|
))
|
|
@@ -1294,17 +1850,36 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
1294
1850
|
var CONTAINER_ATTR = "data-ohw-ai-generated";
|
|
1295
1851
|
var REPLACED_ATTR = "data-ohw-ai-replaced-by";
|
|
1296
1852
|
var REMOVED_ATTR = "data-ohw-ai-removed";
|
|
1853
|
+
var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
|
|
1854
|
+
var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
1855
|
+
function readRootVar(name) {
|
|
1856
|
+
if (typeof document === "undefined") return "";
|
|
1857
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
1858
|
+
}
|
|
1859
|
+
function deriveBrandOverride() {
|
|
1860
|
+
const dark = readRootVar("--ohw-brand-dark");
|
|
1861
|
+
const primary = readRootVar("--ohw-brand-primary");
|
|
1862
|
+
const light = readRootVar("--ohw-brand-light");
|
|
1863
|
+
if (!dark || !primary || !light) return null;
|
|
1864
|
+
const accent = readRootVar("--ohw-brand-accent");
|
|
1865
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1866
|
+
const body = readRootVar("--font-body");
|
|
1867
|
+
return {
|
|
1868
|
+
palette: { dark, primary, accent: accent || dark, light },
|
|
1869
|
+
fonts: {
|
|
1870
|
+
heading: heading || AI_DEFAULT_BRAND.fonts.heading,
|
|
1871
|
+
body: body || AI_DEFAULT_BRAND.fonts.body
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1297
1875
|
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");
|
|
1876
|
+
const dark = readRootVar("--color-dark");
|
|
1877
|
+
const primary = readRootVar("--color-primary");
|
|
1878
|
+
const light = readRootVar("--color-light");
|
|
1304
1879
|
if (!dark || !primary || !light) return null;
|
|
1305
|
-
const accent =
|
|
1306
|
-
const heading =
|
|
1307
|
-
const body =
|
|
1880
|
+
const accent = readRootVar("--color-accent");
|
|
1881
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1882
|
+
const body = readRootVar("--font-body");
|
|
1308
1883
|
return {
|
|
1309
1884
|
palette: { dark, primary, accent: accent || dark, light },
|
|
1310
1885
|
fonts: {
|
|
@@ -1376,6 +1951,24 @@ function syncRemovedSections(state) {
|
|
|
1376
1951
|
}
|
|
1377
1952
|
}
|
|
1378
1953
|
}
|
|
1954
|
+
function syncTemplateHidden(state, pageHasSections) {
|
|
1955
|
+
const hide = state.hideTemplate === true && pageHasSections;
|
|
1956
|
+
for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
|
|
1957
|
+
if (!hide) {
|
|
1958
|
+
el.style.removeProperty("display");
|
|
1959
|
+
el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (!hide) return;
|
|
1963
|
+
for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
|
|
1964
|
+
if (el.hasAttribute(CONTAINER_ATTR)) continue;
|
|
1965
|
+
if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
|
|
1966
|
+
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
1967
|
+
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
|
|
1968
|
+
el.style.display = "none";
|
|
1969
|
+
el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1379
1972
|
function syncReplacedOriginals(state) {
|
|
1380
1973
|
for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
|
|
1381
1974
|
const byId = el.getAttribute(REPLACED_ATTR) ?? "";
|
|
@@ -1394,10 +1987,63 @@ function syncReplacedOriginals(state) {
|
|
|
1394
1987
|
}
|
|
1395
1988
|
}
|
|
1396
1989
|
}
|
|
1990
|
+
var sectionOrderIndex = /* @__PURE__ */ new Map();
|
|
1991
|
+
function setAiSectionOrder(raw, currentPath) {
|
|
1992
|
+
const next = /* @__PURE__ */ new Map();
|
|
1993
|
+
if (raw) {
|
|
1994
|
+
try {
|
|
1995
|
+
const entries = JSON.parse(raw);
|
|
1996
|
+
for (const entry of entries) {
|
|
1997
|
+
if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
|
|
1998
|
+
}
|
|
1999
|
+
} catch {
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
sectionOrderIndex = next;
|
|
2003
|
+
}
|
|
2004
|
+
function applyExplicitOrder(entries) {
|
|
2005
|
+
if (sectionOrderIndex.size === 0) return entries;
|
|
2006
|
+
return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
|
|
2007
|
+
if (a.order === void 0 && b.order === void 0) return a.index - b.index;
|
|
2008
|
+
if (a.order === void 0) return 1;
|
|
2009
|
+
if (b.order === void 0) return -1;
|
|
2010
|
+
return a.order - b.order;
|
|
2011
|
+
}).map((item) => item.entry);
|
|
2012
|
+
}
|
|
2013
|
+
function orderByChain(sections) {
|
|
2014
|
+
const ids = new Set(sections.map((entry) => entry.id));
|
|
2015
|
+
const after = /* @__PURE__ */ new Map();
|
|
2016
|
+
const roots = [];
|
|
2017
|
+
for (const entry of sections) {
|
|
2018
|
+
const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
|
|
2019
|
+
if (anchor && ids.has(anchor)) {
|
|
2020
|
+
const bucket = after.get(anchor);
|
|
2021
|
+
if (bucket) bucket.push(entry);
|
|
2022
|
+
else after.set(anchor, [entry]);
|
|
2023
|
+
} else {
|
|
2024
|
+
roots.push(entry);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
const out = [];
|
|
2028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2029
|
+
const visit = (entry) => {
|
|
2030
|
+
if (seen.has(entry.id)) return;
|
|
2031
|
+
seen.add(entry.id);
|
|
2032
|
+
out.push(entry);
|
|
2033
|
+
for (const child of after.get(entry.id) ?? []) visit(child);
|
|
2034
|
+
};
|
|
2035
|
+
for (const root of roots) visit(root);
|
|
2036
|
+
return out.length === sections.length ? out : sections;
|
|
2037
|
+
}
|
|
1397
2038
|
function applyAiSectionsToDom(state, options) {
|
|
1398
2039
|
if (typeof document === "undefined") return;
|
|
2040
|
+
const brandOverride = deriveBrandOverride();
|
|
1399
2041
|
const templateBrand = deriveTemplateBrand();
|
|
1400
|
-
const
|
|
2042
|
+
const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
|
|
2043
|
+
const pagePath = window.location.pathname;
|
|
2044
|
+
const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
|
|
2045
|
+
const activeIds = new Set(pageSections.map((entry) => entry.id));
|
|
2046
|
+
const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
|
|
1401
2047
|
for (const [id, section] of mounted) {
|
|
1402
2048
|
if (!activeIds.has(id)) {
|
|
1403
2049
|
section.root.unmount();
|
|
@@ -1405,8 +2051,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1405
2051
|
mounted.delete(id);
|
|
1406
2052
|
}
|
|
1407
2053
|
}
|
|
1408
|
-
for (const entry of
|
|
1409
|
-
const serialized = JSON.stringify(entry);
|
|
2054
|
+
for (const entry of ordered) {
|
|
2055
|
+
const serialized = JSON.stringify(entry) + brandKey;
|
|
1410
2056
|
const existing = mounted.get(entry.id);
|
|
1411
2057
|
if (existing && existing.serialized === serialized && existing.container.isConnected) {
|
|
1412
2058
|
continue;
|
|
@@ -1431,7 +2077,7 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1431
2077
|
AiTreeRenderer,
|
|
1432
2078
|
{
|
|
1433
2079
|
tree: entry.tree,
|
|
1434
|
-
brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2080
|
+
brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
1435
2081
|
resolveMedia,
|
|
1436
2082
|
editKeyPrefix: `ai.${entry.id}`
|
|
1437
2083
|
}
|
|
@@ -1440,8 +2086,20 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1440
2086
|
});
|
|
1441
2087
|
mounted.set(entry.id, { root, container, serialized });
|
|
1442
2088
|
}
|
|
2089
|
+
if (state.hideTemplate === true) {
|
|
2090
|
+
let prev = null;
|
|
2091
|
+
for (const entry of ordered) {
|
|
2092
|
+
const el = mounted.get(entry.id)?.container;
|
|
2093
|
+
if (!el) continue;
|
|
2094
|
+
if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
|
|
2095
|
+
prev.insertAdjacentElement("afterend", el);
|
|
2096
|
+
}
|
|
2097
|
+
prev = el;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
1443
2100
|
syncReplacedOriginals(state);
|
|
1444
2101
|
syncRemovedSections(state);
|
|
2102
|
+
syncTemplateHidden(state, pageSections.length > 0);
|
|
1445
2103
|
}
|
|
1446
2104
|
|
|
1447
2105
|
// src/useLinkHrefGuardian.ts
|
|
@@ -7140,13 +7798,17 @@ function MediaOverlay({
|
|
|
7140
7798
|
hover,
|
|
7141
7799
|
isUploading,
|
|
7142
7800
|
fadingOut = false,
|
|
7801
|
+
selected = false,
|
|
7802
|
+
hovered = false,
|
|
7143
7803
|
onFadeOutComplete,
|
|
7144
7804
|
onReplace,
|
|
7805
|
+
onSelect,
|
|
7145
7806
|
onVideoSettingsChange
|
|
7146
7807
|
}) {
|
|
7147
7808
|
const { rect } = hover;
|
|
7148
7809
|
const skeletonRef = React8.useRef(null);
|
|
7149
7810
|
const isVideo = hover.elementType === "video";
|
|
7811
|
+
const showChrome = !selected || hovered;
|
|
7150
7812
|
const autoplay = hover.videoAutoplay ?? true;
|
|
7151
7813
|
const muted = hover.videoMuted ?? true;
|
|
7152
7814
|
const probeRef = React8.useRef(null);
|
|
@@ -7193,7 +7855,7 @@ function MediaOverlay({
|
|
|
7193
7855
|
}
|
|
7194
7856
|
);
|
|
7195
7857
|
}
|
|
7196
|
-
const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7858
|
+
const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7197
7859
|
"div",
|
|
7198
7860
|
{
|
|
7199
7861
|
"data-ohw-bridge": "",
|
|
@@ -7263,10 +7925,12 @@ function MediaOverlay({
|
|
|
7263
7925
|
// in-document, pointer-events does it natively. The button below opts back in, so
|
|
7264
7926
|
// Replace still works.
|
|
7265
7927
|
pointerEvents: hover.hasTextOverlap ? "none" : "auto",
|
|
7266
|
-
|
|
7267
|
-
|
|
7928
|
+
// Selected: a firm component ring with no wash, so the image reads as chosen rather
|
|
7929
|
+
// than hovered. Hover keeps the existing tinted preview.
|
|
7930
|
+
boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
|
|
7931
|
+
background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
|
|
7268
7932
|
},
|
|
7269
|
-
onClick: () => onReplace(hover.key),
|
|
7933
|
+
onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
|
|
7270
7934
|
children: [
|
|
7271
7935
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
7272
7936
|
Button,
|
|
@@ -7320,7 +7984,7 @@ function MediaOverlay({
|
|
|
7320
7984
|
},
|
|
7321
7985
|
children: [
|
|
7322
7986
|
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
|
|
7987
|
+
replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
|
|
7324
7988
|
]
|
|
7325
7989
|
}
|
|
7326
7990
|
)
|
|
@@ -7404,6 +8068,8 @@ function parseSectionsFromRoot(root) {
|
|
|
7404
8068
|
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
7405
8069
|
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
7406
8070
|
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
8071
|
+
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
8072
|
+
continue;
|
|
7407
8073
|
seen.add(id);
|
|
7408
8074
|
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
7409
8075
|
sections.push({ id, label });
|
|
@@ -7689,6 +8355,7 @@ function AiSectionOverlay({
|
|
|
7689
8355
|
}) {
|
|
7690
8356
|
const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
|
|
7691
8357
|
const [reviewId, setReviewId] = (0, import_react8.useState)(null);
|
|
8358
|
+
const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
|
|
7692
8359
|
const reviewIdRef = (0, import_react8.useRef)(null);
|
|
7693
8360
|
reviewIdRef.current = reviewId;
|
|
7694
8361
|
const selectedIdRef = (0, import_react8.useRef)(null);
|
|
@@ -7750,6 +8417,7 @@ function AiSectionOverlay({
|
|
|
7750
8417
|
}
|
|
7751
8418
|
const found = readRect(sectionId) != null;
|
|
7752
8419
|
setReviewId(found ? sectionId : null);
|
|
8420
|
+
setReviewButtonsHidden(e.data.hideButtons === true);
|
|
7753
8421
|
postToParent2({ type: "ow:ai-review-started", sectionId, found });
|
|
7754
8422
|
if (found) {
|
|
7755
8423
|
document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
@@ -7877,13 +8545,16 @@ function AiSectionOverlay({
|
|
|
7877
8545
|
border: `2px solid ${PRIMARY2}`,
|
|
7878
8546
|
borderRadius: edgeAwareRadius(reviewRect),
|
|
7879
8547
|
zIndex: 2147483200,
|
|
7880
|
-
// The veil itself: swallows clicks so the section stays locked until decided.
|
|
8548
|
+
// The veil itself: swallows clicks so the section stays locked until decided. This
|
|
8549
|
+
// stopPropagation only guards the bubble phase; the bridge's capture-phase click
|
|
8550
|
+
// handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
|
|
8551
|
+
// Accept/Discard resolves to the media beneath and opens the file picker.
|
|
7881
8552
|
background: "rgba(8, 133, 254, 0.04)",
|
|
7882
8553
|
pointerEvents: "auto",
|
|
7883
8554
|
cursor: "default"
|
|
7884
8555
|
},
|
|
7885
8556
|
onClick: (e) => e.stopPropagation(),
|
|
7886
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
8557
|
+
children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
7887
8558
|
"div",
|
|
7888
8559
|
{
|
|
7889
8560
|
style: {
|
|
@@ -10450,8 +11121,13 @@ function referenceBox(slot) {
|
|
|
10450
11121
|
const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
|
|
10451
11122
|
(el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
|
|
10452
11123
|
) : null;
|
|
10453
|
-
|
|
10454
|
-
|
|
11124
|
+
if (neighbour) {
|
|
11125
|
+
const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
|
|
11126
|
+
if (box2?.width && box2.height) return box2;
|
|
11127
|
+
}
|
|
11128
|
+
const own = slot.getBoundingClientRect();
|
|
11129
|
+
if (own.width && own.height) return own;
|
|
11130
|
+
const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
|
|
10455
11131
|
return box?.width && box.height ? box : null;
|
|
10456
11132
|
}
|
|
10457
11133
|
function iconMarkupSizedFor(slot, markup) {
|
|
@@ -13320,6 +13996,7 @@ function useSectionDrag({
|
|
|
13320
13996
|
}
|
|
13321
13997
|
const orderJson = JSON.stringify(entries);
|
|
13322
13998
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
13999
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
13323
14000
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
13324
14001
|
applyPersistedOrder(entries);
|
|
13325
14002
|
clearSectionDragVisuals();
|
|
@@ -15439,6 +16116,70 @@ function OhhwellsBridge() {
|
|
|
15439
16116
|
const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
|
|
15440
16117
|
const dragOverElRef = (0, import_react17.useRef)(null);
|
|
15441
16118
|
const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
|
|
16119
|
+
const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
|
|
16120
|
+
const selectedMediaElRef = (0, import_react17.useRef)(null);
|
|
16121
|
+
const clearMediaSelection = (0, import_react17.useCallback)(() => {
|
|
16122
|
+
const prev = selectedMediaElRef.current;
|
|
16123
|
+
selectedMediaElRef.current = null;
|
|
16124
|
+
setSelectedMedia(null);
|
|
16125
|
+
const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
|
|
16126
|
+
if (sectionEl) {
|
|
16127
|
+
postToParentRef.current({
|
|
16128
|
+
type: "ow:section-selected",
|
|
16129
|
+
sectionId: sectionEl.dataset.ohwSection ?? null,
|
|
16130
|
+
sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
|
|
16131
|
+
key: null
|
|
16132
|
+
});
|
|
16133
|
+
}
|
|
16134
|
+
}, []);
|
|
16135
|
+
const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
|
|
16136
|
+
clearMediaSelectionRef.current = clearMediaSelection;
|
|
16137
|
+
const selectMediaElement = (0, import_react17.useCallback)((el) => {
|
|
16138
|
+
const r2 = el.getBoundingClientRect();
|
|
16139
|
+
const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
|
|
16140
|
+
selectedMediaElRef.current = el;
|
|
16141
|
+
setSelectedMedia({
|
|
16142
|
+
key: el.dataset.ohwKey ?? "",
|
|
16143
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
16144
|
+
elementType: el.dataset.ohwEditable ?? "image",
|
|
16145
|
+
hasTextOverlap: false,
|
|
16146
|
+
isDragOver: false,
|
|
16147
|
+
...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
|
|
16148
|
+
});
|
|
16149
|
+
const sectionEl = el.closest("[data-ohw-section]");
|
|
16150
|
+
aiSectionApiRef.current?.selectFromElement(el, { report: false });
|
|
16151
|
+
postToParentRef.current({
|
|
16152
|
+
type: "ow:section-selected",
|
|
16153
|
+
sectionId: sectionEl?.dataset.ohwSection ?? null,
|
|
16154
|
+
sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
|
|
16155
|
+
key: el.dataset.ohwKey ?? null,
|
|
16156
|
+
// Display name for the pill — the raw key prettifies into fragments ("Img"); the
|
|
16157
|
+
// bridge knows what the node IS, so it names it.
|
|
16158
|
+
keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
|
|
16159
|
+
});
|
|
16160
|
+
}, []);
|
|
16161
|
+
const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
|
|
16162
|
+
selectMediaElementRef.current = selectMediaElement;
|
|
16163
|
+
(0, import_react17.useEffect)(() => {
|
|
16164
|
+
if (!selectedMedia) return;
|
|
16165
|
+
const update = () => {
|
|
16166
|
+
const el = selectedMediaElRef.current;
|
|
16167
|
+
if (!el || !el.isConnected) {
|
|
16168
|
+
clearMediaSelection();
|
|
16169
|
+
return;
|
|
16170
|
+
}
|
|
16171
|
+
const r2 = el.getBoundingClientRect();
|
|
16172
|
+
setSelectedMedia(
|
|
16173
|
+
(prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
|
|
16174
|
+
);
|
|
16175
|
+
};
|
|
16176
|
+
window.addEventListener("scroll", update, true);
|
|
16177
|
+
window.addEventListener("resize", update);
|
|
16178
|
+
return () => {
|
|
16179
|
+
window.removeEventListener("scroll", update, true);
|
|
16180
|
+
window.removeEventListener("resize", update);
|
|
16181
|
+
};
|
|
16182
|
+
}, [selectedMedia !== null]);
|
|
15442
16183
|
const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
|
|
15443
16184
|
const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
|
|
15444
16185
|
const hoveredGapRef = (0, import_react17.useRef)(null);
|
|
@@ -15722,6 +16463,8 @@ function OhhwellsBridge() {
|
|
|
15722
16463
|
const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
|
|
15723
16464
|
const editContentRef = (0, import_react17.useRef)({});
|
|
15724
16465
|
const aiSectionsRef = (0, import_react17.useRef)("");
|
|
16466
|
+
const brandKitRef = (0, import_react17.useRef)("");
|
|
16467
|
+
const stylesRef = (0, import_react17.useRef)("");
|
|
15725
16468
|
const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
|
|
15726
16469
|
const [sitePages, setSitePages] = (0, import_react17.useState)([]);
|
|
15727
16470
|
const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
|
|
@@ -17042,13 +17785,29 @@ function OhhwellsBridge() {
|
|
|
17042
17785
|
}
|
|
17043
17786
|
const applyContent = (content) => {
|
|
17044
17787
|
const imageLoads = [];
|
|
17788
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17789
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
17790
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17791
|
+
} else {
|
|
17792
|
+
brandKitRef.current = "";
|
|
17793
|
+
applyBrandToDom(null);
|
|
17794
|
+
}
|
|
17045
17795
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
17046
17796
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
17797
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
17047
17798
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
17048
17799
|
}
|
|
17800
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17801
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
17802
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17803
|
+
}
|
|
17804
|
+
applyBrandChrome(content);
|
|
17049
17805
|
for (const [key, val] of Object.entries(content)) {
|
|
17050
17806
|
if (key === "__ohw_sections") continue;
|
|
17051
17807
|
if (key === AI_SECTIONS_KEY) continue;
|
|
17808
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17809
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17810
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17052
17811
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17053
17812
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17054
17813
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17236,8 +17995,25 @@ function OhhwellsBridge() {
|
|
|
17236
17995
|
initSectionInstancesFromContent(content, window.location.pathname);
|
|
17237
17996
|
observer?.disconnect();
|
|
17238
17997
|
try {
|
|
17998
|
+
applyBrandChrome(content);
|
|
17999
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
18000
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
18001
|
+
} else {
|
|
18002
|
+
applyBrandToDom(null);
|
|
18003
|
+
}
|
|
18004
|
+
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
18005
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
18006
|
+
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
18007
|
+
}
|
|
18008
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
18009
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
18010
|
+
}
|
|
17239
18011
|
for (const [key, val] of Object.entries(content)) {
|
|
17240
18012
|
if (key === "__ohw_sections") continue;
|
|
18013
|
+
if (key === AI_SECTIONS_KEY) continue;
|
|
18014
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
18015
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
18016
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17241
18017
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17242
18018
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17243
18019
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17380,9 +18156,21 @@ function OhhwellsBridge() {
|
|
|
17380
18156
|
}, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
|
|
17381
18157
|
(0, import_react17.useEffect)(() => {
|
|
17382
18158
|
if (!isEditMode) return;
|
|
18159
|
+
let lastPosted = 0;
|
|
17383
18160
|
const measure = () => {
|
|
17384
18161
|
const h = document.body.scrollHeight;
|
|
17385
|
-
if (h > 50
|
|
18162
|
+
if (h > 50 && Math.abs(h - lastPosted) > 1) {
|
|
18163
|
+
lastPosted = h;
|
|
18164
|
+
postToParent2({ type: "ow:height", height: h });
|
|
18165
|
+
}
|
|
18166
|
+
};
|
|
18167
|
+
let raf = null;
|
|
18168
|
+
const schedule = () => {
|
|
18169
|
+
if (raf != null) return;
|
|
18170
|
+
raf = requestAnimationFrame(() => {
|
|
18171
|
+
raf = null;
|
|
18172
|
+
measure();
|
|
18173
|
+
});
|
|
17386
18174
|
};
|
|
17387
18175
|
const t1 = setTimeout(measure, 50);
|
|
17388
18176
|
const t2 = setTimeout(measure, 500);
|
|
@@ -17402,6 +18190,7 @@ function OhhwellsBridge() {
|
|
|
17402
18190
|
return () => {
|
|
17403
18191
|
clearTimeout(t1);
|
|
17404
18192
|
clearTimeout(t2);
|
|
18193
|
+
if (raf != null) cancelAnimationFrame(raf);
|
|
17405
18194
|
clearResizeTimers();
|
|
17406
18195
|
window.removeEventListener("resize", handleResize);
|
|
17407
18196
|
};
|
|
@@ -17644,10 +18433,14 @@ function OhhwellsBridge() {
|
|
|
17644
18433
|
return;
|
|
17645
18434
|
}
|
|
17646
18435
|
const target = e.target;
|
|
18436
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17647
18437
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17648
18438
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17649
18439
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
17650
18440
|
if (isInsideLinkEditor(target)) return;
|
|
18441
|
+
if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
|
|
18442
|
+
clearMediaSelectionRef.current();
|
|
18443
|
+
}
|
|
17651
18444
|
if (isInsideFloatingPanel(target)) return;
|
|
17652
18445
|
if (target.closest("[data-ohw-form-toolbar]")) return;
|
|
17653
18446
|
if (target.closest(
|
|
@@ -17817,8 +18610,11 @@ function OhhwellsBridge() {
|
|
|
17817
18610
|
if (isMediaEditable(editable) && !buttonOnMedia) {
|
|
17818
18611
|
e.preventDefault();
|
|
17819
18612
|
e.stopPropagation();
|
|
17820
|
-
|
|
17821
|
-
|
|
18613
|
+
if (selectedMediaElRef.current === editable) {
|
|
18614
|
+
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
|
|
18615
|
+
} else {
|
|
18616
|
+
selectMediaElementRef.current(editable);
|
|
18617
|
+
}
|
|
17822
18618
|
return;
|
|
17823
18619
|
}
|
|
17824
18620
|
const socialItem = getSocialItem(editable);
|
|
@@ -17959,6 +18755,7 @@ function OhhwellsBridge() {
|
|
|
17959
18755
|
};
|
|
17960
18756
|
const handleDblClick = (e) => {
|
|
17961
18757
|
const target = e.target;
|
|
18758
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17962
18759
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17963
18760
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17964
18761
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
@@ -18759,7 +19556,9 @@ function OhhwellsBridge() {
|
|
|
18759
19556
|
return;
|
|
18760
19557
|
}
|
|
18761
19558
|
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
18762
|
-
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).
|
|
19559
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
|
|
19560
|
+
(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
|
|
19561
|
+
).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
18763
19562
|
const ZONE = 20;
|
|
18764
19563
|
for (let i = 0; i < sections.length; i++) {
|
|
18765
19564
|
const a = sections[i];
|
|
@@ -19091,10 +19890,23 @@ function OhhwellsBridge() {
|
|
|
19091
19890
|
if (e.data?.type !== "ow:hydrate") return;
|
|
19092
19891
|
const content = e.data.content;
|
|
19093
19892
|
if (!content) return;
|
|
19893
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
19894
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
19895
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
19896
|
+
} else {
|
|
19897
|
+
brandKitRef.current = "";
|
|
19898
|
+
applyBrandToDom(null);
|
|
19899
|
+
}
|
|
19094
19900
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
19095
19901
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
19902
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
19096
19903
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
19097
19904
|
}
|
|
19905
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
19906
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
19907
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
19908
|
+
}
|
|
19909
|
+
applyBrandChrome(content);
|
|
19098
19910
|
let sectionsJson = null;
|
|
19099
19911
|
for (const [key, val] of Object.entries(content)) {
|
|
19100
19912
|
if (key === "__ohw_sections") {
|
|
@@ -19102,6 +19914,9 @@ function OhhwellsBridge() {
|
|
|
19102
19914
|
continue;
|
|
19103
19915
|
}
|
|
19104
19916
|
if (key === AI_SECTIONS_KEY) continue;
|
|
19917
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
19918
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
19919
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
19105
19920
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
19106
19921
|
if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
|
|
19107
19922
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -19117,6 +19932,8 @@ function OhhwellsBridge() {
|
|
|
19117
19932
|
if (video && video.src !== val) applyVideoSrc(video, val);
|
|
19118
19933
|
} else if (el.dataset.ohwEditable === "link") {
|
|
19119
19934
|
applyLinkHref(el, val);
|
|
19935
|
+
} else if (el.dataset.ohwEditable === "icon") {
|
|
19936
|
+
applyIconMarkup(el, val);
|
|
19120
19937
|
} else if (isIconMarkupValue(val)) {
|
|
19121
19938
|
} else {
|
|
19122
19939
|
el.innerHTML = val;
|
|
@@ -19201,12 +20018,21 @@ function OhhwellsBridge() {
|
|
|
19201
20018
|
nodes: collectEditableNodes(editContentRef.current)
|
|
19202
20019
|
});
|
|
19203
20020
|
};
|
|
20021
|
+
const clearInteractionChrome = () => {
|
|
20022
|
+
deactivateRef.current();
|
|
20023
|
+
deselectRef.current();
|
|
20024
|
+
clearMediaSelectionRef.current();
|
|
20025
|
+
};
|
|
19204
20026
|
const handleAiApplyTree = (e) => {
|
|
19205
20027
|
if (e.data?.type !== "ow:ai-apply-tree") return;
|
|
19206
20028
|
const payload = e.data.payload;
|
|
19207
20029
|
if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
|
|
20030
|
+
clearInteractionChrome();
|
|
19208
20031
|
const previous = aiSectionsRef.current;
|
|
19209
|
-
const nextState = applyTreeToState(parseAiSectionsState(previous),
|
|
20032
|
+
const nextState = applyTreeToState(parseAiSectionsState(previous), {
|
|
20033
|
+
...payload,
|
|
20034
|
+
path: payload.path ?? window.location.pathname
|
|
20035
|
+
});
|
|
19210
20036
|
const nextValue = serializeAiSectionsState(nextState);
|
|
19211
20037
|
aiSectionsRef.current = nextValue;
|
|
19212
20038
|
applyAiSectionsToDom(nextState);
|
|
@@ -19227,6 +20053,7 @@ function OhhwellsBridge() {
|
|
|
19227
20053
|
const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
19228
20054
|
if (!exists) return;
|
|
19229
20055
|
if (isPageFrameSection(exists)) return;
|
|
20056
|
+
clearInteractionChrome();
|
|
19230
20057
|
const previous = aiSectionsRef.current;
|
|
19231
20058
|
const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
|
|
19232
20059
|
const nextValue = serializeAiSectionsState(nextState);
|
|
@@ -19242,14 +20069,45 @@ function OhhwellsBridge() {
|
|
|
19242
20069
|
const handleAiSetSections = (e) => {
|
|
19243
20070
|
if (e.data?.type !== "ow:ai-set-sections") return;
|
|
19244
20071
|
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20072
|
+
clearInteractionChrome();
|
|
19245
20073
|
aiSectionsRef.current = value;
|
|
19246
20074
|
applyAiSectionsToDom(parseAiSectionsState(value));
|
|
20075
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
19247
20076
|
const restoredHeight = document.body.scrollHeight;
|
|
19248
20077
|
if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
|
|
19249
20078
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
|
|
19250
20079
|
postAiSectionsChanged();
|
|
19251
20080
|
};
|
|
19252
20081
|
window.addEventListener("message", handleAiSetSections);
|
|
20082
|
+
const handleAiSetBrand = (e) => {
|
|
20083
|
+
if (e.data?.type !== "ow:ai-set-brand") return;
|
|
20084
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20085
|
+
const previous = brandKitRef.current;
|
|
20086
|
+
brandKitRef.current = value;
|
|
20087
|
+
applyBrandToDom(parseBrandKit(value));
|
|
20088
|
+
if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
|
|
20089
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
20090
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
|
|
20091
|
+
postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
|
|
20092
|
+
};
|
|
20093
|
+
window.addEventListener("message", handleAiSetBrand);
|
|
20094
|
+
const handleAiSetStyles = (e) => {
|
|
20095
|
+
if (e.data?.type !== "ow:ai-set-styles") return;
|
|
20096
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20097
|
+
const previous = stylesRef.current;
|
|
20098
|
+
stylesRef.current = value;
|
|
20099
|
+
applyStylesToDom(parseStyleStore(value));
|
|
20100
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
|
|
20101
|
+
postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
|
|
20102
|
+
};
|
|
20103
|
+
window.addEventListener("message", handleAiSetStyles);
|
|
20104
|
+
const handleGetBrand = (e) => {
|
|
20105
|
+
if (e.data?.type !== "ow:get-brand") return;
|
|
20106
|
+
const template = deriveTemplateBrand();
|
|
20107
|
+
const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
|
|
20108
|
+
postToParentRef.current({ type: "ow:brand-value", value });
|
|
20109
|
+
};
|
|
20110
|
+
window.addEventListener("message", handleGetBrand);
|
|
19253
20111
|
const handleMoveSection = (e) => {
|
|
19254
20112
|
if (e.data?.type !== "ow:move-section") return;
|
|
19255
20113
|
const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
|
|
@@ -19259,6 +20117,7 @@ function OhhwellsBridge() {
|
|
|
19259
20117
|
if (!entries) return;
|
|
19260
20118
|
const orderJson = JSON.stringify(entries);
|
|
19261
20119
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20120
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
19262
20121
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
19263
20122
|
window.dispatchEvent(new Event("resize"));
|
|
19264
20123
|
};
|
|
@@ -19322,6 +20181,7 @@ function OhhwellsBridge() {
|
|
|
19322
20181
|
}
|
|
19323
20182
|
deselectRef.current();
|
|
19324
20183
|
deactivateRef.current();
|
|
20184
|
+
clearMediaSelectionRef.current();
|
|
19325
20185
|
};
|
|
19326
20186
|
window.addEventListener("message", handleDeactivate);
|
|
19327
20187
|
const handleToastAction = (e) => {
|
|
@@ -19407,6 +20267,10 @@ function OhhwellsBridge() {
|
|
|
19407
20267
|
const handleKeyDown = (e) => {
|
|
19408
20268
|
if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
|
|
19409
20269
|
if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
|
|
20270
|
+
if (e.key === "Escape" && selectedMediaElRef.current) {
|
|
20271
|
+
clearMediaSelectionRef.current();
|
|
20272
|
+
return;
|
|
20273
|
+
}
|
|
19410
20274
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
|
|
19411
20275
|
e.preventDefault();
|
|
19412
20276
|
selectAllTextInEditable(activeElRef.current);
|
|
@@ -19566,6 +20430,12 @@ function OhhwellsBridge() {
|
|
|
19566
20430
|
if (aiSectionsRef.current) {
|
|
19567
20431
|
nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
|
|
19568
20432
|
}
|
|
20433
|
+
if (stylesRef.current) {
|
|
20434
|
+
nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
|
|
20435
|
+
}
|
|
20436
|
+
if (brandKitRef.current) {
|
|
20437
|
+
nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
|
|
20438
|
+
}
|
|
19569
20439
|
document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
|
|
19570
20440
|
const formKey = formKeyOf(form);
|
|
19571
20441
|
if (!formKey) return;
|
|
@@ -19980,6 +20850,9 @@ function OhhwellsBridge() {
|
|
|
19980
20850
|
window.removeEventListener("message", handleAiApplyTree);
|
|
19981
20851
|
window.removeEventListener("message", handleAiDeleteSection);
|
|
19982
20852
|
window.removeEventListener("message", handleAiSetSections);
|
|
20853
|
+
window.removeEventListener("message", handleAiSetBrand);
|
|
20854
|
+
window.removeEventListener("message", handleAiSetStyles);
|
|
20855
|
+
window.removeEventListener("message", handleGetBrand);
|
|
19983
20856
|
window.removeEventListener("message", handleMoveSection);
|
|
19984
20857
|
window.removeEventListener("message", handlePanelDragging);
|
|
19985
20858
|
window.removeEventListener("message", handleDeleteSection);
|
|
@@ -20190,7 +21063,7 @@ function OhhwellsBridge() {
|
|
|
20190
21063
|
postToParent2({
|
|
20191
21064
|
type: "ow:ready",
|
|
20192
21065
|
version: "1",
|
|
20193
|
-
bridgeVersion: "0.1.
|
|
21066
|
+
bridgeVersion: "0.1.78",
|
|
20194
21067
|
path: pathname,
|
|
20195
21068
|
nodes: collectEditableNodes(editContentRef.current),
|
|
20196
21069
|
sections
|
|
@@ -20597,11 +21470,22 @@ function OhhwellsBridge() {
|
|
|
20597
21470
|
const showEditLink = toolbarShowEditLink;
|
|
20598
21471
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
20599
21472
|
linkPopoverOpenRef.current = linkPopover !== null;
|
|
21473
|
+
const handleMediaSelect = (0, import_react17.useCallback)((key) => {
|
|
21474
|
+
const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
|
|
21475
|
+
(m) => (m.dataset.ohwKey ?? "") === key
|
|
21476
|
+
) ?? null;
|
|
21477
|
+
if (!el) return;
|
|
21478
|
+
selectMediaElementRef.current(el);
|
|
21479
|
+
}, []);
|
|
20600
21480
|
const handleMediaReplace = (0, import_react17.useCallback)(
|
|
20601
21481
|
(key) => {
|
|
20602
|
-
postToParent2({
|
|
21482
|
+
postToParent2({
|
|
21483
|
+
type: "ow:image-pick",
|
|
21484
|
+
key,
|
|
21485
|
+
elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
|
|
21486
|
+
});
|
|
20603
21487
|
},
|
|
20604
|
-
[postToParent2, mediaHover?.elementType]
|
|
21488
|
+
[postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
|
|
20605
21489
|
);
|
|
20606
21490
|
const handleEditCarousel = (0, import_react17.useCallback)(
|
|
20607
21491
|
(key) => {
|
|
@@ -20673,12 +21557,25 @@ function OhhwellsBridge() {
|
|
|
20673
21557
|
},
|
|
20674
21558
|
`uploading-${key}`
|
|
20675
21559
|
)),
|
|
20676
|
-
mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
21560
|
+
mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
20677
21561
|
MediaOverlay,
|
|
20678
21562
|
{
|
|
20679
21563
|
hover: mediaHover,
|
|
20680
21564
|
isUploading: false,
|
|
20681
21565
|
onReplace: handleMediaReplace,
|
|
21566
|
+
onSelect: handleMediaSelect,
|
|
21567
|
+
onVideoSettingsChange: handleVideoSettingsChange
|
|
21568
|
+
}
|
|
21569
|
+
),
|
|
21570
|
+
selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
21571
|
+
MediaOverlay,
|
|
21572
|
+
{
|
|
21573
|
+
hover: selectedMedia,
|
|
21574
|
+
selected: true,
|
|
21575
|
+
hovered: mediaHover?.key === selectedMedia.key,
|
|
21576
|
+
isUploading: false,
|
|
21577
|
+
onReplace: handleMediaReplace,
|
|
21578
|
+
onSelect: handleMediaSelect,
|
|
20682
21579
|
onVideoSettingsChange: handleVideoSettingsChange
|
|
20683
21580
|
}
|
|
20684
21581
|
),
|