veo-sdk 0.6.1 → 0.6.3

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.
Files changed (35) hide show
  1. package/dist/builder-PPLIAUCM.mjs +5 -0
  2. package/dist/{builder-BKPMANUB.mjs.map → builder-PPLIAUCM.mjs.map} +1 -1
  3. package/dist/builder.cjs +641 -105
  4. package/dist/builder.cjs.map +1 -1
  5. package/dist/builder.d.cts +78 -8
  6. package/dist/builder.d.ts +78 -8
  7. package/dist/builder.mjs +4 -4
  8. package/dist/{chunk-GVPAJFNW.mjs → chunk-3JEAPZ6A.mjs} +3 -3
  9. package/dist/{chunk-GVPAJFNW.mjs.map → chunk-3JEAPZ6A.mjs.map} +1 -1
  10. package/dist/{chunk-VWF4UHUU.mjs → chunk-DU6I4ZU2.mjs} +284 -15
  11. package/dist/chunk-DU6I4ZU2.mjs.map +1 -0
  12. package/dist/{chunk-NGP7IJVE.mjs → chunk-SZCIXA5X.mjs} +357 -82
  13. package/dist/chunk-SZCIXA5X.mjs.map +1 -0
  14. package/dist/detect-theme-X4K3TLUL.mjs +446 -0
  15. package/dist/detect-theme-X4K3TLUL.mjs.map +1 -0
  16. package/dist/{guide-preview-DylsEhrg.d.cts → guide-preview-DEFhfA4o.d.cts} +115 -0
  17. package/dist/{guide-preview-DylsEhrg.d.ts → guide-preview-DEFhfA4o.d.ts} +115 -0
  18. package/dist/index.cjs +1515 -56
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +18 -1
  21. package/dist/index.d.ts +18 -1
  22. package/dist/index.mjs +442 -17
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/veo-builder.js +132 -25
  25. package/dist/veo-builder.js.map +1 -1
  26. package/dist/veo-guides.js +84 -12
  27. package/dist/veo-guides.js.map +1 -1
  28. package/dist/veo-theme.js +2 -0
  29. package/dist/veo-theme.js.map +1 -0
  30. package/dist/veo.js +2 -2
  31. package/dist/veo.js.map +1 -1
  32. package/package.json +2 -2
  33. package/dist/builder-BKPMANUB.mjs +0 -5
  34. package/dist/chunk-NGP7IJVE.mjs.map +0 -1
  35. package/dist/chunk-VWF4UHUU.mjs.map +0 -1
package/dist/builder.cjs CHANGED
@@ -283,6 +283,92 @@ function clearBuilderToken() {
283
283
  }
284
284
  }
285
285
 
