@ohhwells/bridge 0.1.78 → 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1059 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1059 -138
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -83,7 +83,12 @@ function parseAiSectionsState(raw) {
|
|
|
83
83
|
media: entry.media && typeof entry.media === "object" ? entry.media : {}
|
|
84
84
|
}));
|
|
85
85
|
const removed = Array.isArray(parsed.removed) ? parsed.removed.filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
86
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
v: 1,
|
|
88
|
+
sections,
|
|
89
|
+
...removed.length ? { removed } : {},
|
|
90
|
+
...parsed.hideTemplate === true ? { hideTemplate: true } : {}
|
|
91
|
+
};
|
|
87
92
|
} catch {
|
|
88
93
|
return EMPTY_AI_SECTIONS;
|
|
89
94
|
}
|
|
@@ -96,6 +101,7 @@ function applyTreeToState(state, payload) {
|
|
|
96
101
|
const entry = {
|
|
97
102
|
id: payload.id,
|
|
98
103
|
label: payload.label ?? "Generated section",
|
|
104
|
+
...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
|
|
99
105
|
afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
|
|
100
106
|
...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
|
|
101
107
|
...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
|
|
@@ -118,6 +124,317 @@ function deleteSectionFromState(state, sectionId) {
|
|
|
118
124
|
return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
// src/lib/brand-chrome.ts
|
|
128
|
+
var BRAND_NAME_KEY = "__ohw_brand_name";
|
|
129
|
+
var BRAND_TITLE_KEY = "__ohw_site_title";
|
|
130
|
+
var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
|
|
131
|
+
var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
|
|
132
|
+
BRAND_NAME_KEY,
|
|
133
|
+
BRAND_TITLE_KEY,
|
|
134
|
+
BRAND_FAVICON_LETTER_KEY
|
|
135
|
+
]);
|
|
136
|
+
function upsertMeta(selector, attr, token, value) {
|
|
137
|
+
let el = document.head.querySelector(selector);
|
|
138
|
+
if (!el) {
|
|
139
|
+
el = document.createElement("meta");
|
|
140
|
+
el.setAttribute(attr, token);
|
|
141
|
+
document.head.appendChild(el);
|
|
142
|
+
}
|
|
143
|
+
if (el.getAttribute("content") !== value) el.setAttribute("content", value);
|
|
144
|
+
}
|
|
145
|
+
function escapeXml(value) {
|
|
146
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
147
|
+
}
|
|
148
|
+
function applyLetterFavicon(letter) {
|
|
149
|
+
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>`;
|
|
150
|
+
const href = `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
151
|
+
let link = document.head.querySelector('link[rel="icon"]');
|
|
152
|
+
if (!link) {
|
|
153
|
+
link = document.createElement("link");
|
|
154
|
+
link.rel = "icon";
|
|
155
|
+
document.head.appendChild(link);
|
|
156
|
+
}
|
|
157
|
+
link.type = "image/svg+xml";
|
|
158
|
+
if (link.href !== href) link.href = href;
|
|
159
|
+
}
|
|
160
|
+
function applyBrandChrome(content) {
|
|
161
|
+
const name = content[BRAND_NAME_KEY];
|
|
162
|
+
if (typeof name === "string" && name.length > 0) {
|
|
163
|
+
document.querySelectorAll("[data-ohw-wordmark]").forEach((el) => {
|
|
164
|
+
if (el.textContent !== name) el.textContent = name;
|
|
165
|
+
if (el.getAttribute("title") !== name) el.setAttribute("title", name);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const title = content[BRAND_TITLE_KEY];
|
|
169
|
+
if (typeof title === "string" && title.length > 0) {
|
|
170
|
+
if (document.title !== title) document.title = title;
|
|
171
|
+
upsertMeta('meta[property="og:title"]', "property", "og:title", title);
|
|
172
|
+
upsertMeta('meta[name="twitter:title"]', "name", "twitter:title", title);
|
|
173
|
+
}
|
|
174
|
+
const letter = content[BRAND_FAVICON_LETTER_KEY];
|
|
175
|
+
if (typeof letter === "string" && letter.length > 0) {
|
|
176
|
+
applyLetterFavicon(letter);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/lib/brand-kit.ts
|
|
181
|
+
var BRAND_KIT_KEY = "__ohw_brand";
|
|
182
|
+
var BRAND_VAR_PREFIX = "--ohw-brand-";
|
|
183
|
+
var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
|
|
184
|
+
(role) => `${BRAND_VAR_PREFIX}${role}`
|
|
185
|
+
);
|
|
186
|
+
var FONT_VARS = {
|
|
187
|
+
heading: ["--font-heading", "--font-display", "--brand-font-heading"],
|
|
188
|
+
body: ["--font-body", "--brand-font-body"]
|
|
189
|
+
};
|
|
190
|
+
var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
|
|
191
|
+
function brandColorVars(kit) {
|
|
192
|
+
const { dark, primary, accent, light } = kit.palette;
|
|
193
|
+
const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
|
|
194
|
+
return {
|
|
195
|
+
[`${BRAND_VAR_PREFIX}primary`]: primary,
|
|
196
|
+
[`${BRAND_VAR_PREFIX}accent`]: accent,
|
|
197
|
+
[`${BRAND_VAR_PREFIX}light`]: light,
|
|
198
|
+
[`${BRAND_VAR_PREFIX}dark`]: dark,
|
|
199
|
+
[`${BRAND_VAR_PREFIX}surface`]: mix(light, 95, dark),
|
|
200
|
+
[`${BRAND_VAR_PREFIX}border`]: mix(light, 85, dark),
|
|
201
|
+
[`${BRAND_VAR_PREFIX}muted`]: mix(dark, 62, light)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function parseBrandKit(raw) {
|
|
205
|
+
if (!raw) return null;
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(raw);
|
|
208
|
+
const p = parsed?.palette;
|
|
209
|
+
const f = parsed?.fonts;
|
|
210
|
+
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") {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
palette: { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light },
|
|
215
|
+
fonts: { heading: f.heading, body: f.body }
|
|
216
|
+
};
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function familyOf(stack) {
|
|
222
|
+
const first = stack.split(",")[0]?.trim() ?? "";
|
|
223
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
224
|
+
}
|
|
225
|
+
function loadBrandFonts(families) {
|
|
226
|
+
const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
|
|
227
|
+
if (unique.length === 0) return;
|
|
228
|
+
const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
|
|
229
|
+
const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
|
|
230
|
+
let link = document.getElementById(BRAND_FONT_LINK_ID);
|
|
231
|
+
if (!link) {
|
|
232
|
+
link = document.createElement("link");
|
|
233
|
+
link.id = BRAND_FONT_LINK_ID;
|
|
234
|
+
link.rel = "stylesheet";
|
|
235
|
+
document.head.appendChild(link);
|
|
236
|
+
}
|
|
237
|
+
if (link.href !== href) link.href = href;
|
|
238
|
+
}
|
|
239
|
+
function applyBrandToDom(kit) {
|
|
240
|
+
const root = document.documentElement;
|
|
241
|
+
if (!kit) {
|
|
242
|
+
for (const name of BRAND_VAR_NAMES) root.style.removeProperty(name);
|
|
243
|
+
for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
|
|
244
|
+
document.getElementById(BRAND_FONT_LINK_ID)?.remove();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for (const [name, value] of Object.entries(brandColorVars(kit))) root.style.setProperty(name, value);
|
|
248
|
+
for (const name of FONT_VARS.heading) root.style.setProperty(name, kit.fonts.heading);
|
|
249
|
+
for (const name of FONT_VARS.body) root.style.setProperty(name, kit.fonts.body);
|
|
250
|
+
loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/lib/section-styles.ts
|
|
254
|
+
var STYLE_STORE_KEY = "__ohw_styles";
|
|
255
|
+
var STYLE_SHEET_ID = "ohw-section-styles";
|
|
256
|
+
function parseStyleStore(raw) {
|
|
257
|
+
if (!raw) return null;
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(raw);
|
|
260
|
+
if (parsed?.v !== 1) return null;
|
|
261
|
+
return {
|
|
262
|
+
v: 1,
|
|
263
|
+
sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
|
|
264
|
+
nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
|
|
265
|
+
};
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
var BG_VALUES = {
|
|
271
|
+
surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
|
|
272
|
+
accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
|
|
273
|
+
"accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
|
|
274
|
+
};
|
|
275
|
+
var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
|
|
276
|
+
function styleSheetCss() {
|
|
277
|
+
const rules = [];
|
|
278
|
+
for (const [tone, value] of Object.entries(BG_VALUES)) {
|
|
279
|
+
rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
|
|
280
|
+
}
|
|
281
|
+
rules.push(
|
|
282
|
+
`[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
|
|
283
|
+
);
|
|
284
|
+
rules.push(
|
|
285
|
+
`[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
|
|
286
|
+
`[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
|
|
287
|
+
);
|
|
288
|
+
for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
|
|
289
|
+
rules.push(
|
|
290
|
+
`[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
rules.push(
|
|
294
|
+
`[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
|
|
295
|
+
);
|
|
296
|
+
for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
|
|
297
|
+
rules.push(
|
|
298
|
+
`[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const pad = { tight: 40, balanced: 64, airy: 96 };
|
|
302
|
+
for (const [spacing, px] of Object.entries(pad)) {
|
|
303
|
+
rules.push(
|
|
304
|
+
`[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
return rules.join("\n");
|
|
308
|
+
}
|
|
309
|
+
var STYLE_FONT_LINK_ID = "ohw-style-fonts";
|
|
310
|
+
function loadStyleFonts(families) {
|
|
311
|
+
const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
|
|
312
|
+
const existing = document.getElementById(STYLE_FONT_LINK_ID);
|
|
313
|
+
if (unique.length === 0) {
|
|
314
|
+
existing?.remove();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const spec = unique.map((f) => `${f.replace(/ /g, "+")}:400,500,600,700`).join("|");
|
|
318
|
+
const href = `https://fonts.googleapis.com/css?family=${spec}&display=swap`;
|
|
319
|
+
let link = existing;
|
|
320
|
+
if (!link) {
|
|
321
|
+
link = document.createElement("link");
|
|
322
|
+
link.id = STYLE_FONT_LINK_ID;
|
|
323
|
+
link.rel = "stylesheet";
|
|
324
|
+
document.head.appendChild(link);
|
|
325
|
+
}
|
|
326
|
+
if (link.href !== href) link.href = href;
|
|
327
|
+
}
|
|
328
|
+
var SECTION_ATTRS = {
|
|
329
|
+
sectionBackground: "data-ohw-style-bg",
|
|
330
|
+
textDistribution: "data-ohw-style-distribution",
|
|
331
|
+
headlineScale: "data-ohw-style-headline",
|
|
332
|
+
imageAspect: "data-ohw-style-aspect",
|
|
333
|
+
spacing: "data-ohw-style-spacing"
|
|
334
|
+
};
|
|
335
|
+
var NODE_WROTE_ATTR = "data-ohw-style-node";
|
|
336
|
+
var NODE_PROPS = ["color", "font-family", "font-size", "background"];
|
|
337
|
+
function saveInline(el, prop) {
|
|
338
|
+
const attr = `data-ohw-style-prev-${prop}`;
|
|
339
|
+
if (!el.hasAttribute(attr)) el.setAttribute(attr, el.style.getPropertyValue(prop));
|
|
340
|
+
}
|
|
341
|
+
function restoreInline(el, prop) {
|
|
342
|
+
const attr = `data-ohw-style-prev-${prop}`;
|
|
343
|
+
if (!el.hasAttribute(attr)) return;
|
|
344
|
+
const prev = el.getAttribute(attr) ?? "";
|
|
345
|
+
if (prev) el.style.setProperty(prop, prev);
|
|
346
|
+
else el.style.removeProperty(prop);
|
|
347
|
+
el.removeAttribute(attr);
|
|
348
|
+
}
|
|
349
|
+
function ensureStyleSheet() {
|
|
350
|
+
let el = document.getElementById(STYLE_SHEET_ID);
|
|
351
|
+
if (!el) {
|
|
352
|
+
el = document.createElement("style");
|
|
353
|
+
el.id = STYLE_SHEET_ID;
|
|
354
|
+
document.head.appendChild(el);
|
|
355
|
+
}
|
|
356
|
+
const css = styleSheetCss();
|
|
357
|
+
if (el.textContent !== css) el.textContent = css;
|
|
358
|
+
}
|
|
359
|
+
function clearSectionAttrs(root) {
|
|
360
|
+
for (const attr of Object.values(SECTION_ATTRS)) {
|
|
361
|
+
for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
|
|
362
|
+
}
|
|
363
|
+
for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
|
|
364
|
+
restoreInline(el, "background");
|
|
365
|
+
el.removeAttribute("data-ohw-style-bgcolor");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function clearNodeProps(root) {
|
|
369
|
+
for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
|
|
370
|
+
const h = el;
|
|
371
|
+
for (const prop of NODE_PROPS) restoreInline(h, prop);
|
|
372
|
+
h.removeAttribute(NODE_WROTE_ATTR);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function buttonSurfaceOf(el) {
|
|
376
|
+
return el.closest("a, button") ?? el;
|
|
377
|
+
}
|
|
378
|
+
function applyStylesToDom(store) {
|
|
379
|
+
ensureStyleSheet();
|
|
380
|
+
clearSectionAttrs(document);
|
|
381
|
+
clearNodeProps(document);
|
|
382
|
+
loadStyleFonts(
|
|
383
|
+
store ? Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []) : []
|
|
384
|
+
);
|
|
385
|
+
if (!store) return;
|
|
386
|
+
for (const [sectionId, override] of Object.entries(store.sections)) {
|
|
387
|
+
const sections = document.querySelectorAll(
|
|
388
|
+
`[data-ohw-section="${CSS.escape(sectionId)}"]`
|
|
389
|
+
);
|
|
390
|
+
for (const section of Array.from(sections)) {
|
|
391
|
+
for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
|
|
392
|
+
const value = override[prop];
|
|
393
|
+
if (value === void 0) continue;
|
|
394
|
+
if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
|
|
395
|
+
section.setAttribute(attr, String(value).replace(":", "-"));
|
|
396
|
+
}
|
|
397
|
+
if (override.sectionBackgroundColor !== void 0) {
|
|
398
|
+
saveInline(section, "background");
|
|
399
|
+
section.style.setProperty("background", override.sectionBackgroundColor, "important");
|
|
400
|
+
section.setAttribute("data-ohw-style-bgcolor", "");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
for (const [key, override] of Object.entries(store.nodes)) {
|
|
405
|
+
const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
|
|
406
|
+
for (const el of Array.from(nodes)) {
|
|
407
|
+
if (override.color !== void 0) {
|
|
408
|
+
saveInline(el, "color");
|
|
409
|
+
el.style.setProperty("color", override.color, "important");
|
|
410
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
411
|
+
}
|
|
412
|
+
if (override.fontFamily !== void 0) {
|
|
413
|
+
saveInline(el, "font-family");
|
|
414
|
+
el.style.setProperty("font-family", `'${override.fontFamily}'`, "important");
|
|
415
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
416
|
+
}
|
|
417
|
+
if (override.fontSize !== void 0) {
|
|
418
|
+
saveInline(el, "font-size");
|
|
419
|
+
el.style.setProperty("font-size", `${override.fontSize}px`, "important");
|
|
420
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
421
|
+
}
|
|
422
|
+
if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
|
|
423
|
+
const surface = buttonSurfaceOf(el);
|
|
424
|
+
if (override.buttonBackground !== void 0) {
|
|
425
|
+
saveInline(surface, "background");
|
|
426
|
+
surface.style.setProperty("background", override.buttonBackground, "important");
|
|
427
|
+
}
|
|
428
|
+
if (override.buttonText !== void 0) {
|
|
429
|
+
saveInline(surface, "color");
|
|
430
|
+
surface.style.setProperty("color", override.buttonText, "important");
|
|
431
|
+
}
|
|
432
|
+
surface.setAttribute(NODE_WROTE_ATTR, "");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
121
438
|
// src/ui/ai-tree/aiSectionsManager.tsx
|
|
122
439
|
import { flushSync } from "react-dom";
|
|
123
440
|
import { createRoot } from "react-dom/client";
|
|
@@ -132,7 +449,8 @@ function lucideByName(name) {
|
|
|
132
449
|
}
|
|
133
450
|
var typeStyle = (spec, font) => ({
|
|
134
451
|
fontFamily: font,
|
|
135
|
-
|
|
452
|
+
// Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
|
|
453
|
+
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,
|
|
136
454
|
lineHeight: spec.line,
|
|
137
455
|
fontWeight: spec.weight
|
|
138
456
|
});
|
|
@@ -141,26 +459,61 @@ var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.t
|
|
|
141
459
|
var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
|
|
142
460
|
'<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>'
|
|
143
461
|
)}`;
|
|
462
|
+
var AI_MOBILE_CSS = [
|
|
463
|
+
"@media (max-width: 768px){",
|
|
464
|
+
"[data-ai-section]{overflow-x:hidden}",
|
|
465
|
+
"[data-ai-container]{padding:0 20px !important}",
|
|
466
|
+
"[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
|
|
467
|
+
"[data-ai-cell]{width:100%;min-width:0}",
|
|
468
|
+
"[data-ai-grid]{grid-template-columns:1fr !important}",
|
|
469
|
+
// Group containers flatten to a column on phones; span placements come along for free.
|
|
470
|
+
"[data-ai-group]{display:flex !important;flex-direction:column !important}",
|
|
471
|
+
"[data-ai-group] > *{grid-column:auto !important}",
|
|
472
|
+
"[data-ai-section] img{max-width:100%}",
|
|
473
|
+
"}",
|
|
474
|
+
"@media (min-width: 769px) and (max-width: 1024px){",
|
|
475
|
+
"[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
|
|
476
|
+
"}"
|
|
477
|
+
].join("");
|
|
144
478
|
var FEATURE_LINE_CSS = [
|
|
145
479
|
"[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
|
|
146
480
|
'[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
|
|
147
481
|
`background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
|
|
148
482
|
`mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
|
|
149
483
|
].join("");
|
|
484
|
+
function hexLuminance(color) {
|
|
485
|
+
const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
|
|
486
|
+
if (!m) return null;
|
|
487
|
+
const [r2, g, b] = [0, 2, 4].map((i) => {
|
|
488
|
+
const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
|
|
489
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
490
|
+
});
|
|
491
|
+
return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
492
|
+
}
|
|
493
|
+
function hexContrast(a, b) {
|
|
494
|
+
const la = hexLuminance(a);
|
|
495
|
+
const lb = hexLuminance(b);
|
|
496
|
+
if (la === null || lb === null) return null;
|
|
497
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
498
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
499
|
+
}
|
|
500
|
+
function accentBandContext(brand) {
|
|
501
|
+
const p = brand.palette;
|
|
502
|
+
const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
|
|
503
|
+
if (lightWins) {
|
|
504
|
+
return {
|
|
505
|
+
brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
|
|
506
|
+
buttonLabel: p.primary
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
|
|
511
|
+
buttonLabel: p.light
|
|
512
|
+
};
|
|
513
|
+
}
|
|
150
514
|
function textAttrs(ctx, path) {
|
|
151
515
|
return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
|
|
152
516
|
}
|
|
153
|
-
var AI_RESPONSIVE_CSS = [
|
|
154
|
-
"@media (max-width: 960px) {",
|
|
155
|
-
" [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
|
|
156
|
-
' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
|
|
157
|
-
"}",
|
|
158
|
-
"@media (max-width: 640px) {",
|
|
159
|
-
" [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
|
|
160
|
-
" [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
|
|
161
|
-
" [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
|
|
162
|
-
"}"
|
|
163
|
-
].join("\n");
|
|
164
517
|
var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
165
518
|
function MediaBox({
|
|
166
519
|
refValue,
|
|
@@ -173,17 +526,13 @@ function MediaBox({
|
|
|
173
526
|
const url = refValue ? ctx.resolveMedia(refValue) : null;
|
|
174
527
|
const isIcon = /^(lucide|simple):/.test(refValue);
|
|
175
528
|
const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
|
|
176
|
-
const editAttrs = ctx.keyFor && editPath ? {
|
|
177
|
-
"data-ohw-key": ctx.keyFor(editPath),
|
|
178
|
-
"data-ohw-editable": isIcon ? "icon" : "image"
|
|
179
|
-
} : {};
|
|
529
|
+
const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
|
|
180
530
|
if (isIcon) {
|
|
181
531
|
const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
|
|
182
532
|
return /* @__PURE__ */ jsx(
|
|
183
533
|
"span",
|
|
184
534
|
{
|
|
185
535
|
"data-ai-icon": refValue,
|
|
186
|
-
...editAttrs,
|
|
187
536
|
style: {
|
|
188
537
|
display: "inline-flex",
|
|
189
538
|
width: 48,
|
|
@@ -236,7 +585,7 @@ function ButtonEl({
|
|
|
236
585
|
}) {
|
|
237
586
|
const secondary = slots.variant === "secondary";
|
|
238
587
|
const href = str(slots.href);
|
|
239
|
-
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
|
|
588
|
+
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
|
|
240
589
|
return /* @__PURE__ */ jsx(
|
|
241
590
|
"a",
|
|
242
591
|
{
|
|
@@ -252,7 +601,7 @@ function ButtonEl({
|
|
|
252
601
|
textDecoration: "none",
|
|
253
602
|
cursor: "pointer",
|
|
254
603
|
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
|
|
255
|
-
...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: AI_TREE_TOKENS.textPrimaryForeground }
|
|
604
|
+
...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 }
|
|
256
605
|
},
|
|
257
606
|
children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
258
607
|
}
|
|
@@ -758,7 +1107,24 @@ function CardBlock({ node, ctx, path }) {
|
|
|
758
1107
|
minWidth: 0
|
|
759
1108
|
},
|
|
760
1109
|
children: [
|
|
761
|
-
media && (horizontal ? /* @__PURE__ */ jsx(
|
|
1110
|
+
media && (horizontal ? /* @__PURE__ */ jsx(
|
|
1111
|
+
"div",
|
|
1112
|
+
{
|
|
1113
|
+
style: (
|
|
1114
|
+
// An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
|
|
1115
|
+
// text to the far side. Photos keep the half-and-half split. The inset has no
|
|
1116
|
+
// inner padding (the photo split absorbed that), so the icon carries its own gap.
|
|
1117
|
+
/^(lucide|simple):/.test(mediaRef) ? {
|
|
1118
|
+
flexShrink: 0,
|
|
1119
|
+
display: "flex",
|
|
1120
|
+
alignItems: "center",
|
|
1121
|
+
padding: mediaInset,
|
|
1122
|
+
[mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
|
|
1123
|
+
} : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
|
|
1124
|
+
),
|
|
1125
|
+
children: media
|
|
1126
|
+
}
|
|
1127
|
+
) : /* @__PURE__ */ jsx(
|
|
762
1128
|
"div",
|
|
763
1129
|
{
|
|
764
1130
|
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" },
|
|
@@ -849,13 +1215,44 @@ function AccordionBlock({ node, ctx, path }) {
|
|
|
849
1215
|
) })
|
|
850
1216
|
] }, i)) });
|
|
851
1217
|
}
|
|
1218
|
+
function useIsMobile() {
|
|
1219
|
+
const [mobile, setMobile] = React.useState(
|
|
1220
|
+
() => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
|
|
1221
|
+
);
|
|
1222
|
+
React.useEffect(() => {
|
|
1223
|
+
const mq = window.matchMedia("(max-width: 768px)");
|
|
1224
|
+
const update = () => setMobile(mq.matches);
|
|
1225
|
+
update();
|
|
1226
|
+
mq.addEventListener("change", update);
|
|
1227
|
+
return () => mq.removeEventListener("change", update);
|
|
1228
|
+
}, []);
|
|
1229
|
+
return mobile;
|
|
1230
|
+
}
|
|
852
1231
|
function Carousel({ items, itemsPerRow, ctx }) {
|
|
1232
|
+
const isMobile = useIsMobile();
|
|
1233
|
+
const perPage = isMobile ? 1 : itemsPerRow;
|
|
1234
|
+
const pages = Math.max(1, Math.ceil(items.length / perPage));
|
|
853
1235
|
const [page, setPage] = React.useState(0);
|
|
854
|
-
const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
|
|
855
1236
|
const current = Math.min(page, pages - 1);
|
|
1237
|
+
if (pages <= 1) {
|
|
1238
|
+
const cols = Math.max(1, Math.min(items.length, itemsPerRow));
|
|
1239
|
+
return /* @__PURE__ */ jsx(
|
|
1240
|
+
"div",
|
|
1241
|
+
{
|
|
1242
|
+
"data-ai-grid": String(cols),
|
|
1243
|
+
style: {
|
|
1244
|
+
display: "grid",
|
|
1245
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
1246
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1247
|
+
alignItems: "start"
|
|
1248
|
+
},
|
|
1249
|
+
children: items
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
856
1253
|
const pageGroups = Array.from(
|
|
857
1254
|
{ length: pages },
|
|
858
|
-
(_, p) => items.slice(p *
|
|
1255
|
+
(_, p) => items.slice(p * perPage, (p + 1) * perPage)
|
|
859
1256
|
);
|
|
860
1257
|
const chrome = (enabled) => ({
|
|
861
1258
|
border: `1px solid ${ctx.brand.palette.dark}`,
|
|
@@ -880,55 +1277,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
|
|
|
880
1277
|
cursor: "pointer",
|
|
881
1278
|
padding: 0
|
|
882
1279
|
});
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
children: /* @__PURE__ */ jsx(ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
893
|
-
}
|
|
894
|
-
),
|
|
895
|
-
/* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx(
|
|
1280
|
+
const viewport = /* @__PURE__ */ jsx("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
|
|
1281
|
+
"div",
|
|
1282
|
+
{
|
|
1283
|
+
style: {
|
|
1284
|
+
display: "flex",
|
|
1285
|
+
transform: `translateX(-${current * 100}%)`,
|
|
1286
|
+
transition: "transform 0.4s ease"
|
|
1287
|
+
},
|
|
1288
|
+
children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
|
|
896
1289
|
"div",
|
|
897
1290
|
{
|
|
1291
|
+
"data-ai-grid": String(perPage),
|
|
898
1292
|
style: {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1293
|
+
flex: "0 0 100%",
|
|
1294
|
+
display: "grid",
|
|
1295
|
+
gridTemplateColumns: `repeat(${perPage}, 1fr)`,
|
|
1296
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1297
|
+
alignItems: "start"
|
|
902
1298
|
},
|
|
903
|
-
children:
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1299
|
+
children: group
|
|
1300
|
+
},
|
|
1301
|
+
p
|
|
1302
|
+
))
|
|
1303
|
+
}
|
|
1304
|
+
) });
|
|
1305
|
+
const prevBtn = /* @__PURE__ */ jsx(
|
|
1306
|
+
"button",
|
|
1307
|
+
{
|
|
1308
|
+
type: "button",
|
|
1309
|
+
"aria-label": "Previous",
|
|
1310
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
1311
|
+
style: chrome(current > 0),
|
|
1312
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1313
|
+
}
|
|
1314
|
+
);
|
|
1315
|
+
const nextBtn = /* @__PURE__ */ jsx(
|
|
1316
|
+
"button",
|
|
1317
|
+
{
|
|
1318
|
+
type: "button",
|
|
1319
|
+
"aria-label": "Next",
|
|
1320
|
+
onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
|
|
1321
|
+
style: chrome(current < pages - 1),
|
|
1322
|
+
children: /* @__PURE__ */ jsx(ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1323
|
+
}
|
|
1324
|
+
);
|
|
1325
|
+
const dots = /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) });
|
|
1326
|
+
if (isMobile) {
|
|
1327
|
+
return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
|
|
1328
|
+
viewport,
|
|
1329
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
|
|
1330
|
+
prevBtn,
|
|
1331
|
+
nextBtn
|
|
1332
|
+
] }),
|
|
1333
|
+
dots
|
|
1334
|
+
] });
|
|
1335
|
+
}
|
|
1336
|
+
return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
|
|
1337
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
|
|
1338
|
+
prevBtn,
|
|
1339
|
+
viewport,
|
|
1340
|
+
nextBtn
|
|
930
1341
|
] }),
|
|
931
|
-
|
|
1342
|
+
dots
|
|
932
1343
|
] });
|
|
933
1344
|
}
|
|
934
1345
|
function CollectionBlock({ node, ctx, path }) {
|
|
@@ -1007,7 +1418,7 @@ function CollectionBlock({ node, ctx, path }) {
|
|
|
1007
1418
|
return /* @__PURE__ */ jsx(
|
|
1008
1419
|
"div",
|
|
1009
1420
|
{
|
|
1010
|
-
"data-ai-grid":
|
|
1421
|
+
"data-ai-grid": "",
|
|
1011
1422
|
style: {
|
|
1012
1423
|
display: "grid",
|
|
1013
1424
|
gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
|
|
@@ -1022,6 +1433,49 @@ function renderNode(node, ctx, path) {
|
|
|
1022
1433
|
switch (node.type) {
|
|
1023
1434
|
case "text":
|
|
1024
1435
|
return /* @__PURE__ */ jsx(TextBlock, { slots, ctx, path });
|
|
1436
|
+
// Layout container: arranges child blocks, contributes no content of its own. `grid` is a
|
|
1437
|
+
// nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
|
|
1438
|
+
// mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
|
|
1439
|
+
// is a column. Children render through this same dispatcher, so edit markers, media
|
|
1440
|
+
// resolution, and copy paths all work unchanged inside a group.
|
|
1441
|
+
case "group": {
|
|
1442
|
+
const layout = str(slots.layout);
|
|
1443
|
+
const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
|
|
1444
|
+
const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ jsx(
|
|
1445
|
+
"div",
|
|
1446
|
+
{
|
|
1447
|
+
style: layout === "grid" ? {
|
|
1448
|
+
gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
|
|
1449
|
+
minWidth: 0
|
|
1450
|
+
} : { minWidth: 0 },
|
|
1451
|
+
children: renderNode(child, ctx, `${path}.c${i}`)
|
|
1452
|
+
},
|
|
1453
|
+
i
|
|
1454
|
+
));
|
|
1455
|
+
if (layout === "grid") {
|
|
1456
|
+
return /* @__PURE__ */ jsx(
|
|
1457
|
+
"div",
|
|
1458
|
+
{
|
|
1459
|
+
"data-ai-group": "grid",
|
|
1460
|
+
style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
|
|
1461
|
+
children: kids
|
|
1462
|
+
}
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
if (layout === "split") {
|
|
1466
|
+
const ratio = str(slots.ratio);
|
|
1467
|
+
const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
|
|
1468
|
+
return /* @__PURE__ */ jsx(
|
|
1469
|
+
"div",
|
|
1470
|
+
{
|
|
1471
|
+
"data-ai-group": "split",
|
|
1472
|
+
style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
|
|
1473
|
+
children: kids
|
|
1474
|
+
}
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
return /* @__PURE__ */ jsx("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
|
|
1478
|
+
}
|
|
1025
1479
|
case "button":
|
|
1026
1480
|
return /* @__PURE__ */ jsx(ButtonEl, { slots, ctx, path });
|
|
1027
1481
|
case "button-row":
|
|
@@ -1102,33 +1556,111 @@ function renderNode(node, ctx, path) {
|
|
|
1102
1556
|
}
|
|
1103
1557
|
);
|
|
1104
1558
|
}
|
|
1105
|
-
case "form":
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1559
|
+
case "form": {
|
|
1560
|
+
const formAttrs = ctx.keyFor ? {
|
|
1561
|
+
"data-ohw-editable": "form",
|
|
1562
|
+
"data-ohw-key": ctx.keyFor(`${path}.form`),
|
|
1563
|
+
"data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
|
|
1564
|
+
} : {};
|
|
1565
|
+
const fieldStyle = {
|
|
1566
|
+
width: "100%",
|
|
1567
|
+
boxSizing: "border-box",
|
|
1568
|
+
border: `1px solid color-mix(in srgb, ${ctx.brand.palette.dark} 45%, #ffffff)`,
|
|
1569
|
+
borderRadius: 0,
|
|
1570
|
+
padding: 12,
|
|
1571
|
+
background: "#fff",
|
|
1572
|
+
color: ctx.brand.palette.dark,
|
|
1573
|
+
outline: "none",
|
|
1574
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
|
|
1575
|
+
};
|
|
1576
|
+
const labelStyle = {
|
|
1577
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
|
|
1578
|
+
color: ctx.brand.palette.dark,
|
|
1579
|
+
textAlign: "left",
|
|
1580
|
+
width: "100%"
|
|
1581
|
+
};
|
|
1582
|
+
const centered = ctx.sectionAlignment === "center";
|
|
1583
|
+
const submitAlign = centered ? "center" : "flex-start";
|
|
1584
|
+
const children = node.children ?? [];
|
|
1585
|
+
return (
|
|
1586
|
+
// 32px between the field group and the submit. In a stacked (centered) section the form is
|
|
1587
|
+
// capped at 780px and centered — the section's 12-col grid would otherwise leave it hugging
|
|
1588
|
+
// the left edge; a split section lets it fill its own column.
|
|
1589
|
+
/* @__PURE__ */ jsxs(
|
|
1590
|
+
"form",
|
|
1591
|
+
{
|
|
1592
|
+
...formAttrs,
|
|
1593
|
+
"data-ai-form": "",
|
|
1594
|
+
style: {
|
|
1595
|
+
display: "flex",
|
|
1596
|
+
flexDirection: "column",
|
|
1597
|
+
gap: 32,
|
|
1598
|
+
width: "100%",
|
|
1599
|
+
...centered ? { maxWidth: 780, marginLeft: "auto", marginRight: "auto" } : {}
|
|
1600
|
+
},
|
|
1601
|
+
children: [
|
|
1602
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 24, width: "100%", alignItems: "flex-start" }, children: children.map((child, i) => {
|
|
1603
|
+
if (child.type !== "input") return null;
|
|
1604
|
+
const cs = child.slots ?? {};
|
|
1605
|
+
const kind = str(cs.kind);
|
|
1606
|
+
const label = str(cs.label);
|
|
1607
|
+
const placeholder = str(cs.placeholder);
|
|
1608
|
+
const required = cs.required === true;
|
|
1609
|
+
const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
|
|
1610
|
+
const isTextarea = kind === "textarea";
|
|
1611
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, width: "100%" }, children: [
|
|
1612
|
+
/* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
|
|
1613
|
+
isTextarea ? /* @__PURE__ */ jsx(
|
|
1614
|
+
"textarea",
|
|
1615
|
+
{
|
|
1616
|
+
name,
|
|
1617
|
+
placeholder,
|
|
1618
|
+
required,
|
|
1619
|
+
style: { ...fieldStyle, height: 180, resize: "vertical" }
|
|
1620
|
+
}
|
|
1621
|
+
) : /* @__PURE__ */ jsx(
|
|
1622
|
+
"input",
|
|
1623
|
+
{
|
|
1624
|
+
name,
|
|
1625
|
+
type: kind === "email" ? "email" : "text",
|
|
1626
|
+
placeholder,
|
|
1627
|
+
required,
|
|
1628
|
+
style: { ...fieldStyle, height: 48 }
|
|
1629
|
+
}
|
|
1630
|
+
)
|
|
1631
|
+
] }, i);
|
|
1632
|
+
}) }),
|
|
1633
|
+
children.map((child, i) => {
|
|
1634
|
+
if (child.type === "input") return null;
|
|
1635
|
+
const cs = child.slots ?? {};
|
|
1636
|
+
return /* @__PURE__ */ jsx(
|
|
1637
|
+
"button",
|
|
1638
|
+
{
|
|
1639
|
+
type: "submit",
|
|
1640
|
+
style: {
|
|
1641
|
+
alignSelf: submitAlign,
|
|
1642
|
+
border: "none",
|
|
1643
|
+
cursor: "pointer",
|
|
1644
|
+
padding: "12px 24px",
|
|
1645
|
+
// Corner radius follows the host template's own buttons (measured from a template
|
|
1646
|
+
// CTA); 8px only when the page has no template button to match.
|
|
1647
|
+
borderRadius: ctx.buttonRadius ?? 8,
|
|
1648
|
+
// Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
|
|
1649
|
+
// reads correctly on custom palettes.
|
|
1650
|
+
background: ctx.brand.palette.primary,
|
|
1651
|
+
color: ctx.buttonLabel ?? ctx.brand.palette.light,
|
|
1652
|
+
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
1653
|
+
},
|
|
1654
|
+
children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
1655
|
+
},
|
|
1656
|
+
i
|
|
1657
|
+
);
|
|
1658
|
+
})
|
|
1659
|
+
]
|
|
1660
|
+
}
|
|
1661
|
+
)
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1132
1664
|
case "schedule-widget":
|
|
1133
1665
|
return /* @__PURE__ */ jsx(
|
|
1134
1666
|
"div",
|
|
@@ -1149,16 +1681,27 @@ function renderNode(node, ctx, path) {
|
|
|
1149
1681
|
return null;
|
|
1150
1682
|
}
|
|
1151
1683
|
}
|
|
1152
|
-
function AiTreeRenderer({
|
|
1684
|
+
function AiTreeRenderer({
|
|
1685
|
+
tree,
|
|
1686
|
+
brand,
|
|
1687
|
+
buttonRadius,
|
|
1688
|
+
resolveMedia,
|
|
1689
|
+
editKeyPrefix
|
|
1690
|
+
}) {
|
|
1153
1691
|
if (!isRenderableTree(tree)) {
|
|
1154
1692
|
return null;
|
|
1155
1693
|
}
|
|
1156
1694
|
const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
|
|
1695
|
+
const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
|
|
1696
|
+
const blockBrand = band?.brand ?? resolvedBrand;
|
|
1157
1697
|
const ctx = {
|
|
1158
|
-
brand:
|
|
1698
|
+
brand: blockBrand,
|
|
1159
1699
|
resolveMedia: resolveMedia ?? (() => null),
|
|
1160
|
-
cardSurface:
|
|
1161
|
-
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
|
|
1700
|
+
cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
|
|
1701
|
+
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
|
|
1702
|
+
sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
|
|
1703
|
+
buttonRadius,
|
|
1704
|
+
...band ? { buttonLabel: band.buttonLabel } : {}
|
|
1162
1705
|
};
|
|
1163
1706
|
const settings = tree.settings ?? {};
|
|
1164
1707
|
const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
|
|
@@ -1166,27 +1709,41 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1166
1709
|
const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
|
|
1167
1710
|
const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
|
|
1168
1711
|
const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
|
|
1712
|
+
const toneBackground = (() => {
|
|
1713
|
+
const { dark, primary, light } = resolvedBrand.palette;
|
|
1714
|
+
switch (settings.sectionBackground) {
|
|
1715
|
+
case "surface":
|
|
1716
|
+
return `color-mix(in srgb, ${light} 94%, ${dark})`;
|
|
1717
|
+
case "accent":
|
|
1718
|
+
return primary;
|
|
1719
|
+
case "accent-soft":
|
|
1720
|
+
return `color-mix(in srgb, ${primary} 12%, ${light})`;
|
|
1721
|
+
default:
|
|
1722
|
+
return void 0;
|
|
1723
|
+
}
|
|
1724
|
+
})();
|
|
1725
|
+
const distributed = !isOverlay && settings.textDistribution;
|
|
1169
1726
|
return /* @__PURE__ */ jsxs(
|
|
1170
1727
|
"section",
|
|
1171
1728
|
{
|
|
1172
1729
|
"data-ai-section": tree.tag ?? "",
|
|
1173
1730
|
...bgAttrs,
|
|
1174
|
-
"data-ai-responsive": "",
|
|
1175
1731
|
style: {
|
|
1176
1732
|
position: "relative",
|
|
1177
1733
|
padding: `${pad}px 0`,
|
|
1178
|
-
background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
|
|
1734
|
+
background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
|
|
1179
1735
|
backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
|
|
1180
1736
|
backgroundSize: "cover",
|
|
1181
|
-
backgroundPosition: "center"
|
|
1737
|
+
backgroundPosition: "center",
|
|
1738
|
+
color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
|
|
1182
1739
|
},
|
|
1183
1740
|
children: [
|
|
1184
|
-
/* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
|
|
1185
1741
|
isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
|
|
1742
|
+
/* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
|
|
1186
1743
|
/* @__PURE__ */ jsx(
|
|
1187
1744
|
"div",
|
|
1188
1745
|
{
|
|
1189
|
-
"data-ai-
|
|
1746
|
+
"data-ai-container": "",
|
|
1190
1747
|
style: {
|
|
1191
1748
|
position: "relative",
|
|
1192
1749
|
maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
|
|
@@ -1197,15 +1754,29 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1197
1754
|
children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
|
|
1198
1755
|
"div",
|
|
1199
1756
|
{
|
|
1200
|
-
"data-ai-
|
|
1757
|
+
"data-ai-row": "",
|
|
1201
1758
|
style: {
|
|
1202
1759
|
display: "grid",
|
|
1203
1760
|
gridTemplateColumns: "repeat(12, 1fr)",
|
|
1204
1761
|
gap: AI_TREE_TOKENS.spacing6,
|
|
1205
|
-
alignItems: settings.verticalPosition === "top" ? "start" : "center",
|
|
1762
|
+
alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
|
|
1206
1763
|
marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
|
|
1207
1764
|
},
|
|
1208
|
-
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
|
|
1765
|
+
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
|
|
1766
|
+
"div",
|
|
1767
|
+
{
|
|
1768
|
+
"data-ai-cell": "",
|
|
1769
|
+
style: {
|
|
1770
|
+
gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
|
|
1771
|
+
minWidth: 0,
|
|
1772
|
+
// space-between: each column becomes a flex column whose content spreads over
|
|
1773
|
+
// the full row height instead of clumping at the top.
|
|
1774
|
+
...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
|
|
1775
|
+
},
|
|
1776
|
+
children: renderNode(block, ctx, `r${r2}.b${b}`)
|
|
1777
|
+
},
|
|
1778
|
+
b
|
|
1779
|
+
))
|
|
1209
1780
|
},
|
|
1210
1781
|
r2
|
|
1211
1782
|
))
|
|
@@ -1221,17 +1792,36 @@ import { jsx as jsx2 } from "react/jsx-runtime";
|
|
|
1221
1792
|
var CONTAINER_ATTR = "data-ohw-ai-generated";
|
|
1222
1793
|
var REPLACED_ATTR = "data-ohw-ai-replaced-by";
|
|
1223
1794
|
var REMOVED_ATTR = "data-ohw-ai-removed";
|
|
1795
|
+
var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
|
|
1796
|
+
var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
1797
|
+
function readRootVar(name) {
|
|
1798
|
+
if (typeof document === "undefined") return "";
|
|
1799
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
1800
|
+
}
|
|
1801
|
+
function deriveBrandOverride() {
|
|
1802
|
+
const dark = readRootVar("--ohw-brand-dark");
|
|
1803
|
+
const primary = readRootVar("--ohw-brand-primary");
|
|
1804
|
+
const light = readRootVar("--ohw-brand-light");
|
|
1805
|
+
if (!dark || !primary || !light) return null;
|
|
1806
|
+
const accent = readRootVar("--ohw-brand-accent");
|
|
1807
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1808
|
+
const body = readRootVar("--font-body");
|
|
1809
|
+
return {
|
|
1810
|
+
palette: { dark, primary, accent: accent || dark, light },
|
|
1811
|
+
fonts: {
|
|
1812
|
+
heading: heading || AI_DEFAULT_BRAND.fonts.heading,
|
|
1813
|
+
body: body || AI_DEFAULT_BRAND.fonts.body
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1224
1817
|
function deriveTemplateBrand() {
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1228
|
-
const dark = read("--color-dark");
|
|
1229
|
-
const primary = read("--color-primary");
|
|
1230
|
-
const light = read("--color-light");
|
|
1818
|
+
const dark = readRootVar("--color-dark");
|
|
1819
|
+
const primary = readRootVar("--color-primary");
|
|
1820
|
+
const light = readRootVar("--color-light");
|
|
1231
1821
|
if (!dark || !primary || !light) return null;
|
|
1232
|
-
const accent =
|
|
1233
|
-
const heading =
|
|
1234
|
-
const body =
|
|
1822
|
+
const accent = readRootVar("--color-accent");
|
|
1823
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1824
|
+
const body = readRootVar("--font-body");
|
|
1235
1825
|
return {
|
|
1236
1826
|
palette: { dark, primary, accent: accent || dark, light },
|
|
1237
1827
|
fonts: {
|
|
@@ -1240,6 +1830,13 @@ function deriveTemplateBrand() {
|
|
|
1240
1830
|
}
|
|
1241
1831
|
};
|
|
1242
1832
|
}
|
|
1833
|
+
function deriveTemplateButtonRadius() {
|
|
1834
|
+
if (typeof document === "undefined") return null;
|
|
1835
|
+
const btn = document.querySelector('[data-ohw-role="button"]');
|
|
1836
|
+
if (!btn) return null;
|
|
1837
|
+
const radius = getComputedStyle(btn).borderTopLeftRadius;
|
|
1838
|
+
return radius || null;
|
|
1839
|
+
}
|
|
1243
1840
|
var mounted = /* @__PURE__ */ new Map();
|
|
1244
1841
|
function findTemplateSection(id) {
|
|
1245
1842
|
for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
|
|
@@ -1303,6 +1900,24 @@ function syncRemovedSections(state) {
|
|
|
1303
1900
|
}
|
|
1304
1901
|
}
|
|
1305
1902
|
}
|
|
1903
|
+
function syncTemplateHidden(state, pageHasSections) {
|
|
1904
|
+
const hide = state.hideTemplate === true && pageHasSections;
|
|
1905
|
+
for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
|
|
1906
|
+
if (!hide) {
|
|
1907
|
+
el.style.removeProperty("display");
|
|
1908
|
+
el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (!hide) return;
|
|
1912
|
+
for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
|
|
1913
|
+
if (el.hasAttribute(CONTAINER_ATTR)) continue;
|
|
1914
|
+
if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
|
|
1915
|
+
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
1916
|
+
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
|
|
1917
|
+
el.style.display = "none";
|
|
1918
|
+
el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1306
1921
|
function syncReplacedOriginals(state) {
|
|
1307
1922
|
for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
|
|
1308
1923
|
const byId = el.getAttribute(REPLACED_ATTR) ?? "";
|
|
@@ -1321,10 +1936,64 @@ function syncReplacedOriginals(state) {
|
|
|
1321
1936
|
}
|
|
1322
1937
|
}
|
|
1323
1938
|
}
|
|
1939
|
+
var sectionOrderIndex = /* @__PURE__ */ new Map();
|
|
1940
|
+
function setAiSectionOrder(raw, currentPath) {
|
|
1941
|
+
const next = /* @__PURE__ */ new Map();
|
|
1942
|
+
if (raw) {
|
|
1943
|
+
try {
|
|
1944
|
+
const entries = JSON.parse(raw);
|
|
1945
|
+
for (const entry of entries) {
|
|
1946
|
+
if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
|
|
1947
|
+
}
|
|
1948
|
+
} catch {
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
sectionOrderIndex = next;
|
|
1952
|
+
}
|
|
1953
|
+
function applyExplicitOrder(entries) {
|
|
1954
|
+
if (sectionOrderIndex.size === 0) return entries;
|
|
1955
|
+
return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
|
|
1956
|
+
if (a.order === void 0 && b.order === void 0) return a.index - b.index;
|
|
1957
|
+
if (a.order === void 0) return 1;
|
|
1958
|
+
if (b.order === void 0) return -1;
|
|
1959
|
+
return a.order - b.order;
|
|
1960
|
+
}).map((item) => item.entry);
|
|
1961
|
+
}
|
|
1962
|
+
function orderByChain(sections) {
|
|
1963
|
+
const ids = new Set(sections.map((entry) => entry.id));
|
|
1964
|
+
const after = /* @__PURE__ */ new Map();
|
|
1965
|
+
const roots = [];
|
|
1966
|
+
for (const entry of sections) {
|
|
1967
|
+
const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
|
|
1968
|
+
if (anchor && ids.has(anchor)) {
|
|
1969
|
+
const bucket = after.get(anchor);
|
|
1970
|
+
if (bucket) bucket.push(entry);
|
|
1971
|
+
else after.set(anchor, [entry]);
|
|
1972
|
+
} else {
|
|
1973
|
+
roots.push(entry);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
const out = [];
|
|
1977
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1978
|
+
const visit = (entry) => {
|
|
1979
|
+
if (seen.has(entry.id)) return;
|
|
1980
|
+
seen.add(entry.id);
|
|
1981
|
+
out.push(entry);
|
|
1982
|
+
for (const child of after.get(entry.id) ?? []) visit(child);
|
|
1983
|
+
};
|
|
1984
|
+
for (const root of roots) visit(root);
|
|
1985
|
+
return out.length === sections.length ? out : sections;
|
|
1986
|
+
}
|
|
1324
1987
|
function applyAiSectionsToDom(state, options) {
|
|
1325
1988
|
if (typeof document === "undefined") return;
|
|
1989
|
+
const brandOverride = deriveBrandOverride();
|
|
1326
1990
|
const templateBrand = deriveTemplateBrand();
|
|
1327
|
-
const
|
|
1991
|
+
const templateButtonRadius = deriveTemplateButtonRadius();
|
|
1992
|
+
const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
|
|
1993
|
+
const pagePath = window.location.pathname;
|
|
1994
|
+
const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
|
|
1995
|
+
const activeIds = new Set(pageSections.map((entry) => entry.id));
|
|
1996
|
+
const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
|
|
1328
1997
|
for (const [id, section] of mounted) {
|
|
1329
1998
|
if (!activeIds.has(id)) {
|
|
1330
1999
|
section.root.unmount();
|
|
@@ -1332,8 +2001,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1332
2001
|
mounted.delete(id);
|
|
1333
2002
|
}
|
|
1334
2003
|
}
|
|
1335
|
-
for (const entry of
|
|
1336
|
-
const serialized = JSON.stringify(entry);
|
|
2004
|
+
for (const entry of ordered) {
|
|
2005
|
+
const serialized = JSON.stringify(entry) + brandKey;
|
|
1337
2006
|
const existing = mounted.get(entry.id);
|
|
1338
2007
|
if (existing && existing.serialized === serialized && existing.container.isConnected) {
|
|
1339
2008
|
continue;
|
|
@@ -1358,7 +2027,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1358
2027
|
AiTreeRenderer,
|
|
1359
2028
|
{
|
|
1360
2029
|
tree: entry.tree,
|
|
1361
|
-
brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2030
|
+
brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2031
|
+
buttonRadius: templateButtonRadius,
|
|
1362
2032
|
resolveMedia,
|
|
1363
2033
|
editKeyPrefix: `ai.${entry.id}`
|
|
1364
2034
|
}
|
|
@@ -1367,8 +2037,20 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1367
2037
|
});
|
|
1368
2038
|
mounted.set(entry.id, { root, container, serialized });
|
|
1369
2039
|
}
|
|
2040
|
+
if (state.hideTemplate === true) {
|
|
2041
|
+
let prev = null;
|
|
2042
|
+
for (const entry of ordered) {
|
|
2043
|
+
const el = mounted.get(entry.id)?.container;
|
|
2044
|
+
if (!el) continue;
|
|
2045
|
+
if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
|
|
2046
|
+
prev.insertAdjacentElement("afterend", el);
|
|
2047
|
+
}
|
|
2048
|
+
prev = el;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1370
2051
|
syncReplacedOriginals(state);
|
|
1371
2052
|
syncRemovedSections(state);
|
|
2053
|
+
syncTemplateHidden(state, pageSections.length > 0);
|
|
1372
2054
|
}
|
|
1373
2055
|
|
|
1374
2056
|
// src/useLinkHrefGuardian.ts
|
|
@@ -7067,13 +7749,17 @@ function MediaOverlay({
|
|
|
7067
7749
|
hover,
|
|
7068
7750
|
isUploading,
|
|
7069
7751
|
fadingOut = false,
|
|
7752
|
+
selected = false,
|
|
7753
|
+
hovered = false,
|
|
7070
7754
|
onFadeOutComplete,
|
|
7071
7755
|
onReplace,
|
|
7756
|
+
onSelect,
|
|
7072
7757
|
onVideoSettingsChange
|
|
7073
7758
|
}) {
|
|
7074
7759
|
const { rect } = hover;
|
|
7075
7760
|
const skeletonRef = React8.useRef(null);
|
|
7076
7761
|
const isVideo = hover.elementType === "video";
|
|
7762
|
+
const showChrome = !selected || hovered;
|
|
7077
7763
|
const autoplay = hover.videoAutoplay ?? true;
|
|
7078
7764
|
const muted = hover.videoMuted ?? true;
|
|
7079
7765
|
const probeRef = React8.useRef(null);
|
|
@@ -7120,7 +7806,7 @@ function MediaOverlay({
|
|
|
7120
7806
|
}
|
|
7121
7807
|
);
|
|
7122
7808
|
}
|
|
7123
|
-
const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs7(
|
|
7809
|
+
const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ jsxs7(
|
|
7124
7810
|
"div",
|
|
7125
7811
|
{
|
|
7126
7812
|
"data-ohw-bridge": "",
|
|
@@ -7190,10 +7876,12 @@ function MediaOverlay({
|
|
|
7190
7876
|
// in-document, pointer-events does it natively. The button below opts back in, so
|
|
7191
7877
|
// Replace still works.
|
|
7192
7878
|
pointerEvents: hover.hasTextOverlap ? "none" : "auto",
|
|
7193
|
-
|
|
7194
|
-
|
|
7879
|
+
// Selected: a firm component ring with no wash, so the image reads as chosen rather
|
|
7880
|
+
// than hovered. Hover keeps the existing tinted preview.
|
|
7881
|
+
boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
|
|
7882
|
+
background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
|
|
7195
7883
|
},
|
|
7196
|
-
onClick: () => onReplace(hover.key),
|
|
7884
|
+
onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
|
|
7197
7885
|
children: [
|
|
7198
7886
|
/* @__PURE__ */ jsxs7(
|
|
7199
7887
|
Button,
|
|
@@ -7247,7 +7935,7 @@ function MediaOverlay({
|
|
|
7247
7935
|
},
|
|
7248
7936
|
children: [
|
|
7249
7937
|
isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
|
|
7250
|
-
replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
|
|
7938
|
+
replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
|
|
7251
7939
|
]
|
|
7252
7940
|
}
|
|
7253
7941
|
)
|
|
@@ -7331,6 +8019,8 @@ function parseSectionsFromRoot(root) {
|
|
|
7331
8019
|
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
7332
8020
|
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
7333
8021
|
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
8022
|
+
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
8023
|
+
continue;
|
|
7334
8024
|
seen.add(id);
|
|
7335
8025
|
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
7336
8026
|
sections.push({ id, label });
|
|
@@ -7616,6 +8306,7 @@ function AiSectionOverlay({
|
|
|
7616
8306
|
}) {
|
|
7617
8307
|
const [selectedId, setSelectedId] = useState5(null);
|
|
7618
8308
|
const [reviewId, setReviewId] = useState5(null);
|
|
8309
|
+
const [reviewButtonsHidden, setReviewButtonsHidden] = useState5(false);
|
|
7619
8310
|
const reviewIdRef = useRef4(null);
|
|
7620
8311
|
reviewIdRef.current = reviewId;
|
|
7621
8312
|
const selectedIdRef = useRef4(null);
|
|
@@ -7677,6 +8368,7 @@ function AiSectionOverlay({
|
|
|
7677
8368
|
}
|
|
7678
8369
|
const found = readRect(sectionId) != null;
|
|
7679
8370
|
setReviewId(found ? sectionId : null);
|
|
8371
|
+
setReviewButtonsHidden(e.data.hideButtons === true);
|
|
7680
8372
|
postToParent2({ type: "ow:ai-review-started", sectionId, found });
|
|
7681
8373
|
if (found) {
|
|
7682
8374
|
document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
@@ -7804,13 +8496,16 @@ function AiSectionOverlay({
|
|
|
7804
8496
|
border: `2px solid ${PRIMARY2}`,
|
|
7805
8497
|
borderRadius: edgeAwareRadius(reviewRect),
|
|
7806
8498
|
zIndex: 2147483200,
|
|
7807
|
-
// The veil itself: swallows clicks so the section stays locked until decided.
|
|
8499
|
+
// The veil itself: swallows clicks so the section stays locked until decided. This
|
|
8500
|
+
// stopPropagation only guards the bubble phase; the bridge's capture-phase click
|
|
8501
|
+
// handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
|
|
8502
|
+
// Accept/Discard resolves to the media beneath and opens the file picker.
|
|
7808
8503
|
background: "rgba(8, 133, 254, 0.04)",
|
|
7809
8504
|
pointerEvents: "auto",
|
|
7810
8505
|
cursor: "default"
|
|
7811
8506
|
},
|
|
7812
8507
|
onClick: (e) => e.stopPropagation(),
|
|
7813
|
-
children: /* @__PURE__ */ jsxs9(
|
|
8508
|
+
children: !reviewButtonsHidden && /* @__PURE__ */ jsxs9(
|
|
7814
8509
|
"div",
|
|
7815
8510
|
{
|
|
7816
8511
|
style: {
|
|
@@ -10377,8 +11072,13 @@ function referenceBox(slot) {
|
|
|
10377
11072
|
const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
|
|
10378
11073
|
(el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
|
|
10379
11074
|
) : null;
|
|
10380
|
-
|
|
10381
|
-
|
|
11075
|
+
if (neighbour) {
|
|
11076
|
+
const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
|
|
11077
|
+
if (box2?.width && box2.height) return box2;
|
|
11078
|
+
}
|
|
11079
|
+
const own = slot.getBoundingClientRect();
|
|
11080
|
+
if (own.width && own.height) return own;
|
|
11081
|
+
const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
|
|
10382
11082
|
return box?.width && box.height ? box : null;
|
|
10383
11083
|
}
|
|
10384
11084
|
function iconMarkupSizedFor(slot, markup) {
|
|
@@ -13253,6 +13953,7 @@ function useSectionDrag({
|
|
|
13253
13953
|
}
|
|
13254
13954
|
const orderJson = JSON.stringify(entries);
|
|
13255
13955
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
13956
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
13256
13957
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
13257
13958
|
applyPersistedOrder(entries);
|
|
13258
13959
|
clearSectionDragVisuals();
|
|
@@ -15372,6 +16073,70 @@ function OhhwellsBridge() {
|
|
|
15372
16073
|
const hoveredImageHasTextOverlapRef = useRef10(false);
|
|
15373
16074
|
const dragOverElRef = useRef10(null);
|
|
15374
16075
|
const [mediaHover, setMediaHover] = useState13(null);
|
|
16076
|
+
const [selectedMedia, setSelectedMedia] = useState13(null);
|
|
16077
|
+
const selectedMediaElRef = useRef10(null);
|
|
16078
|
+
const clearMediaSelection = useCallback8(() => {
|
|
16079
|
+
const prev = selectedMediaElRef.current;
|
|
16080
|
+
selectedMediaElRef.current = null;
|
|
16081
|
+
setSelectedMedia(null);
|
|
16082
|
+
const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
|
|
16083
|
+
if (sectionEl) {
|
|
16084
|
+
postToParentRef.current({
|
|
16085
|
+
type: "ow:section-selected",
|
|
16086
|
+
sectionId: sectionEl.dataset.ohwSection ?? null,
|
|
16087
|
+
sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
|
|
16088
|
+
key: null
|
|
16089
|
+
});
|
|
16090
|
+
}
|
|
16091
|
+
}, []);
|
|
16092
|
+
const clearMediaSelectionRef = useRef10(clearMediaSelection);
|
|
16093
|
+
clearMediaSelectionRef.current = clearMediaSelection;
|
|
16094
|
+
const selectMediaElement = useCallback8((el) => {
|
|
16095
|
+
const r2 = el.getBoundingClientRect();
|
|
16096
|
+
const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
|
|
16097
|
+
selectedMediaElRef.current = el;
|
|
16098
|
+
setSelectedMedia({
|
|
16099
|
+
key: el.dataset.ohwKey ?? "",
|
|
16100
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
16101
|
+
elementType: el.dataset.ohwEditable ?? "image",
|
|
16102
|
+
hasTextOverlap: false,
|
|
16103
|
+
isDragOver: false,
|
|
16104
|
+
...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
|
|
16105
|
+
});
|
|
16106
|
+
const sectionEl = el.closest("[data-ohw-section]");
|
|
16107
|
+
aiSectionApiRef.current?.selectFromElement(el, { report: false });
|
|
16108
|
+
postToParentRef.current({
|
|
16109
|
+
type: "ow:section-selected",
|
|
16110
|
+
sectionId: sectionEl?.dataset.ohwSection ?? null,
|
|
16111
|
+
sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
|
|
16112
|
+
key: el.dataset.ohwKey ?? null,
|
|
16113
|
+
// Display name for the pill — the raw key prettifies into fragments ("Img"); the
|
|
16114
|
+
// bridge knows what the node IS, so it names it.
|
|
16115
|
+
keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
|
|
16116
|
+
});
|
|
16117
|
+
}, []);
|
|
16118
|
+
const selectMediaElementRef = useRef10(selectMediaElement);
|
|
16119
|
+
selectMediaElementRef.current = selectMediaElement;
|
|
16120
|
+
useEffect13(() => {
|
|
16121
|
+
if (!selectedMedia) return;
|
|
16122
|
+
const update = () => {
|
|
16123
|
+
const el = selectedMediaElRef.current;
|
|
16124
|
+
if (!el || !el.isConnected) {
|
|
16125
|
+
clearMediaSelection();
|
|
16126
|
+
return;
|
|
16127
|
+
}
|
|
16128
|
+
const r2 = el.getBoundingClientRect();
|
|
16129
|
+
setSelectedMedia(
|
|
16130
|
+
(prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
|
|
16131
|
+
);
|
|
16132
|
+
};
|
|
16133
|
+
window.addEventListener("scroll", update, true);
|
|
16134
|
+
window.addEventListener("resize", update);
|
|
16135
|
+
return () => {
|
|
16136
|
+
window.removeEventListener("scroll", update, true);
|
|
16137
|
+
window.removeEventListener("resize", update);
|
|
16138
|
+
};
|
|
16139
|
+
}, [selectedMedia !== null]);
|
|
15375
16140
|
const [carouselHover, setCarouselHover] = useState13(null);
|
|
15376
16141
|
const [uploadingRects, setUploadingRects] = useState13({});
|
|
15377
16142
|
const hoveredGapRef = useRef10(null);
|
|
@@ -15655,6 +16420,8 @@ function OhhwellsBridge() {
|
|
|
15655
16420
|
const addNavAfterAnchorRef = useRef10(null);
|
|
15656
16421
|
const editContentRef = useRef10({});
|
|
15657
16422
|
const aiSectionsRef = useRef10("");
|
|
16423
|
+
const brandKitRef = useRef10("");
|
|
16424
|
+
const stylesRef = useRef10("");
|
|
15658
16425
|
const pendingDeleteUndoRef = useRef10(null);
|
|
15659
16426
|
const [sitePages, setSitePages] = useState13([]);
|
|
15660
16427
|
const [sectionsByPath, setSectionsByPath] = useState13({});
|
|
@@ -16975,13 +17742,29 @@ function OhhwellsBridge() {
|
|
|
16975
17742
|
}
|
|
16976
17743
|
const applyContent = (content) => {
|
|
16977
17744
|
const imageLoads = [];
|
|
17745
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17746
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
17747
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17748
|
+
} else {
|
|
17749
|
+
brandKitRef.current = "";
|
|
17750
|
+
applyBrandToDom(null);
|
|
17751
|
+
}
|
|
16978
17752
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
16979
17753
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
17754
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
16980
17755
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
16981
17756
|
}
|
|
17757
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17758
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
17759
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17760
|
+
}
|
|
17761
|
+
applyBrandChrome(content);
|
|
16982
17762
|
for (const [key, val] of Object.entries(content)) {
|
|
16983
17763
|
if (key === "__ohw_sections") continue;
|
|
16984
17764
|
if (key === AI_SECTIONS_KEY) continue;
|
|
17765
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17766
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17767
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
16985
17768
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
16986
17769
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
16987
17770
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17169,8 +17952,25 @@ function OhhwellsBridge() {
|
|
|
17169
17952
|
initSectionInstancesFromContent(content, window.location.pathname);
|
|
17170
17953
|
observer?.disconnect();
|
|
17171
17954
|
try {
|
|
17955
|
+
applyBrandChrome(content);
|
|
17956
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17957
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17958
|
+
} else {
|
|
17959
|
+
applyBrandToDom(null);
|
|
17960
|
+
}
|
|
17961
|
+
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
17962
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
17963
|
+
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
17964
|
+
}
|
|
17965
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17966
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17967
|
+
}
|
|
17172
17968
|
for (const [key, val] of Object.entries(content)) {
|
|
17173
17969
|
if (key === "__ohw_sections") continue;
|
|
17970
|
+
if (key === AI_SECTIONS_KEY) continue;
|
|
17971
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17972
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17973
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17174
17974
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17175
17975
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17176
17976
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17313,9 +18113,21 @@ function OhhwellsBridge() {
|
|
|
17313
18113
|
}, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
|
|
17314
18114
|
useEffect13(() => {
|
|
17315
18115
|
if (!isEditMode) return;
|
|
18116
|
+
let lastPosted = 0;
|
|
17316
18117
|
const measure = () => {
|
|
17317
18118
|
const h = document.body.scrollHeight;
|
|
17318
|
-
if (h > 50
|
|
18119
|
+
if (h > 50 && Math.abs(h - lastPosted) > 1) {
|
|
18120
|
+
lastPosted = h;
|
|
18121
|
+
postToParent2({ type: "ow:height", height: h });
|
|
18122
|
+
}
|
|
18123
|
+
};
|
|
18124
|
+
let raf = null;
|
|
18125
|
+
const schedule = () => {
|
|
18126
|
+
if (raf != null) return;
|
|
18127
|
+
raf = requestAnimationFrame(() => {
|
|
18128
|
+
raf = null;
|
|
18129
|
+
measure();
|
|
18130
|
+
});
|
|
17319
18131
|
};
|
|
17320
18132
|
const t1 = setTimeout(measure, 50);
|
|
17321
18133
|
const t2 = setTimeout(measure, 500);
|
|
@@ -17335,6 +18147,7 @@ function OhhwellsBridge() {
|
|
|
17335
18147
|
return () => {
|
|
17336
18148
|
clearTimeout(t1);
|
|
17337
18149
|
clearTimeout(t2);
|
|
18150
|
+
if (raf != null) cancelAnimationFrame(raf);
|
|
17338
18151
|
clearResizeTimers();
|
|
17339
18152
|
window.removeEventListener("resize", handleResize);
|
|
17340
18153
|
};
|
|
@@ -17577,10 +18390,14 @@ function OhhwellsBridge() {
|
|
|
17577
18390
|
return;
|
|
17578
18391
|
}
|
|
17579
18392
|
const target = e.target;
|
|
18393
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17580
18394
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17581
18395
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17582
18396
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
17583
18397
|
if (isInsideLinkEditor(target)) return;
|
|
18398
|
+
if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
|
|
18399
|
+
clearMediaSelectionRef.current();
|
|
18400
|
+
}
|
|
17584
18401
|
if (isInsideFloatingPanel(target)) return;
|
|
17585
18402
|
if (target.closest("[data-ohw-form-toolbar]")) return;
|
|
17586
18403
|
if (target.closest(
|
|
@@ -17750,8 +18567,11 @@ function OhhwellsBridge() {
|
|
|
17750
18567
|
if (isMediaEditable(editable) && !buttonOnMedia) {
|
|
17751
18568
|
e.preventDefault();
|
|
17752
18569
|
e.stopPropagation();
|
|
17753
|
-
|
|
17754
|
-
|
|
18570
|
+
if (selectedMediaElRef.current === editable) {
|
|
18571
|
+
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
|
|
18572
|
+
} else {
|
|
18573
|
+
selectMediaElementRef.current(editable);
|
|
18574
|
+
}
|
|
17755
18575
|
return;
|
|
17756
18576
|
}
|
|
17757
18577
|
const socialItem = getSocialItem(editable);
|
|
@@ -17892,6 +18712,7 @@ function OhhwellsBridge() {
|
|
|
17892
18712
|
};
|
|
17893
18713
|
const handleDblClick = (e) => {
|
|
17894
18714
|
const target = e.target;
|
|
18715
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17895
18716
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17896
18717
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17897
18718
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
@@ -18692,7 +19513,9 @@ function OhhwellsBridge() {
|
|
|
18692
19513
|
return;
|
|
18693
19514
|
}
|
|
18694
19515
|
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
18695
|
-
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).
|
|
19516
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
|
|
19517
|
+
(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
|
|
19518
|
+
).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
18696
19519
|
const ZONE = 20;
|
|
18697
19520
|
for (let i = 0; i < sections.length; i++) {
|
|
18698
19521
|
const a = sections[i];
|
|
@@ -19024,10 +19847,23 @@ function OhhwellsBridge() {
|
|
|
19024
19847
|
if (e.data?.type !== "ow:hydrate") return;
|
|
19025
19848
|
const content = e.data.content;
|
|
19026
19849
|
if (!content) return;
|
|
19850
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
19851
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
19852
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
19853
|
+
} else {
|
|
19854
|
+
brandKitRef.current = "";
|
|
19855
|
+
applyBrandToDom(null);
|
|
19856
|
+
}
|
|
19027
19857
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
19028
19858
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
19859
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
19029
19860
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
19030
19861
|
}
|
|
19862
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
19863
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
19864
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
19865
|
+
}
|
|
19866
|
+
applyBrandChrome(content);
|
|
19031
19867
|
let sectionsJson = null;
|
|
19032
19868
|
for (const [key, val] of Object.entries(content)) {
|
|
19033
19869
|
if (key === "__ohw_sections") {
|
|
@@ -19035,6 +19871,9 @@ function OhhwellsBridge() {
|
|
|
19035
19871
|
continue;
|
|
19036
19872
|
}
|
|
19037
19873
|
if (key === AI_SECTIONS_KEY) continue;
|
|
19874
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
19875
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
19876
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
19038
19877
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
19039
19878
|
if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
|
|
19040
19879
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -19050,6 +19889,8 @@ function OhhwellsBridge() {
|
|
|
19050
19889
|
if (video && video.src !== val) applyVideoSrc(video, val);
|
|
19051
19890
|
} else if (el.dataset.ohwEditable === "link") {
|
|
19052
19891
|
applyLinkHref(el, val);
|
|
19892
|
+
} else if (el.dataset.ohwEditable === "icon") {
|
|
19893
|
+
applyIconMarkup(el, val);
|
|
19053
19894
|
} else if (isIconMarkupValue(val)) {
|
|
19054
19895
|
} else {
|
|
19055
19896
|
el.innerHTML = val;
|
|
@@ -19134,12 +19975,21 @@ function OhhwellsBridge() {
|
|
|
19134
19975
|
nodes: collectEditableNodes(editContentRef.current)
|
|
19135
19976
|
});
|
|
19136
19977
|
};
|
|
19978
|
+
const clearInteractionChrome = () => {
|
|
19979
|
+
deactivateRef.current();
|
|
19980
|
+
deselectRef.current();
|
|
19981
|
+
clearMediaSelectionRef.current();
|
|
19982
|
+
};
|
|
19137
19983
|
const handleAiApplyTree = (e) => {
|
|
19138
19984
|
if (e.data?.type !== "ow:ai-apply-tree") return;
|
|
19139
19985
|
const payload = e.data.payload;
|
|
19140
19986
|
if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
|
|
19987
|
+
clearInteractionChrome();
|
|
19141
19988
|
const previous = aiSectionsRef.current;
|
|
19142
|
-
const nextState = applyTreeToState(parseAiSectionsState(previous),
|
|
19989
|
+
const nextState = applyTreeToState(parseAiSectionsState(previous), {
|
|
19990
|
+
...payload,
|
|
19991
|
+
path: payload.path ?? window.location.pathname
|
|
19992
|
+
});
|
|
19143
19993
|
const nextValue = serializeAiSectionsState(nextState);
|
|
19144
19994
|
aiSectionsRef.current = nextValue;
|
|
19145
19995
|
applyAiSectionsToDom(nextState);
|
|
@@ -19160,6 +20010,7 @@ function OhhwellsBridge() {
|
|
|
19160
20010
|
const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
19161
20011
|
if (!exists) return;
|
|
19162
20012
|
if (isPageFrameSection(exists)) return;
|
|
20013
|
+
clearInteractionChrome();
|
|
19163
20014
|
const previous = aiSectionsRef.current;
|
|
19164
20015
|
const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
|
|
19165
20016
|
const nextValue = serializeAiSectionsState(nextState);
|
|
@@ -19175,14 +20026,45 @@ function OhhwellsBridge() {
|
|
|
19175
20026
|
const handleAiSetSections = (e) => {
|
|
19176
20027
|
if (e.data?.type !== "ow:ai-set-sections") return;
|
|
19177
20028
|
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20029
|
+
clearInteractionChrome();
|
|
19178
20030
|
aiSectionsRef.current = value;
|
|
19179
20031
|
applyAiSectionsToDom(parseAiSectionsState(value));
|
|
20032
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
19180
20033
|
const restoredHeight = document.body.scrollHeight;
|
|
19181
20034
|
if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
|
|
19182
20035
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
|
|
19183
20036
|
postAiSectionsChanged();
|
|
19184
20037
|
};
|
|
19185
20038
|
window.addEventListener("message", handleAiSetSections);
|
|
20039
|
+
const handleAiSetBrand = (e) => {
|
|
20040
|
+
if (e.data?.type !== "ow:ai-set-brand") return;
|
|
20041
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20042
|
+
const previous = brandKitRef.current;
|
|
20043
|
+
brandKitRef.current = value;
|
|
20044
|
+
applyBrandToDom(parseBrandKit(value));
|
|
20045
|
+
if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
|
|
20046
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
20047
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
|
|
20048
|
+
postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
|
|
20049
|
+
};
|
|
20050
|
+
window.addEventListener("message", handleAiSetBrand);
|
|
20051
|
+
const handleAiSetStyles = (e) => {
|
|
20052
|
+
if (e.data?.type !== "ow:ai-set-styles") return;
|
|
20053
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20054
|
+
const previous = stylesRef.current;
|
|
20055
|
+
stylesRef.current = value;
|
|
20056
|
+
applyStylesToDom(parseStyleStore(value));
|
|
20057
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
|
|
20058
|
+
postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
|
|
20059
|
+
};
|
|
20060
|
+
window.addEventListener("message", handleAiSetStyles);
|
|
20061
|
+
const handleGetBrand = (e) => {
|
|
20062
|
+
if (e.data?.type !== "ow:get-brand") return;
|
|
20063
|
+
const template = deriveTemplateBrand();
|
|
20064
|
+
const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
|
|
20065
|
+
postToParentRef.current({ type: "ow:brand-value", value });
|
|
20066
|
+
};
|
|
20067
|
+
window.addEventListener("message", handleGetBrand);
|
|
19186
20068
|
const handleMoveSection = (e) => {
|
|
19187
20069
|
if (e.data?.type !== "ow:move-section") return;
|
|
19188
20070
|
const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
|
|
@@ -19192,6 +20074,7 @@ function OhhwellsBridge() {
|
|
|
19192
20074
|
if (!entries) return;
|
|
19193
20075
|
const orderJson = JSON.stringify(entries);
|
|
19194
20076
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20077
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
19195
20078
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
19196
20079
|
window.dispatchEvent(new Event("resize"));
|
|
19197
20080
|
};
|
|
@@ -19255,6 +20138,7 @@ function OhhwellsBridge() {
|
|
|
19255
20138
|
}
|
|
19256
20139
|
deselectRef.current();
|
|
19257
20140
|
deactivateRef.current();
|
|
20141
|
+
clearMediaSelectionRef.current();
|
|
19258
20142
|
};
|
|
19259
20143
|
window.addEventListener("message", handleDeactivate);
|
|
19260
20144
|
const handleToastAction = (e) => {
|
|
@@ -19340,6 +20224,10 @@ function OhhwellsBridge() {
|
|
|
19340
20224
|
const handleKeyDown = (e) => {
|
|
19341
20225
|
if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
|
|
19342
20226
|
if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
|
|
20227
|
+
if (e.key === "Escape" && selectedMediaElRef.current) {
|
|
20228
|
+
clearMediaSelectionRef.current();
|
|
20229
|
+
return;
|
|
20230
|
+
}
|
|
19343
20231
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
|
|
19344
20232
|
e.preventDefault();
|
|
19345
20233
|
selectAllTextInEditable(activeElRef.current);
|
|
@@ -19499,6 +20387,12 @@ function OhhwellsBridge() {
|
|
|
19499
20387
|
if (aiSectionsRef.current) {
|
|
19500
20388
|
nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
|
|
19501
20389
|
}
|
|
20390
|
+
if (stylesRef.current) {
|
|
20391
|
+
nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
|
|
20392
|
+
}
|
|
20393
|
+
if (brandKitRef.current) {
|
|
20394
|
+
nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
|
|
20395
|
+
}
|
|
19502
20396
|
document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
|
|
19503
20397
|
const formKey = formKeyOf(form);
|
|
19504
20398
|
if (!formKey) return;
|
|
@@ -19913,6 +20807,9 @@ function OhhwellsBridge() {
|
|
|
19913
20807
|
window.removeEventListener("message", handleAiApplyTree);
|
|
19914
20808
|
window.removeEventListener("message", handleAiDeleteSection);
|
|
19915
20809
|
window.removeEventListener("message", handleAiSetSections);
|
|
20810
|
+
window.removeEventListener("message", handleAiSetBrand);
|
|
20811
|
+
window.removeEventListener("message", handleAiSetStyles);
|
|
20812
|
+
window.removeEventListener("message", handleGetBrand);
|
|
19916
20813
|
window.removeEventListener("message", handleMoveSection);
|
|
19917
20814
|
window.removeEventListener("message", handlePanelDragging);
|
|
19918
20815
|
window.removeEventListener("message", handleDeleteSection);
|
|
@@ -20123,7 +21020,7 @@ function OhhwellsBridge() {
|
|
|
20123
21020
|
postToParent2({
|
|
20124
21021
|
type: "ow:ready",
|
|
20125
21022
|
version: "1",
|
|
20126
|
-
bridgeVersion: "0.1.
|
|
21023
|
+
bridgeVersion: "0.1.79",
|
|
20127
21024
|
path: pathname,
|
|
20128
21025
|
nodes: collectEditableNodes(editContentRef.current),
|
|
20129
21026
|
sections
|
|
@@ -20530,11 +21427,22 @@ function OhhwellsBridge() {
|
|
|
20530
21427
|
const showEditLink = toolbarShowEditLink;
|
|
20531
21428
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
20532
21429
|
linkPopoverOpenRef.current = linkPopover !== null;
|
|
21430
|
+
const handleMediaSelect = useCallback8((key) => {
|
|
21431
|
+
const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
|
|
21432
|
+
(m) => (m.dataset.ohwKey ?? "") === key
|
|
21433
|
+
) ?? null;
|
|
21434
|
+
if (!el) return;
|
|
21435
|
+
selectMediaElementRef.current(el);
|
|
21436
|
+
}, []);
|
|
20533
21437
|
const handleMediaReplace = useCallback8(
|
|
20534
21438
|
(key) => {
|
|
20535
|
-
postToParent2({
|
|
21439
|
+
postToParent2({
|
|
21440
|
+
type: "ow:image-pick",
|
|
21441
|
+
key,
|
|
21442
|
+
elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
|
|
21443
|
+
});
|
|
20536
21444
|
},
|
|
20537
|
-
[postToParent2, mediaHover?.elementType]
|
|
21445
|
+
[postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
|
|
20538
21446
|
);
|
|
20539
21447
|
const handleEditCarousel = useCallback8(
|
|
20540
21448
|
(key) => {
|
|
@@ -20606,12 +21514,25 @@ function OhhwellsBridge() {
|
|
|
20606
21514
|
},
|
|
20607
21515
|
`uploading-${key}`
|
|
20608
21516
|
)),
|
|
20609
|
-
mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
|
|
21517
|
+
mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx33(
|
|
20610
21518
|
MediaOverlay,
|
|
20611
21519
|
{
|
|
20612
21520
|
hover: mediaHover,
|
|
20613
21521
|
isUploading: false,
|
|
20614
21522
|
onReplace: handleMediaReplace,
|
|
21523
|
+
onSelect: handleMediaSelect,
|
|
21524
|
+
onVideoSettingsChange: handleVideoSettingsChange
|
|
21525
|
+
}
|
|
21526
|
+
),
|
|
21527
|
+
selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx33(
|
|
21528
|
+
MediaOverlay,
|
|
21529
|
+
{
|
|
21530
|
+
hover: selectedMedia,
|
|
21531
|
+
selected: true,
|
|
21532
|
+
hovered: mediaHover?.key === selectedMedia.key,
|
|
21533
|
+
isUploading: false,
|
|
21534
|
+
onReplace: handleMediaReplace,
|
|
21535
|
+
onSelect: handleMediaSelect,
|
|
20615
21536
|
onVideoSettingsChange: handleVideoSettingsChange
|
|
20616
21537
|
}
|
|
20617
21538
|
),
|