@ohhwells/bridge 0.1.78 → 0.1.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1030 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +1030 -133
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +1 -1
package/dist/index.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,63 @@ 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
|
+
// The 50:50 form collapses to a single stacked column on phones.
|
|
473
|
+
"[data-ai-form]{grid-template-columns:1fr !important}",
|
|
474
|
+
"[data-ai-section] img{max-width:100%}",
|
|
475
|
+
"}",
|
|
476
|
+
"@media (min-width: 769px) and (max-width: 1024px){",
|
|
477
|
+
"[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
|
|
478
|
+
"}"
|
|
479
|
+
].join("");
|
|
144
480
|
var FEATURE_LINE_CSS = [
|
|
145
481
|
"[data-ai-features]>div{position:relative;padding-left:40px;min-height:24px}",
|
|
146
482
|
'[data-ai-features]>div::before{content:"";position:absolute;left:0;top:1px;width:24px;height:24px;',
|
|
147
483
|
`background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
|
|
148
484
|
`mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
|
|
149
485
|
].join("");
|
|
486
|
+
function hexLuminance(color) {
|
|
487
|
+
const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
|
|
488
|
+
if (!m) return null;
|
|
489
|
+
const [r2, g, b] = [0, 2, 4].map((i) => {
|
|
490
|
+
const c = parseInt(m[1].slice(i, i + 2), 16) / 255;
|
|
491
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
492
|
+
});
|
|
493
|
+
return 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
494
|
+
}
|
|
495
|
+
function hexContrast(a, b) {
|
|
496
|
+
const la = hexLuminance(a);
|
|
497
|
+
const lb = hexLuminance(b);
|
|
498
|
+
if (la === null || lb === null) return null;
|
|
499
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
500
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
501
|
+
}
|
|
502
|
+
function accentBandContext(brand) {
|
|
503
|
+
const p = brand.palette;
|
|
504
|
+
const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
|
|
505
|
+
if (lightWins) {
|
|
506
|
+
return {
|
|
507
|
+
brand: { ...brand, palette: { dark: p.light, primary: p.light, accent: p.light, light: p.primary } },
|
|
508
|
+
buttonLabel: p.primary
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
brand: { ...brand, palette: { dark: p.dark, primary: p.dark, accent: p.dark, light: p.light } },
|
|
513
|
+
buttonLabel: p.light
|
|
514
|
+
};
|
|
515
|
+
}
|
|
150
516
|
function textAttrs(ctx, path) {
|
|
151
517
|
return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
|
|
152
518
|
}
|
|
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
519
|
var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
165
520
|
function MediaBox({
|
|
166
521
|
refValue,
|
|
@@ -173,17 +528,13 @@ function MediaBox({
|
|
|
173
528
|
const url = refValue ? ctx.resolveMedia(refValue) : null;
|
|
174
529
|
const isIcon = /^(lucide|simple):/.test(refValue);
|
|
175
530
|
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
|
-
} : {};
|
|
531
|
+
const editAttrs = ctx.keyFor && editPath && !isIcon ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "image" } : {};
|
|
180
532
|
if (isIcon) {
|
|
181
533
|
const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
|
|
182
534
|
return /* @__PURE__ */ jsx(
|
|
183
535
|
"span",
|
|
184
536
|
{
|
|
185
537
|
"data-ai-icon": refValue,
|
|
186
|
-
...editAttrs,
|
|
187
538
|
style: {
|
|
188
539
|
display: "inline-flex",
|
|
189
540
|
width: 48,
|
|
@@ -236,7 +587,7 @@ function ButtonEl({
|
|
|
236
587
|
}) {
|
|
237
588
|
const secondary = slots.variant === "secondary";
|
|
238
589
|
const href = str(slots.href);
|
|
239
|
-
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`) } : {};
|
|
590
|
+
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
|
|
240
591
|
return /* @__PURE__ */ jsx(
|
|
241
592
|
"a",
|
|
242
593
|
{
|
|
@@ -252,7 +603,7 @@ function ButtonEl({
|
|
|
252
603
|
textDecoration: "none",
|
|
253
604
|
cursor: "pointer",
|
|
254
605
|
...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 }
|
|
606
|
+
...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
607
|
},
|
|
257
608
|
children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
258
609
|
}
|
|
@@ -758,7 +1109,24 @@ function CardBlock({ node, ctx, path }) {
|
|
|
758
1109
|
minWidth: 0
|
|
759
1110
|
},
|
|
760
1111
|
children: [
|
|
761
|
-
media && (horizontal ? /* @__PURE__ */ jsx(
|
|
1112
|
+
media && (horizontal ? /* @__PURE__ */ jsx(
|
|
1113
|
+
"div",
|
|
1114
|
+
{
|
|
1115
|
+
style: (
|
|
1116
|
+
// An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
|
|
1117
|
+
// text to the far side. Photos keep the half-and-half split. The inset has no
|
|
1118
|
+
// inner padding (the photo split absorbed that), so the icon carries its own gap.
|
|
1119
|
+
/^(lucide|simple):/.test(mediaRef) ? {
|
|
1120
|
+
flexShrink: 0,
|
|
1121
|
+
display: "flex",
|
|
1122
|
+
alignItems: "center",
|
|
1123
|
+
padding: mediaInset,
|
|
1124
|
+
[mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
|
|
1125
|
+
} : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
|
|
1126
|
+
),
|
|
1127
|
+
children: media
|
|
1128
|
+
}
|
|
1129
|
+
) : /* @__PURE__ */ jsx(
|
|
762
1130
|
"div",
|
|
763
1131
|
{
|
|
764
1132
|
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 +1217,44 @@ function AccordionBlock({ node, ctx, path }) {
|
|
|
849
1217
|
) })
|
|
850
1218
|
] }, i)) });
|
|
851
1219
|
}
|
|
1220
|
+
function useIsMobile() {
|
|
1221
|
+
const [mobile, setMobile] = React.useState(
|
|
1222
|
+
() => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
|
|
1223
|
+
);
|
|
1224
|
+
React.useEffect(() => {
|
|
1225
|
+
const mq = window.matchMedia("(max-width: 768px)");
|
|
1226
|
+
const update = () => setMobile(mq.matches);
|
|
1227
|
+
update();
|
|
1228
|
+
mq.addEventListener("change", update);
|
|
1229
|
+
return () => mq.removeEventListener("change", update);
|
|
1230
|
+
}, []);
|
|
1231
|
+
return mobile;
|
|
1232
|
+
}
|
|
852
1233
|
function Carousel({ items, itemsPerRow, ctx }) {
|
|
1234
|
+
const isMobile = useIsMobile();
|
|
1235
|
+
const perPage = isMobile ? 1 : itemsPerRow;
|
|
1236
|
+
const pages = Math.max(1, Math.ceil(items.length / perPage));
|
|
853
1237
|
const [page, setPage] = React.useState(0);
|
|
854
|
-
const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
|
|
855
1238
|
const current = Math.min(page, pages - 1);
|
|
1239
|
+
if (pages <= 1) {
|
|
1240
|
+
const cols = Math.max(1, Math.min(items.length, itemsPerRow));
|
|
1241
|
+
return /* @__PURE__ */ jsx(
|
|
1242
|
+
"div",
|
|
1243
|
+
{
|
|
1244
|
+
"data-ai-grid": String(cols),
|
|
1245
|
+
style: {
|
|
1246
|
+
display: "grid",
|
|
1247
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
1248
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1249
|
+
alignItems: "start"
|
|
1250
|
+
},
|
|
1251
|
+
children: items
|
|
1252
|
+
}
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
856
1255
|
const pageGroups = Array.from(
|
|
857
1256
|
{ length: pages },
|
|
858
|
-
(_, p) => items.slice(p *
|
|
1257
|
+
(_, p) => items.slice(p * perPage, (p + 1) * perPage)
|
|
859
1258
|
);
|
|
860
1259
|
const chrome = (enabled) => ({
|
|
861
1260
|
border: `1px solid ${ctx.brand.palette.dark}`,
|
|
@@ -880,55 +1279,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
|
|
|
880
1279
|
cursor: "pointer",
|
|
881
1280
|
padding: 0
|
|
882
1281
|
});
|
|
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(
|
|
1282
|
+
const viewport = /* @__PURE__ */ jsx("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
|
|
1283
|
+
"div",
|
|
1284
|
+
{
|
|
1285
|
+
style: {
|
|
1286
|
+
display: "flex",
|
|
1287
|
+
transform: `translateX(-${current * 100}%)`,
|
|
1288
|
+
transition: "transform 0.4s ease"
|
|
1289
|
+
},
|
|
1290
|
+
children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
|
|
896
1291
|
"div",
|
|
897
1292
|
{
|
|
1293
|
+
"data-ai-grid": String(perPage),
|
|
898
1294
|
style: {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1295
|
+
flex: "0 0 100%",
|
|
1296
|
+
display: "grid",
|
|
1297
|
+
gridTemplateColumns: `repeat(${perPage}, 1fr)`,
|
|
1298
|
+
gap: AI_TREE_TOKENS.spacing8,
|
|
1299
|
+
alignItems: "start"
|
|
902
1300
|
},
|
|
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
|
-
|
|
1301
|
+
children: group
|
|
1302
|
+
},
|
|
1303
|
+
p
|
|
1304
|
+
))
|
|
1305
|
+
}
|
|
1306
|
+
) });
|
|
1307
|
+
const prevBtn = /* @__PURE__ */ jsx(
|
|
1308
|
+
"button",
|
|
1309
|
+
{
|
|
1310
|
+
type: "button",
|
|
1311
|
+
"aria-label": "Previous",
|
|
1312
|
+
onClick: () => setPage((p) => Math.max(0, p - 1)),
|
|
1313
|
+
style: chrome(current > 0),
|
|
1314
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1317
|
+
const nextBtn = /* @__PURE__ */ jsx(
|
|
1318
|
+
"button",
|
|
1319
|
+
{
|
|
1320
|
+
type: "button",
|
|
1321
|
+
"aria-label": "Next",
|
|
1322
|
+
onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
|
|
1323
|
+
style: chrome(current < pages - 1),
|
|
1324
|
+
children: /* @__PURE__ */ jsx(ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
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)) });
|
|
1328
|
+
if (isMobile) {
|
|
1329
|
+
return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
|
|
1330
|
+
viewport,
|
|
1331
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
|
|
1332
|
+
prevBtn,
|
|
1333
|
+
nextBtn
|
|
1334
|
+
] }),
|
|
1335
|
+
dots
|
|
1336
|
+
] });
|
|
1337
|
+
}
|
|
1338
|
+
return /* @__PURE__ */ jsxs("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
|
|
1339
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
|
|
1340
|
+
prevBtn,
|
|
1341
|
+
viewport,
|
|
1342
|
+
nextBtn
|
|
930
1343
|
] }),
|
|
931
|
-
|
|
1344
|
+
dots
|
|
932
1345
|
] });
|
|
933
1346
|
}
|
|
934
1347
|
function CollectionBlock({ node, ctx, path }) {
|
|
@@ -1007,7 +1420,7 @@ function CollectionBlock({ node, ctx, path }) {
|
|
|
1007
1420
|
return /* @__PURE__ */ jsx(
|
|
1008
1421
|
"div",
|
|
1009
1422
|
{
|
|
1010
|
-
"data-ai-grid":
|
|
1423
|
+
"data-ai-grid": "",
|
|
1011
1424
|
style: {
|
|
1012
1425
|
display: "grid",
|
|
1013
1426
|
gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
|
|
@@ -1022,6 +1435,49 @@ function renderNode(node, ctx, path) {
|
|
|
1022
1435
|
switch (node.type) {
|
|
1023
1436
|
case "text":
|
|
1024
1437
|
return /* @__PURE__ */ jsx(TextBlock, { slots, ctx, path });
|
|
1438
|
+
// Layout container: arranges child blocks, contributes no content of its own. `grid` is a
|
|
1439
|
+
// nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
|
|
1440
|
+
// mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
|
|
1441
|
+
// is a column. Children render through this same dispatcher, so edit markers, media
|
|
1442
|
+
// resolution, and copy paths all work unchanged inside a group.
|
|
1443
|
+
case "group": {
|
|
1444
|
+
const layout = str(slots.layout);
|
|
1445
|
+
const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
|
|
1446
|
+
const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ jsx(
|
|
1447
|
+
"div",
|
|
1448
|
+
{
|
|
1449
|
+
style: layout === "grid" ? {
|
|
1450
|
+
gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
|
|
1451
|
+
minWidth: 0
|
|
1452
|
+
} : { minWidth: 0 },
|
|
1453
|
+
children: renderNode(child, ctx, `${path}.c${i}`)
|
|
1454
|
+
},
|
|
1455
|
+
i
|
|
1456
|
+
));
|
|
1457
|
+
if (layout === "grid") {
|
|
1458
|
+
return /* @__PURE__ */ jsx(
|
|
1459
|
+
"div",
|
|
1460
|
+
{
|
|
1461
|
+
"data-ai-group": "grid",
|
|
1462
|
+
style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
|
|
1463
|
+
children: kids
|
|
1464
|
+
}
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
if (layout === "split") {
|
|
1468
|
+
const ratio = str(slots.ratio);
|
|
1469
|
+
const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
|
|
1470
|
+
return /* @__PURE__ */ jsx(
|
|
1471
|
+
"div",
|
|
1472
|
+
{
|
|
1473
|
+
"data-ai-group": "split",
|
|
1474
|
+
style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
|
|
1475
|
+
children: kids
|
|
1476
|
+
}
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
return /* @__PURE__ */ jsx("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
|
|
1480
|
+
}
|
|
1025
1481
|
case "button":
|
|
1026
1482
|
return /* @__PURE__ */ jsx(ButtonEl, { slots, ctx, path });
|
|
1027
1483
|
case "button-row":
|
|
@@ -1102,33 +1558,102 @@ function renderNode(node, ctx, path) {
|
|
|
1102
1558
|
}
|
|
1103
1559
|
);
|
|
1104
1560
|
}
|
|
1105
|
-
case "form":
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1561
|
+
case "form": {
|
|
1562
|
+
const formAttrs = ctx.keyFor ? {
|
|
1563
|
+
"data-ohw-editable": "form",
|
|
1564
|
+
"data-ohw-key": ctx.keyFor(`${path}.form`),
|
|
1565
|
+
"data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
|
|
1566
|
+
} : {};
|
|
1567
|
+
const fieldStyle = {
|
|
1568
|
+
width: "100%",
|
|
1569
|
+
boxSizing: "border-box",
|
|
1570
|
+
border: `1px solid ${ctx.brand.palette.accent}`,
|
|
1571
|
+
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
1572
|
+
padding: "12px 14px",
|
|
1573
|
+
background: "#fff",
|
|
1574
|
+
color: ctx.brand.palette.dark,
|
|
1575
|
+
outline: "none",
|
|
1576
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
|
|
1577
|
+
};
|
|
1578
|
+
const labelStyle = {
|
|
1579
|
+
...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
|
|
1580
|
+
color: ctx.brand.palette.dark
|
|
1581
|
+
};
|
|
1582
|
+
return /* @__PURE__ */ jsx(
|
|
1583
|
+
"form",
|
|
1584
|
+
{
|
|
1585
|
+
...formAttrs,
|
|
1586
|
+
"data-ai-form": "",
|
|
1587
|
+
style: {
|
|
1588
|
+
display: "grid",
|
|
1589
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
1590
|
+
columnGap: 64,
|
|
1591
|
+
rowGap: AI_TREE_TOKENS.spacing4
|
|
1592
|
+
},
|
|
1593
|
+
children: (node.children ?? []).map((child, i) => {
|
|
1594
|
+
if (child.type === "input") {
|
|
1595
|
+
const cs2 = child.slots ?? {};
|
|
1596
|
+
const kind = str(cs2.kind);
|
|
1597
|
+
const label = str(cs2.label);
|
|
1598
|
+
const placeholder = str(cs2.placeholder);
|
|
1599
|
+
const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
|
|
1600
|
+
const isTextarea = kind === "textarea";
|
|
1601
|
+
return /* @__PURE__ */ jsxs(
|
|
1602
|
+
"div",
|
|
1603
|
+
{
|
|
1604
|
+
style: {
|
|
1605
|
+
display: "flex",
|
|
1606
|
+
flexDirection: "column",
|
|
1607
|
+
gap: 8,
|
|
1608
|
+
...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
|
|
1609
|
+
},
|
|
1610
|
+
children: [
|
|
1611
|
+
/* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
|
|
1612
|
+
isTextarea ? /* @__PURE__ */ jsx(
|
|
1613
|
+
"textarea",
|
|
1614
|
+
{
|
|
1615
|
+
name,
|
|
1616
|
+
placeholder,
|
|
1617
|
+
style: { ...fieldStyle, height: 140, resize: "vertical" }
|
|
1618
|
+
}
|
|
1619
|
+
) : /* @__PURE__ */ jsx(
|
|
1620
|
+
"input",
|
|
1621
|
+
{
|
|
1622
|
+
name,
|
|
1623
|
+
type: kind === "email" ? "email" : "text",
|
|
1624
|
+
placeholder,
|
|
1625
|
+
style: { ...fieldStyle, height: 48 }
|
|
1626
|
+
}
|
|
1627
|
+
)
|
|
1628
|
+
]
|
|
1629
|
+
},
|
|
1630
|
+
i
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
const cs = child.slots ?? {};
|
|
1634
|
+
return /* @__PURE__ */ jsx(
|
|
1635
|
+
"button",
|
|
1120
1636
|
{
|
|
1637
|
+
type: "submit",
|
|
1121
1638
|
style: {
|
|
1122
|
-
|
|
1639
|
+
gridColumn: "1 / -1",
|
|
1640
|
+
justifySelf: "start",
|
|
1641
|
+
border: "none",
|
|
1642
|
+
cursor: "pointer",
|
|
1643
|
+
padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
|
|
1123
1644
|
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1645
|
+
background: ctx.brand.palette.primary,
|
|
1646
|
+
color: AI_TREE_TOKENS.textPrimaryForeground,
|
|
1647
|
+
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
1648
|
+
},
|
|
1649
|
+
children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
1650
|
+
},
|
|
1651
|
+
i
|
|
1652
|
+
);
|
|
1653
|
+
})
|
|
1129
1654
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1132
1657
|
case "schedule-widget":
|
|
1133
1658
|
return /* @__PURE__ */ jsx(
|
|
1134
1659
|
"div",
|
|
@@ -1154,11 +1679,14 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1154
1679
|
return null;
|
|
1155
1680
|
}
|
|
1156
1681
|
const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
|
|
1682
|
+
const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
|
|
1683
|
+
const blockBrand = band?.brand ?? resolvedBrand;
|
|
1157
1684
|
const ctx = {
|
|
1158
|
-
brand:
|
|
1685
|
+
brand: blockBrand,
|
|
1159
1686
|
resolveMedia: resolveMedia ?? (() => null),
|
|
1160
|
-
cardSurface:
|
|
1161
|
-
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null
|
|
1687
|
+
cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
|
|
1688
|
+
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
|
|
1689
|
+
...band ? { buttonLabel: band.buttonLabel } : {}
|
|
1162
1690
|
};
|
|
1163
1691
|
const settings = tree.settings ?? {};
|
|
1164
1692
|
const pad = AI_TREE_TOKENS.sectionPadding[settings.spacing ?? "balanced"];
|
|
@@ -1166,27 +1694,41 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1166
1694
|
const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
|
|
1167
1695
|
const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
|
|
1168
1696
|
const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
|
|
1697
|
+
const toneBackground = (() => {
|
|
1698
|
+
const { dark, primary, light } = resolvedBrand.palette;
|
|
1699
|
+
switch (settings.sectionBackground) {
|
|
1700
|
+
case "surface":
|
|
1701
|
+
return `color-mix(in srgb, ${light} 94%, ${dark})`;
|
|
1702
|
+
case "accent":
|
|
1703
|
+
return primary;
|
|
1704
|
+
case "accent-soft":
|
|
1705
|
+
return `color-mix(in srgb, ${primary} 12%, ${light})`;
|
|
1706
|
+
default:
|
|
1707
|
+
return void 0;
|
|
1708
|
+
}
|
|
1709
|
+
})();
|
|
1710
|
+
const distributed = !isOverlay && settings.textDistribution;
|
|
1169
1711
|
return /* @__PURE__ */ jsxs(
|
|
1170
1712
|
"section",
|
|
1171
1713
|
{
|
|
1172
1714
|
"data-ai-section": tree.tag ?? "",
|
|
1173
1715
|
...bgAttrs,
|
|
1174
|
-
"data-ai-responsive": "",
|
|
1175
1716
|
style: {
|
|
1176
1717
|
position: "relative",
|
|
1177
1718
|
padding: `${pad}px 0`,
|
|
1178
|
-
background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
|
|
1719
|
+
background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
|
|
1179
1720
|
backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
|
|
1180
1721
|
backgroundSize: "cover",
|
|
1181
|
-
backgroundPosition: "center"
|
|
1722
|
+
backgroundPosition: "center",
|
|
1723
|
+
color: settings.sectionBackground === "accent" ? blockBrand.palette.dark : void 0
|
|
1182
1724
|
},
|
|
1183
1725
|
children: [
|
|
1184
|
-
/* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
|
|
1185
1726
|
isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
|
|
1727
|
+
/* @__PURE__ */ jsx("style", { children: AI_MOBILE_CSS }),
|
|
1186
1728
|
/* @__PURE__ */ jsx(
|
|
1187
1729
|
"div",
|
|
1188
1730
|
{
|
|
1189
|
-
"data-ai-
|
|
1731
|
+
"data-ai-container": "",
|
|
1190
1732
|
style: {
|
|
1191
1733
|
position: "relative",
|
|
1192
1734
|
maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
|
|
@@ -1197,15 +1739,29 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
|
|
|
1197
1739
|
children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
|
|
1198
1740
|
"div",
|
|
1199
1741
|
{
|
|
1200
|
-
"data-ai-
|
|
1742
|
+
"data-ai-row": "",
|
|
1201
1743
|
style: {
|
|
1202
1744
|
display: "grid",
|
|
1203
1745
|
gridTemplateColumns: "repeat(12, 1fr)",
|
|
1204
1746
|
gap: AI_TREE_TOKENS.spacing6,
|
|
1205
|
-
alignItems: settings.verticalPosition === "top" ? "start" : "center",
|
|
1747
|
+
alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
|
|
1206
1748
|
marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
|
|
1207
1749
|
},
|
|
1208
|
-
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
|
|
1750
|
+
children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ jsx(
|
|
1751
|
+
"div",
|
|
1752
|
+
{
|
|
1753
|
+
"data-ai-cell": "",
|
|
1754
|
+
style: {
|
|
1755
|
+
gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
|
|
1756
|
+
minWidth: 0,
|
|
1757
|
+
// space-between: each column becomes a flex column whose content spreads over
|
|
1758
|
+
// the full row height instead of clumping at the top.
|
|
1759
|
+
...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
|
|
1760
|
+
},
|
|
1761
|
+
children: renderNode(block, ctx, `r${r2}.b${b}`)
|
|
1762
|
+
},
|
|
1763
|
+
b
|
|
1764
|
+
))
|
|
1209
1765
|
},
|
|
1210
1766
|
r2
|
|
1211
1767
|
))
|
|
@@ -1221,17 +1777,36 @@ import { jsx as jsx2 } from "react/jsx-runtime";
|
|
|
1221
1777
|
var CONTAINER_ATTR = "data-ohw-ai-generated";
|
|
1222
1778
|
var REPLACED_ATTR = "data-ohw-ai-replaced-by";
|
|
1223
1779
|
var REMOVED_ATTR = "data-ohw-ai-removed";
|
|
1780
|
+
var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
|
|
1781
|
+
var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
1782
|
+
function readRootVar(name) {
|
|
1783
|
+
if (typeof document === "undefined") return "";
|
|
1784
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
1785
|
+
}
|
|
1786
|
+
function deriveBrandOverride() {
|
|
1787
|
+
const dark = readRootVar("--ohw-brand-dark");
|
|
1788
|
+
const primary = readRootVar("--ohw-brand-primary");
|
|
1789
|
+
const light = readRootVar("--ohw-brand-light");
|
|
1790
|
+
if (!dark || !primary || !light) return null;
|
|
1791
|
+
const accent = readRootVar("--ohw-brand-accent");
|
|
1792
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1793
|
+
const body = readRootVar("--font-body");
|
|
1794
|
+
return {
|
|
1795
|
+
palette: { dark, primary, accent: accent || dark, light },
|
|
1796
|
+
fonts: {
|
|
1797
|
+
heading: heading || AI_DEFAULT_BRAND.fonts.heading,
|
|
1798
|
+
body: body || AI_DEFAULT_BRAND.fonts.body
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1224
1802
|
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");
|
|
1803
|
+
const dark = readRootVar("--color-dark");
|
|
1804
|
+
const primary = readRootVar("--color-primary");
|
|
1805
|
+
const light = readRootVar("--color-light");
|
|
1231
1806
|
if (!dark || !primary || !light) return null;
|
|
1232
|
-
const accent =
|
|
1233
|
-
const heading =
|
|
1234
|
-
const body =
|
|
1807
|
+
const accent = readRootVar("--color-accent");
|
|
1808
|
+
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1809
|
+
const body = readRootVar("--font-body");
|
|
1235
1810
|
return {
|
|
1236
1811
|
palette: { dark, primary, accent: accent || dark, light },
|
|
1237
1812
|
fonts: {
|
|
@@ -1303,6 +1878,24 @@ function syncRemovedSections(state) {
|
|
|
1303
1878
|
}
|
|
1304
1879
|
}
|
|
1305
1880
|
}
|
|
1881
|
+
function syncTemplateHidden(state, pageHasSections) {
|
|
1882
|
+
const hide = state.hideTemplate === true && pageHasSections;
|
|
1883
|
+
for (const el of Array.from(document.querySelectorAll(`[${TEMPLATE_HIDDEN_ATTR}]`))) {
|
|
1884
|
+
if (!hide) {
|
|
1885
|
+
el.style.removeProperty("display");
|
|
1886
|
+
el.removeAttribute(TEMPLATE_HIDDEN_ATTR);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (!hide) return;
|
|
1890
|
+
for (const el of Array.from(document.querySelectorAll("[data-ohw-section]"))) {
|
|
1891
|
+
if (el.hasAttribute(CONTAINER_ATTR)) continue;
|
|
1892
|
+
if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
|
|
1893
|
+
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
1894
|
+
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
|
|
1895
|
+
el.style.display = "none";
|
|
1896
|
+
el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1306
1899
|
function syncReplacedOriginals(state) {
|
|
1307
1900
|
for (const el of document.querySelectorAll(`[${REPLACED_ATTR}]`)) {
|
|
1308
1901
|
const byId = el.getAttribute(REPLACED_ATTR) ?? "";
|
|
@@ -1321,10 +1914,63 @@ function syncReplacedOriginals(state) {
|
|
|
1321
1914
|
}
|
|
1322
1915
|
}
|
|
1323
1916
|
}
|
|
1917
|
+
var sectionOrderIndex = /* @__PURE__ */ new Map();
|
|
1918
|
+
function setAiSectionOrder(raw, currentPath) {
|
|
1919
|
+
const next = /* @__PURE__ */ new Map();
|
|
1920
|
+
if (raw) {
|
|
1921
|
+
try {
|
|
1922
|
+
const entries = JSON.parse(raw);
|
|
1923
|
+
for (const entry of entries) {
|
|
1924
|
+
if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
|
|
1925
|
+
}
|
|
1926
|
+
} catch {
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
sectionOrderIndex = next;
|
|
1930
|
+
}
|
|
1931
|
+
function applyExplicitOrder(entries) {
|
|
1932
|
+
if (sectionOrderIndex.size === 0) return entries;
|
|
1933
|
+
return entries.map((entry, index) => ({ entry, index, order: sectionOrderIndex.get(entry.id) })).sort((a, b) => {
|
|
1934
|
+
if (a.order === void 0 && b.order === void 0) return a.index - b.index;
|
|
1935
|
+
if (a.order === void 0) return 1;
|
|
1936
|
+
if (b.order === void 0) return -1;
|
|
1937
|
+
return a.order - b.order;
|
|
1938
|
+
}).map((item) => item.entry);
|
|
1939
|
+
}
|
|
1940
|
+
function orderByChain(sections) {
|
|
1941
|
+
const ids = new Set(sections.map((entry) => entry.id));
|
|
1942
|
+
const after = /* @__PURE__ */ new Map();
|
|
1943
|
+
const roots = [];
|
|
1944
|
+
for (const entry of sections) {
|
|
1945
|
+
const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
|
|
1946
|
+
if (anchor && ids.has(anchor)) {
|
|
1947
|
+
const bucket = after.get(anchor);
|
|
1948
|
+
if (bucket) bucket.push(entry);
|
|
1949
|
+
else after.set(anchor, [entry]);
|
|
1950
|
+
} else {
|
|
1951
|
+
roots.push(entry);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
const out = [];
|
|
1955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1956
|
+
const visit = (entry) => {
|
|
1957
|
+
if (seen.has(entry.id)) return;
|
|
1958
|
+
seen.add(entry.id);
|
|
1959
|
+
out.push(entry);
|
|
1960
|
+
for (const child of after.get(entry.id) ?? []) visit(child);
|
|
1961
|
+
};
|
|
1962
|
+
for (const root of roots) visit(root);
|
|
1963
|
+
return out.length === sections.length ? out : sections;
|
|
1964
|
+
}
|
|
1324
1965
|
function applyAiSectionsToDom(state, options) {
|
|
1325
1966
|
if (typeof document === "undefined") return;
|
|
1967
|
+
const brandOverride = deriveBrandOverride();
|
|
1326
1968
|
const templateBrand = deriveTemplateBrand();
|
|
1327
|
-
const
|
|
1969
|
+
const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
|
|
1970
|
+
const pagePath = window.location.pathname;
|
|
1971
|
+
const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
|
|
1972
|
+
const activeIds = new Set(pageSections.map((entry) => entry.id));
|
|
1973
|
+
const ordered = state.hideTemplate === true ? applyExplicitOrder(orderByChain(pageSections)) : pageSections;
|
|
1328
1974
|
for (const [id, section] of mounted) {
|
|
1329
1975
|
if (!activeIds.has(id)) {
|
|
1330
1976
|
section.root.unmount();
|
|
@@ -1332,8 +1978,8 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1332
1978
|
mounted.delete(id);
|
|
1333
1979
|
}
|
|
1334
1980
|
}
|
|
1335
|
-
for (const entry of
|
|
1336
|
-
const serialized = JSON.stringify(entry);
|
|
1981
|
+
for (const entry of ordered) {
|
|
1982
|
+
const serialized = JSON.stringify(entry) + brandKey;
|
|
1337
1983
|
const existing = mounted.get(entry.id);
|
|
1338
1984
|
if (existing && existing.serialized === serialized && existing.container.isConnected) {
|
|
1339
1985
|
continue;
|
|
@@ -1358,7 +2004,7 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1358
2004
|
AiTreeRenderer,
|
|
1359
2005
|
{
|
|
1360
2006
|
tree: entry.tree,
|
|
1361
|
-
brand: entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2007
|
+
brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
1362
2008
|
resolveMedia,
|
|
1363
2009
|
editKeyPrefix: `ai.${entry.id}`
|
|
1364
2010
|
}
|
|
@@ -1367,8 +2013,20 @@ function applyAiSectionsToDom(state, options) {
|
|
|
1367
2013
|
});
|
|
1368
2014
|
mounted.set(entry.id, { root, container, serialized });
|
|
1369
2015
|
}
|
|
2016
|
+
if (state.hideTemplate === true) {
|
|
2017
|
+
let prev = null;
|
|
2018
|
+
for (const entry of ordered) {
|
|
2019
|
+
const el = mounted.get(entry.id)?.container;
|
|
2020
|
+
if (!el) continue;
|
|
2021
|
+
if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
|
|
2022
|
+
prev.insertAdjacentElement("afterend", el);
|
|
2023
|
+
}
|
|
2024
|
+
prev = el;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1370
2027
|
syncReplacedOriginals(state);
|
|
1371
2028
|
syncRemovedSections(state);
|
|
2029
|
+
syncTemplateHidden(state, pageSections.length > 0);
|
|
1372
2030
|
}
|
|
1373
2031
|
|
|
1374
2032
|
// src/useLinkHrefGuardian.ts
|
|
@@ -7067,13 +7725,17 @@ function MediaOverlay({
|
|
|
7067
7725
|
hover,
|
|
7068
7726
|
isUploading,
|
|
7069
7727
|
fadingOut = false,
|
|
7728
|
+
selected = false,
|
|
7729
|
+
hovered = false,
|
|
7070
7730
|
onFadeOutComplete,
|
|
7071
7731
|
onReplace,
|
|
7732
|
+
onSelect,
|
|
7072
7733
|
onVideoSettingsChange
|
|
7073
7734
|
}) {
|
|
7074
7735
|
const { rect } = hover;
|
|
7075
7736
|
const skeletonRef = React8.useRef(null);
|
|
7076
7737
|
const isVideo = hover.elementType === "video";
|
|
7738
|
+
const showChrome = !selected || hovered;
|
|
7077
7739
|
const autoplay = hover.videoAutoplay ?? true;
|
|
7078
7740
|
const muted = hover.videoMuted ?? true;
|
|
7079
7741
|
const probeRef = React8.useRef(null);
|
|
@@ -7120,7 +7782,7 @@ function MediaOverlay({
|
|
|
7120
7782
|
}
|
|
7121
7783
|
);
|
|
7122
7784
|
}
|
|
7123
|
-
const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs7(
|
|
7785
|
+
const settingsBar = isVideo && !hover.isDragOver && showChrome ? /* @__PURE__ */ jsxs7(
|
|
7124
7786
|
"div",
|
|
7125
7787
|
{
|
|
7126
7788
|
"data-ohw-bridge": "",
|
|
@@ -7190,10 +7852,12 @@ function MediaOverlay({
|
|
|
7190
7852
|
// in-document, pointer-events does it natively. The button below opts back in, so
|
|
7191
7853
|
// Replace still works.
|
|
7192
7854
|
pointerEvents: hover.hasTextOverlap ? "none" : "auto",
|
|
7193
|
-
|
|
7194
|
-
|
|
7855
|
+
// Selected: a firm component ring with no wash, so the image reads as chosen rather
|
|
7856
|
+
// than hovered. Hover keeps the existing tinted preview.
|
|
7857
|
+
boxShadow: selected ? "inset 0 0 0 2px var(--color-primary)" : "inset 0 0 0 1.5px var(--color-primary)",
|
|
7858
|
+
background: selected ? "transparent" : "color-mix(in srgb, var(--color-primary) 20%, transparent)"
|
|
7195
7859
|
},
|
|
7196
|
-
onClick: () => onReplace(hover.key),
|
|
7860
|
+
onClick: () => onSelect && !selected ? onSelect(hover.key) : onReplace(hover.key),
|
|
7197
7861
|
children: [
|
|
7198
7862
|
/* @__PURE__ */ jsxs7(
|
|
7199
7863
|
Button,
|
|
@@ -7247,7 +7911,7 @@ function MediaOverlay({
|
|
|
7247
7911
|
},
|
|
7248
7912
|
children: [
|
|
7249
7913
|
isVideo ? /* @__PURE__ */ jsx15(Film, { size: 14 }) : /* @__PURE__ */ jsx15(ImageIcon, { size: 14 }),
|
|
7250
|
-
replaceMode === "full" ? isVideo ? "Replace video" : "Replace image" : null
|
|
7914
|
+
replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
|
|
7251
7915
|
]
|
|
7252
7916
|
}
|
|
7253
7917
|
)
|
|
@@ -7331,6 +7995,8 @@ function parseSectionsFromRoot(root) {
|
|
|
7331
7995
|
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
7332
7996
|
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
7333
7997
|
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
7998
|
+
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
7999
|
+
continue;
|
|
7334
8000
|
seen.add(id);
|
|
7335
8001
|
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
7336
8002
|
sections.push({ id, label });
|
|
@@ -7616,6 +8282,7 @@ function AiSectionOverlay({
|
|
|
7616
8282
|
}) {
|
|
7617
8283
|
const [selectedId, setSelectedId] = useState5(null);
|
|
7618
8284
|
const [reviewId, setReviewId] = useState5(null);
|
|
8285
|
+
const [reviewButtonsHidden, setReviewButtonsHidden] = useState5(false);
|
|
7619
8286
|
const reviewIdRef = useRef4(null);
|
|
7620
8287
|
reviewIdRef.current = reviewId;
|
|
7621
8288
|
const selectedIdRef = useRef4(null);
|
|
@@ -7677,6 +8344,7 @@ function AiSectionOverlay({
|
|
|
7677
8344
|
}
|
|
7678
8345
|
const found = readRect(sectionId) != null;
|
|
7679
8346
|
setReviewId(found ? sectionId : null);
|
|
8347
|
+
setReviewButtonsHidden(e.data.hideButtons === true);
|
|
7680
8348
|
postToParent2({ type: "ow:ai-review-started", sectionId, found });
|
|
7681
8349
|
if (found) {
|
|
7682
8350
|
document.querySelector(`[data-ohw-instance="${CSS.escape(sectionId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
@@ -7804,13 +8472,16 @@ function AiSectionOverlay({
|
|
|
7804
8472
|
border: `2px solid ${PRIMARY2}`,
|
|
7805
8473
|
borderRadius: edgeAwareRadius(reviewRect),
|
|
7806
8474
|
zIndex: 2147483200,
|
|
7807
|
-
// The veil itself: swallows clicks so the section stays locked until decided.
|
|
8475
|
+
// The veil itself: swallows clicks so the section stays locked until decided. This
|
|
8476
|
+
// stopPropagation only guards the bubble phase; the bridge's capture-phase click
|
|
8477
|
+
// handlers additionally bail on `[data-ohw-ai-review]`, without which a click on
|
|
8478
|
+
// Accept/Discard resolves to the media beneath and opens the file picker.
|
|
7808
8479
|
background: "rgba(8, 133, 254, 0.04)",
|
|
7809
8480
|
pointerEvents: "auto",
|
|
7810
8481
|
cursor: "default"
|
|
7811
8482
|
},
|
|
7812
8483
|
onClick: (e) => e.stopPropagation(),
|
|
7813
|
-
children: /* @__PURE__ */ jsxs9(
|
|
8484
|
+
children: !reviewButtonsHidden && /* @__PURE__ */ jsxs9(
|
|
7814
8485
|
"div",
|
|
7815
8486
|
{
|
|
7816
8487
|
style: {
|
|
@@ -10377,8 +11048,13 @@ function referenceBox(slot) {
|
|
|
10377
11048
|
const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find(
|
|
10378
11049
|
(el) => el !== slot && !el.hasAttribute("data-ohw-social-icon-placeholder")
|
|
10379
11050
|
) : null;
|
|
10380
|
-
|
|
10381
|
-
|
|
11051
|
+
if (neighbour) {
|
|
11052
|
+
const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
|
|
11053
|
+
if (box2?.width && box2.height) return box2;
|
|
11054
|
+
}
|
|
11055
|
+
const own = slot.getBoundingClientRect();
|
|
11056
|
+
if (own.width && own.height) return own;
|
|
11057
|
+
const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
|
|
10382
11058
|
return box?.width && box.height ? box : null;
|
|
10383
11059
|
}
|
|
10384
11060
|
function iconMarkupSizedFor(slot, markup) {
|
|
@@ -13253,6 +13929,7 @@ function useSectionDrag({
|
|
|
13253
13929
|
}
|
|
13254
13930
|
const orderJson = JSON.stringify(entries);
|
|
13255
13931
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
13932
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
13256
13933
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
13257
13934
|
applyPersistedOrder(entries);
|
|
13258
13935
|
clearSectionDragVisuals();
|
|
@@ -15372,6 +16049,70 @@ function OhhwellsBridge() {
|
|
|
15372
16049
|
const hoveredImageHasTextOverlapRef = useRef10(false);
|
|
15373
16050
|
const dragOverElRef = useRef10(null);
|
|
15374
16051
|
const [mediaHover, setMediaHover] = useState13(null);
|
|
16052
|
+
const [selectedMedia, setSelectedMedia] = useState13(null);
|
|
16053
|
+
const selectedMediaElRef = useRef10(null);
|
|
16054
|
+
const clearMediaSelection = useCallback8(() => {
|
|
16055
|
+
const prev = selectedMediaElRef.current;
|
|
16056
|
+
selectedMediaElRef.current = null;
|
|
16057
|
+
setSelectedMedia(null);
|
|
16058
|
+
const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
|
|
16059
|
+
if (sectionEl) {
|
|
16060
|
+
postToParentRef.current({
|
|
16061
|
+
type: "ow:section-selected",
|
|
16062
|
+
sectionId: sectionEl.dataset.ohwSection ?? null,
|
|
16063
|
+
sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
|
|
16064
|
+
key: null
|
|
16065
|
+
});
|
|
16066
|
+
}
|
|
16067
|
+
}, []);
|
|
16068
|
+
const clearMediaSelectionRef = useRef10(clearMediaSelection);
|
|
16069
|
+
clearMediaSelectionRef.current = clearMediaSelection;
|
|
16070
|
+
const selectMediaElement = useCallback8((el) => {
|
|
16071
|
+
const r2 = el.getBoundingClientRect();
|
|
16072
|
+
const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
|
|
16073
|
+
selectedMediaElRef.current = el;
|
|
16074
|
+
setSelectedMedia({
|
|
16075
|
+
key: el.dataset.ohwKey ?? "",
|
|
16076
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
16077
|
+
elementType: el.dataset.ohwEditable ?? "image",
|
|
16078
|
+
hasTextOverlap: false,
|
|
16079
|
+
isDragOver: false,
|
|
16080
|
+
...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
|
|
16081
|
+
});
|
|
16082
|
+
const sectionEl = el.closest("[data-ohw-section]");
|
|
16083
|
+
aiSectionApiRef.current?.selectFromElement(el, { report: false });
|
|
16084
|
+
postToParentRef.current({
|
|
16085
|
+
type: "ow:section-selected",
|
|
16086
|
+
sectionId: sectionEl?.dataset.ohwSection ?? null,
|
|
16087
|
+
sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
|
|
16088
|
+
key: el.dataset.ohwKey ?? null,
|
|
16089
|
+
// Display name for the pill — the raw key prettifies into fragments ("Img"); the
|
|
16090
|
+
// bridge knows what the node IS, so it names it.
|
|
16091
|
+
keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
|
|
16092
|
+
});
|
|
16093
|
+
}, []);
|
|
16094
|
+
const selectMediaElementRef = useRef10(selectMediaElement);
|
|
16095
|
+
selectMediaElementRef.current = selectMediaElement;
|
|
16096
|
+
useEffect13(() => {
|
|
16097
|
+
if (!selectedMedia) return;
|
|
16098
|
+
const update = () => {
|
|
16099
|
+
const el = selectedMediaElRef.current;
|
|
16100
|
+
if (!el || !el.isConnected) {
|
|
16101
|
+
clearMediaSelection();
|
|
16102
|
+
return;
|
|
16103
|
+
}
|
|
16104
|
+
const r2 = el.getBoundingClientRect();
|
|
16105
|
+
setSelectedMedia(
|
|
16106
|
+
(prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
|
|
16107
|
+
);
|
|
16108
|
+
};
|
|
16109
|
+
window.addEventListener("scroll", update, true);
|
|
16110
|
+
window.addEventListener("resize", update);
|
|
16111
|
+
return () => {
|
|
16112
|
+
window.removeEventListener("scroll", update, true);
|
|
16113
|
+
window.removeEventListener("resize", update);
|
|
16114
|
+
};
|
|
16115
|
+
}, [selectedMedia !== null]);
|
|
15375
16116
|
const [carouselHover, setCarouselHover] = useState13(null);
|
|
15376
16117
|
const [uploadingRects, setUploadingRects] = useState13({});
|
|
15377
16118
|
const hoveredGapRef = useRef10(null);
|
|
@@ -15655,6 +16396,8 @@ function OhhwellsBridge() {
|
|
|
15655
16396
|
const addNavAfterAnchorRef = useRef10(null);
|
|
15656
16397
|
const editContentRef = useRef10({});
|
|
15657
16398
|
const aiSectionsRef = useRef10("");
|
|
16399
|
+
const brandKitRef = useRef10("");
|
|
16400
|
+
const stylesRef = useRef10("");
|
|
15658
16401
|
const pendingDeleteUndoRef = useRef10(null);
|
|
15659
16402
|
const [sitePages, setSitePages] = useState13([]);
|
|
15660
16403
|
const [sectionsByPath, setSectionsByPath] = useState13({});
|
|
@@ -16975,13 +17718,29 @@ function OhhwellsBridge() {
|
|
|
16975
17718
|
}
|
|
16976
17719
|
const applyContent = (content) => {
|
|
16977
17720
|
const imageLoads = [];
|
|
17721
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17722
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
17723
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17724
|
+
} else {
|
|
17725
|
+
brandKitRef.current = "";
|
|
17726
|
+
applyBrandToDom(null);
|
|
17727
|
+
}
|
|
16978
17728
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
16979
17729
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
17730
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
16980
17731
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
16981
17732
|
}
|
|
17733
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17734
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
17735
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17736
|
+
}
|
|
17737
|
+
applyBrandChrome(content);
|
|
16982
17738
|
for (const [key, val] of Object.entries(content)) {
|
|
16983
17739
|
if (key === "__ohw_sections") continue;
|
|
16984
17740
|
if (key === AI_SECTIONS_KEY) continue;
|
|
17741
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17742
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17743
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
16985
17744
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
16986
17745
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
16987
17746
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17169,8 +17928,25 @@ function OhhwellsBridge() {
|
|
|
17169
17928
|
initSectionInstancesFromContent(content, window.location.pathname);
|
|
17170
17929
|
observer?.disconnect();
|
|
17171
17930
|
try {
|
|
17931
|
+
applyBrandChrome(content);
|
|
17932
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
17933
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
17934
|
+
} else {
|
|
17935
|
+
applyBrandToDom(null);
|
|
17936
|
+
}
|
|
17937
|
+
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
17938
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
17939
|
+
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
17940
|
+
}
|
|
17941
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
17942
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
17943
|
+
}
|
|
17172
17944
|
for (const [key, val] of Object.entries(content)) {
|
|
17173
17945
|
if (key === "__ohw_sections") continue;
|
|
17946
|
+
if (key === AI_SECTIONS_KEY) continue;
|
|
17947
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
17948
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
17949
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
17174
17950
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
17175
17951
|
if (LOGO_IMAGE_KEYS.includes(key)) continue;
|
|
17176
17952
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -17313,9 +18089,21 @@ function OhhwellsBridge() {
|
|
|
17313
18089
|
}, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
|
|
17314
18090
|
useEffect13(() => {
|
|
17315
18091
|
if (!isEditMode) return;
|
|
18092
|
+
let lastPosted = 0;
|
|
17316
18093
|
const measure = () => {
|
|
17317
18094
|
const h = document.body.scrollHeight;
|
|
17318
|
-
if (h > 50
|
|
18095
|
+
if (h > 50 && Math.abs(h - lastPosted) > 1) {
|
|
18096
|
+
lastPosted = h;
|
|
18097
|
+
postToParent2({ type: "ow:height", height: h });
|
|
18098
|
+
}
|
|
18099
|
+
};
|
|
18100
|
+
let raf = null;
|
|
18101
|
+
const schedule = () => {
|
|
18102
|
+
if (raf != null) return;
|
|
18103
|
+
raf = requestAnimationFrame(() => {
|
|
18104
|
+
raf = null;
|
|
18105
|
+
measure();
|
|
18106
|
+
});
|
|
17319
18107
|
};
|
|
17320
18108
|
const t1 = setTimeout(measure, 50);
|
|
17321
18109
|
const t2 = setTimeout(measure, 500);
|
|
@@ -17335,6 +18123,7 @@ function OhhwellsBridge() {
|
|
|
17335
18123
|
return () => {
|
|
17336
18124
|
clearTimeout(t1);
|
|
17337
18125
|
clearTimeout(t2);
|
|
18126
|
+
if (raf != null) cancelAnimationFrame(raf);
|
|
17338
18127
|
clearResizeTimers();
|
|
17339
18128
|
window.removeEventListener("resize", handleResize);
|
|
17340
18129
|
};
|
|
@@ -17577,10 +18366,14 @@ function OhhwellsBridge() {
|
|
|
17577
18366
|
return;
|
|
17578
18367
|
}
|
|
17579
18368
|
const target = e.target;
|
|
18369
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17580
18370
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17581
18371
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17582
18372
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
17583
18373
|
if (isInsideLinkEditor(target)) return;
|
|
18374
|
+
if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
|
|
18375
|
+
clearMediaSelectionRef.current();
|
|
18376
|
+
}
|
|
17584
18377
|
if (isInsideFloatingPanel(target)) return;
|
|
17585
18378
|
if (target.closest("[data-ohw-form-toolbar]")) return;
|
|
17586
18379
|
if (target.closest(
|
|
@@ -17750,8 +18543,11 @@ function OhhwellsBridge() {
|
|
|
17750
18543
|
if (isMediaEditable(editable) && !buttonOnMedia) {
|
|
17751
18544
|
e.preventDefault();
|
|
17752
18545
|
e.stopPropagation();
|
|
17753
|
-
|
|
17754
|
-
|
|
18546
|
+
if (selectedMediaElRef.current === editable) {
|
|
18547
|
+
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
|
|
18548
|
+
} else {
|
|
18549
|
+
selectMediaElementRef.current(editable);
|
|
18550
|
+
}
|
|
17755
18551
|
return;
|
|
17756
18552
|
}
|
|
17757
18553
|
const socialItem = getSocialItem(editable);
|
|
@@ -17892,6 +18688,7 @@ function OhhwellsBridge() {
|
|
|
17892
18688
|
};
|
|
17893
18689
|
const handleDblClick = (e) => {
|
|
17894
18690
|
const target = e.target;
|
|
18691
|
+
if (target.closest("[data-ohw-ai-review]")) return;
|
|
17895
18692
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
17896
18693
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
17897
18694
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
@@ -18692,7 +19489,9 @@ function OhhwellsBridge() {
|
|
|
18692
19489
|
return;
|
|
18693
19490
|
}
|
|
18694
19491
|
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
18695
|
-
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).
|
|
19492
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).filter(
|
|
19493
|
+
(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
|
|
19494
|
+
).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
18696
19495
|
const ZONE = 20;
|
|
18697
19496
|
for (let i = 0; i < sections.length; i++) {
|
|
18698
19497
|
const a = sections[i];
|
|
@@ -19024,10 +19823,23 @@ function OhhwellsBridge() {
|
|
|
19024
19823
|
if (e.data?.type !== "ow:hydrate") return;
|
|
19025
19824
|
const content = e.data.content;
|
|
19026
19825
|
if (!content) return;
|
|
19826
|
+
if (typeof content[BRAND_KIT_KEY] === "string") {
|
|
19827
|
+
brandKitRef.current = content[BRAND_KIT_KEY];
|
|
19828
|
+
applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
|
|
19829
|
+
} else {
|
|
19830
|
+
brandKitRef.current = "";
|
|
19831
|
+
applyBrandToDom(null);
|
|
19832
|
+
}
|
|
19027
19833
|
if (typeof content[AI_SECTIONS_KEY] === "string") {
|
|
19028
19834
|
aiSectionsRef.current = content[AI_SECTIONS_KEY];
|
|
19835
|
+
setAiSectionOrder(content[SECTION_ORDER_KEY], window.location.pathname);
|
|
19029
19836
|
applyAiSectionsToDom(parseAiSectionsState(content[AI_SECTIONS_KEY]));
|
|
19030
19837
|
}
|
|
19838
|
+
if (typeof content[STYLE_STORE_KEY] === "string") {
|
|
19839
|
+
stylesRef.current = content[STYLE_STORE_KEY];
|
|
19840
|
+
applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
|
|
19841
|
+
}
|
|
19842
|
+
applyBrandChrome(content);
|
|
19031
19843
|
let sectionsJson = null;
|
|
19032
19844
|
for (const [key, val] of Object.entries(content)) {
|
|
19033
19845
|
if (key === "__ohw_sections") {
|
|
@@ -19035,6 +19847,9 @@ function OhhwellsBridge() {
|
|
|
19035
19847
|
continue;
|
|
19036
19848
|
}
|
|
19037
19849
|
if (key === AI_SECTIONS_KEY) continue;
|
|
19850
|
+
if (key === BRAND_KIT_KEY) continue;
|
|
19851
|
+
if (key === STYLE_STORE_KEY) continue;
|
|
19852
|
+
if (BRAND_CHROME_KEYS.has(key)) continue;
|
|
19038
19853
|
if (key === LOGO_PLACEHOLDER_KEY) continue;
|
|
19039
19854
|
if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
|
|
19040
19855
|
if (applyVideoSettingNode(key, val)) continue;
|
|
@@ -19050,6 +19865,8 @@ function OhhwellsBridge() {
|
|
|
19050
19865
|
if (video && video.src !== val) applyVideoSrc(video, val);
|
|
19051
19866
|
} else if (el.dataset.ohwEditable === "link") {
|
|
19052
19867
|
applyLinkHref(el, val);
|
|
19868
|
+
} else if (el.dataset.ohwEditable === "icon") {
|
|
19869
|
+
applyIconMarkup(el, val);
|
|
19053
19870
|
} else if (isIconMarkupValue(val)) {
|
|
19054
19871
|
} else {
|
|
19055
19872
|
el.innerHTML = val;
|
|
@@ -19134,12 +19951,21 @@ function OhhwellsBridge() {
|
|
|
19134
19951
|
nodes: collectEditableNodes(editContentRef.current)
|
|
19135
19952
|
});
|
|
19136
19953
|
};
|
|
19954
|
+
const clearInteractionChrome = () => {
|
|
19955
|
+
deactivateRef.current();
|
|
19956
|
+
deselectRef.current();
|
|
19957
|
+
clearMediaSelectionRef.current();
|
|
19958
|
+
};
|
|
19137
19959
|
const handleAiApplyTree = (e) => {
|
|
19138
19960
|
if (e.data?.type !== "ow:ai-apply-tree") return;
|
|
19139
19961
|
const payload = e.data.payload;
|
|
19140
19962
|
if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
|
|
19963
|
+
clearInteractionChrome();
|
|
19141
19964
|
const previous = aiSectionsRef.current;
|
|
19142
|
-
const nextState = applyTreeToState(parseAiSectionsState(previous),
|
|
19965
|
+
const nextState = applyTreeToState(parseAiSectionsState(previous), {
|
|
19966
|
+
...payload,
|
|
19967
|
+
path: payload.path ?? window.location.pathname
|
|
19968
|
+
});
|
|
19143
19969
|
const nextValue = serializeAiSectionsState(nextState);
|
|
19144
19970
|
aiSectionsRef.current = nextValue;
|
|
19145
19971
|
applyAiSectionsToDom(nextState);
|
|
@@ -19160,6 +19986,7 @@ function OhhwellsBridge() {
|
|
|
19160
19986
|
const exists = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
19161
19987
|
if (!exists) return;
|
|
19162
19988
|
if (isPageFrameSection(exists)) return;
|
|
19989
|
+
clearInteractionChrome();
|
|
19163
19990
|
const previous = aiSectionsRef.current;
|
|
19164
19991
|
const nextState = deleteSectionFromState(parseAiSectionsState(previous), sectionId);
|
|
19165
19992
|
const nextValue = serializeAiSectionsState(nextState);
|
|
@@ -19175,14 +20002,45 @@ function OhhwellsBridge() {
|
|
|
19175
20002
|
const handleAiSetSections = (e) => {
|
|
19176
20003
|
if (e.data?.type !== "ow:ai-set-sections") return;
|
|
19177
20004
|
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20005
|
+
clearInteractionChrome();
|
|
19178
20006
|
aiSectionsRef.current = value;
|
|
19179
20007
|
applyAiSectionsToDom(parseAiSectionsState(value));
|
|
20008
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
19180
20009
|
const restoredHeight = document.body.scrollHeight;
|
|
19181
20010
|
if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
|
|
19182
20011
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
|
|
19183
20012
|
postAiSectionsChanged();
|
|
19184
20013
|
};
|
|
19185
20014
|
window.addEventListener("message", handleAiSetSections);
|
|
20015
|
+
const handleAiSetBrand = (e) => {
|
|
20016
|
+
if (e.data?.type !== "ow:ai-set-brand") return;
|
|
20017
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20018
|
+
const previous = brandKitRef.current;
|
|
20019
|
+
brandKitRef.current = value;
|
|
20020
|
+
applyBrandToDom(parseBrandKit(value));
|
|
20021
|
+
if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
|
|
20022
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
20023
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
|
|
20024
|
+
postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
|
|
20025
|
+
};
|
|
20026
|
+
window.addEventListener("message", handleAiSetBrand);
|
|
20027
|
+
const handleAiSetStyles = (e) => {
|
|
20028
|
+
if (e.data?.type !== "ow:ai-set-styles") return;
|
|
20029
|
+
const value = typeof e.data.value === "string" ? e.data.value : "";
|
|
20030
|
+
const previous = stylesRef.current;
|
|
20031
|
+
stylesRef.current = value;
|
|
20032
|
+
applyStylesToDom(parseStyleStore(value));
|
|
20033
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
|
|
20034
|
+
postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
|
|
20035
|
+
};
|
|
20036
|
+
window.addEventListener("message", handleAiSetStyles);
|
|
20037
|
+
const handleGetBrand = (e) => {
|
|
20038
|
+
if (e.data?.type !== "ow:get-brand") return;
|
|
20039
|
+
const template = deriveTemplateBrand();
|
|
20040
|
+
const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
|
|
20041
|
+
postToParentRef.current({ type: "ow:brand-value", value });
|
|
20042
|
+
};
|
|
20043
|
+
window.addEventListener("message", handleGetBrand);
|
|
19186
20044
|
const handleMoveSection = (e) => {
|
|
19187
20045
|
if (e.data?.type !== "ow:move-section") return;
|
|
19188
20046
|
const instanceId = typeof e.data.instanceId === "string" ? e.data.instanceId : "";
|
|
@@ -19192,6 +20050,7 @@ function OhhwellsBridge() {
|
|
|
19192
20050
|
if (!entries) return;
|
|
19193
20051
|
const orderJson = JSON.stringify(entries);
|
|
19194
20052
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20053
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
19195
20054
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
19196
20055
|
window.dispatchEvent(new Event("resize"));
|
|
19197
20056
|
};
|
|
@@ -19255,6 +20114,7 @@ function OhhwellsBridge() {
|
|
|
19255
20114
|
}
|
|
19256
20115
|
deselectRef.current();
|
|
19257
20116
|
deactivateRef.current();
|
|
20117
|
+
clearMediaSelectionRef.current();
|
|
19258
20118
|
};
|
|
19259
20119
|
window.addEventListener("message", handleDeactivate);
|
|
19260
20120
|
const handleToastAction = (e) => {
|
|
@@ -19340,6 +20200,10 @@ function OhhwellsBridge() {
|
|
|
19340
20200
|
const handleKeyDown = (e) => {
|
|
19341
20201
|
if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
|
|
19342
20202
|
if (e.key === "Escape" && document.querySelector("[data-ohw-more-menu]")) return;
|
|
20203
|
+
if (e.key === "Escape" && selectedMediaElRef.current) {
|
|
20204
|
+
clearMediaSelectionRef.current();
|
|
20205
|
+
return;
|
|
20206
|
+
}
|
|
19343
20207
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
|
|
19344
20208
|
e.preventDefault();
|
|
19345
20209
|
selectAllTextInEditable(activeElRef.current);
|
|
@@ -19499,6 +20363,12 @@ function OhhwellsBridge() {
|
|
|
19499
20363
|
if (aiSectionsRef.current) {
|
|
19500
20364
|
nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
|
|
19501
20365
|
}
|
|
20366
|
+
if (stylesRef.current) {
|
|
20367
|
+
nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
|
|
20368
|
+
}
|
|
20369
|
+
if (brandKitRef.current) {
|
|
20370
|
+
nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
|
|
20371
|
+
}
|
|
19502
20372
|
document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
|
|
19503
20373
|
const formKey = formKeyOf(form);
|
|
19504
20374
|
if (!formKey) return;
|
|
@@ -19913,6 +20783,9 @@ function OhhwellsBridge() {
|
|
|
19913
20783
|
window.removeEventListener("message", handleAiApplyTree);
|
|
19914
20784
|
window.removeEventListener("message", handleAiDeleteSection);
|
|
19915
20785
|
window.removeEventListener("message", handleAiSetSections);
|
|
20786
|
+
window.removeEventListener("message", handleAiSetBrand);
|
|
20787
|
+
window.removeEventListener("message", handleAiSetStyles);
|
|
20788
|
+
window.removeEventListener("message", handleGetBrand);
|
|
19916
20789
|
window.removeEventListener("message", handleMoveSection);
|
|
19917
20790
|
window.removeEventListener("message", handlePanelDragging);
|
|
19918
20791
|
window.removeEventListener("message", handleDeleteSection);
|
|
@@ -20123,7 +20996,7 @@ function OhhwellsBridge() {
|
|
|
20123
20996
|
postToParent2({
|
|
20124
20997
|
type: "ow:ready",
|
|
20125
20998
|
version: "1",
|
|
20126
|
-
bridgeVersion: "0.1.
|
|
20999
|
+
bridgeVersion: "0.1.78",
|
|
20127
21000
|
path: pathname,
|
|
20128
21001
|
nodes: collectEditableNodes(editContentRef.current),
|
|
20129
21002
|
sections
|
|
@@ -20530,11 +21403,22 @@ function OhhwellsBridge() {
|
|
|
20530
21403
|
const showEditLink = toolbarShowEditLink;
|
|
20531
21404
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
20532
21405
|
linkPopoverOpenRef.current = linkPopover !== null;
|
|
21406
|
+
const handleMediaSelect = useCallback8((key) => {
|
|
21407
|
+
const el = hoveredImageRef.current?.dataset.ohwKey === key ? hoveredImageRef.current : Array.from(document.querySelectorAll(MEDIA_SELECTOR)).find(
|
|
21408
|
+
(m) => (m.dataset.ohwKey ?? "") === key
|
|
21409
|
+
) ?? null;
|
|
21410
|
+
if (!el) return;
|
|
21411
|
+
selectMediaElementRef.current(el);
|
|
21412
|
+
}, []);
|
|
20533
21413
|
const handleMediaReplace = useCallback8(
|
|
20534
21414
|
(key) => {
|
|
20535
|
-
postToParent2({
|
|
21415
|
+
postToParent2({
|
|
21416
|
+
type: "ow:image-pick",
|
|
21417
|
+
key,
|
|
21418
|
+
elementType: mediaHover?.elementType ?? selectedMedia?.elementType ?? "image"
|
|
21419
|
+
});
|
|
20536
21420
|
},
|
|
20537
|
-
[postToParent2, mediaHover?.elementType]
|
|
21421
|
+
[postToParent2, mediaHover?.elementType, selectedMedia?.elementType]
|
|
20538
21422
|
);
|
|
20539
21423
|
const handleEditCarousel = useCallback8(
|
|
20540
21424
|
(key) => {
|
|
@@ -20606,12 +21490,25 @@ function OhhwellsBridge() {
|
|
|
20606
21490
|
},
|
|
20607
21491
|
`uploading-${key}`
|
|
20608
21492
|
)),
|
|
20609
|
-
mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx33(
|
|
21493
|
+
mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx33(
|
|
20610
21494
|
MediaOverlay,
|
|
20611
21495
|
{
|
|
20612
21496
|
hover: mediaHover,
|
|
20613
21497
|
isUploading: false,
|
|
20614
21498
|
onReplace: handleMediaReplace,
|
|
21499
|
+
onSelect: handleMediaSelect,
|
|
21500
|
+
onVideoSettingsChange: handleVideoSettingsChange
|
|
21501
|
+
}
|
|
21502
|
+
),
|
|
21503
|
+
selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx33(
|
|
21504
|
+
MediaOverlay,
|
|
21505
|
+
{
|
|
21506
|
+
hover: selectedMedia,
|
|
21507
|
+
selected: true,
|
|
21508
|
+
hovered: mediaHover?.key === selectedMedia.key,
|
|
21509
|
+
isUploading: false,
|
|
21510
|
+
onReplace: handleMediaReplace,
|
|
21511
|
+
onSelect: handleMediaSelect,
|
|
20615
21512
|
onVideoSettingsChange: handleVideoSettingsChange
|
|
20616
21513
|
}
|
|
20617
21514
|
),
|