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/index.cjs CHANGED
@@ -251,7 +251,8 @@ var init_constants = __esm({
251
251
  throttleMs: 100,
252
252
  maxTextLength: 200,
253
253
  captureElementMetadata: true,
254
- captureAncestors: 5
254
+ captureAncestors: 5,
255
+ captureClickQuality: true
255
256
  };
256
257
  MAX_ANCESTORS = 10;
257
258
  ALWAYS_BLOCK_SELECTORS = [
@@ -902,6 +903,10 @@ function clampNum(n, min, max) {
902
903
  return Math.min(max, Math.max(min, n));
903
904
  }
904
905
  function applyBlockStyle(el2, style, kind) {
906
+ if (kind === "text") {
907
+ const explicit = style && typeof style === "object" ? style.preset : void 0;
908
+ el2.dataset.veoPreset = typeof explicit === "string" ? explicit : el2.classList.contains("veo-guide-title") ? "title" : "paragraph";
909
+ }
905
910
  if (!style || typeof style !== "object") return;
906
911
  const s = style;
907
912
  if (kind === "image" && s.bleed === true) {
@@ -918,8 +923,9 @@ function applyBlockStyle(el2, style, kind) {
918
923
  if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
919
924
  const preset = TEXT_PRESETS[s.preset];
920
925
  if (preset) {
921
- el2.style.fontSize = `${preset.size}px`;
922
- el2.style.fontWeight = String(preset.weight);
926
+ const token = s.preset === "small" ? null : `--veo-t-${s.preset}`;
927
+ el2.style.fontSize = token ? `var(${token}-size, ${preset.size}px)` : `${preset.size}px`;
928
+ el2.style.fontWeight = token ? `var(${token}-weight, ${preset.weight})` : String(preset.weight);
923
929
  el2.style.lineHeight = String(preset.lineHeight);
924
930
  }
925
931
  }
@@ -1635,20 +1641,92 @@ var init_styles = __esm({
1635
1641
  .veo-guide-actions {
1636
1642
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
1637
1643
  }
1644
+ /* Tema del workspace (design system): cada preset de texto toma sus tokens de
1645
+ las variables que publica \`applyThemeVars\` en la tarjeta. Las reglas solo
1646
+ existen bajo .veo-card--themed; los estilos inline del bloque las pisan. El
1647
+ \`*\` cubre el rich text (la regla global de fuente aplica a cada hijo). */
1648
+ .veo-card--themed [data-veo-preset="title"] {
1649
+ color: var(--veo-t-title-color); font-size: var(--veo-t-title-size);
1650
+ font-weight: var(--veo-t-title-weight); text-align: var(--veo-t-title-align);
1651
+ }
1652
+ .veo-card--themed [data-veo-preset="subtitle"] {
1653
+ color: var(--veo-t-subtitle-color); font-size: var(--veo-t-subtitle-size);
1654
+ font-weight: var(--veo-t-subtitle-weight); text-align: var(--veo-t-subtitle-align);
1655
+ }
1656
+ .veo-card--themed [data-veo-preset="paragraph"] {
1657
+ color: var(--veo-t-paragraph-color); font-size: var(--veo-t-paragraph-size);
1658
+ font-weight: var(--veo-t-paragraph-weight); text-align: var(--veo-t-paragraph-align);
1659
+ }
1660
+ .veo-card--themed [data-veo-preset="small"] { color: var(--veo-t-paragraph-color); }
1661
+ .veo-card--themed [data-veo-preset="title"], .veo-card--themed [data-veo-preset="title"] * {
1662
+ font-family: var(--veo-t-title-font, var(--veo-font));
1663
+ }
1664
+ .veo-card--themed [data-veo-preset="subtitle"], .veo-card--themed [data-veo-preset="subtitle"] * {
1665
+ font-family: var(--veo-t-subtitle-font, var(--veo-font));
1666
+ }
1667
+ .veo-card--themed [data-veo-preset="paragraph"], .veo-card--themed [data-veo-preset="paragraph"] *,
1668
+ .veo-card--themed [data-veo-preset="small"], .veo-card--themed [data-veo-preset="small"] * {
1669
+ font-family: var(--veo-t-paragraph-font, var(--veo-font));
1670
+ }
1671
+ /* Formulario del tema (pesta\xF1a Formulario). Dentro de un .veo-form los tokens
1672
+ de texto \`--veo-t-*\` se REDEFINEN con \xABTexto principal\xBB: la pregunta es un
1673
+ bloque con preset (su tama\xF1o va inline con esas variables), as\xED toma los del
1674
+ formulario y no los del modal. Etiquetas y opciones = \xABTexto secundario\xBB. */
1675
+ .veo-card--themed-form .veo-form {
1676
+ background: var(--veo-f-bg); padding: var(--veo-f-pad);
1677
+ border: var(--veo-f-border, none); border-radius: var(--veo-f-radius, 8px);
1678
+ box-shadow: var(--veo-f-shadow, none);
1679
+ --veo-t-title-color: var(--veo-f-primary-color); --veo-t-title-size: var(--veo-f-primary-size);
1680
+ --veo-t-title-weight: var(--veo-f-primary-weight); --veo-t-title-align: var(--veo-f-primary-align);
1681
+ --veo-t-title-font: var(--veo-f-primary-font);
1682
+ --veo-t-subtitle-color: var(--veo-f-primary-color); --veo-t-subtitle-size: var(--veo-f-primary-size);
1683
+ --veo-t-subtitle-weight: var(--veo-f-primary-weight); --veo-t-subtitle-align: var(--veo-f-primary-align);
1684
+ --veo-t-subtitle-font: var(--veo-f-primary-font);
1685
+ --veo-t-paragraph-color: var(--veo-f-primary-color); --veo-t-paragraph-size: var(--veo-f-primary-size);
1686
+ --veo-t-paragraph-weight: var(--veo-f-primary-weight); --veo-t-paragraph-align: var(--veo-f-primary-align);
1687
+ --veo-t-paragraph-font: var(--veo-f-primary-font);
1688
+ }
1689
+ .veo-card--themed-form .veo-form-label,
1690
+ .veo-card--themed-form .veo-form-option,
1691
+ .veo-card--themed-form .veo-scale-label {
1692
+ color: var(--veo-f-secondary-color); font-weight: var(--veo-f-secondary-weight);
1693
+ font-family: var(--veo-f-secondary-font, var(--veo-font));
1694
+ }
1695
+ .veo-card--themed-form .veo-form-label,
1696
+ .veo-card--themed-form .veo-form-option {
1697
+ font-size: var(--veo-f-secondary-size); text-align: var(--veo-f-secondary-align);
1698
+ }
1699
+ .veo-card--themed-form .veo-form-input:focus { outline-color: var(--veo-f-accent); }
1700
+ .veo-card--themed-form .veo-form-option input { accent-color: var(--veo-f-accent); }
1701
+ .veo-card--themed-form .veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-f-accent)); }
1702
+ .veo-card--themed-form .veo-scale-active.veo-nps-btn {
1703
+ background: var(--veo-scale-color, var(--veo-f-accent)); color: var(--veo-f-on-accent);
1704
+ border-color: var(--veo-scale-color, var(--veo-f-accent));
1705
+ }
1638
1706
  .veo-guide-cta {
1639
1707
  background: var(--veo-cta-bg, var(--veo-primary));
1640
- color: var(--veo-cta-color, #fff); border: none;
1641
- padding: 9px 18px; border-radius: var(--veo-cta-radius, 8px);
1642
- font-size: var(--veo-cta-size, 14px); font-weight: 500; cursor: pointer;
1708
+ color: var(--veo-cta-color, #fff); border: var(--veo-cta-border, none);
1709
+ padding: var(--veo-cta-pad, 9px 18px); border-radius: var(--veo-cta-radius, 8px);
1710
+ font-size: var(--veo-cta-size, 14px); font-weight: var(--veo-cta-weight, 500); cursor: pointer;
1643
1711
  transition: opacity 120ms ease;
1644
1712
  }
1645
1713
  .veo-guide-cta:hover { opacity: 0.9; }
1646
1714
  /* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
1647
1715
  .veo-guide-cta--secondary {
1648
- background: transparent; color: var(--veo-primary);
1649
- border: 1px solid var(--veo-primary);
1716
+ background: var(--veo-cta2-bg, transparent); color: var(--veo-cta2-color, var(--veo-primary));
1717
+ border: var(--veo-cta2-border, 1px solid var(--veo-primary));
1718
+ padding: var(--veo-cta2-pad, var(--veo-cta-pad, 9px 18px));
1719
+ border-radius: var(--veo-cta2-radius, var(--veo-cta-radius, 8px));
1720
+ font-size: var(--veo-cta2-size, var(--veo-cta-size, 14px));
1721
+ font-weight: var(--veo-cta2-weight, var(--veo-cta-weight, 500));
1722
+ }
1723
+ .veo-guide-cta--link {
1724
+ background: transparent; color: var(--veo-cta3-color, var(--veo-primary));
1725
+ border: var(--veo-cta3-border, none); padding: var(--veo-cta3-pad, 0);
1726
+ border-radius: var(--veo-cta3-radius, 0);
1727
+ font-size: var(--veo-cta3-size, var(--veo-cta-size, 14px));
1728
+ font-weight: var(--veo-cta3-weight, var(--veo-cta-weight, 500));
1650
1729
  }
1651
- .veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
1652
1730
  .veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
1653
1731
  .veo-guide-cta:active { transform: translateY(1px); }
1654
1732
  .veo-guide-close {
@@ -2030,8 +2108,18 @@ function positionArrow(arrowEl, placement, data) {
2030
2108
  if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
2031
2109
  if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
2032
2110
  arrowEl.style.setProperty(side, "-6px");
2111
+ const width = arrowEl.dataset.veoBorder;
2112
+ if (width) {
2113
+ for (const edge of ["top", "right", "bottom", "left"]) {
2114
+ arrowEl.style.setProperty(
2115
+ `border-${edge}-width`,
2116
+ OUTER_EDGES[side].includes(edge) ? `${width}px` : "0"
2117
+ );
2118
+ }
2119
+ arrowEl.style.zIndex = "1";
2120
+ }
2033
2121
  }
2034
- var STATIC_SIDE;
2122
+ var STATIC_SIDE, OUTER_EDGES;
2035
2123
  var init_floating_arrow = __esm({
2036
2124
  "src/plugins/guides/renderers/floating-arrow.ts"() {
2037
2125
  STATIC_SIDE = {
@@ -2040,6 +2128,12 @@ var init_floating_arrow = __esm({
2040
2128
  left: "right",
2041
2129
  right: "left"
2042
2130
  };
2131
+ OUTER_EDGES = {
2132
+ top: ["top", "left"],
2133
+ right: ["top", "right"],
2134
+ bottom: ["bottom", "right"],
2135
+ left: ["bottom", "left"]
2136
+ };
2043
2137
  }
2044
2138
  });
2045
2139
  function readBadgeConfig(step) {
@@ -3213,6 +3307,173 @@ var init_form_content = __esm({
3213
3307
  }
3214
3308
  });
3215
3309
 
3310
+ // src/plugins/guides/guide-theme.ts
3311
+ function channels(hex) {
3312
+ const h = hex.replace("#", "");
3313
+ const full = h.length === 3 ? [...h].map((x) => x + x).join("") : h.slice(0, 6);
3314
+ const ch = (i) => Number.parseInt(full.slice(i, i + 2), 16) || 0;
3315
+ return [ch(0), ch(2), ch(4)];
3316
+ }
3317
+ function boxShadow(shadow, hex) {
3318
+ if (shadow === "none") return "none";
3319
+ const spec = SHADOW_SPEC[shadow];
3320
+ const [r, g, b] = channels(hex);
3321
+ return `0 ${spec.y}px ${spec.blur}px rgba(${r}, ${g}, ${b}, ${spec.alpha})`;
3322
+ }
3323
+ function readableOn(hex) {
3324
+ const [r, g, b] = channels(hex);
3325
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.6 ? "#111827" : "#ffffff";
3326
+ }
3327
+ function themeModeFor(theme, design) {
3328
+ if (!theme || design.theme !== WORKSPACE_THEME_ID) return null;
3329
+ return (design.colorMode === "dark" ? theme.dark : theme.light) ?? null;
3330
+ }
3331
+ function themeRowJustify(mode, inline, preset) {
3332
+ const box = inline ? mode.embedded : mode.overlay;
3333
+ if (box.align === "distribute") return null;
3334
+ const button = preset === "secondary" ? mode.buttons?.secondary : preset === "link" ? mode.buttons?.tertiary : preset === void 0 || preset === null || preset === "primary" ? mode.buttons?.primary : void 0;
3335
+ return button ? JUSTIFY[button.align] ?? null : null;
3336
+ }
3337
+ function appFont(ref, doc) {
3338
+ const view = doc.defaultView;
3339
+ if (!view) return null;
3340
+ if (ref === "inherit") return view.getComputedStyle(doc.body).fontFamily || null;
3341
+ if (ref === "app-heading") {
3342
+ const heading = Array.from(doc.querySelectorAll("h1,h2,h3")).find(
3343
+ (el2) => el2.getClientRects().length > 0
3344
+ );
3345
+ return view.getComputedStyle(heading ?? doc.body).fontFamily || null;
3346
+ }
3347
+ return ref;
3348
+ }
3349
+ function themeContainerDesign(mode, inline, tooltip = false) {
3350
+ const box = inline ? mode.embedded : mode.overlay;
3351
+ const tip = tooltip ? mode.tooltip : void 0;
3352
+ const padding = tip?.padding ?? box.padding;
3353
+ const out = {
3354
+ // El embebido va en el flujo de la página: sin fondo propio.
3355
+ background: tip ? tip.background : inline ? "transparent" : box.background ?? mode.colors.background,
3356
+ color: mode.colors.text,
3357
+ paddingTop: padding.top,
3358
+ paddingRight: padding.right,
3359
+ paddingBottom: padding.bottom,
3360
+ paddingLeft: padding.left,
3361
+ borderColor: tip?.borderColor ?? box.borderColor,
3362
+ borderWidth: tip?.borderWidth ?? box.borderWidth,
3363
+ radius: tip?.borderRadius ?? box.borderRadius
3364
+ };
3365
+ const shadow = tip?.shadow ?? box.shadow;
3366
+ const shadowColor = tip?.shadowColor ?? box.shadowColor;
3367
+ if (shadow === "none") {
3368
+ Object.assign(out, { shadowColor: "transparent", shadowBlur: 0 });
3369
+ } else {
3370
+ const spec = SHADOW_SPEC[shadow];
3371
+ const [r, g, b] = channels(shadowColor);
3372
+ Object.assign(out, {
3373
+ shadowX: 0,
3374
+ shadowY: spec.y,
3375
+ shadowBlur: spec.blur,
3376
+ shadowSpread: 0,
3377
+ shadowColor: `rgba(${r}, ${g}, ${b}, ${spec.alpha})`
3378
+ });
3379
+ }
3380
+ return out;
3381
+ }
3382
+ function applyThemeVars(card, mode, inline, tooltip = false) {
3383
+ const doc = card.ownerDocument ?? document;
3384
+ const box = inline ? mode.embedded : mode.overlay;
3385
+ const set = (name, value) => {
3386
+ if (value) card.style.setProperty(name, value);
3387
+ };
3388
+ card.classList.add("veo-card--themed");
3389
+ set("--veo-primary", mode.colors.accent);
3390
+ set("--veo-cta-color", readableOn(mode.colors.accent));
3391
+ const primary = mode.buttons?.primary;
3392
+ const publish = (prefix, b) => {
3393
+ if (!b) return;
3394
+ const p = b.padding;
3395
+ set(`${prefix}-bg`, b.background);
3396
+ set(`${prefix}-color`, b.color);
3397
+ set(`${prefix}-pad`, `${p.top}px ${p.right}px ${p.bottom}px ${p.left}px`);
3398
+ set(`${prefix}-size`, `${b.fontSize}px`);
3399
+ set(`${prefix}-weight`, String(b.fontWeight));
3400
+ set(`${prefix}-radius`, `${b.borderRadius}px`);
3401
+ set(
3402
+ `${prefix}-border`,
3403
+ b.borderWidth > 0 ? `${b.borderWidth}px solid ${b.borderColor}` : "none"
3404
+ );
3405
+ };
3406
+ publish("--veo-cta", primary);
3407
+ publish("--veo-cta2", mode.buttons?.secondary);
3408
+ publish("--veo-cta3", mode.buttons?.tertiary);
3409
+ set("--veo-text", mode.colors.text);
3410
+ set("--veo-text-secondary", mode.colors.textMuted);
3411
+ set("--veo-font", appFont("inherit", doc));
3412
+ set(
3413
+ "--veo-actions-justify",
3414
+ box.align === "distribute" || !primary ? JUSTIFY[box.align] : JUSTIFY[primary.align]
3415
+ );
3416
+ const form = mode.form;
3417
+ if (form) {
3418
+ card.classList.add("veo-card--themed-form");
3419
+ const fp = form.padding;
3420
+ set("--veo-f-bg", form.background);
3421
+ set("--veo-f-accent", form.accent);
3422
+ set("--veo-f-on-accent", form.onAccent);
3423
+ set("--veo-f-pad", `${fp.top}px ${fp.right}px ${fp.bottom}px ${fp.left}px`);
3424
+ if (typeof form.borderWidth === "number" && form.borderColor) {
3425
+ set(
3426
+ "--veo-f-border",
3427
+ form.borderWidth > 0 ? `${form.borderWidth}px solid ${form.borderColor}` : "none"
3428
+ );
3429
+ }
3430
+ if (typeof form.borderRadius === "number") set("--veo-f-radius", `${form.borderRadius}px`);
3431
+ if (form.shadow && form.shadowColor) {
3432
+ set("--veo-f-shadow", boxShadow(form.shadow, form.shadowColor));
3433
+ }
3434
+ for (const [name, t] of [
3435
+ ["primary", form.text.primary],
3436
+ ["secondary", form.text.secondary]
3437
+ ]) {
3438
+ set(`--veo-f-${name}-color`, t.color);
3439
+ set(`--veo-f-${name}-size`, `${t.fontSize}px`);
3440
+ set(`--veo-f-${name}-weight`, String(t.fontWeight));
3441
+ set(`--veo-f-${name}-align`, t.align);
3442
+ set(`--veo-f-${name}-font`, appFont(t.fontFamily, doc));
3443
+ }
3444
+ }
3445
+ const tip = tooltip ? mode.tooltip?.text : void 0;
3446
+ const tokens = [
3447
+ ["title", tip?.primary ?? mode.text.title],
3448
+ ["subtitle", tip?.secondary ?? mode.text.subtitle],
3449
+ ["paragraph", tip?.secondary ?? mode.text.paragraph]
3450
+ ];
3451
+ for (const [name, t] of tokens) {
3452
+ set(`--veo-t-${name}-color`, t.color);
3453
+ set(`--veo-t-${name}-size`, `${t.fontSize}px`);
3454
+ set(`--veo-t-${name}-weight`, String(t.fontWeight));
3455
+ set(`--veo-t-${name}-align`, t.align);
3456
+ set(`--veo-t-${name}-font`, appFont(t.fontFamily, doc));
3457
+ }
3458
+ }
3459
+ var WORKSPACE_THEME_ID, SHADOW_SPEC, JUSTIFY;
3460
+ var init_guide_theme = __esm({
3461
+ "src/plugins/guides/guide-theme.ts"() {
3462
+ WORKSPACE_THEME_ID = "workspace";
3463
+ SHADOW_SPEC = {
3464
+ soft: { y: 4, blur: 14, alpha: 0.12 },
3465
+ medium: { y: 12, blur: 32, alpha: 0.22 },
3466
+ strong: { y: 24, blur: 64, alpha: 0.38 }
3467
+ };
3468
+ JUSTIFY = {
3469
+ left: "flex-start",
3470
+ center: "center",
3471
+ right: "flex-end",
3472
+ distribute: "space-between"
3473
+ };
3474
+ }
3475
+ });
3476
+
3216
3477
  // src/plugins/guides/layout.ts
3217
3478
  function groupLayout(blocks) {
3218
3479
  const rows = /* @__PURE__ */ new Map();
@@ -3673,6 +3934,7 @@ function makeCaret(doc, design) {
3673
3934
  if (typeof design.borderWidth === "number" && design.borderWidth > 0) {
3674
3935
  const bc = typeof design.borderColor === "string" ? design.borderColor : "#bbbbbb";
3675
3936
  s.border = `${design.borderWidth}px solid ${bc}`;
3937
+ caret.dataset.veoBorder = String(design.borderWidth);
3676
3938
  }
3677
3939
  return caret;
3678
3940
  }
@@ -3765,6 +4027,7 @@ var init_composite_renderer = __esm({
3765
4027
  init_checklist_block();
3766
4028
  init_code_block();
3767
4029
  init_form_content();
4030
+ init_guide_theme();
3768
4031
  init_inline_host();
3769
4032
  init_layout();
3770
4033
  init_rich_text();
@@ -3786,6 +4049,11 @@ var init_composite_renderer = __esm({
3786
4049
  bottom: "flex-end"
3787
4050
  };
3788
4051
  CompositeRenderer = class extends BaseRenderer {
4052
+ constructor() {
4053
+ super(...arguments);
4054
+ /** Alineación de filas de botones según el tema del workspace (null = sin tema). */
4055
+ this.themeRowJustify = null;
4056
+ }
3789
4057
  async render(context) {
3790
4058
  const idx = context.nav?.stepIndex ?? 0;
3791
4059
  const step = context.guide.guideSteps[idx];
@@ -3816,7 +4084,13 @@ var init_composite_renderer = __esm({
3816
4084
  if (typeof design.zIndex === "number") host.style.zIndex = String(design.zIndex);
3817
4085
  const card = doc.createElement("div");
3818
4086
  card.className = "veo-card";
3819
- applyCompositeDesign(card, design);
4087
+ const themeMode = themeModeFor(context.guide.theme, design);
4088
+ const inline = step.presentation === "inline";
4089
+ const tooltipLike = !inline && (badgeMode || step.position === "relative-to-element");
4090
+ if (themeMode) applyThemeVars(card, themeMode, inline, tooltipLike);
4091
+ this.themeRowJustify = themeMode ? (preset) => themeRowJustify(themeMode, inline, preset) : null;
4092
+ const cardDesign = themeMode ? { ...themeContainerDesign(themeMode, inline, tooltipLike), ...design } : design;
4093
+ applyCompositeDesign(card, cardDesign);
3820
4094
  if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
3821
4095
  card.setAttribute("role", "dialog");
3822
4096
  card.setAttribute("aria-label", design.ariaLabel.trim());
@@ -3887,7 +4161,7 @@ var init_composite_renderer = __esm({
3887
4161
  });
3888
4162
  }
3889
4163
  card.appendChild(closeBtn);
3890
- reserveCloseSpace(card, blocks, closePos, design);
4164
+ reserveCloseSpace(card, blocks, closePos, cardDesign);
3891
4165
  }
3892
4166
  if (badgeMode && anchor) {
3893
4167
  closeTooltip = this.renderBadgeMode(
@@ -3900,7 +4174,8 @@ var init_composite_renderer = __esm({
3900
4174
  doc,
3901
4175
  context,
3902
4176
  idx,
3903
- design
4177
+ // Con el tema ya fundido: la flecha del tooltip toma su fondo y borde.
4178
+ cardDesign
3904
4179
  );
3905
4180
  return;
3906
4181
  }
@@ -3923,7 +4198,7 @@ var init_composite_renderer = __esm({
3923
4198
  }
3924
4199
  const wrapper = doc.createElement("div");
3925
4200
  wrapper.className = "veo-composite-anchored";
3926
- const caretEl = design.caret === false ? null : makeCaret(doc, design);
4201
+ const caretEl = design.caret === false ? null : makeCaret(doc, cardDesign);
3927
4202
  if (caretEl) wrapper.appendChild(caretEl);
3928
4203
  wrapper.appendChild(card);
3929
4204
  root.appendChild(wrapper);
@@ -3983,6 +4258,7 @@ var init_composite_renderer = __esm({
3983
4258
  const row = doc.createElement("div");
3984
4259
  row.className = "veo-guide-actions";
3985
4260
  let rowAlign;
4261
+ const firstPreset = b.style?.preset;
3986
4262
  while (i < cell.length && cell[i]?.block.type === "button") {
3987
4263
  const { block: bb, index: bi } = cell[i];
3988
4264
  if (!rowAlign) {
@@ -3993,6 +4269,10 @@ var init_composite_renderer = __esm({
3993
4269
  i++;
3994
4270
  }
3995
4271
  if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
4272
+ else {
4273
+ const themed = this.themeRowJustify?.(firstPreset);
4274
+ if (themed) row.style.justifyContent = themed;
4275
+ }
3996
4276
  container.appendChild(row);
3997
4277
  continue;
3998
4278
  }
@@ -4959,7 +5239,8 @@ function normalize(input) {
4959
5239
  guideSteps: input.guideSteps,
4960
5240
  activationRules,
4961
5241
  displayFrequency: "always",
4962
- displayPriority: 0
5242
+ displayPriority: 0,
5243
+ ...input.theme ? { theme: input.theme } : {}
4963
5244
  };
4964
5245
  }
4965
5246
  function createSingleStepRenderer(type) {
@@ -5410,14 +5691,19 @@ function injectBuilderChrome(opts) {
5410
5691
  let previewOn = false;
5411
5692
  let hiddenOn = false;
5412
5693
  let heightCb = null;
5413
- const height = () => CHROME_TOPBAR_H + (collapsed ? 0 : CHROME_STEPS_H);
5694
+ let tagging = false;
5695
+ const height = () => CHROME_TOPBAR_H + (collapsed || tagging ? 0 : CHROME_STEPS_H);
5414
5696
  const applyHeight = () => {
5415
5697
  const h = previewOn || hiddenOn ? 0 : height();
5416
5698
  html.style.marginTop = dock === "top" ? `${h}px` : prevMarginTop;
5417
5699
  html.style.marginBottom = dock === "bottom" ? `${h}px` : prevMarginBottom;
5418
5700
  root.style.setProperty("--veo-chrome-h", `${height()}px`);
5419
5701
  root.classList.toggle("is-bottom", dock === "bottom");
5420
- stepsBar.hidden = collapsed;
5702
+ stepsBar.hidden = collapsed || tagging;
5703
+ togglePill.hidden = tagging;
5704
+ preview.hidden = tagging;
5705
+ saveGroup.hidden = tagging;
5706
+ showPanel.title = tagging ? "Mostrar el panel" : "Mostrar el editor";
5421
5707
  const open = dock === "top" ? "chevronUp" : "chevronDown";
5422
5708
  const closedIcon = dock === "top" ? "chevronDown" : "chevronUp";
5423
5709
  togglePill.innerHTML = `<span>${collapsed ? "Mostrar pasos" : "Ocultar pasos"}</span>${svg(
@@ -5470,8 +5756,13 @@ function injectBuilderChrome(opts) {
5470
5756
  const render = () => {
5471
5757
  const s = current;
5472
5758
  if (!s) return;
5759
+ const nextTagging = s.variant === "tagging";
5760
+ if (nextTagging !== tagging) {
5761
+ tagging = nextTagging;
5762
+ applyHeight();
5763
+ }
5473
5764
  const date = shortDate(s.createdAt);
5474
- nameEl.textContent = `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5765
+ nameEl.textContent = tagging ? s.guideName.trim() || "Modo tagging" : `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5475
5766
  if (s.status) {
5476
5767
  statusEl.hidden = false;
5477
5768
  statusEl.textContent = s.status.label;
@@ -5591,6 +5882,8 @@ var init_builder_chrome = __esm({
5591
5882
  font-size: 14px; color: #1c1917;
5592
5883
  }
5593
5884
  .veo-chrome[hidden] { display: none; }
5885
+ /* Modo tagging oculta controles por atributo: gana a los display de sus clases. */
5886
+ .veo-chrome [hidden], .veo-steps-toggle[hidden] { display: none !important; }
5594
5887
  .veo-chrome.is-bottom { top: auto; bottom: 0; flex-direction: column-reverse; }
5595
5888
  .veo-chrome.is-bottom .veo-tb-menu { top: auto; bottom: calc(100% + 8px); }
5596
5889
  .veo-chrome.is-bottom .veo-steps { border-bottom: 0; border-top: 1px solid #d6cfc8; }
@@ -5741,9 +6034,9 @@ function injectBuilderPanel(opts) {
5741
6034
  card.className = "veo-panel";
5742
6035
  let topOffset = opts.topOffset ?? 0;
5743
6036
  let left = 40;
5744
- let top = topOffset + MARGIN;
6037
+ let top2 = topOffset + MARGIN;
5745
6038
  card.style.left = `${left}px`;
5746
- card.style.top = `${top}px`;
6039
+ card.style.top = `${top2}px`;
5747
6040
  card.style.width = `${PANEL_W}px`;
5748
6041
  card.style.height = `${PANEL_H}px`;
5749
6042
  const header = document.createElement("div");
@@ -5787,7 +6080,7 @@ function injectBuilderPanel(opts) {
5787
6080
  if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
5788
6081
  card.style.minHeight = "0";
5789
6082
  card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
5790
- card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
6083
+ card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top2 - MARGIN)}px`;
5791
6084
  } else if (compactRestore) {
5792
6085
  card.style.minHeight = "";
5793
6086
  card.style.width = `${compactRestore.w}px`;
@@ -5798,19 +6091,19 @@ function injectBuilderPanel(opts) {
5798
6091
  maximize.addEventListener("click", () => {
5799
6092
  maximized = !maximized;
5800
6093
  if (maximized) {
5801
- restore = { left, top, w: card.offsetWidth, h: card.offsetHeight };
6094
+ restore = { left, top: top2, w: card.offsetWidth, h: card.offsetHeight };
5802
6095
  left = MARGIN;
5803
- top = topOffset + MARGIN;
6096
+ top2 = topOffset + MARGIN;
5804
6097
  card.style.left = `${left}px`;
5805
- card.style.top = `${top}px`;
6098
+ card.style.top = `${top2}px`;
5806
6099
  card.style.width = `${Math.max(MIN_W, window.innerWidth - MARGIN * 2)}px`;
5807
- card.style.height = `${Math.max(MIN_H, window.innerHeight - top - MARGIN)}px`;
6100
+ card.style.height = `${Math.max(MIN_H, window.innerHeight - top2 - MARGIN)}px`;
5808
6101
  maximize.title = "Restaurar";
5809
6102
  } else if (restore) {
5810
6103
  left = restore.left;
5811
- top = restore.top;
6104
+ top2 = restore.top;
5812
6105
  card.style.left = `${left}px`;
5813
- card.style.top = `${top}px`;
6106
+ card.style.top = `${top2}px`;
5814
6107
  card.style.width = `${restore.w}px`;
5815
6108
  card.style.height = `${restore.h}px`;
5816
6109
  maximize.title = "Maximizar";
@@ -5846,13 +6139,13 @@ function injectBuilderPanel(opts) {
5846
6139
  const sx = e.clientX;
5847
6140
  const sy = e.clientY;
5848
6141
  const ol = left;
5849
- const ot = top;
6142
+ const ot = top2;
5850
6143
  iframe.style.pointerEvents = "none";
5851
6144
  const move = (ev) => {
5852
6145
  left = clamp3(ol + (ev.clientX - sx), 0, Math.max(0, window.innerWidth - card.offsetWidth));
5853
- top = clamp3(ot + (ev.clientY - sy), topOffset, Math.max(topOffset, window.innerHeight - 40));
6146
+ top2 = clamp3(ot + (ev.clientY - sy), topOffset, Math.max(topOffset, window.innerHeight - 40));
5854
6147
  card.style.left = `${left}px`;
5855
- card.style.top = `${top}px`;
6148
+ card.style.top = `${top2}px`;
5856
6149
  };
5857
6150
  const up = () => {
5858
6151
  window.removeEventListener("mousemove", move);
@@ -5898,9 +6191,9 @@ function injectBuilderPanel(opts) {
5898
6191
  isVisible: () => visible,
5899
6192
  setTopOffset: (px) => {
5900
6193
  topOffset = px;
5901
- if (top < topOffset + MARGIN) {
5902
- top = topOffset + MARGIN;
5903
- card.style.top = `${top}px`;
6194
+ if (top2 < topOffset + MARGIN) {
6195
+ top2 = topOffset + MARGIN;
6196
+ card.style.top = `${top2}px`;
5904
6197
  }
5905
6198
  }
5906
6199
  };
@@ -6081,9 +6374,9 @@ function attachModal(opts, shadow, card) {
6081
6374
  const span = window.innerWidth - w;
6082
6375
  return span <= 0 ? 0.5 : round3(clamp4(left / span, 0, 1));
6083
6376
  };
6084
- const posYFor = (top, h) => {
6377
+ const posYFor = (top2, h) => {
6085
6378
  const span = window.innerHeight - h;
6086
- return span <= 0 ? 0.5 : round3(clamp4(top / span, 0, 1));
6379
+ return span <= 0 ? 0.5 : round3(clamp4(top2 / span, 0, 1));
6087
6380
  };
6088
6381
  const onCardDown = (e) => {
6089
6382
  if (isInteractive(e.target)) return;
@@ -6223,6 +6516,65 @@ var init_design_manipulator = __esm({
6223
6516
  }
6224
6517
  });
6225
6518
 
6519
+ // src/plugins/builder/interactive-elements.ts
6520
+ function isInteractiveElement(element) {
6521
+ try {
6522
+ return element.matches(INTERACTIVE_SELECTOR);
6523
+ } catch {
6524
+ return false;
6525
+ }
6526
+ }
6527
+ function closestInteractive(element) {
6528
+ try {
6529
+ return element.closest(INTERACTIVE_SELECTOR);
6530
+ } catch {
6531
+ return null;
6532
+ }
6533
+ }
6534
+ function elementLabelText(element, max = 120) {
6535
+ const clean = (raw) => (raw ?? "").replace(EMOJI_RE, "").replace(/\s+/g, " ").trim();
6536
+ const text = clean(element.textContent);
6537
+ if (text) return text.slice(0, max);
6538
+ const fallback = clean(element.getAttribute("aria-label")) || clean(element.getAttribute("title"));
6539
+ return fallback.slice(0, max);
6540
+ }
6541
+ var INTERACTIVE_ROLES, INTERACTIVE_SELECTOR, EMOJI_RE;
6542
+ var init_interactive_elements = __esm({
6543
+ "src/plugins/builder/interactive-elements.ts"() {
6544
+ INTERACTIVE_ROLES = [
6545
+ "button",
6546
+ "link",
6547
+ "tab",
6548
+ "menuitem",
6549
+ "menuitemcheckbox",
6550
+ "menuitemradio",
6551
+ "checkbox",
6552
+ "radio",
6553
+ "switch",
6554
+ "option",
6555
+ "combobox",
6556
+ "textbox",
6557
+ "searchbox",
6558
+ "slider",
6559
+ "spinbutton"
6560
+ ];
6561
+ INTERACTIVE_SELECTOR = [
6562
+ "button",
6563
+ "a[href]",
6564
+ 'input:not([type="hidden"])',
6565
+ "select",
6566
+ "textarea",
6567
+ "form",
6568
+ "summary",
6569
+ '[contenteditable="true"]',
6570
+ "[onclick]",
6571
+ "[data-veo-tag]",
6572
+ ...INTERACTIVE_ROLES.map((r) => `[role="${r}"]`)
6573
+ ].join(", ");
6574
+ EMOJI_RE = /\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u{FE0F}|\u{200D}|\u{20E3}/gu;
6575
+ }
6576
+ });
6577
+
6226
6578
  // src/plugins/builder/element-picker.ts
6227
6579
  function startElementPicker(options) {
6228
6580
  if (!hasDocument()) {
@@ -6241,6 +6593,7 @@ var init_element_picker = __esm({
6241
6593
  "src/plugins/builder/element-picker.ts"() {
6242
6594
  init_safe_env();
6243
6595
  init_selector_builder();
6596
+ init_interactive_elements();
6244
6597
  PICKER_Z = 2147483646;
6245
6598
  PICKER_HOST_ATTR = "data-veo-picker";
6246
6599
  DEFAULT_MAX_ANCESTORS = 5;
@@ -6310,21 +6663,24 @@ var init_element_picker = __esm({
6310
6663
  this.label = null;
6311
6664
  this.current = null;
6312
6665
  this.maxAncestors = DEFAULT_MAX_ANCESTORS;
6666
+ this.only = null;
6313
6667
  this.onPick = null;
6314
6668
  this.onCancel = null;
6315
6669
  this.onMove = (event) => {
6316
- const target = event.target;
6317
- if (!(target instanceof Element) || this.isOwn(target)) return;
6670
+ if (!(event.target instanceof Element) || this.isOwn(event.target)) return;
6671
+ const target = this.resolveTarget(event.target);
6318
6672
  this.current = target;
6319
- this.draw(target);
6673
+ if (target) this.draw(target);
6674
+ else this.hideBox();
6320
6675
  };
6321
6676
  this.onClick = (event) => {
6322
6677
  if (event.altKey) return;
6323
- const target = this.current ?? (event.target instanceof Element ? event.target : null);
6324
- if (!target || this.isOwn(target)) return;
6678
+ if (this.isOwn(event.target)) return;
6325
6679
  event.preventDefault();
6326
6680
  event.stopPropagation();
6327
6681
  event.stopImmediatePropagation();
6682
+ const target = this.current ?? this.resolveTarget(event.target);
6683
+ if (!target) return;
6328
6684
  const picked = this.describe(target);
6329
6685
  const cb = this.onPick;
6330
6686
  this.stop();
@@ -6351,6 +6707,7 @@ var init_element_picker = __esm({
6351
6707
  start(options) {
6352
6708
  this.stop();
6353
6709
  this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
6710
+ this.only = options.only ?? null;
6354
6711
  this.onPick = options.onPick;
6355
6712
  this.onCancel = options.onCancel ?? null;
6356
6713
  const host = document.createElement("div");
@@ -6413,9 +6770,19 @@ var init_element_picker = __esm({
6413
6770
  this.box = null;
6414
6771
  this.label = null;
6415
6772
  this.current = null;
6773
+ this.only = null;
6416
6774
  this.onPick = null;
6417
6775
  this.onCancel = null;
6418
6776
  }
6777
+ /** Elemento que se elegiría para el nodo bajo el cursor (o null si no hay). */
6778
+ resolveTarget(target) {
6779
+ if (!(target instanceof Element) || this.isOwn(target)) return null;
6780
+ return this.only === "interactive" ? closestInteractive(target) : target;
6781
+ }
6782
+ hideBox() {
6783
+ if (this.box) this.box.style.display = "none";
6784
+ if (this.label) this.label.style.display = "none";
6785
+ }
6419
6786
  /** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
6420
6787
  isOwn(target) {
6421
6788
  return target instanceof Node && this.host !== null && this.host.contains(target);
@@ -6452,7 +6819,7 @@ var init_element_picker = __esm({
6452
6819
  tag: element.tagName.toLowerCase(),
6453
6820
  id: element.id || null,
6454
6821
  classes: filterHumanClasses(element),
6455
- text: (element.textContent ?? "").trim().slice(0, 120),
6822
+ text: elementLabelText(element),
6456
6823
  rect: {
6457
6824
  x: Math.round(rect.left),
6458
6825
  y: Math.round(rect.top),
@@ -7448,7 +7815,7 @@ function initBuilderMode() {
7448
7815
  if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7449
7816
  }
7450
7817
  panel?.setTitle(
7451
- 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"
7818
+ 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")
7452
7819
  );
7453
7820
  break;
7454
7821
  case "panel-visibility":
@@ -7486,7 +7853,8 @@ function initBuilderMode() {
7486
7853
  post({ type: "pick-cancelled" });
7487
7854
  },
7488
7855
  ...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
7489
- ...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
7856
+ ...cmd.hint !== void 0 ? { hint: cmd.hint } : {},
7857
+ ...cmd.only !== void 0 ? { only: cmd.only } : {}
7490
7858
  });
7491
7859
  break;
7492
7860
  }
@@ -7495,6 +7863,15 @@ function initBuilderMode() {
7495
7863
  picker = null;
7496
7864
  chrome?.setHidden(false);
7497
7865
  break;
7866
+ case "scan-page":
7867
+ post({ type: "page-scan", payload: scanPage(cmd.selectors) });
7868
+ break;
7869
+ case "highlight":
7870
+ setHighlight(cmd.selector);
7871
+ break;
7872
+ case "show-confirmation":
7873
+ showConfirmation(cmd.payload);
7874
+ break;
7498
7875
  case "preview":
7499
7876
  if (!cmd.guide) break;
7500
7877
  if (manipulator?.isDragging()) pendingPreview = cmd;
@@ -7579,10 +7956,154 @@ function initBuilderMode() {
7579
7956
  post({ type: "closed" });
7580
7957
  panel?.teardown();
7581
7958
  panel = null;
7959
+ highlightBox?.remove();
7960
+ highlightBox = null;
7961
+ confirmation?.remove();
7962
+ confirmation = null;
7963
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7964
+ confirmationKey = null;
7582
7965
  chrome?.teardown();
7583
7966
  chrome = null;
7584
7967
  clearBuilderToken();
7585
7968
  };
7969
+ const scanPage = (selectors) => {
7970
+ const present = selectors.filter((sel) => {
7971
+ try {
7972
+ return document.querySelector(sel) !== null;
7973
+ } catch {
7974
+ return false;
7975
+ }
7976
+ });
7977
+ const taggedEls = /* @__PURE__ */ new Set();
7978
+ for (const sel of present) {
7979
+ try {
7980
+ for (const e of Array.from(document.querySelectorAll(sel))) taggedEls.add(e);
7981
+ } catch {
7982
+ }
7983
+ }
7984
+ const seen = /* @__PURE__ */ new Set();
7985
+ const candidates = [];
7986
+ const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
7987
+ for (const element of Array.from(nodes)) {
7988
+ if (candidates.length >= 200) break;
7989
+ if (element.closest("[data-veo-builder-chrome],[data-veo-builder-panel],[data-veo-picker]")) {
7990
+ continue;
7991
+ }
7992
+ if (element.getClientRects().length === 0) continue;
7993
+ let tagged = false;
7994
+ for (let n = element; n; n = n.parentElement) {
7995
+ if (taggedEls.has(n)) {
7996
+ tagged = true;
7997
+ break;
7998
+ }
7999
+ }
8000
+ if (tagged) continue;
8001
+ const selector = buildSelectorPath(element, 5);
8002
+ if (!selector || seen.has(selector)) continue;
8003
+ seen.add(selector);
8004
+ candidates.push({
8005
+ selector,
8006
+ shortSelector: buildSelectorPath(element, 0),
8007
+ tag: element.tagName.toLowerCase(),
8008
+ id: element.id || null,
8009
+ classes: filterHumanClasses(element),
8010
+ text: elementLabelText(element)
8011
+ });
8012
+ }
8013
+ const { pathname, hash } = window.location;
8014
+ const routePath = /^#\/./.test(hash) ? `${pathname}${hash}` : pathname;
8015
+ return { pagePath: pathname, routePath, candidates, present };
8016
+ };
8017
+ let confirmation = null;
8018
+ let confirmationKey = null;
8019
+ const closeConfirmation = () => {
8020
+ if (!confirmation) return;
8021
+ confirmation.remove();
8022
+ confirmation = null;
8023
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
8024
+ confirmationKey = null;
8025
+ panel?.setVisible(true);
8026
+ post({ type: "confirmation-closed" });
8027
+ };
8028
+ const showConfirmation = (card) => {
8029
+ if (confirmation) {
8030
+ confirmation.remove();
8031
+ confirmation = null;
8032
+ }
8033
+ panel?.setVisible(false);
8034
+ const host = document.createElement("div");
8035
+ host.setAttribute("data-veo-builder-chrome", "");
8036
+ host.setAttribute("data-veo-confirmation", "");
8037
+ const shadow = host.attachShadow({ mode: "open" });
8038
+ const style = document.createElement("style");
8039
+ style.textContent = CONFIRMATION_STYLES;
8040
+ const backdrop = document.createElement("div");
8041
+ backdrop.className = "veo-confirm-backdrop";
8042
+ const dialog = document.createElement("div");
8043
+ dialog.className = "veo-confirm";
8044
+ dialog.setAttribute("role", "dialog");
8045
+ dialog.setAttribute("aria-modal", "true");
8046
+ dialog.setAttribute("aria-label", card.title);
8047
+ const badge = document.createElement("div");
8048
+ badge.className = "veo-confirm-check";
8049
+ 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>';
8050
+ const title = document.createElement("h2");
8051
+ title.className = "veo-confirm-title";
8052
+ title.textContent = card.title;
8053
+ const message = document.createElement("p");
8054
+ message.className = "veo-confirm-msg";
8055
+ message.textContent = card.message;
8056
+ const rows = document.createElement("dl");
8057
+ rows.className = "veo-confirm-rows";
8058
+ for (const r of card.rows) {
8059
+ const row = document.createElement("div");
8060
+ row.className = "veo-confirm-row";
8061
+ const dt = document.createElement("dt");
8062
+ dt.textContent = r.label;
8063
+ const dd = document.createElement("dd");
8064
+ dd.textContent = r.value;
8065
+ row.append(dt, dd);
8066
+ rows.appendChild(row);
8067
+ }
8068
+ const button = document.createElement("button");
8069
+ button.type = "button";
8070
+ button.className = "veo-confirm-btn";
8071
+ button.textContent = card.buttonLabel ?? "Listo";
8072
+ button.addEventListener("click", closeConfirmation);
8073
+ dialog.append(badge, title, message);
8074
+ if (card.rows.length > 0) dialog.appendChild(rows);
8075
+ dialog.appendChild(button);
8076
+ shadow.append(style, backdrop, dialog);
8077
+ document.body.appendChild(host);
8078
+ confirmation = host;
8079
+ confirmationKey = (e) => {
8080
+ if (e.key !== "Escape") return;
8081
+ e.preventDefault();
8082
+ e.stopPropagation();
8083
+ closeConfirmation();
8084
+ };
8085
+ document.addEventListener("keydown", confirmationKey, true);
8086
+ button.focus();
8087
+ };
8088
+ let highlightBox = null;
8089
+ const setHighlight = (selector) => {
8090
+ highlightBox?.remove();
8091
+ highlightBox = null;
8092
+ if (!selector) return;
8093
+ let target = null;
8094
+ try {
8095
+ target = document.querySelector(selector);
8096
+ } catch {
8097
+ return;
8098
+ }
8099
+ if (!target) return;
8100
+ const r = target.getBoundingClientRect();
8101
+ const box = document.createElement("div");
8102
+ box.setAttribute("data-veo-builder-chrome", "");
8103
+ 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;`;
8104
+ document.body.appendChild(box);
8105
+ highlightBox = box;
8106
+ };
7586
8107
  if (ctx.panel) {
7587
8108
  chrome = injectBuilderChrome({
7588
8109
  appLabel: window.location.hostname,
@@ -7710,10 +8231,12 @@ function previewProgressFetcher() {
7710
8231
  return data.items ?? [];
7711
8232
  };
7712
8233
  }
8234
+ var CONFIRMATION_STYLES;
7713
8235
  var init_builder_mode = __esm({
7714
8236
  "src/plugins/builder/builder-mode.ts"() {
7715
8237
  init_builder_token();
7716
8238
  init_safe_env();
8239
+ init_selector_builder();
7717
8240
  init_activation_devices();
7718
8241
  init_activation_rules();
7719
8242
  init_block_builder();
@@ -7728,6 +8251,42 @@ var init_builder_mode = __esm({
7728
8251
  init_builder_panel();
7729
8252
  init_design_manipulator();
7730
8253
  init_element_picker();
8254
+ init_interactive_elements();
8255
+ CONFIRMATION_STYLES = `
8256
+ :host { all: initial; }
8257
+ .veo-confirm-backdrop {
8258
+ position: fixed; inset: 0; z-index: 2147483645;
8259
+ background: rgba(20, 16, 14, 0.45);
8260
+ }
8261
+ .veo-confirm {
8262
+ position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
8263
+ z-index: 2147483646; box-sizing: border-box;
8264
+ width: min(432px, calc(100vw - 32px)); padding: 28px 24px 24px;
8265
+ border-radius: 16px; background: #fff; color: #1d1917;
8266
+ box-shadow: 0 24px 64px rgba(0,0,0,0.28);
8267
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
8268
+ text-align: center;
8269
+ }
8270
+ .veo-confirm-check {
8271
+ width: 64px; height: 64px; margin: 0 auto; border-radius: 50%;
8272
+ background: #e6f6df; color: #2f9a4e; display: flex; align-items: center; justify-content: center;
8273
+ }
8274
+ .veo-confirm-check svg { width: 30px; height: 30px; }
8275
+ .veo-confirm-title { margin: 18px 0 0; font-size: 17px; font-weight: 600; }
8276
+ .veo-confirm-msg { margin: 8px 0 0; font-size: 13px; color: #6b6461; }
8277
+ .veo-confirm-rows {
8278
+ margin: 18px 0 0; padding: 12px 14px; border-radius: 12px; background: #f6f2ef; text-align: left;
8279
+ }
8280
+ .veo-confirm-row { display: flex; justify-content: space-between; gap: 16px; font-size: 13px; padding: 4px 0; }
8281
+ .veo-confirm-row dt { color: #6b6461; margin: 0; }
8282
+ .veo-confirm-row dd { margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8283
+ .veo-confirm-btn {
8284
+ display: block; width: 100%; margin-top: 18px; height: 44px; border: 0; border-radius: 10px;
8285
+ background: #b74d00; color: #fff; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
8286
+ }
8287
+ .veo-confirm-btn:hover { background: #953c00; }
8288
+ .veo-confirm-btn:focus-visible { outline: 3px solid rgba(183,77,0,0.35); outline-offset: 2px; }
8289
+ `;
7731
8290
  }
7732
8291
  });
7733
8292
 
@@ -7860,11 +8419,15 @@ var init_builder_session = __esm({
7860
8419
  // src/plugins/builder/index.ts
7861
8420
  var builder_exports = {};
7862
8421
  __export(builder_exports, {
8422
+ INTERACTIVE_SELECTOR: () => INTERACTIVE_SELECTOR,
7863
8423
  VEO_BLOCK_MIME: () => VEO_BLOCK_MIME,
7864
8424
  attachBlockDropTarget: () => attachBlockDropTarget,
7865
8425
  attachDesignManipulator: () => attachDesignManipulator,
8426
+ closestInteractive: () => closestInteractive,
7866
8427
  createBuilderSession: () => createBuilderSession,
8428
+ elementLabelText: () => elementLabelText,
7867
8429
  initBuilderMode: () => initBuilderMode,
8430
+ isInteractiveElement: () => isInteractiveElement,
7868
8431
  startElementPicker: () => startElementPicker,
7869
8432
  stopElementPicker: () => stopElementPicker
7870
8433
  });
@@ -7875,6 +8438,472 @@ var init_builder = __esm({
7875
8438
  init_builder_session();
7876
8439
  init_design_manipulator();
7877
8440
  init_element_picker();
8441
+ init_interactive_elements();
8442
+ }
8443
+ });
8444
+
8445
+ // src/plugins/theme-detect/color.ts
8446
+ function parseRgb(value) {
8447
+ const m = RGB_RX.exec(value.trim());
8448
+ if (!m) return null;
8449
+ const a = m[4] === void 0 ? 1 : m[4].endsWith("%") ? Number.parseFloat(m[4]) / 100 : +m[4];
8450
+ return [+m[1], +m[2], +m[3], a];
8451
+ }
8452
+ function toCssColor(raw) {
8453
+ const v = raw.trim();
8454
+ const parts = v.split(/[\s,/]+/).filter(Boolean);
8455
+ const bare = parts.length >= 3 && parts.every((p) => /^-?[\d.]+(%|deg)?$/.test(p));
8456
+ if (!bare) return v;
8457
+ return v.includes("%") ? `hsl(${v})` : `rgb(${v})`;
8458
+ }
8459
+ function looksLikeColor(raw) {
8460
+ const v = raw.trim();
8461
+ if (!v || v.length > 64) return false;
8462
+ return /^(#|rgb|hsl|hwb|lab|lch|oklab|oklch|color\(|color-mix|light-dark)/i.test(v) || toCssColor(v) !== v;
8463
+ }
8464
+ function flatten(c, bg) {
8465
+ if (c[3] >= 1) return c;
8466
+ const mix = (i) => Math.round(c[i] * c[3] + bg[i] * (1 - c[3]));
8467
+ return [mix(0), mix(1), mix(2), 1];
8468
+ }
8469
+ function toHex(c) {
8470
+ const h = (n) => Math.round(n).toString(16).padStart(2, "0");
8471
+ return `#${h(c[0])}${h(c[1])}${h(c[2])}`.toUpperCase();
8472
+ }
8473
+ function luminance(c) {
8474
+ return (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255;
8475
+ }
8476
+ function saturation(c) {
8477
+ const mx = Math.max(c[0], c[1], c[2]) / 255;
8478
+ const mn = Math.min(c[0], c[1], c[2]) / 255;
8479
+ const l = (mx + mn) / 2;
8480
+ return mx === mn ? 0 : (mx - mn) / (1 - Math.abs(2 * l - 1));
8481
+ }
8482
+ function createColorResolver(doc) {
8483
+ const probe = doc.createElement("span");
8484
+ probe.style.display = "none";
8485
+ doc.body.appendChild(probe);
8486
+ const view = doc.defaultView;
8487
+ let ctx;
8488
+ const cache = /* @__PURE__ */ new Map();
8489
+ const viaCanvas = (computed) => {
8490
+ if (ctx === void 0) {
8491
+ try {
8492
+ const canvas = doc.createElement("canvas");
8493
+ canvas.width = 1;
8494
+ canvas.height = 1;
8495
+ ctx = canvas.getContext("2d", { willReadFrequently: true });
8496
+ } catch {
8497
+ ctx = null;
8498
+ }
8499
+ }
8500
+ if (!ctx) return null;
8501
+ ctx.clearRect(0, 0, 1, 1);
8502
+ ctx.fillStyle = "#000";
8503
+ ctx.fillStyle = computed;
8504
+ ctx.fillRect(0, 0, 1, 1);
8505
+ const d = ctx.getImageData(0, 0, 1, 1).data;
8506
+ return [d[0] ?? 0, d[1] ?? 0, d[2] ?? 0, (d[3] ?? 255) / 255];
8507
+ };
8508
+ return {
8509
+ resolve(raw) {
8510
+ const key = raw.trim();
8511
+ if (!key || !view) return null;
8512
+ const hit = cache.get(key);
8513
+ if (hit !== void 0) return hit;
8514
+ let out = null;
8515
+ probe.style.color = "";
8516
+ probe.style.color = toCssColor(key);
8517
+ if (probe.style.color) {
8518
+ const computed = view.getComputedStyle(probe).color;
8519
+ out = parseRgb(computed) ?? viaCanvas(computed);
8520
+ }
8521
+ cache.set(key, out);
8522
+ return out;
8523
+ },
8524
+ dispose() {
8525
+ probe.remove();
8526
+ }
8527
+ };
8528
+ }
8529
+ var RGB_RX;
8530
+ var init_color = __esm({
8531
+ "src/plugins/theme-detect/color.ts"() {
8532
+ RGB_RX = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/;
8533
+ }
8534
+ });
8535
+
8536
+ // src/plugins/theme-detect/fonts.ts
8537
+ function cleanFamily(name) {
8538
+ return name.trim().replace(/^["']|["']$/g, "");
8539
+ }
8540
+ function primaryFamily(stack) {
8541
+ const families = stack.split(",").map(cleanFamily).filter(Boolean);
8542
+ return families.find((f) => !GENERIC.test(f) && !/fallback/i.test(f)) ?? families[0] ?? "";
8543
+ }
8544
+ function prettyFamily(family) {
8545
+ return family.replace(/^__/, "").replace(/_Fallback_[0-9a-f]+$/i, " Fallback").replace(/_[0-9a-f]{6,}$/i, "").replace(/_/g, " ").trim();
8546
+ }
8547
+ function describeFont(doc, el2, faces) {
8548
+ const view = doc.defaultView;
8549
+ if (!el2 || !view) return void 0;
8550
+ const family = primaryFamily(view.getComputedStyle(el2).fontFamily);
8551
+ if (!family) return void 0;
8552
+ const font = {
8553
+ name: prettyFamily(family).slice(0, 80),
8554
+ family: family.slice(0, 120)
8555
+ };
8556
+ const weights = /* @__PURE__ */ new Set();
8557
+ try {
8558
+ for (const face of Array.from(doc.fonts ?? [])) {
8559
+ if (cleanFamily(face.family) === family && face.status === "loaded") weights.add(face.weight);
8560
+ }
8561
+ } catch {
8562
+ }
8563
+ if (weights.size > 0) font.weights = [...weights].sort().join(" ").slice(0, 80);
8564
+ const own = faces.filter((f) => f.family === family);
8565
+ const url = own.find((f) => f.url)?.url ?? null;
8566
+ let origin = null;
8567
+ try {
8568
+ origin = url ? new URL(url, doc.baseURI).origin : null;
8569
+ } catch {
8570
+ origin = null;
8571
+ }
8572
+ const links = Array.from(doc.querySelectorAll("link[href]")).map((l) => l.href);
8573
+ if (origin) {
8574
+ font.hosting = origin.includes("fonts.gstatic.com") ? "google" : origin === view.location.origin ? "self" : "external";
8575
+ } else if (own.length > 0) {
8576
+ font.hosting = "self";
8577
+ } else if (links.some((h) => h.includes("fonts.googleapis.com"))) {
8578
+ font.hosting = "google";
8579
+ } else if (links.some((h) => h.includes("use.typekit.net"))) {
8580
+ font.hosting = "adobe";
8581
+ } else {
8582
+ font.hosting = "system";
8583
+ }
8584
+ return font;
8585
+ }
8586
+ var GENERIC;
8587
+ var init_fonts = __esm({
8588
+ "src/plugins/theme-detect/fonts.ts"() {
8589
+ GENERIC = /^(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-sans-serif|ui-serif|ui-monospace|ui-rounded|emoji|math|fangsong|inherit|initial|-apple-system|BlinkMacSystemFont|Segoe UI|Roboto|Helvetica|Helvetica Neue|Arial|Noto Sans|Apple Color Emoji|Segoe UI Emoji|Segoe UI Symbol|Noto Color Emoji)$/i;
8590
+ }
8591
+ });
8592
+
8593
+ // src/plugins/theme-detect/detect-theme.ts
8594
+ var detect_theme_exports = {};
8595
+ __export(detect_theme_exports, {
8596
+ detectTheme: () => detectTheme,
8597
+ snapshotHash: () => snapshotHash
8598
+ });
8599
+ function scanSheets(doc) {
8600
+ const scan = {
8601
+ names: /* @__PURE__ */ new Set(),
8602
+ byMode: { light: /* @__PURE__ */ new Map(), dark: /* @__PURE__ */ new Map() },
8603
+ faces: []
8604
+ };
8605
+ const walk = (rules, inDarkMedia) => {
8606
+ for (const rule of Array.from(rules)) {
8607
+ const style = rule.style;
8608
+ const selector = rule.selectorText;
8609
+ if (rule.constructor.name === "CSSFontFaceRule" && style) {
8610
+ const src = style.getPropertyValue("src");
8611
+ const url = /url\(["']?([^"')]+)["']?\)/.exec(src)?.[1] ?? null;
8612
+ scan.faces.push({ family: cleanFamily(style.getPropertyValue("font-family")), url });
8613
+ } else if (style && typeof selector === "string") {
8614
+ const selectors = selector.split(",").map((s) => s.trim());
8615
+ const mode = selectors.some((s) => DARK_SELECTOR.test(s)) ? "dark" : selectors.some((s) => LIGHT_SELECTOR.test(s)) ? inDarkMedia ? "dark" : "light" : null;
8616
+ for (const prop of Array.from(style)) {
8617
+ if (!prop.startsWith("--")) continue;
8618
+ scan.names.add(prop);
8619
+ if (mode) scan.byMode[mode].set(prop, style.getPropertyValue(prop).trim());
8620
+ }
8621
+ }
8622
+ const nested = rule.cssRules;
8623
+ if (nested) {
8624
+ const media = rule.conditionText ?? "";
8625
+ walk(nested, inDarkMedia || /prefers-color-scheme:\s*dark/.test(media));
8626
+ }
8627
+ }
8628
+ };
8629
+ for (const sheet of Array.from(doc.styleSheets)) {
8630
+ try {
8631
+ walk(sheet.cssRules, false);
8632
+ } catch {
8633
+ }
8634
+ }
8635
+ return scan;
8636
+ }
8637
+ function top(counts) {
8638
+ let best = null;
8639
+ let max = 0;
8640
+ for (const [hex, n] of counts) {
8641
+ if (n > max) {
8642
+ max = n;
8643
+ best = hex;
8644
+ }
8645
+ }
8646
+ return best;
8647
+ }
8648
+ function detectTheme(doc = document) {
8649
+ const view = doc.defaultView;
8650
+ const body = doc.body;
8651
+ if (!view || !body) return null;
8652
+ const html = doc.documentElement;
8653
+ const colors = createColorResolver(doc);
8654
+ const modeProbe = doc.createElement("div");
8655
+ try {
8656
+ const bodyCs = view.getComputedStyle(body);
8657
+ let bg = [255, 255, 255, 1];
8658
+ for (const el2 of [body, html]) {
8659
+ const c = colors.resolve(view.getComputedStyle(el2).backgroundColor);
8660
+ if (c && c[3] > 0) {
8661
+ bg = flatten(c, [255, 255, 255, 1]);
8662
+ break;
8663
+ }
8664
+ }
8665
+ const text = flatten(colors.resolve(bodyCs.color) ?? [0, 0, 0, 1], bg);
8666
+ const textHex = toHex(text);
8667
+ const muted = /* @__PURE__ */ new Map();
8668
+ const border = /* @__PURE__ */ new Map();
8669
+ const accent = /* @__PURE__ */ new Map();
8670
+ const link = /* @__PURE__ */ new Map();
8671
+ const bump = (m, k) => {
8672
+ m.set(k, (m.get(k) ?? 0) + 1);
8673
+ };
8674
+ const contrast = Math.abs(luminance(text) - luminance(bg));
8675
+ const elements = Array.from(body.querySelectorAll("*")).slice(0, MAX_ELEMENTS);
8676
+ for (const el2 of elements) {
8677
+ if (el2.getClientRects().length === 0) continue;
8678
+ const cs = view.getComputedStyle(el2);
8679
+ const hasText = Array.from(el2.childNodes).some(
8680
+ (n) => n.nodeType === 3 && (n.textContent ?? "").trim() !== ""
8681
+ );
8682
+ if (hasText) {
8683
+ const c = colors.resolve(cs.color);
8684
+ if (c && c[3] > 0) {
8685
+ const flat = flatten(c, bg);
8686
+ const hex = toHex(flat);
8687
+ const softer = Math.abs(luminance(flat) - luminance(bg)) < contrast;
8688
+ if (hex !== textHex && saturation(flat) < 0.3 && softer) bump(muted, hex);
8689
+ if (el2.closest("a") && saturation(flat) >= 0.3) bump(link, hex);
8690
+ }
8691
+ }
8692
+ if (Number.parseFloat(cs.borderTopWidth) > 0 && cs.borderTopStyle !== "none") {
8693
+ const c = colors.resolve(cs.borderTopColor);
8694
+ if (c && c[3] > 0) {
8695
+ const flat = flatten(c, bg);
8696
+ if (saturation(flat) < 0.3 && toHex(flat) !== textHex) bump(border, toHex(flat));
8697
+ }
8698
+ }
8699
+ if (el2.matches('button,[role="button"],input[type="submit"],a[class*="btn"],a[class*="button"]')) {
8700
+ const c = colors.resolve(cs.backgroundColor);
8701
+ if (c && c[3] > 0.5 && saturation(c) >= 0.3) bump(accent, toHex(c));
8702
+ }
8703
+ }
8704
+ const meta = doc.querySelector('meta[name="theme-color"]');
8705
+ const metaColor = meta ? colors.resolve(meta.content) : null;
8706
+ const sampled = {
8707
+ background: toHex(bg),
8708
+ text: textHex
8709
+ };
8710
+ const sampledAccent = top(accent) ?? top(link) ?? (metaColor ? toHex(metaColor) : null);
8711
+ if (sampledAccent) sampled.accent = sampledAccent;
8712
+ const sampledMuted = top(muted);
8713
+ if (sampledMuted) sampled.textMuted = sampledMuted;
8714
+ const sampledBorder = top(border);
8715
+ if (sampledBorder) sampled.border = sampledBorder;
8716
+ const hints = [
8717
+ html.className,
8718
+ html.dataset.theme,
8719
+ html.dataset.mode,
8720
+ body.className,
8721
+ body.dataset.theme
8722
+ ].join(" ").toLowerCase();
8723
+ const mode = /\bdark\b/.test(hints) ? "dark" : /\blight\b/.test(hints) ? "light" : view.getComputedStyle(html).colorScheme === "dark" || luminance(bg) < 0.4 ? "dark" : "light";
8724
+ const other = mode === "dark" ? "light" : "dark";
8725
+ modeProbe.className = other;
8726
+ modeProbe.dataset.theme = other;
8727
+ modeProbe.dataset.mode = other;
8728
+ modeProbe.style.cssText = `display:none;color-scheme:${other}`;
8729
+ body.appendChild(modeProbe);
8730
+ const probeCs = view.getComputedStyle(modeProbe);
8731
+ const hexOf = (raw, over) => {
8732
+ if (!looksLikeColor(raw)) return null;
8733
+ const c = colors.resolve(raw);
8734
+ if (!c || c[3] === 0) return null;
8735
+ if (c[3] > 0.98) return toHex(c);
8736
+ return over ? toHex(flatten(c, over)) : null;
8737
+ };
8738
+ const scan = scanSheets(doc);
8739
+ const roleVar = {};
8740
+ const current = {};
8741
+ for (const role of ROLES) {
8742
+ for (const name of KNOWN_VARS[role]) {
8743
+ const hex = hexOf(bodyCs.getPropertyValue(name), bg);
8744
+ if (hex) {
8745
+ roleVar[role] = { name, confidence: 3 };
8746
+ current[role] = { hex, confidence: 3 };
8747
+ break;
8748
+ }
8749
+ }
8750
+ }
8751
+ const missing = ROLES.filter((r) => !roleVar[r] && sampled[r]);
8752
+ if (missing.length > 0) {
8753
+ const candidates = [];
8754
+ for (const name of Array.from(scan.names).slice(0, MAX_VARS)) {
8755
+ const cur = hexOf(bodyCs.getPropertyValue(name));
8756
+ if (cur) candidates.push({ name, cur, oth: hexOf(probeCs.getPropertyValue(name)) });
8757
+ }
8758
+ for (const role of missing) {
8759
+ let best = null;
8760
+ for (const c of candidates) {
8761
+ if (c.cur !== sampled[role]) continue;
8762
+ const score = (ROLE_NAME[role].test(c.name) ? 2 : 0) + (c.oth && c.oth !== c.cur ? 2 : 0) - (PALETTE_NAME.test(c.name) ? 1 : 0);
8763
+ if (!best || score > best.score) best = { name: c.name, score };
8764
+ }
8765
+ if (best && best.score >= 2) roleVar[role] = { name: best.name, confidence: 2 };
8766
+ }
8767
+ }
8768
+ for (const role of ROLES) {
8769
+ const hex = sampled[role];
8770
+ if (!current[role] && hex) current[role] = { hex, confidence: roleVar[role] ? 2 : 1 };
8771
+ }
8772
+ const otherPalette = {};
8773
+ let differs = false;
8774
+ const otherOrder = ["background", "accent", "text", "textMuted", "border"];
8775
+ let otherBg = null;
8776
+ const noteBg = (role, hex) => {
8777
+ if (role === "background") otherBg = colors.resolve(hex);
8778
+ };
8779
+ for (const role of otherOrder) {
8780
+ const ref = roleVar[role];
8781
+ if (!ref) continue;
8782
+ const hex = hexOf(probeCs.getPropertyValue(ref.name), otherBg);
8783
+ if (!hex) continue;
8784
+ noteBg(role, hex);
8785
+ otherPalette[role] = { hex, confidence: ref.confidence };
8786
+ if (hex !== current[role]?.hex) differs = true;
8787
+ }
8788
+ if (!differs) {
8789
+ for (const role of ROLES) delete otherPalette[role];
8790
+ otherBg = null;
8791
+ const sheet = scan.byMode[other];
8792
+ const resolveVar = (value, depth = 0) => depth > 6 ? value : value.replace(
8793
+ /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*))?\)/g,
8794
+ (_m, name, fb) => resolveVar(
8795
+ sheet.get(name) ?? scan.byMode.light.get(name) ?? (bodyCs.getPropertyValue(name).trim() || fb || ""),
8796
+ depth + 1
8797
+ )
8798
+ );
8799
+ for (const role of otherOrder) {
8800
+ const ref = roleVar[role];
8801
+ const raw = ref ? sheet.get(ref.name) : void 0;
8802
+ const hex = raw ? hexOf(resolveVar(raw), otherBg) : null;
8803
+ if (!hex) continue;
8804
+ noteBg(role, hex);
8805
+ otherPalette[role] = { hex, confidence: 2 };
8806
+ if (hex !== current[role]?.hex) differs = true;
8807
+ }
8808
+ }
8809
+ const palettes = { [mode]: current };
8810
+ if (differs) palettes[other] = otherPalette;
8811
+ const firstVisible = (selector) => Array.from(doc.querySelectorAll(selector)).find(
8812
+ (el2) => el2.getClientRects().length > 0 && (el2.textContent ?? "").trim() !== ""
8813
+ ) ?? null;
8814
+ const bodyFont = describeFont(doc, body, scan.faces);
8815
+ const headingFont = describeFont(doc, firstVisible("h1,h2,h3"), scan.faces);
8816
+ const fonts = {};
8817
+ if (bodyFont) fonts.body = bodyFont;
8818
+ if (headingFont) fonts.heading = headingFont;
8819
+ const snapshot = { host: view.location.host, mode, palettes };
8820
+ const appName = doc.title.trim().slice(0, 120);
8821
+ if (appName) snapshot.appName = appName;
8822
+ if (fonts.body || fonts.heading) snapshot.fonts = fonts;
8823
+ return snapshot;
8824
+ } finally {
8825
+ colors.dispose();
8826
+ modeProbe.remove();
8827
+ }
8828
+ }
8829
+ function snapshotHash(snapshot) {
8830
+ const colorsOf = (p) => ROLES.map((r) => {
8831
+ const c = p?.[r];
8832
+ return c ? `${c.hex}${c.confidence}` : "-";
8833
+ }).join("");
8834
+ return [
8835
+ snapshot.host,
8836
+ colorsOf(snapshot.palettes.light),
8837
+ colorsOf(snapshot.palettes.dark),
8838
+ snapshot.fonts?.body?.family ?? "",
8839
+ snapshot.fonts?.heading?.family ?? ""
8840
+ ].join("|");
8841
+ }
8842
+ var ROLES, KNOWN_VARS, ROLE_NAME, PALETTE_NAME, DARK_SELECTOR, LIGHT_SELECTOR, MAX_ELEMENTS, MAX_VARS;
8843
+ var init_detect_theme = __esm({
8844
+ "src/plugins/theme-detect/detect-theme.ts"() {
8845
+ init_color();
8846
+ init_fonts();
8847
+ ROLES = ["accent", "text", "textMuted", "background", "border"];
8848
+ KNOWN_VARS = {
8849
+ accent: [
8850
+ "--primary",
8851
+ "--color-primary",
8852
+ "--brand",
8853
+ "--color-brand",
8854
+ "--brand-primary",
8855
+ "--mui-palette-primary-main",
8856
+ "--bs-primary",
8857
+ "--ant-color-primary",
8858
+ "--chakra-colors-brand-500",
8859
+ "--accent-color",
8860
+ "--accent"
8861
+ ],
8862
+ text: [
8863
+ "--foreground",
8864
+ "--color-foreground",
8865
+ "--text",
8866
+ "--color-text",
8867
+ "--text-primary",
8868
+ "--bs-body-color",
8869
+ "--mui-palette-text-primary"
8870
+ ],
8871
+ textMuted: [
8872
+ "--muted-foreground",
8873
+ "--color-muted-foreground",
8874
+ "--text-muted",
8875
+ "--text-secondary",
8876
+ "--bs-secondary-color",
8877
+ "--mui-palette-text-secondary"
8878
+ ],
8879
+ background: [
8880
+ "--background",
8881
+ "--color-background",
8882
+ "--bg",
8883
+ "--color-bg",
8884
+ "--bs-body-bg",
8885
+ "--mui-palette-background-default"
8886
+ ],
8887
+ border: [
8888
+ "--border",
8889
+ "--color-border",
8890
+ "--border-color",
8891
+ "--bs-border-color",
8892
+ "--mui-palette-divider"
8893
+ ]
8894
+ };
8895
+ ROLE_NAME = {
8896
+ accent: /brand|primary|accent|main/i,
8897
+ text: /text|foreground|\bfg\b|-fg|ink|content|body/i,
8898
+ textMuted: /muted|secondary|subtle|soft|hint|placeholder|text|-fg/i,
8899
+ background: /background|\bbg\b|-bg|surface|canvas|page|base/i,
8900
+ border: /border|line|divider|stroke|outline|separator/i
8901
+ };
8902
+ PALETTE_NAME = /-(50|[1-9]00|950)$|white|black/i;
8903
+ DARK_SELECTOR = /\.dark\b|\[data-(theme|mode|color-scheme)=["']?dark|\.theme-dark|\.dark-theme/i;
8904
+ LIGHT_SELECTOR = /^(:root|html|:host|body)$|\.light\b|\[data-(theme|mode)=["']?light/i;
8905
+ MAX_ELEMENTS = 1500;
8906
+ MAX_VARS = 600;
7878
8907
  }
7879
8908
  });
7880
8909
 
@@ -7949,6 +8978,298 @@ function definePlugin(plugin) {
7949
8978
 
7950
8979
  // src/plugins/autocapture/dom-listener.ts
7951
8980
  init_safe_env();
8981
+
8982
+ // src/plugins/autocapture/click-quality.ts
8983
+ init_safe_env();
8984
+ init_selector_builder();
8985
+ var NO_DEAD_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option", "label"]);
8986
+ var MAX_ERROR_MESSAGE = 200;
8987
+ var HISTORY_MS = 3e3;
8988
+ var DEFAULTS = {
8989
+ reactionWindowMs: 1e3,
8990
+ errorWindowMs: 2e3,
8991
+ rageClicks: 3,
8992
+ rageWindowMs: 1e3,
8993
+ rageClicksSlow: 5,
8994
+ rageWindowSlowMs: 3e3,
8995
+ maxPerElement: 50
8996
+ };
8997
+ function describeError(value) {
8998
+ let text;
8999
+ if (value instanceof Error) text = `${value.name}: ${value.message}`;
9000
+ else if (typeof value === "string") text = value;
9001
+ else {
9002
+ try {
9003
+ text = JSON.stringify(value) ?? String(value);
9004
+ } catch {
9005
+ text = String(value);
9006
+ }
9007
+ }
9008
+ return text.slice(0, MAX_ERROR_MESSAGE);
9009
+ }
9010
+ var ClickQualityTracker = class {
9011
+ constructor(emit, options = {}) {
9012
+ this.emit = emit;
9013
+ this.installed = false;
9014
+ this.cleanups = [];
9015
+ // Sensores globales (contadores: cada observación compara su foto inicial).
9016
+ this.netCount = 0;
9017
+ this.navCount = 0;
9018
+ this.focusCount = 0;
9019
+ this.unloading = false;
9020
+ this.errors = [];
9021
+ this.history = /* @__PURE__ */ new Map();
9022
+ this.bursts = /* @__PURE__ */ new Map();
9023
+ this.emitted = /* @__PURE__ */ new Map();
9024
+ this.opts = { ...DEFAULTS, ...options };
9025
+ }
9026
+ install() {
9027
+ if (this.installed || !hasWindow()) return;
9028
+ this.installed = true;
9029
+ const on = (type, handler, capture = true) => {
9030
+ window.addEventListener(type, handler, capture);
9031
+ this.cleanups.push(() => window.removeEventListener(type, handler, capture));
9032
+ };
9033
+ on("error", (e) => {
9034
+ const err = e;
9035
+ this.pushError("error", err.error ?? err.message ?? "error");
9036
+ });
9037
+ on("unhandledrejection", (e) => {
9038
+ this.pushError("unhandledrejection", e.reason ?? "rejection");
9039
+ });
9040
+ on("popstate", () => this.navCount++);
9041
+ on("hashchange", () => this.navCount++);
9042
+ on("focusin", () => this.focusCount++);
9043
+ on("pagehide", () => {
9044
+ this.unloading = true;
9045
+ });
9046
+ on("beforeunload", () => {
9047
+ this.unloading = true;
9048
+ });
9049
+ this.patchHistory();
9050
+ this.patchConsole();
9051
+ this.patchFetch();
9052
+ this.patchXhr();
9053
+ }
9054
+ destroy() {
9055
+ for (const c of this.cleanups) c();
9056
+ this.cleanups = [];
9057
+ for (const b of this.bursts.values()) window.clearTimeout(b.timer);
9058
+ this.bursts.clear();
9059
+ this.history.clear();
9060
+ this.installed = false;
9061
+ }
9062
+ /** Llamar por cada clic real sobre un elemento interactivo, ANTES del throttle. */
9063
+ onClick(element, event) {
9064
+ if (!this.installed) return;
9065
+ const now = Date.now();
9066
+ const obs = { at: now, reacted: false };
9067
+ this.remember(element, obs);
9068
+ this.observeReaction(element, obs, event);
9069
+ this.observeErrors(element, now);
9070
+ this.trackRage(element, now);
9071
+ }
9072
+ // ---------------------------------------------------------------- dead ----
9073
+ observeReaction(element, obs, event) {
9074
+ const netAtClick = this.netCount;
9075
+ const navAtClick = this.navCount;
9076
+ const focusAtClick = this.focusCount;
9077
+ const hrefAtClick = window.location.href;
9078
+ const skipDead = this.isDeadExempt(element, event);
9079
+ let observer = null;
9080
+ if (typeof MutationObserver !== "undefined") {
9081
+ observer = new MutationObserver(() => {
9082
+ obs.reacted = true;
9083
+ observer?.disconnect();
9084
+ observer = null;
9085
+ });
9086
+ observer.observe(document.documentElement, {
9087
+ childList: true,
9088
+ attributes: true,
9089
+ characterData: true,
9090
+ subtree: true
9091
+ });
9092
+ }
9093
+ window.setTimeout(() => {
9094
+ observer?.disconnect();
9095
+ const reacted = obs.reacted || this.unloading || this.netCount !== netAtClick || this.navCount !== navAtClick || this.focusCount !== focusAtClick || window.location.href !== hrefAtClick;
9096
+ obs.reacted = reacted;
9097
+ if (!reacted && !skipDead) this.report("$dead_click", element, {});
9098
+ }, this.opts.reactionWindowMs);
9099
+ }
9100
+ isDeadExempt(element, event) {
9101
+ if (event && (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey)) return true;
9102
+ if (event && typeof event.button === "number" && event.button !== 0) return true;
9103
+ const tag = element.tagName.toLowerCase();
9104
+ if (NO_DEAD_TAGS.has(tag)) return true;
9105
+ if (element.closest('[contenteditable=""],[contenteditable="true"]')) return true;
9106
+ const anchor = element.closest("a");
9107
+ if (anchor) {
9108
+ if (anchor.target === "_blank" || anchor.hasAttribute("download")) return true;
9109
+ const href = anchor.getAttribute("href") ?? "";
9110
+ if (/^(mailto|tel|sms):/i.test(href)) return true;
9111
+ }
9112
+ return false;
9113
+ }
9114
+ // --------------------------------------------------------------- error ----
9115
+ observeErrors(element, clickedAt) {
9116
+ const deadline = clickedAt + this.opts.errorWindowMs;
9117
+ const check = () => {
9118
+ const err = this.errors.find((e) => e.at >= clickedAt && e.at <= deadline);
9119
+ if (err) {
9120
+ this.report("$click_error", element, {
9121
+ $error_source: err.source,
9122
+ $error_message: err.message
9123
+ });
9124
+ return;
9125
+ }
9126
+ if (Date.now() < deadline) window.setTimeout(check, 250);
9127
+ };
9128
+ window.setTimeout(check, 250);
9129
+ }
9130
+ pushError(source, value) {
9131
+ const message = describeError(value);
9132
+ if (message.startsWith("[veo]")) return;
9133
+ this.errors.push({ at: Date.now(), source, message });
9134
+ const cutoff = Date.now() - this.opts.errorWindowMs - 1e3;
9135
+ while (this.errors.length > 0 && this.errors[0].at < cutoff) {
9136
+ this.errors.shift();
9137
+ }
9138
+ if (this.errors.length > 50) this.errors.splice(0, this.errors.length - 50);
9139
+ }
9140
+ // ---------------------------------------------------------------- rage ----
9141
+ remember(element, obs) {
9142
+ const list = this.history.get(element) ?? [];
9143
+ const cutoff = obs.at - HISTORY_MS;
9144
+ const kept = list.filter((o) => o.at >= cutoff);
9145
+ kept.push(obs);
9146
+ this.history.set(element, kept);
9147
+ if (this.history.size > 100) {
9148
+ for (const [el2, obsList] of this.history) {
9149
+ if (obsList.every((o) => o.at < cutoff)) this.history.delete(el2);
9150
+ }
9151
+ }
9152
+ }
9153
+ trackRage(element, now) {
9154
+ const list = this.history.get(element) ?? [];
9155
+ const fast = list.filter((o) => o.at >= now - this.opts.rageWindowMs).length;
9156
+ const slow = list.filter((o) => o.at >= now - this.opts.rageWindowSlowMs).length;
9157
+ const existing = this.bursts.get(element);
9158
+ const isRage = fast >= this.opts.rageClicks || slow >= this.opts.rageClicksSlow;
9159
+ if (!existing && !isRage) return;
9160
+ const burst = existing ?? {
9161
+ startedAt: (list.find((o) => o.at >= now - this.opts.rageWindowSlowMs) ?? { at: now }).at,
9162
+ count: 0,
9163
+ timer: 0
9164
+ };
9165
+ burst.count = list.filter((o) => o.at >= burst.startedAt).length;
9166
+ window.clearTimeout(burst.timer);
9167
+ burst.timer = window.setTimeout(() => {
9168
+ this.bursts.delete(element);
9169
+ const clicks = (this.history.get(element) ?? []).filter((o) => o.at >= burst.startedAt);
9170
+ this.report("$rage_click", element, {
9171
+ $click_count: Math.max(clicks.length, burst.count),
9172
+ $reacted_clicks: clicks.filter((o) => o.reacted).length
9173
+ });
9174
+ }, this.opts.reactionWindowMs + 50);
9175
+ this.bursts.set(element, burst);
9176
+ }
9177
+ // ---------------------------------------------------------------- emit ----
9178
+ report(kind, element, extra) {
9179
+ const key = buildSelectorPath(element, 5);
9180
+ const counts = this.emitted.get(key) ?? { $dead_click: 0, $rage_click: 0, $click_error: 0 };
9181
+ if (counts[kind] >= this.opts.maxPerElement) return;
9182
+ counts[kind]++;
9183
+ this.emitted.set(key, counts);
9184
+ try {
9185
+ this.emit(kind, element, extra);
9186
+ } catch {
9187
+ }
9188
+ }
9189
+ // ------------------------------------------------------------- patches ----
9190
+ patchHistory() {
9191
+ const h = window.history;
9192
+ if (!h) return;
9193
+ const origPush = h.pushState;
9194
+ const origReplace = h.replaceState;
9195
+ const self = this;
9196
+ h.pushState = function(...args) {
9197
+ self.navCount++;
9198
+ return origPush.apply(this, args);
9199
+ };
9200
+ h.replaceState = function(...args) {
9201
+ self.navCount++;
9202
+ return origReplace.apply(this, args);
9203
+ };
9204
+ this.cleanups.push(() => {
9205
+ h.pushState = origPush;
9206
+ h.replaceState = origReplace;
9207
+ });
9208
+ }
9209
+ patchConsole() {
9210
+ const orig = console.error;
9211
+ const self = this;
9212
+ console.error = function(...args) {
9213
+ try {
9214
+ self.pushError("console", args.map(describeError).join(" "));
9215
+ } catch {
9216
+ }
9217
+ return orig.apply(this, args);
9218
+ };
9219
+ this.cleanups.push(() => {
9220
+ console.error = orig;
9221
+ });
9222
+ }
9223
+ /**
9224
+ * `fetch`: cuenta la petición (reacción) y registra respuestas ≥500 o
9225
+ * rechazos como error. Se observa con `.then(ok, err)` sobre la promesa
9226
+ * original: eso marca el rechazo como manejado para el navegador, así que
9227
+ * un fetch fallido sin catch de la app no dispara `unhandledrejection` —
9228
+ * lo registramos nosotros como error de red.
9229
+ */
9230
+ patchFetch() {
9231
+ if (typeof window.fetch !== "function") return;
9232
+ const orig = window.fetch;
9233
+ const self = this;
9234
+ window.fetch = function(...args) {
9235
+ self.netCount++;
9236
+ const p = orig.apply(this, args);
9237
+ if (p && typeof p.then === "function") {
9238
+ p.then(
9239
+ (res) => {
9240
+ if (res && res.status >= 500) self.pushError("fetch", `${res.status} ${res.url}`);
9241
+ },
9242
+ (err) => self.pushError("fetch", err)
9243
+ );
9244
+ }
9245
+ return p;
9246
+ };
9247
+ this.cleanups.push(() => {
9248
+ window.fetch = orig;
9249
+ });
9250
+ }
9251
+ patchXhr() {
9252
+ const proto = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype : null;
9253
+ if (!proto) return;
9254
+ const origSend = proto.send;
9255
+ const self = this;
9256
+ proto.send = function(...args) {
9257
+ self.netCount++;
9258
+ try {
9259
+ this.addEventListener("loadend", () => {
9260
+ if (this.status >= 500) self.pushError("xhr", `${this.status} ${this.responseURL}`);
9261
+ });
9262
+ } catch {
9263
+ }
9264
+ return origSend.apply(this, args);
9265
+ };
9266
+ this.cleanups.push(() => {
9267
+ proto.send = origSend;
9268
+ });
9269
+ }
9270
+ };
9271
+
9272
+ // src/plugins/autocapture/dom-listener.ts
7952
9273
  init_constants();
7953
9274
 
7954
9275
  // src/plugins/autocapture/element-serializer.ts
@@ -8059,6 +9380,7 @@ var DomListener = class {
8059
9380
  constructor(client, config) {
8060
9381
  this.client = client;
8061
9382
  this.cleanups = [];
9383
+ this.quality = null;
8062
9384
  const d = DEFAULT_AUTOCAPTURE_CONFIG;
8063
9385
  this.config = {
8064
9386
  enabled: config.enabled ?? d.enabled,
@@ -8071,12 +9393,20 @@ var DomListener = class {
8071
9393
  throttleMs: config.throttleMs ?? d.throttleMs,
8072
9394
  maxTextLength: config.maxTextLength ?? d.maxTextLength,
8073
9395
  captureElementMetadata: config.captureElementMetadata ?? d.captureElementMetadata,
8074
- captureAncestors: Math.min(config.captureAncestors ?? d.captureAncestors, MAX_ANCESTORS)
9396
+ captureAncestors: Math.min(config.captureAncestors ?? d.captureAncestors, MAX_ANCESTORS),
9397
+ captureClickQuality: config.captureClickQuality ?? d.captureClickQuality
8075
9398
  };
8076
9399
  this.throttle = new ElementThrottle(this.config.throttleMs);
8077
9400
  }
8078
9401
  install() {
8079
9402
  if (!hasDocument() || !this.config.enabled) return;
9403
+ if (this.config.captureClicks && this.config.captureClickQuality) {
9404
+ this.quality = new ClickQualityTracker((kind, element, extra) => {
9405
+ const payload = buildAutocapturePayload("click", element, this.config);
9406
+ this.client.track(kind, { ...payload, ...extra });
9407
+ });
9408
+ this.quality.install();
9409
+ }
8080
9410
  if (this.config.captureClicks) this.attach("click", (e) => this.handleClick(e));
8081
9411
  if (this.config.captureSubmits) this.attach("submit", (e) => this.handleSubmit(e), true);
8082
9412
  if (this.config.captureChanges) this.attach("change", (e) => this.handleChange(e), true);
@@ -8084,6 +9414,8 @@ var DomListener = class {
8084
9414
  destroy() {
8085
9415
  for (const cleanup of this.cleanups) cleanup();
8086
9416
  this.cleanups = [];
9417
+ this.quality?.destroy();
9418
+ this.quality = null;
8087
9419
  }
8088
9420
  attach(eventName, handler, useCapture = false) {
8089
9421
  const listener = (e) => {
@@ -8102,6 +9434,7 @@ var DomListener = class {
8102
9434
  const meaningful = this.findMeaningfulAncestor(target);
8103
9435
  if (!meaningful) return;
8104
9436
  if (isElementBlocked(meaningful, this.config.blockSelectors)) return;
9437
+ this.quality?.onClick(meaningful, event);
8105
9438
  if (!this.throttle.shouldFire(meaningful)) return;
8106
9439
  this.emit("click", meaningful);
8107
9440
  }
@@ -8186,6 +9519,119 @@ function spaRouterPlugin() {
8186
9519
  });
8187
9520
  }
8188
9521
 
9522
+ // src/plugins/theme-detect/index.ts
9523
+ init_safe_env();
9524
+ var STORAGE_PREFIX = "veo_theme_";
9525
+ var RESEND_AFTER_MS = 24 * 60 * 60 * 1e3;
9526
+ var SETTLE_MS = 4e3;
9527
+ var MAX_RUNS_PER_PAGE = 4;
9528
+ function readState(key) {
9529
+ try {
9530
+ const raw = localStorage.getItem(key);
9531
+ const parsed = raw ? JSON.parse(raw) : null;
9532
+ return parsed && typeof parsed === "object" ? parsed : {};
9533
+ } catch {
9534
+ return {};
9535
+ }
9536
+ }
9537
+ function writeState(key, state) {
9538
+ try {
9539
+ localStorage.setItem(key, JSON.stringify(state));
9540
+ } catch {
9541
+ }
9542
+ }
9543
+ function guessMode(doc, prefersDark) {
9544
+ const hints = [
9545
+ doc.documentElement.className,
9546
+ doc.documentElement.dataset.theme,
9547
+ doc.documentElement.dataset.mode,
9548
+ doc.body?.className
9549
+ ].join(" ").toLowerCase();
9550
+ if (/\bdark\b/.test(hints)) return "dark";
9551
+ if (/\blight\b/.test(hints)) return "light";
9552
+ return prefersDark ? "dark" : "light";
9553
+ }
9554
+ function isFresh(state, mode, now) {
9555
+ const last = state[mode];
9556
+ return last !== void 0 && now - last.at < RESEND_AFTER_MS;
9557
+ }
9558
+ function shouldSendSnapshot(state, mode, hash, now) {
9559
+ const last = state[mode];
9560
+ return !last || last.hash !== hash || now - last.at >= RESEND_AFTER_MS;
9561
+ }
9562
+ function themeDetectPlugin(config) {
9563
+ const storageKey = `${STORAGE_PREFIX}${config.apiKey.slice(-16) || "default"}`;
9564
+ let observer = null;
9565
+ let timer = null;
9566
+ let destroyed = false;
9567
+ let runs = 0;
9568
+ const run = async () => {
9569
+ if (destroyed || runs >= MAX_RUNS_PER_PAGE) return;
9570
+ const prefersDark = typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
9571
+ const guess = guessMode(document, prefersDark);
9572
+ if (isFresh(readState(storageKey), guess, Date.now())) return;
9573
+ runs++;
9574
+ try {
9575
+ const detector = await config.loadDetector();
9576
+ if (!detector || destroyed) return;
9577
+ const snapshot = detector.detectTheme();
9578
+ if (!snapshot) return;
9579
+ const hash = detector.snapshotHash(snapshot);
9580
+ const state = readState(storageKey);
9581
+ const now = Date.now();
9582
+ const mark = () => {
9583
+ const record = { hash, at: Date.now() };
9584
+ state[snapshot.mode] = record;
9585
+ state[guess] = state[guess] && guess !== snapshot.mode ? state[guess] : record;
9586
+ writeState(storageKey, state);
9587
+ };
9588
+ if (!shouldSendSnapshot(state, snapshot.mode, hash, now)) {
9589
+ mark();
9590
+ return;
9591
+ }
9592
+ if (config.debug) console.log("[veo] theme snapshot", snapshot);
9593
+ const res = await fetch(`${config.apiUrl}/v1/workspaces/theme-snapshot`, {
9594
+ method: "POST",
9595
+ headers: { "X-Api-Key": config.apiKey, "Content-Type": "application/json" },
9596
+ body: JSON.stringify(snapshot),
9597
+ credentials: "omit"
9598
+ });
9599
+ if (res.ok) mark();
9600
+ } catch (err) {
9601
+ if (config.debug) console.warn("[veo] theme detect failed", err);
9602
+ }
9603
+ };
9604
+ const schedule = (delayMs) => {
9605
+ if (timer) clearTimeout(timer);
9606
+ timer = setTimeout(() => {
9607
+ timer = null;
9608
+ const idle = window.requestIdleCallback;
9609
+ if (typeof idle === "function") idle(() => void run());
9610
+ else void run();
9611
+ }, delayMs);
9612
+ };
9613
+ return {
9614
+ name: "theme-detect",
9615
+ install() {
9616
+ if (!hasWindow() || !hasDocument()) return;
9617
+ if (document.readyState === "complete") schedule(SETTLE_MS);
9618
+ else window.addEventListener("load", () => schedule(SETTLE_MS), { once: true });
9619
+ if (typeof MutationObserver === "function") {
9620
+ observer = new MutationObserver(() => schedule(1500));
9621
+ observer.observe(document.documentElement, {
9622
+ attributes: true,
9623
+ attributeFilter: ["class", "data-theme", "data-mode"]
9624
+ });
9625
+ }
9626
+ },
9627
+ destroy() {
9628
+ destroyed = true;
9629
+ observer?.disconnect();
9630
+ if (timer) clearTimeout(timer);
9631
+ }
9632
+ };
9633
+ }
9634
+
8189
9635
  // src/core/client.ts
8190
9636
  init_safe_env();
8191
9637
  init_uuid();
@@ -8338,26 +9784,26 @@ var Session = class {
8338
9784
 
8339
9785
  // src/core/storage.ts
8340
9786
  init_safe_env();
8341
- var STORAGE_PREFIX = "veo:";
9787
+ var STORAGE_PREFIX2 = "veo:";
8342
9788
  var ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
8343
9789
  var LocalStorageAdapter = class {
8344
9790
  get(key) {
8345
- return localStorage.getItem(STORAGE_PREFIX + key);
9791
+ return localStorage.getItem(STORAGE_PREFIX2 + key);
8346
9792
  }
8347
9793
  set(key, value) {
8348
9794
  try {
8349
- localStorage.setItem(STORAGE_PREFIX + key, value);
9795
+ localStorage.setItem(STORAGE_PREFIX2 + key, value);
8350
9796
  } catch {
8351
9797
  }
8352
9798
  }
8353
9799
  remove(key) {
8354
- localStorage.removeItem(STORAGE_PREFIX + key);
9800
+ localStorage.removeItem(STORAGE_PREFIX2 + key);
8355
9801
  }
8356
9802
  clear() {
8357
9803
  const toRemove = [];
8358
9804
  for (let i = 0; i < localStorage.length; i++) {
8359
9805
  const key = localStorage.key(i);
8360
- if (key?.startsWith(STORAGE_PREFIX)) toRemove.push(key);
9806
+ if (key?.startsWith(STORAGE_PREFIX2)) toRemove.push(key);
8361
9807
  }
8362
9808
  for (const key of toRemove) localStorage.removeItem(key);
8363
9809
  }
@@ -8368,7 +9814,7 @@ function readCookies() {
8368
9814
  }
8369
9815
  var CookieAdapter = class {
8370
9816
  get(key) {
8371
- const name = `${STORAGE_PREFIX}${key}=`;
9817
+ const name = `${STORAGE_PREFIX2}${key}=`;
8372
9818
  for (const cookie of readCookies()) {
8373
9819
  if (cookie.startsWith(name)) {
8374
9820
  return decodeURIComponent(cookie.slice(name.length));
@@ -8379,19 +9825,19 @@ var CookieAdapter = class {
8379
9825
  set(key, value, options) {
8380
9826
  const maxAgeSec = Math.floor((options?.ttlMs ?? ONE_YEAR_MS) / 1e3);
8381
9827
  const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
8382
- document.cookie = `${STORAGE_PREFIX}${key}=${encodeURIComponent(
9828
+ document.cookie = `${STORAGE_PREFIX2}${key}=${encodeURIComponent(
8383
9829
  value
8384
9830
  )}; Path=/; Max-Age=${maxAgeSec}; SameSite=Lax${secure}`;
8385
9831
  }
8386
9832
  remove(key) {
8387
- document.cookie = `${STORAGE_PREFIX}${key}=; Path=/; Max-Age=0; SameSite=Lax`;
9833
+ document.cookie = `${STORAGE_PREFIX2}${key}=; Path=/; Max-Age=0; SameSite=Lax`;
8388
9834
  }
8389
9835
  clear() {
8390
9836
  for (const cookie of readCookies()) {
8391
9837
  const eq = cookie.indexOf("=");
8392
9838
  const name = eq === -1 ? cookie : cookie.slice(0, eq);
8393
- if (name.startsWith(STORAGE_PREFIX)) {
8394
- this.remove(name.slice(STORAGE_PREFIX.length));
9839
+ if (name.startsWith(STORAGE_PREFIX2)) {
9840
+ this.remove(name.slice(STORAGE_PREFIX2.length));
8395
9841
  }
8396
9842
  }
8397
9843
  }
@@ -8681,13 +10127,23 @@ var Client = class {
8681
10127
  };
8682
10128
 
8683
10129
  // src/core/setup.ts
8684
- function setupClient(config, onGuides, loadBuilder) {
10130
+ function setupClient(config, onGuides, loadBuilder, loadThemeDetector) {
8685
10131
  const client = new Client(config);
8686
10132
  client.use(spaRouterPlugin());
8687
10133
  if (config.autocapture !== false) {
8688
10134
  const autocaptureConfig = typeof config.autocapture === "object" ? config.autocapture : {};
8689
10135
  client.use(autocapturePlugin(autocaptureConfig));
8690
10136
  }
10137
+ if (config.detectTheme !== false && loadThemeDetector && !isBuilderMode()) {
10138
+ client.use(
10139
+ themeDetectPlugin({
10140
+ apiUrl: resolveApiUrl(config),
10141
+ apiKey: config.apiKey,
10142
+ loadDetector: loadThemeDetector,
10143
+ ...config.debug !== void 0 ? { debug: config.debug } : {}
10144
+ })
10145
+ );
10146
+ }
8691
10147
  onGuides(client);
8692
10148
  if (typeof window !== "undefined") {
8693
10149
  const stub = window.veo;
@@ -10391,7 +11847,10 @@ function init(config) {
10391
11847
  // Fallback de carga del builder para bundlers: `import()` dinámico → el bundler
10392
11848
  // lo code-splitea (solo se descarga en modo builder). Así el consumidor por
10393
11849
  // bundler NO necesita copiar `veo-builder.js` ni setear `builderUrl`.
10394
- () => Promise.resolve().then(() => (init_builder(), builder_exports)).then((m) => m.initBuilderMode())
11850
+ () => Promise.resolve().then(() => (init_builder(), builder_exports)).then((m) => m.initBuilderMode()),
11851
+ // Detector de tema: también por `import()` dinámico → chunk aparte que el
11852
+ // bundler solo descarga cuando toca detectar (como mucho una vez al día).
11853
+ () => Promise.resolve().then(() => (init_detect_theme(), detect_theme_exports))
10395
11854
  );
10396
11855
  }
10397
11856