@ohhwells/bridge 0.1.77 → 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 +1036 -137
- 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 +1036 -137
- 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 });
|
|
@@ -7433,6 +8099,10 @@ function topLevelSections() {
|
|
|
7433
8099
|
function instanceIdOf(el) {
|
|
7434
8100
|
return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
|
|
7435
8101
|
}
|
|
8102
|
+
function findByInstanceId(instanceId) {
|
|
8103
|
+
const escapedId = CSS.escape(instanceId);
|
|
8104
|
+
return document.querySelector(`[data-ohw-instance="${escapedId}"]`) ?? document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
|
|
8105
|
+
}
|
|
7436
8106
|
function planSectionMove(instanceId, targetIndex, currentPath) {
|
|
7437
8107
|
const sections = topLevelSections();
|
|
7438
8108
|
const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
|
|
@@ -7468,7 +8138,7 @@ function syncRemovedFlags(entries) {
|
|
|
7468
8138
|
}
|
|
7469
8139
|
});
|
|
7470
8140
|
for (const id of removedIds) {
|
|
7471
|
-
const el =
|
|
8141
|
+
const el = findByInstanceId(id);
|
|
7472
8142
|
if (el) {
|
|
7473
8143
|
el.style.display = "none";
|
|
7474
8144
|
el.setAttribute(REMOVED_ATTR2, "");
|
|
@@ -7496,9 +8166,7 @@ function applyPersistedOrder(entries) {
|
|
|
7496
8166
|
}
|
|
7497
8167
|
}
|
|
7498
8168
|
function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
|
|
7499
|
-
|
|
7500
|
-
if (!document.querySelector(`[data-ohw-instance="${escapedId}"]`) && !document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`))
|
|
7501
|
-
return null;
|
|
8169
|
+
if (!findByInstanceId(instanceId)) return null;
|
|
7502
8170
|
const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
|
|
7503
8171
|
const allSections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
|
|
7504
8172
|
(el) => !el.parentElement?.closest("[data-ohw-section]") && !isChromeSection(el)
|
|
@@ -7687,6 +8355,7 @@ function AiSectionOverlay({
|
|
|
7687
8355
|
}) {
|
|
7688
8356
|
const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
|
|
7689
8357
|
const [reviewId, setReviewId] = (0, import_react8.useState)(null);
|
|
8358
|
+
const [reviewButtonsHidden, setReviewButtonsHidden] = (0, import_react8.useState)(false);
|
|
7690
8359
|
const reviewIdRef = (0, import_react8.useRef)(null);
|
|
7691
8360
|
reviewIdRef.current = reviewId;
|
|
7692
8361
|
const selectedIdRef = (0, import_react8.useRef)(null);
|
|
@@ -7748,6 +8417,7 @@ function AiSectionOverlay({
|
|
|
7748
8417
|
}
|
|
7749
8418
|
const found = readRect(sectionId) != null;
|
|
7750
8419
|
setReviewId(found ? sectionId : null);
|
|
8420
|
+
setReviewButtonsHidden(e.data.hideButtons === true);
|
|
7751
8421
|
postToParent2({ type: "ow:ai-review-started", sectionId, found });
|
|
7752
8422
|
if (found) {
|
|
7753
8423
|
document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
@@ -7875,13 +8545,16 @@ function AiSectionOverlay({
|
|
|
7875
8545
|
border: `2px solid ${PRIMARY2}`,
|
|
7876
8546
|
borderRadius: edgeAwareRadius(reviewRect),
|
|
7877
8547
|
zIndex: 2147483200,
|
|
7878
|
-
// 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.
|
|
7879
8552
|
background: "rgba(8, 133, 254, 0.04)",
|
|
7880
8553
|
pointerEvents: "auto",
|
|
7881
8554
|
cursor: "default"
|
|
7882
8555
|
},
|
|
7883
8556
|
onClick: (e) => e.stopPropagation(),
|
|
7884
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
8557
|
+
children: !reviewButtonsHidden && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
7885
8558
|
"div",
|
|
7886
8559
|
{
|
|
7887
8560
|
style: {
|
|
@@ -10448,8 +11121,13 @@ function referenceBox(slot) {
|
|
|
10448
11121
|
const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
|
|
10449
11122
|
(el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
|
|
10450
11123
|
) : null;
|
|
10451
|
-
|
|
10452
|
-
|
|
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;
|
|
10453
11131
|
return box?.width && box.height ? box : null;
|
|
10454
11132
|
}
|
|
10455
11133
|
function iconMarkupSizedFor(slot, markup) {
|
|
@@ -13318,6 +13996,7 @@ function useSectionDrag({
|
|
|
13318
13996
|
}
|
|
13319
13997
|
const orderJson = JSON.stringify(entries);
|
|
13320
13998
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
13999
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
13321
14000
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
13322
14001
|
applyPersistedOrder(entries);
|
|
13323
14002
|
clearSectionDragVisuals();
|
|
@@ -15437,6 +16116,70 @@ function OhhwellsBridge() {
|
|
|
15437
16116
|
const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
|
|
15438
16117
|
const dragOverElRef = (0, import_react17.useRef)(null);
|
|
15439
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]);
|
|
15440
16183
|
const [carouselHover, setCarouselHover] = (0, import_react17.useState)(null);
|
|
15441
16184
|
const [uploadingRects, setUploadingRects] = (0, import_react17.useState)({});
|
|
15442
16185
|
const hoveredGapRef = (0, import_react17.useRef)(null);
|
|
@@ -15720,6 +16463,8 @@ function OhhwellsBridge() {
|
|
|
15720
16463
|
const addNavAfterAnchorRef = (0, import_react17.useRef)(null);
|
|
15721
16464
|
const editContentRef = (0, import_react17.useRef)({});
|
|
15722
16465
|
const aiSectionsRef = (0, import_react17.useRef)("");
|
|
16466
|
+
const brandKitRef = (0, import_react17.useRef)("");
|
|
16467
|
+
const stylesRef = (0, import_react17.useRef)("");
|
|
15723
16468
|
const pendingDeleteUndoRef = (0, import_react17.useRef)(null);
|
|
15724
16469
|
const [sitePages, setSitePages] = (0, import_react17.useState)([]);
|
|
15725
16470
|
const [sectionsByPath, setSectionsByPath] = (0, import_react17.useState)({});
|
|
@@ -17040,13 +17785,29 @@ function OhhwellsBridge() {
|
|
|
17040
17785
|
}
|
|
17041
17786
|
const applyContent = (content) => {
|
|
17042
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
|
+
}
|
|
17043
17795
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
17044
17796
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
17797
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
17045
17798
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
17046
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);
|
|
17047
17805
|
for (const [key, val] of Object.entries(content)) {
|
|
17048
17806
|
if (key === "__ohw_sections") continue;
|
|
17049
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;
|
|
17050
17811
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17051
17812
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17052
17813
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17234,8 +17995,25 @@ function OhhwellsBridge() {
|
|
|
17234
17995
|
initSectionInstancesFromContent(content, window.location.pathname);
|
|
17235
17996
|
observer?.disconnect();
|
|
17236
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
|
+
}
|
|
17237
18011
|
for (const [key, val] of Object.entries(content)) {
|
|
17238
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;
|
|
17239
18017
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17240
18018
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17241
18019
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17378,9 +18156,21 @@ function OhhwellsBridge() {
|
|
|
17378
18156
|
}, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
|
|
17379
18157
|
(0, import_react17.useEffect)(() => {
|
|
17380
18158
|
if (!isEditMode) return;
|
|
18159
|
+
let lastPosted = 0;
|
|
17381
18160
|
const measure = () => {
|
|
17382
18161
|
const h = document.body.scrollHeight;
|
|
17383
|
-
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
|
+
});
|
|
17384
18174
|
};
|
|
17385
18175
|
const t1 = setTimeout(measure, 50);
|
|
17386
18176
|
const t2 = setTimeout(measure, 500);
|
|
@@ -17400,6 +18190,7 @@ function OhhwellsBridge() {
|
|
|
17400
18190
|
return () => {
|
|
17401
18191
|
clearTimeout(t1);
|
|
17402
18192
|
clearTimeout(t2);
|
|
18193
|
+
if (raf != null) cancelAnimationFrame(raf);
|
|
17403
18194
|
clearResizeTimers();
|
|
17404
18195
|
window.removeEventListener("resize", handleResize);
|
|
17405
18196
|
};
|
|
@@ -17642,10 +18433,14 @@ function OhhwellsBridge() {
|
|
|
17642
18433
|
return;
|
|
17643
18434
|
}
|
|
17644
18435
|
const target = e.target;
|
|
18436
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17645
18437
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17646
18438
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17647
18439
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
17648
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
|
+
}
|
|
17649
18444
|
if (isInsideFloatingPanel(target)) return;
|
|
17650
18445
|
if (target.closest("[data-ohw-form-toolbar]")) return;
|
|
17651
18446
|
if (target.closest(
|
|
@@ -17815,8 +18610,11 @@ function OhhwellsBridge() {
|
|
|
17815
18610
|
if (isMediaEditable(editable) && !buttonOnMedia) {
|
|
17816
18611
|
e.preventDefault();
|
|
17817
18612
|
e.stopPropagation();
|
|
17818
|
-
|
|
17819
|
-
|
|
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
|
+
}
|
|
17820
18618
|
return;
|
|
17821
18619
|
}
|
|
17822
18620
|
const socialItem = getSocialItem(editable);
|
|
@@ -17957,6 +18755,7 @@ function OhhwellsBridge() {
|
|
|
17957
18755
|
};
|
|
17958
18756
|
const handleDblClick = (e) => {
|
|
17959
18757
|
const target = e.target;
|
|
18758
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17960
18759
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17961
18760
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17962
18761
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
@@ -18757,7 +19556,9 @@ function OhhwellsBridge() {
|
|
|
18757
19556
|
return;
|
|
18758
19557
|
}
|
|
18759
19558
|
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
18760
|
-
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);
|
|
18761
19562
|
const ZONE = 20;
|
|
18762
19563
|
for (let i = 0; i < sections.length; i++) {
|
|
18763
19564
|
const a = sections[i];
|
|
@@ -19089,10 +19890,23 @@ function OhhwellsBridge() {
|
|
|
19089
19890
|
if (e.data?.type !== "ow:hydrate") return;
|
|
19090
19891
|
const content = e.data.content;
|
|
19091
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
|
+
}
|
|
19092
19900
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
19093
19901
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
19902
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
19094
19903
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
19095
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);
|
|
19096
19910
|
let sectionsJson = null;
|
|
19097
19911
|
for (const [key, val] of Object.entries(content)) {
|
|
19098
19912
|
if (key === "__ohw_sections") {
|
|
@@ -19100,6 +19914,9 @@ function OhhwellsBridge() {
|
|
|
19100
19914
|
continue;
|
|
19101
19915
|
}
|
|
19102
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;
|
|
19103
19920
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
19104
19921
|
if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
|
|
19105
19922
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -19115,6 +19932,8 @@ function OhhwellsBridge() {
|
|
|
19115
19932
|
if (video && video.src !== val) applyVideoSrc(video, val);
|
|
19116
19933
|
} else if (el.dataset.ohwEditable === "link") {
|
|
19117
19934
|
applyLinkHref(el, val);
|
|
19935
|
+
} else if (el.dataset.ohwEditable === "icon") {
|
|
19936
|
+
applyIconMarkup(el, val);
|
|
19118
19937
|
} else if (isIconMarkupValue(val)) {
|
|
19119
19938
|
} else {
|
|
19120
19939
|
el.innerHTML = val;
|
|
@@ -19199,12 +20018,21 @@ function OhhwellsBridge() {
|
|
|
19199
20018
|
nodes: collectEditableNodes(editContentRef.current)
|
|
19200
20019
|
});
|
|
19201
20020
|
};
|
|
20021
|
+
const clearInteractionChrome = () => {
|
|
20022
|
+
deactivateRef.current();
|
|
20023
|
+
deselectRef.current();
|
|
20024
|
+
clearMediaSelectionRef.current();
|
|
20025
|
+
};
|
|
19202
20026
|
const handleAiApplyTree = (e) => {
|
|
19203
20027
|
if (e.data?.type !== "ow:ai-apply-tree") return;
|
|
19204
20028
|
const payload = e.data.payload;
|
|
19205
20029
|
if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
|
|
20030
|
+
clearInteractionChrome();
|
|
19206
20031
|
const previous = aiSectionsRef.current;
|
|
19207
|
-
const nextState = applyTreeToState(parseAiSectionsState(previous),
|
|
20032
|
+
const nextState = applyTreeToState(parseAiSectionsState(previous), {
|
|
20033
|
+
...payload,
|
|
20034
|
+
path: payload.path ?? window.location.pathname
|
|
20035
|
+
});
|
|
19208
20036
|
const nextValue = serializeAiSectionsState(nextState);
|
|
19209
20037
|
aiSectionsRef.current = nextValue;
|
|
19210
20038
|
applyAiSectionsToDom(nextState);
|
|
@@ -19225,6 +20053,7 @@ function OhhwellsBridge() {
|
|
|
19225
20053
|
const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
19226
20054
|
if (!exists) return;
|
|
19227
20055
|
if (isPageFrameSection(exists)) return;
|
|
20056
|
+
clearInteractionChrome();
|
|
19228
20057
|
const previous = aiSectionsRef.current;
|
|
19229
20058
|
const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
|
|
19230
20059
|
const nextValue = serializeAiSectionsState(nextState);
|
|
@@ -19240,14 +20069,45 @@ function OhhwellsBridge() {
|
|
|
19240
20069
|
const handleAiSetSections = (e) => {
|
|
19241
20070
|
if (e.data?.type !== "ow:ai-set-sections") return;
|
|
19242
20071
|
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20072
|
+
clearInteractionChrome();
|
|
19243
20073
|
aiSectionsRef.current = value;
|
|
19244
20074
|
applyAiSectionsToDom(parseAiSectionsState(value));
|
|
20075
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
19245
20076
|
const restoredHeight = document.body.scrollHeight;
|
|
19246
20077
|
if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
|
|
19247
20078
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
|
|
19248
20079
|
postAiSectionsChanged();
|
|
19249
20080
|
};
|
|
19250
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);
|
|
19251
20111
|
const handleMoveSection = (e) => {
|
|
19252
20112
|
if (e.data?.type !== "ow:move-section") return;
|
|
19253
20113
|
const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
|
|
@@ -19257,6 +20117,7 @@ function OhhwellsBridge() {
|
|
|
19257
20117
|
if (!entries) return;
|
|
19258
20118
|
const orderJson = JSON.stringify(entries);
|
|
19259
20119
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20120
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
19260
20121
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
19261
20122
|
window.dispatchEvent(new Event("resize"));
|
|
19262
20123
|
};
|
|
@@ -19320,6 +20181,7 @@ function OhhwellsBridge() {
|
|
|
19320
20181
|
}
|
|
19321
20182
|
deselectRef.current();
|
|
19322
20183
|
deactivateRef.current();
|
|
20184
|
+
clearMediaSelectionRef.current();
|
|
19323
20185
|
};
|
|
19324
20186
|
window.addEventListener("message", handleDeactivate);
|
|
19325
20187
|
const handleToastAction = (e) => {
|
|
@@ -19405,6 +20267,10 @@ function OhhwellsBridge() {
|
|
|
19405
20267
|
const handleKeyDown = (e) => {
|
|
19406
20268
|
if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
|
|
19407
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
|
+
}
|
|
19408
20274
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
|
|
19409
20275
|
e.preventDefault();
|
|
19410
20276
|
selectAllTextInEditable(activeElRef.current);
|
|
@@ -19564,6 +20430,12 @@ function OhhwellsBridge() {
|
|
|
19564
20430
|
if (aiSectionsRef.current) {
|
|
19565
20431
|
nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
|
|
19566
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
|
+
}
|
|
19567
20439
|
document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
|
|
19568
20440
|
const formKey = formKeyOf(form);
|
|
19569
20441
|
if (!formKey) return;
|
|
@@ -19978,6 +20850,9 @@ function OhhwellsBridge() {
|
|
|
19978
20850
|
window.removeEventListener("message", handleAiApplyTree);
|
|
19979
20851
|
window.removeEventListener("message", handleAiDeleteSection);
|
|
19980
20852
|
window.removeEventListener("message", handleAiSetSections);
|
|
20853
|
+
window.removeEventListener("message", handleAiSetBrand);
|
|
20854
|
+
window.removeEventListener("message", handleAiSetStyles);
|
|
20855
|
+
window.removeEventListener("message", handleGetBrand);
|
|
19981
20856
|
window.removeEventListener("message", handleMoveSection);
|
|
19982
20857
|
window.removeEventListener("message", handlePanelDragging);
|
|
19983
20858
|
window.removeEventListener("message", handleDeleteSection);
|
|
@@ -20188,7 +21063,7 @@ function OhhwellsBridge() {
|
|
|
20188
21063
|
postToParent2({
|
|
20189
21064
|
type: "ow:ready",
|
|
20190
21065
|
version: "1",
|
|
20191
|
-
bridgeVersion: "0.1.
|
|
21066
|
+
bridgeVersion: "0.1.78",
|
|
20192
21067
|
path: pathname,
|
|
20193
21068
|
nodes: collectEditableNodes(editContentRef.current),
|
|
20194
21069
|
sections
|
|
@@ -20595,11 +21470,22 @@ function OhhwellsBridge() {
|
|
|
20595
21470
|
const showEditLink = toolbarShowEditLink;
|
|
20596
21471
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
20597
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
|
+
}, []);
|
|
20598
21480
|
const handleMediaReplace = (0, import_react17.useCallback)(
|
|
20599
21481
|
(key) => {
|
|
20600
|
-
postToParent2({
|
|
21482
|
+
postToParent2({
|
|
21483
|
+
type: "ow:image-pick",
|
|
21484
|
+
key,
|
|
21485
|
+
elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
|
|
21486
|
+
});
|
|
20601
21487
|
},
|
|
20602
|
-
[postToParent2, mediaHover?.elementType]
|
|
21488
|
+
[postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
|
|
20603
21489
|
);
|
|
20604
21490
|
const handleEditCarousel = (0, import_react17.useCallback)(
|
|
20605
21491
|
(key) => {
|
|
@@ -20671,12 +21557,25 @@ function OhhwellsBridge() {
|
|
|
20671
21557
|
},
|
|
20672
21558
|
`uploading-${key}`
|
|
20673
21559
|
)),
|
|
20674
|
-
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)(
|
|
20675
21561
|
MediaOverlay,
|
|
20676
21562
|
{
|
|
20677
21563
|
hover: mediaHover,
|
|
20678
21564
|
isUploading: false,
|
|
20679
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,
|
|
20680
21579
|
onVideoSettingsChange: handleVideoSettingsChange
|
|
20681
21580
|
}
|
|
20682
21581
|
),
|