286
+ // src/plugins/autocapture/constants.ts
287
+ var HASHED_CLASS_PATTERNS = [
288
+ /^css-[a-z0-9]{4,}$/i,
289
+ // emotion, styled-components nuevas
290
+ /^_[a-z0-9]+_[a-z0-9]{4,}_\d+$/,
291
+ // CSS Modules con hash
292
+ /^[a-z]{2,4}-[a-z0-9]{6,}$/i,
293
+ // Tailwind JIT (a veces)
294
+ /^[a-f0-9]{6,}$/i,
295
+ // hashes hex puros
296
+ /^sc-[a-z0-9]+$/i
297
+ // styled-components legacy
298
+ ];
299
+
300
+ // src/plugins/autocapture/selector-builder.ts
301
+ function isHashedClass(className) {
302
+ return HASHED_CLASS_PATTERNS.some((pattern) => pattern.test(className));
303
+ }
304
+ function filterHumanClasses(element) {
305
+ return Array.from(element.classList).filter((c) => !isHashedClass(c) && c.length < 64);
306
+ }
307
+ var STABLE_ATTRIBUTES = [
308
+ "data-testid",
309
+ "data-test",
310
+ "data-cy",
311
+ "name",
312
+ "aria-label",
313
+ "placeholder",
314
+ "alt",
315
+ "title",
316
+ // `href` identifica muy bien a los links (`<a>`), un target frecuente de
317
+ // autocapture; va después de los atributos semánticos pero antes de los
318
+ // genéricos. Valores largos (URLs absolutas con query) se descartan por
319
+ // MAX_ATTR_VALUE_LENGTH.
320
+ "href",
321
+ "role",
322
+ "type"
323
+ ];
324
+ var MAX_ATTR_VALUE_LENGTH = 100;
325
+ function cssEscape(value) {
326
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
327
+ return CSS.escape(value);
328
+ }
329
+ return value.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
330
+ }
331
+ function escapeAttrValue(value) {
332
+ return value.replace(/[\\"]/g, "\\$&");
333
+ }
334
+ function stableAttributeSelector(element, tag) {
335
+ for (const attr of STABLE_ATTRIBUTES) {
336
+ const value = element.getAttribute(attr);
337
+ if (value && value.length > 0 && value.length <= MAX_ATTR_VALUE_LENGTH) {
338
+ return `${tag}[${attr}="${escapeAttrValue(value)}"]`;
339
+ }
340
+ }
341
+ return null;
342
+ }
343
+ function buildElementSelector(element) {
344
+ const tag = element.tagName.toLowerCase();
345
+ const veoTag = element.getAttribute("data-veo-tag");
346
+ if (veoTag) {
347
+ return `${tag}[data-veo-tag="${escapeAttrValue(veoTag)}"]`;
348
+ }
349
+ if (element.id) {
350
+ const classes2 = filterHumanClasses(element);
351
+ const classPart2 = classes2.length > 0 ? `.${classes2.map(cssEscape).join(".")}` : "";
352
+ return `${tag}#${cssEscape(element.id)}${classPart2}`;
353
+ }
354
+ const attrSelector = stableAttributeSelector(element, tag);
355
+ if (attrSelector) return attrSelector;
356
+ const classes = filterHumanClasses(element);
357
+ const classPart = classes.length > 0 ? `.${classes.map(cssEscape).join(".")}` : "";
358
+ return `${tag}${classPart}`;
359
+ }
360
+ function buildSelectorPath(element, maxAncestors = 5) {
361
+ const parts = [];
362
+ let current = element;
363
+ let depth = 0;
364
+ while (current && current !== document.documentElement && depth <= maxAncestors) {
365
+ parts.unshift(buildElementSelector(current));
366
+ current = current.parentElement;
367
+ depth++;
368
+ }
369
+ return parts.join(" > ");
370
+ }
371
+
286
372
  // src/plugins/guides/activation-devices.ts
287
373
  var MOBILE_MAX_WIDTH = 767;
288
374
  function isDeviceAllowed(devices, viewportWidth = typeof window === "undefined" ? 1024 : window.innerWidth) {
@@ -756,6 +842,10 @@ var TEXT_PRESETS = {
756
842
  small: { size: 12, weight: 400, lineHeight: 1.5 }
757
843
  };
758
844
  function applyBlockStyle(el2, style, kind) {
845
+ if (kind === "text") {
846
+ const explicit = style && typeof style === "object" ? style.preset : void 0;
847
+ el2.dataset.veoPreset = typeof explicit === "string" ? explicit : el2.classList.contains("veo-guide-title") ? "title" : "paragraph";
848
+ }
759
849
  if (!style || typeof style !== "object") return;
760
850
  const s = style;
761
851
  if (kind === "image" && s.bleed === true) {
@@ -772,8 +862,9 @@ function applyBlockStyle(el2, style, kind) {
772
862
  if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
773
863
  const preset = TEXT_PRESETS[s.preset];
774
864
  if (preset) {
775
- el2.style.fontSize = `${preset.size}px`;
776
- el2.style.fontWeight = String(preset.weight);
865
+ const token = s.preset === "small" ? null : `--veo-t-${s.preset}`;
866
+ el2.style.fontSize = token ? `var(${token}-size, ${preset.size}px)` : `${preset.size}px`;
867
+ el2.style.fontWeight = token ? `var(${token}-weight, ${preset.weight})` : String(preset.weight);
777
868
  el2.style.lineHeight = String(preset.lineHeight);
778
869
  }
779
870
  }
@@ -1710,20 +1801,92 @@ var GUIDE_STYLES = `
1710
1801
  .veo-guide-actions {
1711
1802
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
1712
1803
  }
1804
+ /* Tema del workspace (design system): cada preset de texto toma sus tokens de
1805
+ las variables que publica \`applyThemeVars\` en la tarjeta. Las reglas solo
1806
+ existen bajo .veo-card--themed; los estilos inline del bloque las pisan. El
1807
+ \`*\` cubre el rich text (la regla global de fuente aplica a cada hijo). */
1808
+ .veo-card--themed [data-veo-preset="title"] {
1809
+ color: var(--veo-t-title-color); font-size: var(--veo-t-title-size);
1810
+ font-weight: var(--veo-t-title-weight); text-align: var(--veo-t-title-align);
1811
+ }
1812
+ .veo-card--themed [data-veo-preset="subtitle"] {
1813
+ color: var(--veo-t-subtitle-color); font-size: var(--veo-t-subtitle-size);
1814
+ font-weight: var(--veo-t-subtitle-weight); text-align: var(--veo-t-subtitle-align);
1815
+ }
1816
+ .veo-card--themed [data-veo-preset="paragraph"] {
1817
+ color: var(--veo-t-paragraph-color); font-size: var(--veo-t-paragraph-size);
1818
+ font-weight: var(--veo-t-paragraph-weight); text-align: var(--veo-t-paragraph-align);
1819
+ }
1820
+ .veo-card--themed [data-veo-preset="small"] { color: var(--veo-t-paragraph-color); }
1821
+ .veo-card--themed [data-veo-preset="title"], .veo-card--themed [data-veo-preset="title"] * {
1822
+ font-family: var(--veo-t-title-font, var(--veo-font));
1823
+ }
1824
+ .veo-card--themed [data-veo-preset="subtitle"], .veo-card--themed [data-veo-preset="subtitle"] * {
1825
+ font-family: var(--veo-t-subtitle-font, var(--veo-font));
1826
+ }
1827
+ .veo-card--themed [data-veo-preset="paragraph"], .veo-card--themed [data-veo-preset="paragraph"] *,
1828
+ .veo-card--themed [data-veo-preset="small"], .veo-card--themed [data-veo-preset="small"] * {
1829
+ font-family: var(--veo-t-paragraph-font, var(--veo-font));
1830
+ }
1831
+ /* Formulario del tema (pesta\xF1a Formulario). Dentro de un .veo-form los tokens
1832
+ de texto \`--veo-t-*\` se REDEFINEN con \xABTexto principal\xBB: la pregunta es un
1833
+ bloque con preset (su tama\xF1o va inline con esas variables), as\xED toma los del
1834
+ formulario y no los del modal. Etiquetas y opciones = \xABTexto secundario\xBB. */
1835
+ .veo-card--themed-form .veo-form {
1836
+ background: var(--veo-f-bg); padding: var(--veo-f-pad);
1837
+ border: var(--veo-f-border, none); border-radius: var(--veo-f-radius, 8px);
1838
+ box-shadow: var(--veo-f-shadow, none);
1839
+ --veo-t-title-color: var(--veo-f-primary-color); --veo-t-title-size: var(--veo-f-primary-size);
1840
+ --veo-t-title-weight: var(--veo-f-primary-weight); --veo-t-title-align: var(--veo-f-primary-align);
1841
+ --veo-t-title-font: var(--veo-f-primary-font);
1842
+ --veo-t-subtitle-color: var(--veo-f-primary-color); --veo-t-subtitle-size: var(--veo-f-primary-size);
1843
+ --veo-t-subtitle-weight: var(--veo-f-primary-weight); --veo-t-subtitle-align: var(--veo-f-primary-align);
1844
+ --veo-t-subtitle-font: var(--veo-f-primary-font);
1845
+ --veo-t-paragraph-color: var(--veo-f-primary-color); --veo-t-paragraph-size: var(--veo-f-primary-size);
1846
+ --veo-t-paragraph-weight: var(--veo-f-primary-weight); --veo-t-paragraph-align: var(--veo-f-primary-align);
1847
+ --veo-t-paragraph-font: var(--veo-f-primary-font);
1848
+ }
1849
+ .veo-card--themed-form .veo-form-label,
1850
+ .veo-card--themed-form .veo-form-option,
1851
+ .veo-card--themed-form .veo-scale-label {
1852
+ color: var(--veo-f-secondary-color); font-weight: var(--veo-f-secondary-weight);
1853
+ font-family: var(--veo-f-secondary-font, var(--veo-font));
1854
+ }
1855
+ .veo-card--themed-form .veo-form-label,
1856
+ .veo-card--themed-form .veo-form-option {
1857
+ font-size: var(--veo-f-secondary-size); text-align: var(--veo-f-secondary-align);
1858
+ }
1859
+ .veo-card--themed-form .veo-form-input:focus { outline-color: var(--veo-f-accent); }
1860
+ .veo-card--themed-form .veo-form-option input { accent-color: var(--veo-f-accent); }
1861
+ .veo-card--themed-form .veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-f-accent)); }
1862
+ .veo-card--themed-form .veo-scale-active.veo-nps-btn {
1863
+ background: var(--veo-scale-color, var(--veo-f-accent)); color: var(--veo-f-on-accent);
1864
+ border-color: var(--veo-scale-color, var(--veo-f-accent));
1865
+ }
1713
1866
  .veo-guide-cta {
1714
1867
  background: var(--veo-cta-bg, var(--veo-primary));
1715
- color: var(--veo-cta-color, #fff); border: none;
1716
- padding: 9px 18px; border-radius: var(--veo-cta-radius, 8px);
1717
- font-size: var(--veo-cta-size, 14px); font-weight: 500; cursor: pointer;
1868
+ color: var(--veo-cta-color, #fff); border: var(--veo-cta-border, none);
1869
+ padding: var(--veo-cta-pad, 9px 18px); border-radius: var(--veo-cta-radius, 8px);
1870
+ font-size: var(--veo-cta-size, 14px); font-weight: var(--veo-cta-weight, 500); cursor: pointer;
1718
1871
  transition: opacity 120ms ease;
1719
1872
  }
1720
1873
  .veo-guide-cta:hover { opacity: 0.9; }
1721
1874
  /* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
1722
1875
  .veo-guide-cta--secondary {
1723
- background: transparent; color: var(--veo-primary);
1724
- border: 1px solid var(--veo-primary);
1876
+ background: var(--veo-cta2-bg, transparent); color: var(--veo-cta2-color, var(--veo-primary));
1877
+ border: var(--veo-cta2-border, 1px solid var(--veo-primary));
1878
+ padding: var(--veo-cta2-pad, var(--veo-cta-pad, 9px 18px));
1879
+ border-radius: var(--veo-cta2-radius, var(--veo-cta-radius, 8px));
1880
+ font-size: var(--veo-cta2-size, var(--veo-cta-size, 14px));
1881
+ font-weight: var(--veo-cta2-weight, var(--veo-cta-weight, 500));
1882
+ }
1883
+ .veo-guide-cta--link {
1884
+ background: transparent; color: var(--veo-cta3-color, var(--veo-primary));
1885
+ border: var(--veo-cta3-border, none); padding: var(--veo-cta3-pad, 0);
1886
+ border-radius: var(--veo-cta3-radius, 0);
1887
+ font-size: var(--veo-cta3-size, var(--veo-cta-size, 14px));
1888
+ font-weight: var(--veo-cta3-weight, var(--veo-cta-weight, 500));
1725
1889
  }
1726
- .veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
1727
1890
  .veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
1728
1891
  .veo-guide-cta:active { transform: translateY(1px); }
1729
1892
  .veo-guide-close {
@@ -2093,6 +2256,12 @@ var STATIC_SIDE = {
2093
2256
  left: "right",
2094
2257
  right: "left"
2095
2258
  };
2259
+ var OUTER_EDGES = {
2260
+ top: ["top", "left"],
2261
+ right: ["top", "right"],
2262
+ bottom: ["bottom", "right"],
2263
+ left: ["bottom", "left"]
2264
+ };
2096
2265
  function positionArrow(arrowEl, placement, data) {
2097
2266
  const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
2098
2267
  for (const prop of ["top", "bottom", "left", "right"]) {
@@ -2101,6 +2270,16 @@ function positionArrow(arrowEl, placement, data) {
2101
2270
  if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
2102
2271
  if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
2103
2272
  arrowEl.style.setProperty(side, "-6px");
2273
+ const width = arrowEl.dataset.veoBorder;
2274
+ if (width) {
2275
+ for (const edge of ["top", "right", "bottom", "left"]) {
2276
+ arrowEl.style.setProperty(
2277
+ `border-${edge}-width`,
2278
+ OUTER_EDGES[side].includes(edge) ? `${width}px` : "0"
2279
+ );
2280
+ }
2281
+ arrowEl.style.zIndex = "1";
2282
+ }
2104
2283
  }
2105
2284
 
2106
2285
  // src/plugins/guides/renderers/badge-renderer.ts
@@ -2989,6 +3168,168 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [
2989
3168
  return { wrapper, read: () => selected };
2990
3169
  }
2991
3170
 
3171
+ // src/plugins/guides/guide-theme.ts
3172
+ var WORKSPACE_THEME_ID = "workspace";
3173
+ var SHADOW_SPEC = {
3174
+ soft: { y: 4, blur: 14, alpha: 0.12 },
3175
+ medium: { y: 12, blur: 32, alpha: 0.22 },
3176
+ strong: { y: 24, blur: 64, alpha: 0.38 }
3177
+ };
3178
+ var JUSTIFY = {
3179
+ left: "flex-start",
3180
+ center: "center",
3181
+ right: "flex-end",
3182
+ distribute: "space-between"
3183
+ };
3184
+ function channels(hex) {
3185
+ const h = hex.replace("#", "");
3186
+ const full = h.length === 3 ? [...h].map((x) => x + x).join("") : h.slice(0, 6);
3187
+ const ch = (i) => Number.parseInt(full.slice(i, i + 2), 16) || 0;
3188
+ return [ch(0), ch(2), ch(4)];
3189
+ }
3190
+ function boxShadow(shadow, hex) {
3191
+ if (shadow === "none") return "none";
3192
+ const spec = SHADOW_SPEC[shadow];
3193
+ const [r, g, b] = channels(hex);
3194
+ return `0 ${spec.y}px ${spec.blur}px rgba(${r}, ${g}, ${b}, ${spec.alpha})`;
3195
+ }
3196
+ function readableOn(hex) {
3197
+ const [r, g, b] = channels(hex);
3198
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.6 ? "#111827" : "#ffffff";
3199
+ }
3200
+ function themeModeFor(theme, design) {
3201
+ if (!theme || design.theme !== WORKSPACE_THEME_ID) return null;
3202
+ return (design.colorMode === "dark" ? theme.dark : theme.light) ?? null;
3203
+ }
3204
+ function themeRowJustify(mode, inline, preset) {
3205
+ const box = inline ? mode.embedded : mode.overlay;
3206
+ if (box.align === "distribute") return null;
3207
+ const button = preset === "secondary" ? mode.buttons?.secondary : preset === "link" ? mode.buttons?.tertiary : preset === void 0 || preset === null || preset === "primary" ? mode.buttons?.primary : void 0;
3208
+ return button ? JUSTIFY[button.align] ?? null : null;
3209
+ }
3210
+ function appFont(ref, doc) {
3211
+ const view = doc.defaultView;
3212
+ if (!view) return null;
3213
+ if (ref === "inherit") return view.getComputedStyle(doc.body).fontFamily || null;
3214
+ if (ref === "app-heading") {
3215
+ const heading = Array.from(doc.querySelectorAll("h1,h2,h3")).find(
3216
+ (el2) => el2.getClientRects().length > 0
3217
+ );
3218
+ return view.getComputedStyle(heading ?? doc.body).fontFamily || null;
3219
+ }
3220
+ return ref;
3221
+ }
3222
+ function themeContainerDesign(mode, inline, tooltip = false) {
3223
+ const box = inline ? mode.embedded : mode.overlay;
3224
+ const tip = tooltip ? mode.tooltip : void 0;
3225
+ const padding = tip?.padding ?? box.padding;
3226
+ const out = {
3227
+ // El embebido va en el flujo de la página: sin fondo propio.
3228
+ background: tip ? tip.background : inline ? "transparent" : box.background ?? mode.colors.background,
3229
+ color: mode.colors.text,
3230
+ paddingTop: padding.top,
3231
+ paddingRight: padding.right,
3232
+ paddingBottom: padding.bottom,
3233
+ paddingLeft: padding.left,
3234
+ borderColor: tip?.borderColor ?? box.borderColor,
3235
+ borderWidth: tip?.borderWidth ?? box.borderWidth,
3236
+ radius: tip?.borderRadius ?? box.borderRadius
3237
+ };
3238
+ const shadow = tip?.shadow ?? box.shadow;
3239
+ const shadowColor = tip?.shadowColor ?? box.shadowColor;
3240
+ if (shadow === "none") {
3241
+ Object.assign(out, { shadowColor: "transparent", shadowBlur: 0 });
3242
+ } else {
3243
+ const spec = SHADOW_SPEC[shadow];
3244
+ const [r, g, b] = channels(shadowColor);
3245
+ Object.assign(out, {
3246
+ shadowX: 0,
3247
+ shadowY: spec.y,
3248
+ shadowBlur: spec.blur,
3249
+ shadowSpread: 0,
3250
+ shadowColor: `rgba(${r}, ${g}, ${b}, ${spec.alpha})`
3251
+ });
3252
+ }
3253
+ return out;
3254
+ }
3255
+ function applyThemeVars(card, mode, inline, tooltip = false) {
3256
+ const doc = card.ownerDocument ?? document;
3257
+ const box = inline ? mode.embedded : mode.overlay;
3258
+ const set = (name, value) => {
3259
+ if (value) card.style.setProperty(name, value);
3260
+ };
3261
+ card.classList.add("veo-card--themed");
3262
+ set("--veo-primary", mode.colors.accent);
3263
+ set("--veo-cta-color", readableOn(mode.colors.accent));
3264
+ const primary = mode.buttons?.primary;
3265
+ const publish = (prefix, b) => {
3266
+ if (!b) return;
3267
+ const p = b.padding;
3268
+ set(`${prefix}-bg`, b.background);
3269
+ set(`${prefix}-color`, b.color);
3270
+ set(`${prefix}-pad`, `${p.top}px ${p.right}px ${p.bottom}px ${p.left}px`);
3271
+ set(`${prefix}-size`, `${b.fontSize}px`);
3272
+ set(`${prefix}-weight`, String(b.fontWeight));
3273
+ set(`${prefix}-radius`, `${b.borderRadius}px`);
3274
+ set(
3275
+ `${prefix}-border`,
3276
+ b.borderWidth > 0 ? `${b.borderWidth}px solid ${b.borderColor}` : "none"
3277
+ );
3278
+ };
3279
+ publish("--veo-cta", primary);
3280
+ publish("--veo-cta2", mode.buttons?.secondary);
3281
+ publish("--veo-cta3", mode.buttons?.tertiary);
3282
+ set("--veo-text", mode.colors.text);
3283
+ set("--veo-text-secondary", mode.colors.textMuted);
3284
+ set("--veo-font", appFont("inherit", doc));
3285
+ set(
3286
+ "--veo-actions-justify",
3287
+ box.align === "distribute" || !primary ? JUSTIFY[box.align] : JUSTIFY[primary.align]
3288
+ );
3289
+ const form = mode.form;
3290
+ if (form) {
3291
+ card.classList.add("veo-card--themed-form");
3292
+ const fp = form.padding;
3293
+ set("--veo-f-bg", form.background);
3294
+ set("--veo-f-accent", form.accent);
3295
+ set("--veo-f-on-accent", form.onAccent);
3296
+ set("--veo-f-pad", `${fp.top}px ${fp.right}px ${fp.bottom}px ${fp.left}px`);
3297
+ if (typeof form.borderWidth === "number" && form.borderColor) {
3298
+ set(
3299
+ "--veo-f-border",
3300
+ form.borderWidth > 0 ? `${form.borderWidth}px solid ${form.borderColor}` : "none"
3301
+ );
3302
+ }
3303
+ if (typeof form.borderRadius === "number") set("--veo-f-radius", `${form.borderRadius}px`);
3304
+ if (form.shadow && form.shadowColor) {
3305
+ set("--veo-f-shadow", boxShadow(form.shadow, form.shadowColor));
3306
+ }
3307
+ for (const [name, t] of [
3308
+ ["primary", form.text.primary],
3309
+ ["secondary", form.text.secondary]
3310
+ ]) {
3311
+ set(`--veo-f-${name}-color`, t.color);
3312
+ set(`--veo-f-${name}-size`, `${t.fontSize}px`);
3313
+ set(`--veo-f-${name}-weight`, String(t.fontWeight));
3314
+ set(`--veo-f-${name}-align`, t.align);
3315
+ set(`--veo-f-${name}-font`, appFont(t.fontFamily, doc));
3316
+ }
3317
+ }
3318
+ const tip = tooltip ? mode.tooltip?.text : void 0;
3319
+ const tokens = [
3320
+ ["title", tip?.primary ?? mode.text.title],
3321
+ ["subtitle", tip?.secondary ?? mode.text.subtitle],
3322
+ ["paragraph", tip?.secondary ?? mode.text.paragraph]
3323
+ ];
3324
+ for (const [name, t] of tokens) {
3325
+ set(`--veo-t-${name}-color`, t.color);
3326
+ set(`--veo-t-${name}-size`, `${t.fontSize}px`);
3327
+ set(`--veo-t-${name}-weight`, String(t.fontWeight));
3328
+ set(`--veo-t-${name}-align`, t.align);
3329
+ set(`--veo-t-${name}-font`, appFont(t.fontFamily, doc));
3330
+ }
3331
+ }
3332
+
2992
3333
  // src/plugins/guides/layout.ts
2993
3334
  function groupLayout(blocks) {
2994
3335
  const rows = /* @__PURE__ */ new Map();
@@ -3370,6 +3711,11 @@ function applyRowStyle(rowEl, style) {
3370
3711
  if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
3371
3712
  }
3372
3713
  var CompositeRenderer = class extends BaseRenderer {
3714
+ constructor() {
3715
+ super(...arguments);
3716
+ /** Alineación de filas de botones según el tema del workspace (null = sin tema). */
3717
+ this.themeRowJustify = null;
3718
+ }
3373
3719
  async render(context) {
3374
3720
  const idx = context.nav?.stepIndex ?? 0;
3375
3721
  const step = context.guide.guideSteps[idx];
@@ -3400,7 +3746,13 @@ var CompositeRenderer = class extends BaseRenderer {
3400
3746
  if (typeof design.zIndex === "number") host.style.zIndex = String(design.zIndex);
3401
3747
  const card = doc.createElement("div");
3402
3748
  card.className = "veo-card";
3403
- applyCompositeDesign(card, design);
3749
+ const themeMode = themeModeFor(context.guide.theme, design);
3750
+ const inline = step.presentation === "inline";
3751
+ const tooltipLike = !inline && (badgeMode || step.position === "relative-to-element");
3752
+ if (themeMode) applyThemeVars(card, themeMode, inline, tooltipLike);
3753
+ this.themeRowJustify = themeMode ? (preset) => themeRowJustify(themeMode, inline, preset) : null;
3754
+ const cardDesign = themeMode ? { ...themeContainerDesign(themeMode, inline, tooltipLike), ...design } : design;
3755
+ applyCompositeDesign(card, cardDesign);
3404
3756
  if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
3405
3757
  card.setAttribute("role", "dialog");
3406
3758
  card.setAttribute("aria-label", design.ariaLabel.trim());
@@ -3471,7 +3823,7 @@ var CompositeRenderer = class extends BaseRenderer {
3471
3823
  });
3472
3824
  }
3473
3825
  card.appendChild(closeBtn);
3474
- reserveCloseSpace(card, blocks, closePos, design);
3826
+ reserveCloseSpace(card, blocks, closePos, cardDesign);
3475
3827
  }
3476
3828
  if (badgeMode && anchor) {
3477
3829
  closeTooltip = this.renderBadgeMode(
@@ -3484,7 +3836,8 @@ var CompositeRenderer = class extends BaseRenderer {
3484
3836
  doc,
3485
3837
  context,
3486
3838
  idx,
3487
- design
3839
+ // Con el tema ya fundido: la flecha del tooltip toma su fondo y borde.
3840
+ cardDesign
3488
3841
  );
3489
3842
  return;
3490
3843
  }
@@ -3507,7 +3860,7 @@ var CompositeRenderer = class extends BaseRenderer {
3507
3860
  }
3508
3861
  const wrapper = doc.createElement("div");
3509
3862
  wrapper.className = "veo-composite-anchored";
3510
- const caretEl = design.caret === false ? null : makeCaret(doc, design);
3863
+ const caretEl = design.caret === false ? null : makeCaret(doc, cardDesign);
3511
3864
  if (caretEl) wrapper.appendChild(caretEl);
3512
3865
  wrapper.appendChild(card);
3513
3866
  root.appendChild(wrapper);
@@ -3567,6 +3920,7 @@ var CompositeRenderer = class extends BaseRenderer {
3567
3920
  const row = doc.createElement("div");
3568
3921
  row.className = "veo-guide-actions";
3569
3922
  let rowAlign;
3923
+ const firstPreset = b.style?.preset;
3570
3924
  while (i < cell.length && cell[i]?.block.type === "button") {
3571
3925
  const { block: bb, index: bi } = cell[i];
3572
3926
  if (!rowAlign) {
@@ -3577,6 +3931,10 @@ var CompositeRenderer = class extends BaseRenderer {
3577
3931
  i++;
3578
3932
  }
3579
3933
  if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
3934
+ else {
3935
+ const themed = this.themeRowJustify?.(firstPreset);
3936
+ if (themed) row.style.justifyContent = themed;
3937
+ }
3580
3938
  container.appendChild(row);
3581
3939
  continue;
3582
3940
  }
@@ -3989,6 +4347,7 @@ function makeCaret(doc, design) {
3989
4347
  if (typeof design.borderWidth === "number" && design.borderWidth > 0) {
3990
4348
  const bc = typeof design.borderColor === "string" ? design.borderColor : "#bbbbbb";
3991
4349
  s.border = `${design.borderWidth}px solid ${bc}`;
4350
+ caret.dataset.veoBorder = String(design.borderWidth);
3992
4351
  }
3993
4352
  return caret;
3994
4353
  }
@@ -4723,7 +5082,8 @@ function normalize(input) {
4723
5082
  guideSteps: input.guideSteps,
4724
5083
  activationRules,
4725
5084
  displayFrequency: "always",
4726
- displayPriority: 0
5085
+ displayPriority: 0,
5086
+ ...input.theme ? { theme: input.theme } : {}
4727
5087
  };
4728
5088
  }
4729
5089
  function createSingleStepRenderer(type) {
@@ -4943,14 +5303,19 @@ function injectBuilderChrome(opts) {
4943
5303
  let previewOn = false;
4944
5304
  let hiddenOn = false;
4945
5305
  let heightCb = null;
4946
- const height = () => CHROME_TOPBAR_H + (collapsed ? 0 : CHROME_STEPS_H);
5306
+ let tagging = false;
5307
+ const height = () => CHROME_TOPBAR_H + (collapsed || tagging ? 0 : CHROME_STEPS_H);
4947
5308
  const applyHeight = () => {
4948
5309
  const h = previewOn || hiddenOn ? 0 : height();
4949
5310
  html.style.marginTop = dock === "top" ? `${h}px` : prevMarginTop;
4950
5311
  html.style.marginBottom = dock === "bottom" ? `${h}px` : prevMarginBottom;
4951
5312
  root.style.setProperty("--veo-chrome-h", `${height()}px`);
4952
5313
  root.classList.toggle("is-bottom", dock === "bottom");
4953
- stepsBar.hidden = collapsed;
5314
+ stepsBar.hidden = collapsed || tagging;
5315
+ togglePill.hidden = tagging;
5316
+ preview.hidden = tagging;
5317
+ saveGroup.hidden = tagging;
5318
+ showPanel.title = tagging ? "Mostrar el panel" : "Mostrar el editor";
4954
5319
  const open = dock === "top" ? "chevronUp" : "chevronDown";
4955
5320
  const closedIcon = dock === "top" ? "chevronDown" : "chevronUp";
4956
5321
  togglePill.innerHTML = `<span>${collapsed ? "Mostrar pasos" : "Ocultar pasos"}</span>${svg(
@@ -5003,8 +5368,13 @@ function injectBuilderChrome(opts) {
5003
5368
  const render = () => {
5004
5369
  const s = current;
5005
5370
  if (!s) return;
5371
+ const nextTagging = s.variant === "tagging";
5372
+ if (nextTagging !== tagging) {
5373
+ tagging = nextTagging;
5374
+ applyHeight();
5375
+ }
5006
5376
  const date = shortDate(s.createdAt);
5007
- nameEl.textContent = `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5377
+ nameEl.textContent = tagging ? s.guideName.trim() || "Modo tagging" : `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5008
5378
  if (s.status) {
5009
5379
  statusEl.hidden = false;
5010
5380
  statusEl.textContent = s.status.label;
@@ -5103,6 +5473,8 @@ var CHROME_CSS = `
5103
5473
  font-size: 14px; color: #1c1917;
5104
5474
  }
5105
5475
  .veo-chrome[hidden] { display: none; }
5476
+ /* Modo tagging oculta controles por atributo: gana a los display de sus clases. */
5477
+ .veo-chrome [hidden], .veo-steps-toggle[hidden] { display: none !important; }
5106
5478
  .veo-chrome.is-bottom { top: auto; bottom: 0; flex-direction: column-reverse; }
5107
5479
  .veo-chrome.is-bottom .veo-tb-menu { top: auto; bottom: calc(100% + 8px); }
5108
5480
  .veo-chrome.is-bottom .veo-steps { border-bottom: 0; border-top: 1px solid #d6cfc8; }
@@ -5722,90 +6094,51 @@ function attachBanner(opts, shadow, banner) {
5722
6094
  };
5723
6095
  }
5724
6096
 
5725
- // src/plugins/autocapture/constants.ts
5726
- var HASHED_CLASS_PATTERNS = [
5727
- /^css-[a-z0-9]{4,}$/i,
5728
- // emotion, styled-components nuevas
5729
- /^_[a-z0-9]+_[a-z0-9]{4,}_\d+$/,
5730
- // CSS Modules con hash
5731
- /^[a-z]{2,4}-[a-z0-9]{6,}$/i,
5732
- // Tailwind JIT (a veces)
5733
- /^[a-f0-9]{6,}$/i,
5734
- // hashes hex puros
5735
- /^sc-[a-z0-9]+$/i
5736
- // styled-components legacy
5737
- ];
5738
-
5739
- // src/plugins/autocapture/selector-builder.ts
5740
- function isHashedClass(className) {
5741
- return HASHED_CLASS_PATTERNS.some((pattern) => pattern.test(className));
5742
- }
5743
- function filterHumanClasses(element) {
5744
- return Array.from(element.classList).filter((c) => !isHashedClass(c) && c.length < 64);
5745
- }
5746
- var STABLE_ATTRIBUTES = [
5747
- "data-testid",
5748
- "data-test",
5749
- "data-cy",
5750
- "name",
5751
- "aria-label",
5752
- "placeholder",
5753
- "alt",
5754
- "title",
5755
- // `href` identifica muy bien a los links (`<a>`), un target frecuente de
5756
- // autocapture; va después de los atributos semánticos pero antes de los
5757
- // genéricos. Valores largos (URLs absolutas con query) se descartan por
5758
- // MAX_ATTR_VALUE_LENGTH.
5759
- "href",
5760
- "role",
5761
- "type"
6097
+ // src/plugins/builder/interactive-elements.ts
6098
+ var INTERACTIVE_ROLES = [
6099
+ "button",
6100
+ "link",
6101
+ "tab",
6102
+ "menuitem",
6103
+ "menuitemcheckbox",
6104
+ "menuitemradio",
6105
+ "checkbox",
6106
+ "radio",
6107
+ "switch",
6108
+ "option",
6109
+ "combobox",
6110
+ "textbox",
6111
+ "searchbox",
6112
+ "slider",
6113
+ "spinbutton"
5762
6114
  ];
5763
- var MAX_ATTR_VALUE_LENGTH = 100;
5764
- function cssEscape(value) {
5765
- if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
5766
- return CSS.escape(value);
5767
- }
5768
- return value.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
5769
- }
5770
- function escapeAttrValue(value) {
5771
- return value.replace(/[\\"]/g, "\\$&");
5772
- }
5773
- function stableAttributeSelector(element, tag) {
5774
- for (const attr of STABLE_ATTRIBUTES) {
5775
- const value = element.getAttribute(attr);
5776
- if (value && value.length > 0 && value.length <= MAX_ATTR_VALUE_LENGTH) {
5777
- return `${tag}[${attr}="${escapeAttrValue(value)}"]`;
5778
- }
5779
- }
5780
- return null;
5781
- }
5782
- function buildElementSelector(element) {
5783
- const tag = element.tagName.toLowerCase();
5784
- const veoTag = element.getAttribute("data-veo-tag");
5785
- if (veoTag) {
5786
- return `${tag}[data-veo-tag="${escapeAttrValue(veoTag)}"]`;
5787
- }
5788
- if (element.id) {
5789
- const classes2 = filterHumanClasses(element);
5790
- const classPart2 = classes2.length > 0 ? `.${classes2.map(cssEscape).join(".")}` : "";
5791
- return `${tag}#${cssEscape(element.id)}${classPart2}`;
6115
+ var INTERACTIVE_SELECTOR = [
6116
+ "button",
6117
+ "a[href]",
6118
+ 'input:not([type="hidden"])',
6119
+ "select",
6120
+ "textarea",
6121
+ "form",
6122
+ "summary",
6123
+ '[contenteditable="true"]',
6124
+ "[onclick]",
6125
+ "[data-veo-tag]",
6126
+ ...INTERACTIVE_ROLES.map((r) => `[role="${r}"]`)
6127
+ ].join(", ");
6128
+ function closestInteractive(element) {
6129
+ try {
6130
+ return element.closest(INTERACTIVE_SELECTOR);
6131
+ } catch {
6132
+ return null;
5792
6133
  }
5793
- const attrSelector = stableAttributeSelector(element, tag);
5794
- if (attrSelector) return attrSelector;
5795
- const classes = filterHumanClasses(element);
5796
- const classPart = classes.length > 0 ? `.${classes.map(cssEscape).join(".")}` : "";
5797
- return `${tag}${classPart}`;
5798
6134
  }
5799
- function buildSelectorPath(element, maxAncestors = 5) {
5800
- const parts = [];
5801
- let current = element;
5802
- let depth = 0;
5803
- while (current && current !== document.documentElement && depth <= maxAncestors) {
5804
- parts.unshift(buildElementSelector(current));
5805
- current = current.parentElement;
5806
- depth++;
5807
- }
5808
- return parts.join(" > ");
6135
+ var EMOJI_RE = /\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u{FE0F}|\u{200D}|\u{20E3}/gu;
6136
+ function elementLabelText(element, max = 120) {
6137
+ const clean = (raw) => (raw ?? "").replace(EMOJI_RE, "").replace(/\s+/g, " ").trim();
6138
+ const text = clean(element.textContent);
6139
+ if (text) return text.slice(0, max);
6140
+ const fallback = clean(element.getAttribute("aria-label")) || clean(element.getAttribute("title"));
6141
+ return fallback.slice(0, max);
5809
6142
  }
5810
6143
 
5811
6144
  // src/plugins/builder/element-picker.ts
@@ -5878,21 +6211,24 @@ var ElementPicker = class {
5878
6211
  this.label = null;
5879
6212
  this.current = null;
5880
6213
  this.maxAncestors = DEFAULT_MAX_ANCESTORS;
6214
+ this.only = null;
5881
6215
  this.onPick = null;
5882
6216
  this.onCancel = null;
5883
6217
  this.onMove = (event) => {
5884
- const target = event.target;
5885
- if (!(target instanceof Element) || this.isOwn(target)) return;
6218
+ if (!(event.target instanceof Element) || this.isOwn(event.target)) return;
6219
+ const target = this.resolveTarget(event.target);
5886
6220
  this.current = target;
5887
- this.draw(target);
6221
+ if (target) this.draw(target);
6222
+ else this.hideBox();
5888
6223
  };
5889
6224
  this.onClick = (event) => {
5890
6225
  if (event.altKey) return;
5891
- const target = this.current ?? (event.target instanceof Element ? event.target : null);
5892
- if (!target || this.isOwn(target)) return;
6226
+ if (this.isOwn(event.target)) return;
5893
6227
  event.preventDefault();
5894
6228
  event.stopPropagation();
5895
6229
  event.stopImmediatePropagation();
6230
+ const target = this.current ?? this.resolveTarget(event.target);
6231
+ if (!target) return;
5896
6232
  const picked = this.describe(target);
5897
6233
  const cb = this.onPick;
5898
6234
  this.stop();
@@ -5919,6 +6255,7 @@ var ElementPicker = class {
5919
6255
  start(options) {
5920
6256
  this.stop();
5921
6257
  this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
6258
+ this.only = options.only ?? null;
5922
6259
  this.onPick = options.onPick;
5923
6260
  this.onCancel = options.onCancel ?? null;
5924
6261
  const host = document.createElement("div");
@@ -5981,9 +6318,19 @@ var ElementPicker = class {
5981
6318
  this.box = null;
5982
6319
  this.label = null;
5983
6320
  this.current = null;
6321
+ this.only = null;
5984
6322
  this.onPick = null;
5985
6323
  this.onCancel = null;
5986
6324
  }
6325
+ /** Elemento que se elegiría para el nodo bajo el cursor (o null si no hay). */
6326
+ resolveTarget(target) {
6327
+ if (!(target instanceof Element) || this.isOwn(target)) return null;
6328
+ return this.only === "interactive" ? closestInteractive(target) : target;
6329
+ }
6330
+ hideBox() {
6331
+ if (this.box) this.box.style.display = "none";
6332
+ if (this.label) this.label.style.display = "none";
6333
+ }
5987
6334
  /** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
5988
6335
  isOwn(target) {
5989
6336
  return target instanceof Node && this.host !== null && this.host.contains(target);
@@ -6020,7 +6367,7 @@ var ElementPicker = class {
6020
6367
  tag: element.tagName.toLowerCase(),
6021
6368
  id: element.id || null,
6022
6369
  classes: filterHumanClasses(element),
6023
- text: (element.textContent ?? "").trim().slice(0, 120),
6370
+ text: elementLabelText(element),
6024
6371
  rect: {
6025
6372
  x: Math.round(rect.left),
6026
6373
  y: Math.round(rect.top),
@@ -6045,6 +6392,41 @@ function stopElementPicker() {
6045
6392
  }
6046
6393
 
6047
6394
  // src/plugins/builder/builder-mode.ts
6395
+ var CONFIRMATION_STYLES = `
6396
+ :host { all: initial; }
6397
+ .veo-confirm-backdrop {
6398
+ position: fixed; inset: 0; z-index: 2147483645;
6399
+ background: rgba(20, 16, 14, 0.45);
6400
+ }
6401
+ .veo-confirm {
6402
+ position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
6403
+ z-index: 2147483646; box-sizing: border-box;
6404
+ width: min(432px, calc(100vw - 32px)); padding: 28px 24px 24px;
6405
+ border-radius: 16px; background: #fff; color: #1d1917;
6406
+ box-shadow: 0 24px 64px rgba(0,0,0,0.28);
6407
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
6408
+ text-align: center;
6409
+ }
6410
+ .veo-confirm-check {
6411
+ width: 64px; height: 64px; margin: 0 auto; border-radius: 50%;
6412
+ background: #e6f6df; color: #2f9a4e; display: flex; align-items: center; justify-content: center;
6413
+ }
6414
+ .veo-confirm-check svg { width: 30px; height: 30px; }
6415
+ .veo-confirm-title { margin: 18px 0 0; font-size: 17px; font-weight: 600; }
6416
+ .veo-confirm-msg { margin: 8px 0 0; font-size: 13px; color: #6b6461; }
6417
+ .veo-confirm-rows {
6418
+ margin: 18px 0 0; padding: 12px 14px; border-radius: 12px; background: #f6f2ef; text-align: left;
6419
+ }
6420
+ .veo-confirm-row { display: flex; justify-content: space-between; gap: 16px; font-size: 13px; padding: 4px 0; }
6421
+ .veo-confirm-row dt { color: #6b6461; margin: 0; }
6422
+ .veo-confirm-row dd { margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6423
+ .veo-confirm-btn {
6424
+ display: block; width: 100%; margin-top: 18px; height: 44px; border: 0; border-radius: 10px;
6425
+ background: #b74d00; color: #fff; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
6426
+ }
6427
+ .veo-confirm-btn:hover { background: #953c00; }
6428
+ .veo-confirm-btn:focus-visible { outline: 3px solid rgba(183,77,0,0.35); outline-offset: 2px; }
6429
+ `;
6048
6430
  function initBuilderMode() {
6049
6431
  if (!hasWindow() || !hasDocument()) return null;
6050
6432
  const ctx = resolveBuilderContext();
@@ -7026,7 +7408,7 @@ function initBuilderMode() {
7026
7408
  if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7027
7409
  }
7028
7410
  panel?.setTitle(
7029
- cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso"
7411
+ cmd.state.panelTitle ?? (cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso")
7030
7412
  );
7031
7413
  break;
7032
7414
  case "panel-visibility":
@@ -7064,7 +7446,8 @@ function initBuilderMode() {
7064
7446
  post({ type: "pick-cancelled" });
7065
7447
  },
7066
7448
  ...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
7067
- ...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
7449
+ ...cmd.hint !== void 0 ? { hint: cmd.hint } : {},
7450
+ ...cmd.only !== void 0 ? { only: cmd.only } : {}
7068
7451
  });
7069
7452
  break;
7070
7453
  }
@@ -7073,6 +7456,15 @@ function initBuilderMode() {
7073
7456
  picker = null;
7074
7457
  chrome?.setHidden(false);
7075
7458
  break;
7459
+ case "scan-page":
7460
+ post({ type: "page-scan", payload: scanPage(cmd.selectors) });
7461
+ break;
7462
+ case "highlight":
7463
+ setHighlight(cmd.selector);
7464
+ break;
7465
+ case "show-confirmation":
7466
+ showConfirmation(cmd.payload);
7467
+ break;
7076
7468
  case "preview":
7077
7469
  if (!cmd.guide) break;
7078
7470
  if (manipulator?.isDragging()) pendingPreview = cmd;
@@ -7157,10 +7549,154 @@ function initBuilderMode() {
7157
7549
  post({ type: "closed" });
7158
7550
  panel?.teardown();
7159
7551
  panel = null;
7552
+ highlightBox?.remove();
7553
+ highlightBox = null;
7554
+ confirmation?.remove();
7555
+ confirmation = null;
7556
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7557
+ confirmationKey = null;
7160
7558
  chrome?.teardown();
7161
7559
  chrome = null;
7162
7560
  clearBuilderToken();
7163
7561
  };
7562
+ const scanPage = (selectors) => {
7563
+ const present = selectors.filter((sel) => {
7564
+ try {
7565
+ return document.querySelector(sel) !== null;
7566
+ } catch {
7567
+ return false;
7568
+ }
7569
+ });
7570
+ const taggedEls = /* @__PURE__ */ new Set();
7571
+ for (const sel of present) {
7572
+ try {
7573
+ for (const e of Array.from(document.querySelectorAll(sel))) taggedEls.add(e);
7574
+ } catch {
7575
+ }
7576
+ }
7577
+ const seen = /* @__PURE__ */ new Set();
7578
+ const candidates = [];
7579
+ const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
7580
+ for (const element of Array.from(nodes)) {
7581
+ if (candidates.length >= 200) break;
7582
+ if (element.closest("[data-veo-builder-chrome],[data-veo-builder-panel],[data-veo-picker]")) {
7583
+ continue;
7584
+ }
7585
+ if (element.getClientRects().length === 0) continue;
7586
+ let tagged = false;
7587
+ for (let n = element; n; n = n.parentElement) {
7588
+ if (taggedEls.has(n)) {
7589
+ tagged = true;
7590
+ break;
7591
+ }
7592
+ }
7593
+ if (tagged) continue;
7594
+ const selector = buildSelectorPath(element, 5);
7595
+ if (!selector || seen.has(selector)) continue;
7596
+ seen.add(selector);
7597
+ candidates.push({
7598
+ selector,
7599
+ shortSelector: buildSelectorPath(element, 0),
7600
+ tag: element.tagName.toLowerCase(),
7601
+ id: element.id || null,
7602
+ classes: filterHumanClasses(element),
7603
+ text: elementLabelText(element)
7604
+ });
7605
+ }
7606
+ const { pathname, hash } = window.location;
7607
+ const routePath = /^#\/./.test(hash) ? `${pathname}${hash}` : pathname;
7608
+ return { pagePath: pathname, routePath, candidates, present };
7609
+ };
7610
+ let confirmation = null;
7611
+ let confirmationKey = null;
7612
+ const closeConfirmation = () => {
7613
+ if (!confirmation) return;
7614
+ confirmation.remove();
7615
+ confirmation = null;
7616
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7617
+ confirmationKey = null;
7618
+ panel?.setVisible(true);
7619
+ post({ type: "confirmation-closed" });
7620
+ };
7621
+ const showConfirmation = (card) => {
7622
+ if (confirmation) {
7623
+ confirmation.remove();
7624
+ confirmation = null;
7625
+ }
7626
+ panel?.setVisible(false);
7627
+ const host = document.createElement("div");
7628
+ host.setAttribute("data-veo-builder-chrome", "");
7629
+ host.setAttribute("data-veo-confirmation", "");
7630
+ const shadow = host.attachShadow({ mode: "open" });
7631
+ const style = document.createElement("style");
7632
+ style.textContent = CONFIRMATION_STYLES;
7633
+ const backdrop = document.createElement("div");
7634
+ backdrop.className = "veo-confirm-backdrop";
7635
+ const dialog = document.createElement("div");
7636
+ dialog.className = "veo-confirm";
7637
+ dialog.setAttribute("role", "dialog");
7638
+ dialog.setAttribute("aria-modal", "true");
7639
+ dialog.setAttribute("aria-label", card.title);
7640
+ const badge = document.createElement("div");
7641
+ badge.className = "veo-confirm-check";
7642
+ badge.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
7643
+ const title = document.createElement("h2");
7644
+ title.className = "veo-confirm-title";
7645
+ title.textContent = card.title;
7646
+ const message = document.createElement("p");
7647
+ message.className = "veo-confirm-msg";
7648
+ message.textContent = card.message;
7649
+ const rows = document.createElement("dl");
7650
+ rows.className = "veo-confirm-rows";
7651
+ for (const r of card.rows) {
7652
+ const row = document.createElement("div");
7653
+ row.className = "veo-confirm-row";
7654
+ const dt = document.createElement("dt");
7655
+ dt.textContent = r.label;
7656
+ const dd = document.createElement("dd");
7657
+ dd.textContent = r.value;
7658
+ row.append(dt, dd);
7659
+ rows.appendChild(row);
7660
+ }
7661
+ const button = document.createElement("button");
7662
+ button.type = "button";
7663
+ button.className = "veo-confirm-btn";
7664
+ button.textContent = card.buttonLabel ?? "Listo";
7665
+ button.addEventListener("click", closeConfirmation);
7666
+ dialog.append(badge, title, message);
7667
+ if (card.rows.length > 0) dialog.appendChild(rows);
7668
+ dialog.appendChild(button);
7669
+ shadow.append(style, backdrop, dialog);
7670
+ document.body.appendChild(host);
7671
+ confirmation = host;
7672
+ confirmationKey = (e) => {
7673
+ if (e.key !== "Escape") return;
7674
+ e.preventDefault();
7675
+ e.stopPropagation();
7676
+ closeConfirmation();
7677
+ };
7678
+ document.addEventListener("keydown", confirmationKey, true);
7679
+ button.focus();
7680
+ };
7681
+ let highlightBox = null;
7682
+ const setHighlight = (selector) => {
7683
+ highlightBox?.remove();
7684
+ highlightBox = null;
7685
+ if (!selector) return;
7686
+ let target = null;
7687
+ try {
7688
+ target = document.querySelector(selector);
7689
+ } catch {
7690
+ return;
7691
+ }
7692
+ if (!target) return;
7693
+ const r = target.getBoundingClientRect();
7694
+ const box = document.createElement("div");
7695
+ box.setAttribute("data-veo-builder-chrome", "");
7696
+ box.style.cssText = `position:fixed;left:${r.left - 3}px;top:${r.top - 3}px;width:${r.width + 6}px;height:${r.height + 6}px;border:2px solid #FF5B35;border-radius:6px;background:rgba(255,91,53,0.10);pointer-events:none;z-index:2147483646;`;
7697
+ document.body.appendChild(box);
7698
+ highlightBox = box;
7699
+ };
7164
7700
  if (ctx.panel) {
7165
7701
  chrome = injectBuilderChrome({
7166
7702
  appLabel: window.location.hostname,