veo-sdk 0.5.33 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -422,6 +422,23 @@ var init_activation_devices = __esm({
422
422
  }
423
423
  });
424
424
 
425
+ // src/plugins/guides/activation-rules.ts
426
+ function isAutomaticActivation(rules) {
427
+ const trigger = rules?.trigger ?? "immediate";
428
+ if (trigger === "immediate" || trigger === "delayed") return true;
429
+ if (trigger === "on_click" || trigger === "on_hover") return rules?.autoShow === true;
430
+ return false;
431
+ }
432
+ function activationDelayMs(rules) {
433
+ if (!isAutomaticActivation(rules)) return 0;
434
+ const ms = rules?.delayMs ?? 0;
435
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
436
+ }
437
+ var init_activation_rules = __esm({
438
+ "src/plugins/guides/activation-rules.ts"() {
439
+ }
440
+ });
441
+
425
442
  // src/plugins/guides/constants.ts
426
443
  var GUIDE_Z_INDEX, DEFAULT_ANCHOR_WAIT_MS, SAFE_URL_PROTOCOLS, GUIDE_HOST_ATTR, FREQUENCY_CACHE_KEY_PREFIX, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_MAX_RETRIES, FREQUENCY_CACHE_MAX_ENTRIES, FREQUENCY_CACHE_TTL_MS, WALKTHROUGH_STATE_KEY_PREFIX, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS, WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS;
427
444
  var init_constants2 = __esm({
@@ -554,6 +571,129 @@ var init_rich_text = __esm({
554
571
  }
555
572
  });
556
573
 
574
+ // src/plugins/guides/video-block.ts
575
+ function videoEmbedUrl(provider, url) {
576
+ let u;
577
+ try {
578
+ u = new URL(url);
579
+ } catch {
580
+ return null;
581
+ }
582
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
583
+ const host = u.hostname.replace(/^www\./, "").replace(/^m\./, "");
584
+ const id = (re, from) => {
585
+ const m = from.match(re);
586
+ return m?.[1] ?? null;
587
+ };
588
+ switch (provider) {
589
+ case "youtube": {
590
+ let v = null;
591
+ if (host === "youtube.com" || host === "youtube-nocookie.com") {
592
+ v = u.searchParams.get("v") ?? id(/^\/(?:embed|shorts|live)\/([A-Za-z0-9_-]{6,})/, u.pathname);
593
+ } else if (host === "youtu.be") {
594
+ v = id(/^\/([A-Za-z0-9_-]{6,})/, u.pathname);
595
+ }
596
+ if (!v || !/^[A-Za-z0-9_-]{6,}$/.test(v)) return null;
597
+ return `https://www.youtube-nocookie.com/embed/${v}`;
598
+ }
599
+ case "vimeo": {
600
+ if (host !== "vimeo.com" && host !== "player.vimeo.com") return null;
601
+ const v = id(/(\d{6,})/, u.pathname);
602
+ return v ? `https://player.vimeo.com/video/${v}` : null;
603
+ }
604
+ case "loom": {
605
+ if (host !== "loom.com") return null;
606
+ const v = id(/^\/(?:share|embed)\/([A-Za-z0-9]{16,})/, u.pathname);
607
+ return v ? `https://www.loom.com/embed/${v}` : null;
608
+ }
609
+ case "wistia": {
610
+ if (!under(host, "wistia.com") && !under(host, "wistia.net")) return null;
611
+ const v = id(/\/(?:medias|embed\/iframe)\/([A-Za-z0-9]{6,})/, u.pathname);
612
+ return v ? `https://fast.wistia.net/embed/iframe/${v}` : null;
613
+ }
614
+ case "msstream": {
615
+ if (!under(host, "microsoftstream.com")) return null;
616
+ const v = id(/\/(?:embed\/)?video\/([0-9a-f-]{36})/i, u.pathname);
617
+ return v ? `https://web.microsoftstream.com/embed/video/${v}?autoplay=false&showinfo=true` : null;
618
+ }
619
+ case "vidyard": {
620
+ if (!under(host, "vidyard.com")) return null;
621
+ const v = id(/\/watch\/([A-Za-z0-9_-]{8,})/, u.pathname) ?? (host === "play.vidyard.com" ? id(/^\/([A-Za-z0-9_-]{8,})/, u.pathname) : null);
622
+ return v ? `https://play.vidyard.com/${v}` : null;
623
+ }
624
+ case "sharepoint":
625
+ return u.protocol === "https:" && under(host, "sharepoint.com") ? u.href : null;
626
+ case "brightcove":
627
+ return u.protocol === "https:" && host === "players.brightcove.net" && u.searchParams.get("videoId") ? u.href : null;
628
+ case "kaltura":
629
+ return u.protocol === "https:" && under(host, "kaltura.com") && u.pathname.startsWith("/p/") ? u.href : null;
630
+ default:
631
+ return null;
632
+ }
633
+ }
634
+ function under(host, domain) {
635
+ return host === domain || host.endsWith(`.${domain}`);
636
+ }
637
+ function fileVideoUrl(url) {
638
+ try {
639
+ const u = new URL(url);
640
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
641
+ return /\.(mp4|webm|mov)$/i.test(u.pathname) ? u.href : null;
642
+ } catch {
643
+ return null;
644
+ }
645
+ }
646
+ function buildVideoBlock(block, doc, opts = {}) {
647
+ const isFile = block.provider === "file";
648
+ const src = typeof block.url === "string" ? isFile ? fileVideoUrl(block.url) : videoEmbedUrl(block.provider, block.url) : null;
649
+ if (!src) return null;
650
+ const s = block.style ?? {};
651
+ const wrap = doc.createElement("div");
652
+ wrap.className = "veo-guide-video";
653
+ const ratio = s.aspect === "4:3" ? "4 / 3" : "16 / 9";
654
+ wrap.style.aspectRatio = ratio;
655
+ if (s.widthMode === "fill") {
656
+ wrap.style.width = "100%";
657
+ } else {
658
+ const w = typeof s.width === "number" && Number.isFinite(s.width) ? s.width : 400;
659
+ wrap.style.width = `${Math.max(120, Math.min(1600, w))}px`;
660
+ wrap.style.maxWidth = "100%";
661
+ }
662
+ const align = s.align === "center" ? "center" : s.align === "right" ? "flex-end" : "flex-start";
663
+ wrap.style.alignSelf = align;
664
+ if (isFile) {
665
+ const video = doc.createElement("video");
666
+ video.src = src;
667
+ video.controls = true;
668
+ video.setAttribute("playsinline", "");
669
+ video.preload = "metadata";
670
+ video.setAttribute(
671
+ "aria-label",
672
+ typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video"
673
+ );
674
+ if (opts.editing) video.style.pointerEvents = "none";
675
+ wrap.appendChild(video);
676
+ return wrap;
677
+ }
678
+ const frame = doc.createElement("iframe");
679
+ frame.src = src;
680
+ frame.title = typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video";
681
+ frame.setAttribute("loading", "lazy");
682
+ frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
683
+ frame.setAttribute(
684
+ "allow",
685
+ `accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture${opts.editing ? "" : "; fullscreen"}`
686
+ );
687
+ if (!opts.editing) frame.setAttribute("allowfullscreen", "");
688
+ if (opts.editing) frame.style.pointerEvents = "none";
689
+ wrap.appendChild(frame);
690
+ return wrap;
691
+ }
692
+ var init_video_block = __esm({
693
+ "src/plugins/guides/video-block.ts"() {
694
+ }
695
+ });
696
+
557
697
  // src/plugins/guides/block-builder.ts
558
698
  function buildStepContent(step, doc, callbacks) {
559
699
  const container = doc.createElement("div");
@@ -678,8 +818,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
678
818
  i++;
679
819
  }
680
820
  }
681
- function buildContentBlock(block, doc) {
821
+ function buildContentBlock(block, doc, opts = {}) {
682
822
  switch (block.type) {
823
+ case "video":
824
+ return buildVideoBlock(block, doc, opts);
825
+ case "divider":
826
+ return buildDividerBlock(block, doc);
683
827
  case "title": {
684
828
  const el2 = doc.createElement("h2");
685
829
  el2.className = "veo-guide-title";
@@ -703,7 +847,7 @@ function buildContentBlock(block, doc) {
703
847
  const el2 = doc.createElement("img");
704
848
  el2.className = "veo-guide-image";
705
849
  el2.src = block.url;
706
- el2.alt = "";
850
+ el2.alt = typeof block.alt === "string" ? block.alt : "";
707
851
  applyBlockStyle(el2, block.style, "image");
708
852
  return el2;
709
853
  }
@@ -711,6 +855,31 @@ function buildContentBlock(block, doc) {
711
855
  return null;
712
856
  }
713
857
  }
858
+ function buildDividerBlock(block, doc) {
859
+ const s = block.style ?? {};
860
+ const wrap = doc.createElement("div");
861
+ wrap.className = "veo-guide-divider";
862
+ wrap.setAttribute("role", "separator");
863
+ const hr = doc.createElement("hr");
864
+ const custom = s.preset === "custom";
865
+ const color = custom && typeof s.color === "string" && s.color ? s.color : s.colorMode === "dark" ? "rgba(255, 255, 255, 0.35)" : "#e5e7eb";
866
+ const thickness = custom && typeof s.thickness === "number" && Number.isFinite(s.thickness) ? clampNum(Math.round(s.thickness), 1, 12) : 1;
867
+ const lineStyle = typeof s.lineStyle === "string" && DIVIDER_LINE_STYLES.has(s.lineStyle) ? s.lineStyle : "solid";
868
+ hr.style.borderTop = `${thickness}px ${lineStyle} ${color}`;
869
+ wrap.appendChild(hr);
870
+ for (const [k, prop] of [
871
+ ["marginTop", "marginTop"],
872
+ ["marginRight", "marginRight"],
873
+ ["marginBottom", "marginBottom"],
874
+ ["marginLeft", "marginLeft"]
875
+ ]) {
876
+ const v = s[k];
877
+ if (typeof v === "number" && Number.isFinite(v)) {
878
+ wrap.style[prop] = `${clampNum(v, 0, 64)}px`;
879
+ }
880
+ }
881
+ return wrap;
882
+ }
714
883
  function buildButtonBlock(block, doc, callbacks) {
715
884
  const btn = doc.createElement("button");
716
885
  btn.type = "button";
@@ -741,14 +910,34 @@ function applyBlockStyle(el2, style, kind) {
741
910
  }
742
911
  const align = typeof s.align === "string" ? s.align : null;
743
912
  if (align && (align === "left" || align === "center" || align === "right")) {
744
- if (kind === "text") el2.style.textAlign = align;
745
- else if (kind === "image") el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
913
+ if (kind === "text") {
914
+ el2.style.textAlign = align;
915
+ el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
916
+ } else if (kind === "image") el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
917
+ }
918
+ if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
919
+ const preset = TEXT_PRESETS[s.preset];
920
+ if (preset) {
921
+ el2.style.fontSize = `${preset.size}px`;
922
+ el2.style.fontWeight = String(preset.weight);
923
+ el2.style.lineHeight = String(preset.lineHeight);
924
+ }
925
+ }
926
+ if (kind !== "image" && s.colorMode === "dark") {
927
+ el2.style.background = "#1f1d1b";
928
+ el2.style.color = "#f5f0eb";
929
+ el2.style.borderRadius = el2.style.borderRadius || "8px";
746
930
  }
747
931
  if (kind !== "image" && typeof s.color === "string") el2.style.color = s.color;
748
932
  if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
749
933
  el2.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
750
934
  }
751
- if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
935
+ if (kind === "image" && s.naturalSize === true) {
936
+ el2.style.width = "auto";
937
+ el2.style.maxWidth = "100%";
938
+ el2.style.maxHeight = "none";
939
+ el2.style.objectFit = "contain";
940
+ } else if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
752
941
  el2.style.width = `${clampNum(s.width, 10, 100)}%`;
753
942
  }
754
943
  if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
@@ -827,16 +1016,24 @@ function readCtaUrl(step) {
827
1016
  if (typeof candidate !== "string") return void 0;
828
1017
  return isSafeUrl(candidate) ? candidate : void 0;
829
1018
  }
830
- var BLOCK_ALIGN_SELF;
1019
+ var DIVIDER_LINE_STYLES, BLOCK_ALIGN_SELF, TEXT_PRESETS;
831
1020
  var init_block_builder = __esm({
832
1021
  "src/plugins/guides/block-builder.ts"() {
833
1022
  init_constants2();
834
1023
  init_rich_text();
1024
+ init_video_block();
1025
+ DIVIDER_LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dotted", "dashed"]);
835
1026
  BLOCK_ALIGN_SELF = {
836
1027
  left: "flex-start",
837
1028
  center: "center",
838
1029
  right: "flex-end"
839
1030
  };
1031
+ TEXT_PRESETS = {
1032
+ title: { size: 18, weight: 600, lineHeight: 1.3 },
1033
+ subtitle: { size: 16, weight: 600, lineHeight: 1.35 },
1034
+ paragraph: { size: 14, weight: 400, lineHeight: 1.5 },
1035
+ small: { size: 12, weight: 400, lineHeight: 1.5 }
1036
+ };
840
1037
  }
841
1038
  });
842
1039
 
@@ -1199,7 +1396,161 @@ var init_styles = __esm({
1199
1396
  }
1200
1397
  /* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
1201
1398
  el renderer como estilos inline desde step.design; ac\xE1 van los defaults. */
1399
+ /* Edici\xF3n (builder): sin animaci\xF3n de entrada al repintar la preview. */
1400
+ :host(.veo-no-anim) * { animation: none !important; }
1401
+
1402
+ /* Bloque video: caja con relaci\xF3n de aspecto; el iframe la llena. */
1403
+ .veo-guide-video { position: relative; overflow: hidden; border-radius: 8px; background: #000; }
1404
+ .veo-guide-video iframe, .veo-guide-video video {
1405
+ position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block;
1406
+ }
1407
+
1408
+ /* \u2500\u2500 Layout por filas/columnas (composite) \u2500\u2500
1409
+ El espacio tiene UNA fuente por capa: el aire con el borde lo da el padding
1410
+ de la card; la separaci\xF3n entre filas/bloques la da el margen inferior
1411
+ (12px, ah\xED vive la l\xEDnea \xAB+\xBB del editor); los bloques NO llevan padding
1412
+ propio (solo el que el autor les ponga inline). El \xFAltimo elemento no deja
1413
+ margen colgando. */
1414
+ .veo-card > [data-veo-block], .veo-card > .veo-row, .veo-card > .veo-guide-actions,
1415
+ .veo-col > [data-veo-block], .veo-col > .veo-guide-actions { margin-bottom: 12px; }
1416
+ /* La card y cada columna son columnas flex: cada bloque mide SU contenido (como
1417
+ el bot\xF3n) y se ubica con su alineaci\xF3n (align-self); filas, columnas, formularios
1418
+ y filas de botones ocupan todo el ancho. */
1419
+ .veo-card:not(.veo-card--empty), .veo-col {
1420
+ display: flex; flex-direction: column; align-items: flex-start;
1421
+ }
1422
+ .veo-card > [data-veo-block], .veo-col > [data-veo-block] { max-width: 100%; }
1423
+ /* L\xEDnea horizontal: ocupa todo el ancho; el padding del bloque es solo vertical
1424
+ (zona clickeable en el editor) y la l\xEDnea es el borde superior del hr. */
1425
+ .veo-card > .veo-guide-divider, .veo-col > .veo-guide-divider { padding: 6px 0; align-self: stretch; }
1426
+ .veo-guide-divider { box-sizing: border-box; }
1427
+ /* Bloque de c\xF3digo (modo host): ocupa todo el ancho, como un formulario. */
1428
+ .veo-code-host { display: block; min-width: 0; }
1429
+ .veo-card > .veo-code-host, .veo-col > .veo-code-host { align-self: stretch; }
1430
+ .veo-guide-divider hr { margin: 0; border: 0; height: 0; }
1431
+ .veo-card > .veo-row, .veo-card > .veo-guide-actions, .veo-card > .veo-form,
1432
+ .veo-col > .veo-guide-actions, .veo-col > .veo-form { align-self: stretch; }
1433
+ .veo-row { display: flex; gap: 12px; align-items: flex-start; box-sizing: border-box; }
1434
+ .veo-col { flex: 1 1 0; min-width: 0; }
1435
+ .veo-col > :last-child { margin-bottom: 0; }
1436
+ /* Fila con alineaci\xF3n horizontal (editor de fila): las columnas miden su contenido. */
1437
+ .veo-row--aligned > .veo-col { flex: 0 1 auto; }
1438
+
1439
+ /* \u2500\u2500 Editor in-app: bloque seleccionado + barra de acciones + placeholders \u2500\u2500 */
1440
+ .veo-block-placeholder {
1441
+ border: 1px dashed #b0a9a5; border-radius: 6px; padding: 12px; margin-bottom: 12px;
1442
+ color: #8a8582; font-size: 13px; text-align: center;
1443
+ }
1444
+ /* El bloque seleccionado no lleva contorno propio: lo rodea .veo-block-frame
1445
+ (marco aparte, con aire y alto m\xEDnimo, que no altera el layout). */
1446
+ .veo-block--selected { position: relative; z-index: 1; }
1447
+ .veo-block-frame {
1448
+ position: absolute; z-index: 4; pointer-events: none; box-sizing: border-box;
1449
+ border: 2px solid #3b82f6; border-radius: 10px; background: rgba(59, 130, 246, 0.04);
1450
+ }
1451
+ /* Barra del bloque (mock): p\xEDldora blanca con sombra, solo \xEDconos; la papelera
1452
+ en rojo, separada por una l\xEDnea. */
1453
+ .veo-block-toolbar {
1454
+ position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
1455
+ background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
1456
+ box-shadow: 0 6px 20px rgba(0,0,0,0.16);
1457
+ }
1458
+ .veo-block-toolbar button {
1459
+ border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px; border-radius: 8px;
1460
+ display: inline-flex; align-items: center; justify-content: center; line-height: 1;
1461
+ }
1462
+ .veo-block-toolbar button:hover { background: #f5efeb; }
1463
+ .veo-block-toolbar button svg { width: 20px; height: 20px; }
1464
+ .veo-block-toolbar .veo-block-toolbar-remove { color: #e5484d; }
1465
+ /* Separadores: antes de la papelera y antes de las flechas de mover. */
1466
+ .veo-block-toolbar .veo-block-toolbar-remove, .veo-block-toolbar .veo-block-toolbar-move-left {
1467
+ margin-left: 6px; position: relative;
1468
+ }
1469
+ .veo-block-toolbar .veo-block-toolbar-remove::before, .veo-block-toolbar .veo-block-toolbar-move-left::before {
1470
+ content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
1471
+ }
1472
+ .veo-block-toolbar button:disabled { opacity: 0.3; cursor: default; background: transparent; }
1473
+ /* Contenedor (hover): contorno azul de la tarjeta y dos pesta\xF1as centradas
1474
+ sobre su borde superior: \xABEditar contenedor\xBB (outline) y \xABEditar filas\xBB. */
1475
+ .veo-card-frame {
1476
+ position: absolute; z-index: 3; inset: 0; pointer-events: none; box-sizing: border-box;
1477
+ border: 2px solid #3b82f6; border-radius: inherit;
1478
+ }
1479
+ .veo-card-toolbar {
1480
+ position: absolute; z-index: 6; left: 50%; transform: translateX(-50%);
1481
+ display: inline-flex; align-items: flex-end; gap: 8px; white-space: nowrap;
1482
+ }
1483
+ .veo-card-toolbar button {
1484
+ cursor: pointer; padding: 8px 14px; border-radius: 8px; border: 2px solid #3b82f6;
1485
+ font: 600 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
1486
+ background: #fff; color: #3b82f6;
1487
+ }
1488
+ .veo-card-toolbar button:hover { background: #eff6ff; }
1489
+ /* Con fondo = la vista que el panel muestra ahora (contenedor o estructura). */
1490
+ .veo-card-toolbar button.is-active { background: #3b82f6; color: #fff; }
1491
+ .veo-card-toolbar button.is-active:hover { background: #2563eb; border-color: #2563eb; }
1492
+ /* Editor de fila: marco al pasar el mouse por una fila (o la fila del bloque
1493
+ seleccionado) y barra \xABEditar fila \xB7 duplicar\xBB bajo su esquina derecha. */
1494
+ .veo-row-frame {
1495
+ position: absolute; z-index: 3; pointer-events: none; box-sizing: border-box;
1496
+ border: 2px solid #3b82f6; border-radius: 10px;
1497
+ }
1498
+ .veo-row-toolbar {
1499
+ position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
1500
+ background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
1501
+ box-shadow: 0 6px 20px rgba(0,0,0,0.16); white-space: nowrap;
1502
+ }
1503
+ .veo-row-toolbar button {
1504
+ border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px 8px; border-radius: 8px;
1505
+ display: inline-flex; align-items: center; gap: 8px; line-height: 1;
1506
+ font: 500 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
1507
+ }
1508
+ .veo-row-toolbar button:hover { background: #f5efeb; }
1509
+ .veo-row-toolbar button svg { width: 20px; height: 20px; }
1510
+ .veo-row-toolbar .veo-row-toolbar-duplicate { margin-left: 6px; position: relative; }
1511
+ .veo-row-toolbar .veo-row-toolbar-duplicate::before {
1512
+ content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
1513
+ }
1514
+ .veo-row-toolbar .veo-row-toolbar-remove { color: #e5484d; }
1515
+ /* Huecos \xAB+\xBB (mock): zona invisible entre elementos; al pasar el mouse aparece
1516
+ la l\xEDnea azul (horizontal = fila / bloque apilado, vertical = columna) con
1517
+ un \xAB+\xBB circular al centro. */
1518
+ .veo-gap { position: absolute; z-index: 4; }
1519
+ .veo-gap i { position: absolute; background: #3b82f6; opacity: 0; transition: opacity 100ms; pointer-events: none; }
1520
+ .veo-gap--h i { left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px; }
1521
+ .veo-gap--v i { top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px; }
1522
+ .veo-gap button {
1523
+ position: absolute; left: 50%; top: 50%; margin: -11px 0 0 -11px; box-sizing: border-box;
1524
+ opacity: 0; transition: opacity 100ms; width: 22px; height: 22px; border-radius: 50%; padding: 0;
1525
+ border: 2px solid #fff; background: #3b82f6; color: #fff; cursor: pointer;
1526
+ font: 600 15px/18px system-ui, -apple-system, "Segoe UI", sans-serif; text-align: center;
1527
+ box-shadow: 0 2px 8px rgba(0,0,0,0.25);
1528
+ }
1529
+ .veo-gap:hover i, .veo-gap:hover button, .veo-gap:focus-within button { opacity: 1; }
1530
+ .veo-gap button:hover { background: #2563eb; }
1531
+
1532
+ /* Tarjeta sin bloques (paso reci\xE9n creado). La tarjeta NO impone tama\xF1o ni
1533
+ padding propios (as\xED el ancho/padding del dise\xF1o se ven tal cual); el tama\xF1o
1534
+ m\xEDnimo lo pone la zona punteada azul de la preview. */
1535
+ .veo-card--empty { display: flex; align-items: stretch; }
1536
+ .veo-card-empty-zone {
1537
+ flex: 1; min-width: 0; min-height: 160px; margin-top: 14px;
1538
+ display: flex; align-items: center; justify-content: center; gap: 6px; text-align: center;
1539
+ border: 2px dashed #3b82f6; border-radius: 8px; background: #eff6ff;
1540
+ color: #2563eb; font-size: 16px; font-weight: 500; user-select: none; pointer-events: none;
1541
+ padding: 0 12px; box-sizing: border-box; overflow: hidden;
1542
+ }
1543
+ .veo-card-empty-zone b { font-weight: 600; font-size: 22px; line-height: 1; }
1544
+ /* Sin ancho configurado, la zona da un tama\xF1o c\xF3modo por defecto. */
1545
+ .veo-card--empty:not([style*="width"]) .veo-card-empty-zone { min-width: 360px; }
1546
+ /* Tarjeta angosta (la mide el renderer): solo el \xAB+\xBB, sin texto, y m\xE1s baja. */
1547
+ .veo-card--narrow .veo-card-empty-zone span { display: none; }
1548
+ .veo-card--narrow .veo-card-empty-zone { min-height: 120px; }
1202
1549
  .veo-card {
1550
+ /* Contenedor posicionado SIEMPRE: el \u2715 (absolute) se ubica respecto a la
1551
+ tarjeta tambi\xE9n en pasos embebidos (inline), no respecto a la app. Los
1552
+ flotantes lo pisan con position:fixed inline. */
1553
+ position: relative;
1203
1554
  pointer-events: auto;
1204
1555
  background: var(--veo-bg, #ffffff);
1205
1556
  color: var(--veo-text, #1f2937);
@@ -1292,6 +1643,13 @@ var init_styles = __esm({
1292
1643
  transition: opacity 120ms ease;
1293
1644
  }
1294
1645
  .veo-guide-cta:hover { opacity: 0.9; }
1646
+ /* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
1647
+ .veo-guide-cta--secondary {
1648
+ background: transparent; color: var(--veo-primary);
1649
+ border: 1px solid var(--veo-primary);
1650
+ }
1651
+ .veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
1652
+ .veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
1295
1653
  .veo-guide-cta:active { transform: translateY(1px); }
1296
1654
  .veo-guide-close {
1297
1655
  position: absolute; top: -6px; right: -6px;
@@ -1322,6 +1680,10 @@ var init_styles = __esm({
1322
1680
 
1323
1681
  .veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
1324
1682
  .veo-form-field { display: flex; flex-direction: column; gap: 6px; }
1683
+ /* Pregunta de la encuesta (rich text del bloque form). */
1684
+ .veo-form-question { font-size: 16px; font-weight: 600; line-height: 1.35; color: var(--veo-text); }
1685
+ .veo-form-question p { margin: 0; }
1686
+ .veo-form > .veo-guide-actions { margin: 0; }
1325
1687
  .veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
1326
1688
  .veo-form-input {
1327
1689
  font-size: 14px; color: var(--veo-text);
@@ -1330,26 +1692,71 @@ var init_styles = __esm({
1330
1692
  padding: 8px 10px; width: 100%;
1331
1693
  }
1332
1694
  .veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
1333
- .veo-form-options { display: flex; flex-direction: column; gap: 6px; }
1695
+ /* Lista de tareas (mock): \xEDtems con anillo de progreso, texto + subt\xEDtulo,
1696
+ y al pie \xABX de N completadas\xBB + porcentaje + barra. */
1697
+ .veo-checklist {
1698
+ display: flex; flex-direction: column; gap: 22px; align-self: stretch; width: 100%;
1699
+ min-width: 360px; box-sizing: border-box;
1700
+ }
1701
+ .veo-checklist-title { font-size: 24px; font-weight: 700; line-height: 1.25; color: var(--veo-cl-title); }
1702
+ .veo-checklist-title p { margin: 0; }
1703
+ .veo-checklist-items { display: flex; flex-direction: column; gap: 22px; }
1704
+ .veo-checklist-item {
1705
+ display: flex; align-items: center; gap: 18px; width: 100%; text-align: left;
1706
+ background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: inherit;
1707
+ }
1708
+ .veo-checklist-item:hover .veo-checklist-label { text-decoration: underline; }
1709
+ .veo-checklist-ring { width: 44px; height: 44px; flex: 0 0 auto; }
1710
+ .veo-checklist-texts { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
1711
+ .veo-checklist-label {
1712
+ font-size: calc(var(--veo-cl-font-size, 14px) + 4px); font-weight: var(--veo-cl-font-weight, 600);
1713
+ line-height: 1.3; color: var(--veo-cl-title);
1714
+ }
1715
+ .veo-checklist-sub { font-size: calc(var(--veo-cl-font-size, 14px) + 1px); color: var(--veo-cl-subtitle); }
1716
+ .veo-checklist-footer { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
1717
+ .veo-checklist-summary {
1718
+ display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
1719
+ font-size: calc(var(--veo-cl-font-size, 14px) + 1px);
1720
+ }
1721
+ .veo-checklist-count { color: var(--veo-cl-subtitle); }
1722
+ .veo-checklist-percent { font-weight: 700; font-size: calc(var(--veo-cl-font-size, 14px) + 3px); color: var(--veo-cl-bar); }
1723
+ .veo-checklist-bar { height: var(--veo-cl-bar-h, 8px); background: #e7e2de; border-radius: 999px; overflow: hidden; }
1724
+ .veo-checklist--dark .veo-checklist-bar { background: #3c3734; }
1725
+ .veo-checklist-bar-fill { height: 100%; background: var(--veo-cl-bar); border-radius: 999px; transition: width 300ms ease; }
1726
+ .veo-checklist-empty { margin: 0; font-size: 13px; color: var(--veo-cl-subtitle); }
1727
+ /* Opci\xF3n m\xFAltiple (mock): c\xEDrculos/casillas grandes con su texto al lado. */
1728
+ .veo-form-options { display: flex; flex-direction: column; gap: 14px; }
1334
1729
  .veo-form-option {
1335
- display: flex; align-items: center; gap: 8px;
1336
- font-size: 14px; color: var(--veo-text); cursor: pointer;
1337
- }
1338
- .veo-form-option input { accent-color: var(--veo-primary); margin: 0; cursor: pointer; }
1339
- .veo-form-scale { display: flex; gap: 4px; flex-wrap: wrap; }
1730
+ display: flex; align-items: center; gap: 14px;
1731
+ font-size: 16px; color: var(--veo-text); cursor: pointer;
1732
+ }
1733
+ .veo-form-option input {
1734
+ accent-color: var(--veo-primary); margin: 0; cursor: pointer;
1735
+ width: 26px; height: 26px; flex: 0 0 auto;
1736
+ }
1737
+ /* Escala num\xE9rica (mock): casillas cuadradas; con etiquetas, cada casilla
1738
+ lleva la suya debajo (la primera cuelga a la izquierda, la \xFAltima a la derecha). */
1739
+ .veo-form-scale { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
1740
+ .veo-scale-item { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; }
1741
+ .veo-scale-item--first { align-items: flex-start; }
1742
+ .veo-scale-item--last { align-items: flex-end; }
1743
+ .veo-scale-label { font-size: 12px; line-height: 1.2; color: #8a8582; white-space: nowrap; }
1340
1744
  .veo-nps-btn {
1341
- min-width: 30px; height: 30px; padding: 0 4px;
1342
- font-size: 13px; font-weight: 500; color: var(--veo-text);
1343
- background: var(--veo-bg); border: 1px solid #d1d5db; border-radius: 6px;
1745
+ min-width: var(--veo-scale-size, 44px); height: var(--veo-scale-size, 44px); padding: 0 6px;
1746
+ font-size: 16px; font-weight: 600; color: var(--veo-text);
1747
+ background: var(--veo-bg); border: 1px solid #d5cec9; border-radius: 8px;
1344
1748
  cursor: pointer; transition: all 100ms ease;
1345
1749
  }
1346
- .veo-nps-btn:hover { border-color: var(--veo-primary); }
1750
+ .veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
1347
1751
  .veo-rating-btn {
1348
1752
  background: none; border: none; padding: 0 2px;
1349
1753
  font-size: 26px; line-height: 1; color: #d1d5db;
1350
1754
  cursor: pointer; transition: color 100ms ease;
1351
1755
  }
1352
- .veo-scale-active.veo-nps-btn { background: var(--veo-primary); color: #fff; border-color: var(--veo-primary); }
1756
+ .veo-scale-active.veo-nps-btn {
1757
+ background: var(--veo-scale-color, var(--veo-primary)); color: #fff;
1758
+ border-color: var(--veo-scale-color, var(--veo-primary));
1759
+ }
1353
1760
  .veo-scale-active.veo-rating-btn { color: #f59e0b; }
1354
1761
  .veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
1355
1762
  .veo-form-thanks {
@@ -2057,6 +2464,217 @@ var init_banner_renderer = __esm({
2057
2464
  }
2058
2465
  });
2059
2466
 
2467
+ // src/plugins/guides/checklist-block.ts
2468
+ function checklistStyle(style) {
2469
+ const s = style ?? {};
2470
+ const str = (k, fb) => typeof s[k] === "string" && s[k].trim() ? s[k] : fb;
2471
+ const num2 = (k, fb, min, max) => typeof s[k] === "number" && Number.isFinite(s[k]) ? Math.min(max, Math.max(min, s[k])) : fb;
2472
+ const weightRaw = s.fontWeight;
2473
+ const fontWeight = typeof weightRaw === "number" ? Math.min(900, Math.max(100, weightRaw)) : typeof weightRaw === "string" && WEIGHTS[weightRaw.toLowerCase()] ? WEIGHTS[weightRaw.toLowerCase()] : CHECKLIST_DEFAULTS.fontWeight;
2474
+ const dark = s.colorMode === "dark";
2475
+ return {
2476
+ colorMode: dark ? "dark" : "light",
2477
+ iconEmptyColor: str("iconEmptyColor", dark ? "#3c3734" : CHECKLIST_DEFAULTS.iconEmptyColor),
2478
+ iconFillColor: str("iconFillColor", CHECKLIST_DEFAULTS.iconFillColor),
2479
+ barColor: str("barColor", CHECKLIST_DEFAULTS.barColor),
2480
+ barHeight: num2("barHeight", CHECKLIST_DEFAULTS.barHeight, 2, 40),
2481
+ fontSize: num2("fontSize", CHECKLIST_DEFAULTS.fontSize, 10, 40),
2482
+ fontWeight,
2483
+ titleColor: str("titleColor", dark ? "#f5efeb" : CHECKLIST_DEFAULTS.titleColor),
2484
+ subtitleColor: str("subtitleColor", dark ? "#b0a9a5" : CHECKLIST_DEFAULTS.subtitleColor),
2485
+ subtitleTemplate: str("subtitleTemplate", CHECKLIST_DEFAULTS.subtitleTemplate)
2486
+ };
2487
+ }
2488
+ function renderSubtitle(template, p) {
2489
+ return template.replace(/<%=\s*currentStep\s*%>/g, String(p.currentStep)).replace(/<%=\s*totalSteps\s*%>/g, String(p.totalSteps));
2490
+ }
2491
+ function buildRing(doc, fraction, completed, st) {
2492
+ const ns = "http://www.w3.org/2000/svg";
2493
+ const svg2 = doc.createElementNS(ns, "svg");
2494
+ svg2.setAttribute("viewBox", "0 0 28 28");
2495
+ svg2.setAttribute("class", "veo-checklist-ring");
2496
+ svg2.setAttribute("aria-hidden", "true");
2497
+ const track = doc.createElementNS(ns, "circle");
2498
+ track.setAttribute("cx", "14");
2499
+ track.setAttribute("cy", "14");
2500
+ track.setAttribute("r", String(RING_R));
2501
+ track.setAttribute("fill", completed ? st.iconFillColor : "none");
2502
+ track.setAttribute("stroke", st.iconEmptyColor);
2503
+ track.setAttribute("stroke-width", "4.5");
2504
+ svg2.appendChild(track);
2505
+ if (completed) {
2506
+ const check = doc.createElementNS(ns, "path");
2507
+ check.setAttribute("d", "M9 14.5l3.2 3.2L19 11");
2508
+ check.setAttribute("fill", "none");
2509
+ check.setAttribute("stroke", "#fff");
2510
+ check.setAttribute("stroke-width", "2.4");
2511
+ check.setAttribute("stroke-linecap", "round");
2512
+ check.setAttribute("stroke-linejoin", "round");
2513
+ svg2.appendChild(check);
2514
+ return svg2;
2515
+ }
2516
+ const f = Math.max(0, Math.min(1, fraction));
2517
+ if (f > 0) {
2518
+ const arc = doc.createElementNS(ns, "circle");
2519
+ arc.setAttribute("cx", "14");
2520
+ arc.setAttribute("cy", "14");
2521
+ arc.setAttribute("r", String(RING_R));
2522
+ arc.setAttribute("fill", "none");
2523
+ arc.setAttribute("stroke", st.iconFillColor);
2524
+ arc.setAttribute("stroke-width", "4.5");
2525
+ arc.setAttribute("stroke-linecap", "butt");
2526
+ arc.setAttribute("stroke-dasharray", `${(RING_C * f).toFixed(2)} ${RING_C.toFixed(2)}`);
2527
+ arc.setAttribute("transform", "rotate(-90 14 14)");
2528
+ svg2.appendChild(arc);
2529
+ }
2530
+ return svg2;
2531
+ }
2532
+ function buildChecklistBlock(block, doc, context) {
2533
+ const st = checklistStyle(block.style);
2534
+ const items = (block.items ?? []).filter((i) => i && typeof i.guideId === "string" && i.guideId);
2535
+ const root = doc.createElement("div");
2536
+ root.className = `veo-checklist${st.colorMode === "dark" ? " veo-checklist--dark" : ""}`;
2537
+ root.style.setProperty("--veo-cl-font-size", `${st.fontSize}px`);
2538
+ root.style.setProperty("--veo-cl-font-weight", String(st.fontWeight));
2539
+ root.style.setProperty("--veo-cl-title", st.titleColor);
2540
+ root.style.setProperty("--veo-cl-subtitle", st.subtitleColor);
2541
+ root.style.setProperty("--veo-cl-bar", st.barColor);
2542
+ root.style.setProperty("--veo-cl-bar-h", `${st.barHeight}px`);
2543
+ if (typeof block.html === "string" && block.html || typeof block.text === "string" && block.text) {
2544
+ const title = doc.createElement("div");
2545
+ title.className = "veo-checklist-title";
2546
+ if (typeof block.html === "string" && block.html)
2547
+ title.appendChild(renderRichText(block.html, doc));
2548
+ else title.textContent = block.text;
2549
+ root.appendChild(title);
2550
+ }
2551
+ const list = doc.createElement("div");
2552
+ list.className = "veo-checklist-items";
2553
+ list.setAttribute("role", "list");
2554
+ root.appendChild(list);
2555
+ const footer = doc.createElement("div");
2556
+ footer.className = "veo-checklist-footer";
2557
+ const summary = doc.createElement("div");
2558
+ summary.className = "veo-checklist-summary";
2559
+ const count = doc.createElement("span");
2560
+ count.className = "veo-checklist-count";
2561
+ const percent = doc.createElement("span");
2562
+ percent.className = "veo-checklist-percent";
2563
+ summary.append(count, percent);
2564
+ const bar = doc.createElement("div");
2565
+ bar.className = "veo-checklist-bar";
2566
+ const fill = doc.createElement("div");
2567
+ fill.className = "veo-checklist-bar-fill";
2568
+ bar.appendChild(fill);
2569
+ footer.append(summary, bar);
2570
+ root.appendChild(footer);
2571
+ const paint = (progress) => {
2572
+ list.textContent = "";
2573
+ const visible = progress ? items.filter((i) => progress.has(i.guideId)) : items;
2574
+ let done = 0;
2575
+ for (const item of visible) {
2576
+ const p = progress?.get(item.guideId) ?? null;
2577
+ if (p?.completed) done++;
2578
+ const row = doc.createElement("button");
2579
+ row.type = "button";
2580
+ row.className = `veo-checklist-item${p?.completed ? " veo-checklist-item--done" : ""}`;
2581
+ row.setAttribute("role", "listitem");
2582
+ const fraction = p && p.totalSteps > 0 ? p.currentStep / p.totalSteps : 0;
2583
+ row.appendChild(buildRing(doc, fraction, p?.completed === true, st));
2584
+ const texts = doc.createElement("span");
2585
+ texts.className = "veo-checklist-texts";
2586
+ const label = doc.createElement("span");
2587
+ label.className = "veo-checklist-label";
2588
+ label.textContent = (item.text ?? "").trim() || p?.guideName || "Gu\xEDa";
2589
+ if (p && p.published === false) row.title = "Gu\xEDa no publicada: los usuarios no la ver\xE1n";
2590
+ texts.appendChild(label);
2591
+ if (p) {
2592
+ const sub = doc.createElement("span");
2593
+ sub.className = "veo-checklist-sub";
2594
+ sub.textContent = renderSubtitle(st.subtitleTemplate, p);
2595
+ texts.appendChild(sub);
2596
+ }
2597
+ row.appendChild(texts);
2598
+ row.addEventListener("click", (e) => {
2599
+ e.preventDefault();
2600
+ e.stopPropagation();
2601
+ if (context.isPreview) return;
2602
+ const start = p && !p.completed ? Math.min(p.currentStep, p.totalSteps - 1) : 0;
2603
+ if (context.onOpenGuideAt) context.onOpenGuideAt(item.guideId, start);
2604
+ else context.onOpenGuide?.(item.guideId);
2605
+ });
2606
+ list.appendChild(row);
2607
+ }
2608
+ const total = visible.length;
2609
+ const pct = total > 0 ? Math.round(done / total * 100) : 0;
2610
+ count.textContent = `${done} de ${total} ${total === 1 ? "completada" : "completadas"}`;
2611
+ percent.textContent = `${pct}%`;
2612
+ fill.style.width = `${pct}%`;
2613
+ footer.hidden = total === 0;
2614
+ };
2615
+ if (context.isPreview && items.length === 0) {
2616
+ const sample = /* @__PURE__ */ new Map([
2617
+ [
2618
+ "__sample_1",
2619
+ {
2620
+ guideId: "__sample_1",
2621
+ guideName: "Configura tu primer env\xEDo",
2622
+ totalSteps: 2,
2623
+ currentStep: 1,
2624
+ completed: false
2625
+ }
2626
+ ],
2627
+ [
2628
+ "__sample_2",
2629
+ {
2630
+ guideId: "__sample_2",
2631
+ guideName: "Conecta tu tienda",
2632
+ totalSteps: 2,
2633
+ currentStep: 2,
2634
+ completed: true
2635
+ }
2636
+ ]
2637
+ ]);
2638
+ items.push(
2639
+ { guideId: "__sample_1", text: "Configura tu primer env\xEDo" },
2640
+ { guideId: "__sample_2", text: "Conecta tu tienda" }
2641
+ );
2642
+ paint(sample);
2643
+ const hint = doc.createElement("p");
2644
+ hint.className = "veo-checklist-empty";
2645
+ hint.textContent = "Ejemplo: agrega gu\xEDas a la lista desde Opciones.";
2646
+ root.appendChild(hint);
2647
+ return root;
2648
+ }
2649
+ paint(null);
2650
+ if (context.fetchProgress && items.length > 0) {
2651
+ void context.fetchProgress(items.map((i) => i.guideId)).then((rows) => paint(new Map(rows.map((r) => [r.guideId, r])))).catch(() => {
2652
+ });
2653
+ }
2654
+ return root;
2655
+ }
2656
+ var CHECKLIST_DEFAULTS, WEIGHTS, RING_R, RING_C;
2657
+ var init_checklist_block = __esm({
2658
+ "src/plugins/guides/checklist-block.ts"() {
2659
+ init_rich_text();
2660
+ CHECKLIST_DEFAULTS = {
2661
+ colorMode: "light",
2662
+ iconEmptyColor: "#e7e2de",
2663
+ iconFillColor: "#406a31",
2664
+ barColor: "#406a31",
2665
+ barHeight: 8,
2666
+ fontSize: 14,
2667
+ fontWeight: 600,
2668
+ titleColor: "#2c2825",
2669
+ subtitleColor: "#8a8582",
2670
+ subtitleTemplate: "Completado <%= currentStep %> de <%= totalSteps %> pasos"
2671
+ };
2672
+ WEIGHTS = { regular: 400, medium: 500, semibold: 600, bold: 700 };
2673
+ RING_R = 10;
2674
+ RING_C = 2 * Math.PI * RING_R;
2675
+ }
2676
+ });
2677
+
2060
2678
  // src/plugins/guides/renderers/custom-frame.ts
2061
2679
  function mountCustomFrame(doc, step, context, opts) {
2062
2680
  const iframe = doc.createElement("iframe");
@@ -2241,6 +2859,11 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
2241
2859
  }
2242
2860
  if (rawInline) {
2243
2861
  const rawNodes = Array.from(holder.childNodes);
2862
+ if (rawInline.tagBlockId) {
2863
+ for (const n of rawNodes) {
2864
+ if (n instanceof Element) n.setAttribute(RAW_BLOCK_ATTR, rawInline.tagBlockId);
2865
+ }
2866
+ }
2244
2867
  insertRawNodes(rawInline.anchor, rawNodes, rawInline.pos);
2245
2868
  injected.push(...rawNodes);
2246
2869
  } else if (host) {
@@ -2250,24 +2873,40 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
2250
2873
  injected.push(holder);
2251
2874
  const slot = doc.createElement("slot");
2252
2875
  slot.setAttribute("name", slotName);
2253
- node = slot;
2876
+ const wrapper = doc.createElement("div");
2877
+ wrapper.className = "veo-code-host";
2878
+ wrapper.appendChild(slot);
2879
+ node = wrapper;
2254
2880
  } else {
2255
2881
  node = holder;
2256
2882
  }
2257
2883
  }
2884
+ let cancelled = false;
2258
2885
  if (typeof block.js === "string" && block.js.trim()) {
2259
- const s = hostDoc.createElement("script");
2260
- s.textContent = block.js;
2261
- hostDoc.body.appendChild(s);
2262
- injected.push(s);
2886
+ const js = block.js;
2887
+ const run = () => {
2888
+ if (cancelled) return;
2889
+ const s = hostDoc.createElement("script");
2890
+ s.textContent = wrapUserScript(js);
2891
+ hostDoc.body.appendChild(s);
2892
+ injected.push(s);
2893
+ };
2894
+ if (host && !host.isConnected) queueMicrotask(run);
2895
+ else run();
2263
2896
  }
2264
2897
  return {
2265
2898
  node,
2266
2899
  cleanup: () => {
2900
+ cancelled = true;
2267
2901
  for (const n of injected) n.parentNode?.removeChild(n);
2268
2902
  }
2269
2903
  };
2270
2904
  }
2905
+ function wrapUserScript(js) {
2906
+ return `(function(){try{
2907
+ ${js}
2908
+ }catch(e){console.error("[OnUser] Error en el bloque de c\xF3digo:",e)}})();`;
2909
+ }
2271
2910
  function runScript(old, hostDoc) {
2272
2911
  const s = hostDoc.createElement("script");
2273
2912
  for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
@@ -2275,10 +2914,11 @@ function runScript(old, hostDoc) {
2275
2914
  hostDoc.body.appendChild(s);
2276
2915
  return s;
2277
2916
  }
2278
- var codeSlotSeq;
2917
+ var RAW_BLOCK_ATTR, codeSlotSeq;
2279
2918
  var init_code_block = __esm({
2280
2919
  "src/plugins/guides/code-block.ts"() {
2281
2920
  init_custom_frame();
2921
+ RAW_BLOCK_ATTR = "data-veo-raw-block";
2282
2922
  codeSlotSeq = 0;
2283
2923
  }
2284
2924
  });
@@ -2509,8 +3149,14 @@ function buildField(field, doc) {
2509
3149
  wrapper.appendChild(group);
2510
3150
  return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
2511
3151
  }
2512
- case "nps":
2513
- return buildScale(wrapper, doc, 0, 10, "veo-nps-btn");
3152
+ case "nps": {
3153
+ const min = typeof field.min === "number" ? field.min : 0;
3154
+ const max = typeof field.max === "number" && field.max > min ? field.max : 10;
3155
+ return buildScale(wrapper, doc, min, max, "veo-nps-btn", void 0, {
3156
+ labels: field.scaleLabels ?? [],
3157
+ showLabels: field.showLabels !== false
3158
+ });
3159
+ }
2514
3160
  case "rating":
2515
3161
  return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
2516
3162
  default: {
@@ -2523,17 +3169,21 @@ function buildField(field, doc) {
2523
3169
  }
2524
3170
  }
2525
3171
  }
2526
- function buildScale(wrapper, doc, min, max, btnClass, symbol) {
3172
+ function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
2527
3173
  const row = doc.createElement("div");
2528
3174
  row.className = "veo-form-scale";
2529
3175
  let selected;
2530
3176
  const buttons = [];
3177
+ const labelOf = (n) => opts.labels[n - min]?.trim() ?? "";
3178
+ const anyLabel = opts.showLabels && Array.from({ length: max - min + 1 }, (_, i) => min + i).some((n) => labelOf(n));
2531
3179
  for (let n = min; n <= max; n++) {
2532
3180
  const btn = doc.createElement("button");
2533
3181
  btn.type = "button";
2534
3182
  btn.className = btnClass;
2535
3183
  btn.textContent = symbol ?? String(n);
2536
- btn.setAttribute("aria-label", String(n));
3184
+ const lbl = labelOf(n);
3185
+ btn.setAttribute("aria-label", lbl ? `${n} \xB7 ${lbl}` : String(n));
3186
+ if (lbl) btn.title = lbl;
2537
3187
  btn.addEventListener("click", () => {
2538
3188
  selected = n;
2539
3189
  buttons.forEach((b, i) => {
@@ -2542,7 +3192,17 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
2542
3192
  });
2543
3193
  });
2544
3194
  buttons.push(btn);
2545
- row.appendChild(btn);
3195
+ if (!anyLabel) {
3196
+ row.appendChild(btn);
3197
+ continue;
3198
+ }
3199
+ const item = doc.createElement("div");
3200
+ item.className = `veo-scale-item${n === min ? " veo-scale-item--first" : n === max ? " veo-scale-item--last" : ""}`;
3201
+ const text = doc.createElement("span");
3202
+ text.className = "veo-scale-label";
3203
+ text.textContent = lbl;
3204
+ item.append(btn, text);
3205
+ row.appendChild(item);
2546
3206
  }
2547
3207
  wrapper.appendChild(row);
2548
3208
  return { wrapper, read: () => selected };
@@ -2552,6 +3212,126 @@ var init_form_content = __esm({
2552
3212
  init_block_builder();
2553
3213
  }
2554
3214
  });
3215
+
3216
+ // src/plugins/guides/layout.ts
3217
+ function groupLayout(blocks) {
3218
+ const rows = /* @__PURE__ */ new Map();
3219
+ const order = [];
3220
+ let lastRow = -1;
3221
+ blocks.forEach((block, index) => {
3222
+ if (!block) return;
3223
+ let row;
3224
+ if (typeof block.row === "number" && Number.isFinite(block.row)) {
3225
+ row = block.row;
3226
+ lastRow = row;
3227
+ } else {
3228
+ row = lastRow + 0.5;
3229
+ lastRow = row;
3230
+ }
3231
+ const col = typeof block.col === "number" && Number.isFinite(block.col) ? block.col : 0;
3232
+ let r = rows.get(row);
3233
+ if (!r) {
3234
+ r = { row, columns: [] };
3235
+ rows.set(row, r);
3236
+ order.push(row);
3237
+ }
3238
+ let c = r.columns.find((x) => x.col === col);
3239
+ if (!c) {
3240
+ c = { col, blocks: [] };
3241
+ r.columns.push(c);
3242
+ }
3243
+ c.blocks.push({ block, index });
3244
+ });
3245
+ return order.sort((a, b) => a - b).map((k) => {
3246
+ const r = rows.get(k);
3247
+ r.columns.sort((a, b) => a.col - b.col);
3248
+ return r;
3249
+ });
3250
+ }
3251
+ var init_layout = __esm({
3252
+ "src/plugins/guides/layout.ts"() {
3253
+ }
3254
+ });
3255
+
3256
+ // src/plugins/guides/scroll-lock.ts
3257
+ function lockScroll(doc = document) {
3258
+ const html = doc.documentElement;
3259
+ const body = doc.body;
3260
+ if (!html || !body) return () => {
3261
+ };
3262
+ let state = states.get(doc);
3263
+ if (!state) {
3264
+ state = {
3265
+ count: 0,
3266
+ html: { overflow: html.style.overflow, paddingRight: html.style.paddingRight },
3267
+ body: { overflow: body.style.overflow, paddingRight: body.style.paddingRight }
3268
+ };
3269
+ states.set(doc, state);
3270
+ const win = doc.defaultView;
3271
+ const gap = win ? Math.max(0, win.innerWidth - html.clientWidth) : 0;
3272
+ if (gap > 0) body.style.paddingRight = `${gap}px`;
3273
+ html.style.overflow = "hidden";
3274
+ body.style.overflow = "hidden";
3275
+ }
3276
+ state.count += 1;
3277
+ let released = false;
3278
+ return () => {
3279
+ if (released) return;
3280
+ released = true;
3281
+ const s = states.get(doc);
3282
+ if (!s) return;
3283
+ s.count -= 1;
3284
+ if (s.count > 0) return;
3285
+ states.delete(doc);
3286
+ html.style.overflow = s.html.overflow;
3287
+ html.style.paddingRight = s.html.paddingRight;
3288
+ body.style.overflow = s.body.overflow;
3289
+ body.style.paddingRight = s.body.paddingRight;
3290
+ };
3291
+ }
3292
+ var states;
3293
+ var init_scroll_lock = __esm({
3294
+ "src/plugins/guides/scroll-lock.ts"() {
3295
+ states = /* @__PURE__ */ new WeakMap();
3296
+ }
3297
+ });
3298
+
3299
+ // src/plugins/guides/snooze.ts
3300
+ function snoozeDurationMs(every, unit) {
3301
+ const n = Number.isFinite(every) && every > 0 ? Math.floor(every) : 1;
3302
+ return n * (UNIT_MS[unit] ?? UNIT_MS.day);
3303
+ }
3304
+ function snoozeGuide(guideId, every, unit, now = Date.now()) {
3305
+ try {
3306
+ localStorage.setItem(KEY(guideId), String(now + snoozeDurationMs(every, unit)));
3307
+ } catch {
3308
+ }
3309
+ }
3310
+ function isSnoozed(guideId, now = Date.now()) {
3311
+ try {
3312
+ const raw = localStorage.getItem(KEY(guideId));
3313
+ if (raw === null) return false;
3314
+ const until = Number(raw);
3315
+ if (!Number.isFinite(until) || until <= now) {
3316
+ localStorage.removeItem(KEY(guideId));
3317
+ return false;
3318
+ }
3319
+ return true;
3320
+ } catch {
3321
+ return false;
3322
+ }
3323
+ }
3324
+ var KEY, UNIT_MS;
3325
+ var init_snooze = __esm({
3326
+ "src/plugins/guides/snooze.ts"() {
3327
+ KEY = (guideId) => `veo:snooze:${guideId}`;
3328
+ UNIT_MS = {
3329
+ hour: 36e5,
3330
+ day: 864e5,
3331
+ week: 6048e5
3332
+ };
3333
+ }
3334
+ });
2555
3335
  function num(v, fallback) {
2556
3336
  return typeof v === "number" && Number.isFinite(v) ? v : fallback;
2557
3337
  }
@@ -2739,26 +3519,61 @@ var init_badge_marker = __esm({
2739
3519
  SIDE_GAP = 8;
2740
3520
  }
2741
3521
  });
3522
+ function applyRowStyle(rowEl, style) {
3523
+ const s = rowEl.style;
3524
+ if (typeof style.background === "string" && style.background) s.background = style.background;
3525
+ for (const side of ["Top", "Right", "Bottom", "Left"]) {
3526
+ const p = style[`padding${side}`];
3527
+ if (typeof p === "number" && Number.isFinite(p))
3528
+ s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
3529
+ const m = style[`margin${side}`];
3530
+ if (typeof m === "number" && Number.isFinite(m))
3531
+ s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
3532
+ }
3533
+ if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
3534
+ s.minHeight = `${style.minHeight}px`;
3535
+ }
3536
+ if (style.align && ROW_JUSTIFY[style.align]) {
3537
+ s.justifyContent = ROW_JUSTIFY[style.align];
3538
+ rowEl.classList.add("veo-row--aligned");
3539
+ }
3540
+ if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
3541
+ }
2742
3542
  function inlinePosOf(p) {
2743
3543
  return p === "before" || p === "prepend" || p === "append" ? p : "after";
2744
3544
  }
2745
3545
  function applyViewportPosition(card, position, design) {
2746
3546
  const s = card.style;
2747
3547
  s.position = "fixed";
2748
- const M = "16px";
3548
+ const BASE = 16;
3549
+ const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
3550
+ s.margin = "0";
2749
3551
  const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
2750
3552
  const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
2751
3553
  const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
2752
- if (v === "top") s.top = M;
2753
- else if (v === "bottom") s.bottom = M;
3554
+ if (v === "top") s.top = `${BASE + m("marginTop")}px`;
3555
+ else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
2754
3556
  else s.top = "50%";
2755
- if (h === "left") s.left = M;
2756
- else if (h === "right") s.right = M;
3557
+ if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
3558
+ else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
2757
3559
  else s.left = "50%";
2758
- const tx = h === "middle" ? "-50%" : "0";
2759
- const ty = v === "middle" ? "-50%" : "0";
3560
+ const dx = m("marginLeft") - m("marginRight");
3561
+ const dy = m("marginTop") - m("marginBottom");
3562
+ const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
3563
+ const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
2760
3564
  s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
2761
3565
  }
3566
+ function reserveCloseSpace(card, blocks, position, design) {
3567
+ if (blocks.length === 0) return;
3568
+ const side = position.includes("bottom") ? "Bottom" : "Top";
3569
+ const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
3570
+ if (edgeBlock?.type === "image") return;
3571
+ const margin = design[`closeMargin${side}`];
3572
+ const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
3573
+ const current = design[`padding${side}`];
3574
+ const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
3575
+ if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
3576
+ }
2762
3577
  function applyClosePosition(btn, position) {
2763
3578
  const s = btn.style;
2764
3579
  s.top = "auto";
@@ -2827,6 +3642,11 @@ function applyCompositeDesign(card, design) {
2827
3642
  const s = card.style;
2828
3643
  const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
2829
3644
  const strv = (v) => typeof v === "string" && v ? v : null;
3645
+ if (design.colorMode === "dark") {
3646
+ s.background = "#1f1d1b";
3647
+ s.color = "#f5f0eb";
3648
+ card.classList.add("veo-card--dark");
3649
+ }
2830
3650
  if (strv(design.background)) s.background = design.background;
2831
3651
  const textColor = strv(design.color) ?? strv(design.textColor);
2832
3652
  if (textColor) s.color = textColor;
@@ -2848,13 +3668,14 @@ function applyCompositeDesign(card, design) {
2848
3668
  const n = (k) => typeof design[k] === "number" ? design[k] : 0;
2849
3669
  s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
2850
3670
  }
3671
+ const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
2851
3672
  if (design.fullWidth === true) {
2852
3673
  s.width = "100%";
2853
3674
  } else if (design.widthType === "fluid") {
2854
- s.width = "auto";
2855
- if (px(design.width)) s.maxWidth = px(design.width);
2856
- } else if (px(design.width)) {
2857
- s.width = px(design.width);
3675
+ s.width = "100%";
3676
+ if (width) s.maxWidth = width;
3677
+ } else if (width) {
3678
+ s.width = width;
2858
3679
  }
2859
3680
  for (const side of ["Top", "Right", "Bottom", "Left"]) {
2860
3681
  const p = px(design[`padding${side}`]);
@@ -2863,13 +3684,18 @@ function applyCompositeDesign(card, design) {
2863
3684
  if (m) s.setProperty(`margin-${side.toLowerCase()}`, m);
2864
3685
  }
2865
3686
  }
2866
- var ROW_ALIGN, CompositeRenderer, CLOSE_INSET;
3687
+ var ROW_ALIGN, ROW_JUSTIFY, ROW_VALIGN, CompositeRenderer, CLOSE_INSET, CLOSE_RESERVE_PX, CARD_DEFAULT_PAD_PX, MIN_CARD_WIDTH, NARROW_CARD_WIDTH;
2867
3688
  var init_composite_renderer = __esm({
2868
3689
  "src/plugins/guides/renderers/composite-renderer.ts"() {
2869
3690
  init_block_builder();
3691
+ init_checklist_block();
2870
3692
  init_code_block();
2871
3693
  init_form_content();
2872
3694
  init_inline_host();
3695
+ init_layout();
3696
+ init_rich_text();
3697
+ init_scroll_lock();
3698
+ init_snooze();
2873
3699
  init_wait_for_element();
2874
3700
  init_badge_marker();
2875
3701
  init_base_renderer();
@@ -2879,6 +3705,12 @@ var init_composite_renderer = __esm({
2879
3705
  center: "center",
2880
3706
  right: "flex-end"
2881
3707
  };
3708
+ ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
3709
+ ROW_VALIGN = {
3710
+ top: "flex-start",
3711
+ center: "center",
3712
+ bottom: "flex-end"
3713
+ };
2882
3714
  CompositeRenderer = class extends BaseRenderer {
2883
3715
  async render(context) {
2884
3716
  const idx = context.nav?.stepIndex ?? 0;
@@ -2897,7 +3729,8 @@ var init_composite_renderer = __esm({
2897
3729
  const rawDoc = anchor.ownerDocument ?? document;
2898
3730
  const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
2899
3731
  anchor,
2900
- pos: inlinePosOf(step.inlinePosition)
3732
+ pos: inlinePosOf(step.inlinePosition),
3733
+ ...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
2901
3734
  });
2902
3735
  this.registerCleanup(cleanup);
2903
3736
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
@@ -2910,6 +3743,10 @@ var init_composite_renderer = __esm({
2910
3743
  const card = doc.createElement("div");
2911
3744
  card.className = "veo-card";
2912
3745
  applyCompositeDesign(card, design);
3746
+ if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
3747
+ card.setAttribute("role", "dialog");
3748
+ card.setAttribute("aria-label", design.ariaLabel.trim());
3749
+ }
2913
3750
  const dismiss = (action, meta) => {
2914
3751
  context.onInteraction({
2915
3752
  guideId: context.guide.guideId,
@@ -2920,6 +3757,28 @@ var init_composite_renderer = __esm({
2920
3757
  };
2921
3758
  const readers = [];
2922
3759
  this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
3760
+ if (blocks.length === 0) {
3761
+ card.classList.add("veo-card--empty");
3762
+ if (context.isPreview) {
3763
+ const zone = doc.createElement("div");
3764
+ zone.className = "veo-card-empty-zone";
3765
+ const plus = doc.createElement("b");
3766
+ plus.textContent = "+";
3767
+ const text = doc.createElement("span");
3768
+ text.textContent = "Clic para agregar contenido";
3769
+ zone.append(plus, text);
3770
+ card.appendChild(zone);
3771
+ const applyNarrow = () => {
3772
+ card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
3773
+ };
3774
+ if (typeof ResizeObserver !== "undefined") {
3775
+ const ro = new ResizeObserver(applyNarrow);
3776
+ ro.observe(card);
3777
+ this.registerCleanup(() => ro.disconnect());
3778
+ }
3779
+ applyNarrow();
3780
+ }
3781
+ }
2923
3782
  const lastBlock = card.lastElementChild;
2924
3783
  if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
2925
3784
  let closeTooltip = null;
@@ -2937,6 +3796,14 @@ var init_composite_renderer = __esm({
2937
3796
  if (closeColor) closeBtn.style.color = closeColor;
2938
3797
  const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
2939
3798
  applyClosePosition(closeBtn, closePos);
3799
+ for (const side of ["Top", "Right", "Bottom", "Left"]) {
3800
+ const p = design[`closePadding${side}`];
3801
+ if (typeof p === "number")
3802
+ closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
3803
+ const m = design[`closeMargin${side}`];
3804
+ if (typeof m === "number")
3805
+ closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
3806
+ }
2940
3807
  if (hoverColor) {
2941
3808
  closeBtn.addEventListener("mouseenter", () => {
2942
3809
  closeBtn.style.color = hoverColor;
@@ -2946,6 +3813,7 @@ var init_composite_renderer = __esm({
2946
3813
  });
2947
3814
  }
2948
3815
  card.appendChild(closeBtn);
3816
+ reserveCloseSpace(card, blocks, closePos, design);
2949
3817
  }
2950
3818
  if (badgeMode && anchor) {
2951
3819
  closeTooltip = this.renderBadgeMode(
@@ -2976,6 +3844,7 @@ var init_composite_renderer = __esm({
2976
3844
  const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
2977
3845
  bg.style.zIndex = String(bz);
2978
3846
  root.appendChild(bg);
3847
+ this.registerCleanup(lockScroll(doc));
2979
3848
  if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
2980
3849
  }
2981
3850
  const wrapper = doc.createElement("div");
@@ -3002,6 +3871,7 @@ var init_composite_renderer = __esm({
3002
3871
  this.closeOrSkip(context);
3003
3872
  }
3004
3873
  });
3874
+ this.registerCleanup(lockScroll(doc));
3005
3875
  }
3006
3876
  applyViewportPosition(card, step.position, design);
3007
3877
  overlay.appendChild(card);
@@ -3019,83 +3889,217 @@ var init_composite_renderer = __esm({
3019
3889
  ...block.id ? { metadata: { buttonId: block.id } } : {}
3020
3890
  });
3021
3891
  };
3022
- let i = 0;
3023
- while (i < blocks.length) {
3024
- const b = blocks[i];
3025
- if (!b) {
3026
- i++;
3027
- continue;
3028
- }
3029
- if (b.type === "button") {
3030
- const row = doc.createElement("div");
3031
- row.className = "veo-guide-actions";
3032
- let rowAlign;
3033
- while (i < blocks.length && blocks[i]?.type === "button") {
3034
- const bb = blocks[i];
3035
- if (!rowAlign) {
3036
- const a = bb.style?.align;
3037
- if (a === "left" || a === "center" || a === "right") rowAlign = a;
3892
+ const tag = (node, block, index) => {
3893
+ node.dataset.veoBlock = String(index);
3894
+ if (block.editorId) node.dataset.veoBlockId = block.editorId;
3895
+ return node;
3896
+ };
3897
+ const placeholder = (block, label) => {
3898
+ if (!context.isPreview || !block.editorId) return null;
3899
+ const el2 = doc.createElement("div");
3900
+ el2.className = "veo-block-placeholder";
3901
+ el2.textContent = label;
3902
+ return el2;
3903
+ };
3904
+ const buildSequence = (container, cell) => {
3905
+ let i = 0;
3906
+ while (i < cell.length) {
3907
+ const { block: b, index } = cell[i];
3908
+ if (b.type === "button") {
3909
+ const row = doc.createElement("div");
3910
+ row.className = "veo-guide-actions";
3911
+ let rowAlign;
3912
+ while (i < cell.length && cell[i]?.block.type === "button") {
3913
+ const { block: bb, index: bi } = cell[i];
3914
+ if (!rowAlign) {
3915
+ const a = bb.style?.align;
3916
+ if (a === "left" || a === "center" || a === "right") rowAlign = a;
3917
+ }
3918
+ row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
3919
+ i++;
3038
3920
  }
3039
- row.appendChild(this.buildButton(bb, doc, context, idx, readers, emit));
3921
+ if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
3922
+ container.appendChild(row);
3923
+ continue;
3924
+ }
3925
+ if (b.type === "code") {
3926
+ const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
3927
+ const el2 = node2 ?? placeholder(b, "Bloque de c\xF3digo");
3928
+ if (el2) container.appendChild(tag(el2, b, index));
3929
+ this.registerCleanup(cleanup);
3040
3930
  i++;
3931
+ continue;
3041
3932
  }
3042
- if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
3043
- card.appendChild(row);
3044
- continue;
3045
- }
3046
- if (b.type === "code") {
3047
- const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
3048
- if (node2) card.appendChild(node2);
3049
- this.registerCleanup(cleanup);
3050
- i++;
3051
- continue;
3052
- }
3053
- if (b.type === "form") {
3054
- const form = doc.createElement("div");
3055
- form.className = "veo-form";
3056
- for (const field of b.fields ?? []) {
3057
- const { wrapper, read } = buildField(field, doc);
3058
- readers.push({ field, read });
3059
- form.appendChild(wrapper);
3933
+ if (b.type === "form") {
3934
+ container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
3935
+ i++;
3936
+ continue;
3937
+ }
3938
+ if (b.type === "checklist") {
3939
+ const el2 = buildChecklistBlock(b, doc, context);
3940
+ applyBlockStyle(el2, b.style, "text");
3941
+ el2.style.alignSelf = "stretch";
3942
+ el2.style.width = "100%";
3943
+ container.appendChild(tag(el2, b, index));
3944
+ i++;
3945
+ continue;
3060
3946
  }
3061
- card.appendChild(form);
3947
+ const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
3948
+ b,
3949
+ b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
3950
+ );
3951
+ if (node) container.appendChild(tag(node, b, index));
3062
3952
  i++;
3953
+ }
3954
+ };
3955
+ const rowStyles = context.guide.guideSteps[idx]?.rows;
3956
+ for (const row of groupLayout(blocks)) {
3957
+ const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
3958
+ if (row.columns.length === 1 && !rowStyle) {
3959
+ buildSequence(card, row.columns[0].blocks);
3063
3960
  continue;
3064
3961
  }
3065
- const node = buildContentBlock(b, doc);
3066
- if (node) card.appendChild(node);
3067
- i++;
3962
+ const rowEl = doc.createElement("div");
3963
+ rowEl.className = "veo-row";
3964
+ if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
3965
+ for (const column of row.columns) {
3966
+ const colEl = doc.createElement("div");
3967
+ colEl.className = "veo-col";
3968
+ buildSequence(colEl, column.blocks);
3969
+ rowEl.appendChild(colEl);
3970
+ }
3971
+ if (rowStyle) applyRowStyle(rowEl, rowStyle);
3972
+ card.appendChild(rowEl);
3068
3973
  }
3069
3974
  }
3070
3975
  /** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
3071
- buildButton(block, doc, context, idx, readers, emit) {
3976
+ /**
3977
+ * Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
3978
+ * de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
3979
+ * la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
3980
+ * bloque viste la pregunta (preset/alineación/color); los márgenes van al
3981
+ * contenedor del bloque entero.
3982
+ */
3983
+ buildForm(b, doc, context, idx, readers, emit) {
3984
+ const form = doc.createElement("div");
3985
+ form.className = "veo-form";
3986
+ const style = b.style ?? {};
3987
+ const { marginTop, marginBottom, ...questionStyle } = style;
3988
+ for (const [side, v] of [
3989
+ ["marginTop", marginTop],
3990
+ ["marginBottom", marginBottom]
3991
+ ]) {
3992
+ if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
3993
+ }
3994
+ let question = null;
3995
+ if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
3996
+ question = doc.createElement("div");
3997
+ question.className = "veo-form-question";
3998
+ if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
3999
+ else question.textContent = b.text;
4000
+ applyBlockStyle(question, questionStyle, "text");
4001
+ form.appendChild(question);
4002
+ }
4003
+ const questionText = question?.textContent?.trim() ?? "";
4004
+ const own = [];
4005
+ for (const field of b.fields ?? []) {
4006
+ const { wrapper, read } = buildField(field, doc);
4007
+ const st = b.style ?? {};
4008
+ if (typeof st.scaleColor === "string" && st.scaleColor) {
4009
+ wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
4010
+ }
4011
+ if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
4012
+ wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
4013
+ }
4014
+ if (question) {
4015
+ wrapper.querySelector(".veo-form-label")?.remove();
4016
+ if (questionText) {
4017
+ for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
4018
+ inp.setAttribute("aria-label", questionText);
4019
+ }
4020
+ }
4021
+ }
4022
+ const reader = { field, read, blockId: b.id ?? null };
4023
+ readers.push(reader);
4024
+ own.push(reader);
4025
+ form.appendChild(wrapper);
4026
+ }
4027
+ if (b.submit) {
4028
+ const actions = doc.createElement("div");
4029
+ actions.className = "veo-guide-actions";
4030
+ const submit = { ...b.submit, type: "button" };
4031
+ const sa = submit.style?.align;
4032
+ if (sa === "left" || sa === "center" || sa === "right") {
4033
+ actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
4034
+ }
4035
+ actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
4036
+ form.appendChild(actions);
4037
+ }
4038
+ return form;
4039
+ }
4040
+ buildButton(block, doc, context, idx, readers, emit, submitFirst) {
3072
4041
  const btn = doc.createElement("button");
3073
4042
  btn.type = "button";
3074
4043
  btn.className = "veo-guide-cta";
3075
4044
  btn.textContent = block.text?.trim() || "OK";
4045
+ const st = block.style ?? {};
4046
+ if (st.preset === "secondary" || st.preset === "link") {
4047
+ btn.classList.add(`veo-guide-cta--${st.preset}`);
4048
+ }
4049
+ if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
4050
+ btn.setAttribute("aria-label", st.ariaLabel.trim());
4051
+ }
3076
4052
  applyBlockStyle(btn, block.style, "button");
3077
4053
  const nav = context.nav?.callbacks;
3078
- btn.addEventListener("click", () => {
3079
- switch (block.action) {
4054
+ const runSimple = (spec) => {
4055
+ switch (spec.action) {
3080
4056
  case "next":
3081
- emit(block);
3082
4057
  if (nav) nav.onNext();
3083
4058
  else context.onClose();
3084
4059
  break;
3085
4060
  case "prev":
3086
- emit(block);
3087
4061
  nav?.onBack();
3088
4062
  break;
4063
+ case "goto":
4064
+ if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
4065
+ else context.onClose();
4066
+ break;
4067
+ case "snooze":
4068
+ snoozeGuide(
4069
+ context.guide.guideId,
4070
+ typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
4071
+ spec.snoozeUnit ?? "day"
4072
+ );
4073
+ context.onClose();
4074
+ break;
3089
4075
  case "url":
3090
- emit(block);
3091
- if (typeof block.url === "string" && isSafeUrl(block.url)) {
3092
- window.open(block.url, "_blank", "noopener,noreferrer");
4076
+ if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
4077
+ if (spec.newTab === false) window.location.assign(spec.url);
4078
+ else window.open(spec.url, "_blank", "noopener,noreferrer");
3093
4079
  }
3094
4080
  break;
3095
4081
  case "guide":
3096
- emit(block);
3097
- if (block.targetGuideId) context.onOpenGuide?.(block.targetGuideId);
4082
+ if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
4083
+ this.closeOrSkip(context);
4084
+ break;
4085
+ default:
3098
4086
  this.closeOrSkip(context);
4087
+ }
4088
+ };
4089
+ const runSecond = () => {
4090
+ if (block.secondAction?.action) runSimple(block.secondAction);
4091
+ };
4092
+ const runActions = () => {
4093
+ switch (block.action) {
4094
+ case "next":
4095
+ case "prev":
4096
+ case "goto":
4097
+ case "snooze":
4098
+ case "url":
4099
+ case "guide":
4100
+ emit(block);
4101
+ runSimple(block);
4102
+ runSecond();
3099
4103
  break;
3100
4104
  case "submit-forms":
3101
4105
  case "submit-forms-advance":
@@ -3114,7 +4118,12 @@ var init_composite_renderer = __esm({
3114
4118
  default:
3115
4119
  emit(block);
3116
4120
  this.closeOrSkip(context);
4121
+ runSecond();
3117
4122
  }
4123
+ };
4124
+ btn.addEventListener("click", () => {
4125
+ if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
4126
+ else runActions();
3118
4127
  });
3119
4128
  return btn;
3120
4129
  }
@@ -3133,17 +4142,27 @@ var init_composite_renderer = __esm({
3133
4142
  }
3134
4143
  answers[field.key] = value;
3135
4144
  }
3136
- if (readers.length > 0 && Object.keys(answers).length === 0) return;
4145
+ if (Object.keys(answers).length === 0) {
4146
+ onDone();
4147
+ return;
4148
+ }
3137
4149
  btn.disabled = true;
3138
4150
  const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
3139
- void submitFn(answers).then((ok) => {
4151
+ const blockIds = new Set(
4152
+ readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
4153
+ );
4154
+ const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
4155
+ void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
3140
4156
  btn.disabled = false;
3141
4157
  if (!ok) return;
3142
- context.onInteraction({
3143
- guideId: context.guide.guideId,
3144
- stepIndex: idx,
3145
- action: "completed"
3146
- });
4158
+ const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
4159
+ if (lastStep) {
4160
+ context.onInteraction({
4161
+ guideId: context.guide.guideId,
4162
+ stepIndex: idx,
4163
+ action: "completed"
4164
+ });
4165
+ }
3147
4166
  onDone();
3148
4167
  });
3149
4168
  }
@@ -3252,6 +4271,10 @@ var init_composite_renderer = __esm({
3252
4271
  }
3253
4272
  };
3254
4273
  CLOSE_INSET = "10px";
4274
+ CLOSE_RESERVE_PX = 42;
4275
+ CARD_DEFAULT_PAD_PX = 20;
4276
+ MIN_CARD_WIDTH = 120;
4277
+ NARROW_CARD_WIDTH = 260;
3255
4278
  }
3256
4279
  });
3257
4280
  function applyFloatingPosition(wrapper, placement) {
@@ -3347,6 +4370,7 @@ var FormRenderer;
3347
4370
  var init_form_renderer = __esm({
3348
4371
  "src/plugins/guides/renderers/form-renderer.ts"() {
3349
4372
  init_form_content();
4373
+ init_scroll_lock();
3350
4374
  init_base_renderer();
3351
4375
  FormRenderer = class extends BaseRenderer {
3352
4376
  render(context) {
@@ -3362,6 +4386,7 @@ var init_form_renderer = __esm({
3362
4386
  mountFormContent(card, context, doc);
3363
4387
  overlay.appendChild(card);
3364
4388
  root.appendChild(overlay);
4389
+ this.registerCleanup(lockScroll(doc));
3365
4390
  const dismiss = () => {
3366
4391
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
3367
4392
  context.onClose();
@@ -3518,12 +4543,25 @@ var ModalRenderer;
3518
4543
  var init_modal_renderer = __esm({
3519
4544
  "src/plugins/guides/renderers/modal-renderer.ts"() {
3520
4545
  init_block_builder();
4546
+ init_scroll_lock();
3521
4547
  init_walkthrough_block_builder();
3522
4548
  init_base_renderer();
3523
4549
  ModalRenderer = class extends BaseRenderer {
3524
4550
  constructor() {
3525
4551
  super(...arguments);
3526
4552
  this.overlay = null;
4553
+ /** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
4554
+ this.releaseScroll = null;
4555
+ }
4556
+ /** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
4557
+ syncScrollLock(step) {
4558
+ const wantLock = step.style?.backdrop !== false;
4559
+ if (wantLock && !this.releaseScroll) {
4560
+ this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
4561
+ } else if (!wantLock && this.releaseScroll) {
4562
+ this.releaseScroll();
4563
+ this.releaseScroll = null;
4564
+ }
3527
4565
  }
3528
4566
  render(context) {
3529
4567
  const idx = context.nav?.stepIndex ?? 0;
@@ -3535,6 +4573,7 @@ var init_modal_renderer = __esm({
3535
4573
  const overlay = ownerDocument.createElement("div");
3536
4574
  overlay.className = overlayClassOf(step);
3537
4575
  this.overlay = overlay;
4576
+ this.syncScrollLock(step);
3538
4577
  const card = ownerDocument.createElement("div");
3539
4578
  card.className = "veo-modal-card";
3540
4579
  const dismiss = (action, ctaUrl, meta) => {
@@ -3592,9 +4631,12 @@ var init_modal_renderer = __esm({
3592
4631
  }
3593
4632
  onLiveUpdate(step) {
3594
4633
  if (this.overlay) this.overlay.className = overlayClassOf(step);
4634
+ this.syncScrollLock(step);
3595
4635
  }
3596
4636
  destroy() {
3597
4637
  this.overlay = null;
4638
+ this.releaseScroll?.();
4639
+ this.releaseScroll = null;
3598
4640
  super.destroy();
3599
4641
  }
3600
4642
  };
@@ -3785,9 +4827,35 @@ function matchesUrl(matcher, pageUrl) {
3785
4827
  }
3786
4828
  }
3787
4829
  function stepMatchesPage(loc, pageUrl) {
3788
- if (!loc || loc.scope === "sitewide" || !loc.pattern) return true;
4830
+ if (!loc || loc.scope === "sitewide") return true;
4831
+ if (loc.scope === "pages") {
4832
+ const rules = loc.rules ?? [];
4833
+ return rules.some((r) => pageRuleMatches(r, pageUrl));
4834
+ }
4835
+ if (!loc.pattern) return true;
3789
4836
  return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
3790
4837
  }
4838
+ function pageRuleMatches(rule, pageUrl) {
4839
+ const pattern = rule.pattern.toLowerCase();
4840
+ const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
4841
+ switch (rule.type) {
4842
+ case "exact":
4843
+ return candidates.some((c) => c === pattern);
4844
+ case "prefix":
4845
+ return candidates.some((c) => c.startsWith(pattern));
4846
+ case "contains":
4847
+ return candidates.some((c) => c.includes(pattern));
4848
+ case "regex":
4849
+ try {
4850
+ const re = new RegExp(rule.pattern, "i");
4851
+ return candidates.some((c) => re.test(c));
4852
+ } catch {
4853
+ return false;
4854
+ }
4855
+ default:
4856
+ return false;
4857
+ }
4858
+ }
3791
4859
  var init_url_match = __esm({
3792
4860
  "src/plugins/guides/url-match.ts"() {
3793
4861
  }
@@ -3860,6 +4928,7 @@ var PREVIEW_GUIDE_ID, GuidePreviewController, activeController;
3860
4928
  var init_guide_preview = __esm({
3861
4929
  "src/plugins/guides/guide-preview.ts"() {
3862
4930
  init_safe_env();
4931
+ init_activation_rules();
3863
4932
  init_badge_renderer();
3864
4933
  init_banner_renderer();
3865
4934
  init_base_renderer();
@@ -3943,7 +5012,9 @@ var init_guide_preview = __esm({
3943
5012
  if (event.action === "shown") shown = true;
3944
5013
  },
3945
5014
  onClose: () => this.close(true),
3946
- badgeOpen: this.input?.badgeOpen === true,
5015
+ // Sin indicación explícita, el badge se abre solo si la guía arranca
5016
+ // sola (como en el runtime); `manual` lo deja cerrado.
5017
+ badgeOpen: this.input?.badgeOpen ?? isAutomaticActivation(this.input?.activationRules),
3947
5018
  onTrack: () => {
3948
5019
  },
3949
5020
  // Encadenado en preview: no-op — el editor no puede montar otra guía
@@ -3953,6 +5024,7 @@ var init_guide_preview = __esm({
3953
5024
  // Preview de forms: por defecto simula el envío SIN escribir props;
3954
5025
  // el caller puede pasar `formSubmit` real (link de preview).
3955
5026
  onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
5027
+ ...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
3956
5028
  isPreview: true,
3957
5029
  ...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
3958
5030
  });
@@ -4345,8 +5417,14 @@ function injectBuilderChrome(opts) {
4345
5417
  stepsList.append(activation);
4346
5418
  s.steps.forEach((st, i) => {
4347
5419
  const thumb = st.presentation === "inline" ? '<div class="veo-thumb veo-thumb-inline"><i></i><i></i><i></i></div>' : '<div class="veo-thumb"><i></i><i></i><b></b></div>';
4348
- const card = stepCard(thumb, st.label, s.section === "step" && s.activeStep === i);
4349
- card.addEventListener("click", () => act({ action: "select-step", index: i }));
5420
+ const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
5421
+ const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
5422
+ // Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
5423
+ canRemove: true,
5424
+ onEdit: () => act({ action: "select-step", index: i }),
5425
+ onDuplicate: () => act({ action: "duplicate-step", index: i }),
5426
+ onRemove: () => act({ action: "remove-step", index: i })
5427
+ });
4350
5428
  stepsList.append(card);
4351
5429
  });
4352
5430
  const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
@@ -4375,16 +5453,40 @@ function injectBuilderChrome(opts) {
4375
5453
  }
4376
5454
  };
4377
5455
  }
4378
- function stepCard(inner, label, active2) {
4379
- const b = el(
4380
- "button",
4381
- `veo-step${active2 ? " is-active" : ""}`,
4382
- `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
5456
+ function stepCard(inner, label, active2, actions) {
5457
+ const cls = `veo-step${active2 ? " is-active" : ""}`;
5458
+ if (!actions) {
5459
+ const b = el(
5460
+ "button",
5461
+ cls,
5462
+ `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
5463
+ );
5464
+ b.type = "button";
5465
+ b.querySelector(".veo-step-label").textContent = label;
5466
+ b.setAttribute("aria-pressed", active2 ? "true" : "false");
5467
+ return b;
5468
+ }
5469
+ const card = el(
5470
+ "div",
5471
+ cls,
5472
+ `<div class="veo-step-box">${inner}<div class="veo-step-hover"><div class="veo-step-hover-top"><button type="button" class="veo-step-icon veo-step-remove" title="Eliminar paso" aria-label="Eliminar paso">${svg("trash", 14)}</button><button type="button" class="veo-step-icon veo-step-duplicate" title="Duplicar paso" aria-label="Duplicar paso">${svg("copy", 14)}</button></div><div class="veo-step-hover-main"><button type="button" class="veo-step-edit">${svg("pencil", 14)}<span>Editar</span></button></div></div></div><span class="veo-step-label"></span>`
4383
5473
  );
4384
- b.type = "button";
4385
- b.querySelector(".veo-step-label").textContent = label;
4386
- b.setAttribute("aria-pressed", active2 ? "true" : "false");
4387
- return b;
5474
+ card.querySelector(".veo-step-label").textContent = label;
5475
+ card.setAttribute("aria-current", active2 ? "step" : "false");
5476
+ const remove = card.querySelector(".veo-step-remove");
5477
+ remove.disabled = !actions.canRemove;
5478
+ if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
5479
+ const on = (sel, fn) => {
5480
+ card.querySelector(sel)?.addEventListener("click", (e) => {
5481
+ e.stopPropagation();
5482
+ fn();
5483
+ });
5484
+ };
5485
+ on(".veo-step-edit", actions.onEdit);
5486
+ on(".veo-step-duplicate", actions.onDuplicate);
5487
+ on(".veo-step-remove", actions.onRemove);
5488
+ card.addEventListener("click", actions.onEdit);
5489
+ return card;
4388
5490
  }
4389
5491
  var CHROME_TOPBAR_H, CHROME_STEPS_H, ICONS, CHROME_CSS;
4390
5492
  var init_builder_chrome = __esm({
@@ -4401,6 +5503,9 @@ var init_builder_chrome = __esm({
4401
5503
  chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
4402
5504
  chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
4403
5505
  plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
5506
+ pencil: '<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/>',
5507
+ copy: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v8.25A2.25 2.25 0 0 0 6 16.5h2.25m8.25-8.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-7.5A2.25 2.25 0 0 1 8.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 0 0-2.25 2.25v6"/>',
5508
+ trash: '<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/>',
4404
5509
  cursor: '<path stroke-linecap="round" stroke-linejoin="round" d="M15.042 21.672 13.684 16.6m0 0-2.51 2.225.569-9.47 5.227 7.917-3.286-.672ZM12 2.25V4.5m5.834.166-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243-1.59-1.59"/>',
4405
5510
  spinner: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182"/>'
4406
5511
  };
@@ -4487,10 +5592,31 @@ var init_builder_chrome = __esm({
4487
5592
  .veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
4488
5593
  .veo-step { border: 0; background: transparent; padding: 0; cursor: pointer; font: inherit; display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 0 0 auto; }
4489
5594
  .veo-step-box {
5595
+ position: relative; overflow: hidden;
4490
5596
  width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
4491
5597
  display: flex; align-items: center; justify-content: center; color: #2b2623;
4492
5598
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
4493
5599
  }
5600
+ /* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
5601
+ .veo-step-hover {
5602
+ position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
5603
+ display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
5604
+ opacity: 0; transition: opacity 120ms ease-out;
5605
+ }
5606
+ .veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
5607
+ .veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
5608
+ .veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
5609
+ .veo-step-icon {
5610
+ border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
5611
+ width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
5612
+ }
5613
+ .veo-step-icon:hover { background: rgba(255,255,255,0.14); }
5614
+ .veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
5615
+ .veo-step-edit {
5616
+ width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
5617
+ border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
5618
+ }
5619
+ .veo-step-edit:hover { background: #cbe4f9; }
4494
5620
  .veo-step.is-active .veo-step-box { border-color: #b74d00; }
4495
5621
  .veo-step-label { font-size: 13px; color: #6b635d; }
4496
5622
  .veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
@@ -4528,6 +5654,8 @@ function injectBuilderPanel(opts) {
4528
5654
  },
4529
5655
  isVisible: () => false,
4530
5656
  setTopOffset: () => {
5657
+ },
5658
+ setSize: () => {
4531
5659
  }
4532
5660
  };
4533
5661
  }
@@ -4580,6 +5708,21 @@ function injectBuilderPanel(opts) {
4580
5708
  card.style.display = next && !(popped && !popped.closed) ? "" : "none";
4581
5709
  };
4582
5710
  minimize.addEventListener("click", () => setVisible(false));
5711
+ let compactRestore = null;
5712
+ const setSize = (size) => {
5713
+ if (maximized || popped && !popped.closed) return;
5714
+ if (size) {
5715
+ if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
5716
+ card.style.minHeight = "0";
5717
+ card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
5718
+ card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
5719
+ } else if (compactRestore) {
5720
+ card.style.minHeight = "";
5721
+ card.style.width = `${compactRestore.w}px`;
5722
+ card.style.height = `${compactRestore.h}px`;
5723
+ compactRestore = null;
5724
+ }
5725
+ };
4583
5726
  maximize.addEventListener("click", () => {
4584
5727
  maximized = !maximized;
4585
5728
  if (maximized) {
@@ -4679,6 +5822,7 @@ function injectBuilderPanel(opts) {
4679
5822
  title.textContent = t;
4680
5823
  },
4681
5824
  setVisible,
5825
+ setSize,
4682
5826
  isVisible: () => visible,
4683
5827
  setTopOffset: (px) => {
4684
5828
  topOffset = px;
@@ -5264,6 +6408,9 @@ function initBuilderMode() {
5264
6408
  let staticTarget = null;
5265
6409
  let manipulator = null;
5266
6410
  let dropTarget = null;
6411
+ let cardClick = null;
6412
+ let rawBlock = null;
6413
+ let selectedBlockId = null;
5267
6414
  let dragGhost = null;
5268
6415
  let previewStepIndex = 0;
5269
6416
  let pendingPreview = null;
@@ -5272,6 +6419,7 @@ function initBuilderMode() {
5272
6419
  let previewTrigger = null;
5273
6420
  let previewFired = false;
5274
6421
  let previewDelay = null;
6422
+ let previewDismissed = false;
5275
6423
  let panelWasVisible = true;
5276
6424
  const getTarget = () => panel ? panel.target() : staticTarget;
5277
6425
  const post = (event) => {
@@ -5283,15 +6431,751 @@ function initBuilderMode() {
5283
6431
  };
5284
6432
  const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
5285
6433
  const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
5286
- const withholdPreview = () => {
6434
+ const detachPreviewTools = () => {
5287
6435
  manipulator?.detach();
5288
6436
  manipulator = null;
5289
6437
  dropTarget?.detach();
5290
6438
  dropTarget = null;
6439
+ cardClick?.();
6440
+ cardClick = null;
6441
+ rowHover?.();
6442
+ rowHover = null;
6443
+ hoverRowId = null;
6444
+ hoverCard = false;
6445
+ rawBlock?.detach();
6446
+ rawBlock = null;
6447
+ shownPreview = null;
6448
+ };
6449
+ const withholdPreview = () => {
6450
+ detachPreviewTools();
6451
+ shownPreview = null;
5291
6452
  closeGuidePreview();
5292
6453
  };
6454
+ const FRAME_PAD = 4;
6455
+ const FRAME_MIN_H = 0;
6456
+ const TOOLBAR_H = 30;
6457
+ const ICON = {
6458
+ pencil: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"/></svg>',
6459
+ copy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M16.5 8.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v8.25A2.25 2.25 0 0 0 6 16.5h2.25m8.25-8.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-7.5A2.25 2.25 0 0 1 8.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 0 0-2.25 2.25v6"/></svg>',
6460
+ trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg>'
6461
+ };
6462
+ const ICON_MOVE = {
6463
+ left: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M19.5 12h-15m0 0 6.75 6.75M4.5 12l6.75-6.75"/></svg>',
6464
+ right: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 12h15m0 0-6.75-6.75M19.5 12l-6.75 6.75"/></svg>'
6465
+ };
6466
+ const moveAbility = (blocks, blockId) => {
6467
+ for (const row of groupLayout(blocks)) {
6468
+ for (let c = 0; c < row.columns.length; c++) {
6469
+ const col = row.columns[c];
6470
+ if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
6471
+ return { left: c > 0, right: c < row.columns.length - 1 };
6472
+ }
6473
+ }
6474
+ return { left: false, right: false };
6475
+ };
6476
+ let previewBlocks = [];
6477
+ let hoverRowId = null;
6478
+ let selectedRowId = null;
6479
+ let rowHover = null;
6480
+ let hoverCard = false;
6481
+ let panelSection = null;
6482
+ let shownPreview = null;
6483
+ const structureOf = (step) => {
6484
+ if (!step) return "";
6485
+ const { blocks, ...rest } = step;
6486
+ return JSON.stringify({
6487
+ rest,
6488
+ blocks: (blocks ?? []).map((b) => {
6489
+ const { text: _t, html: _h, style: _s, ...keep } = b;
6490
+ return keep;
6491
+ })
6492
+ });
6493
+ };
6494
+ const tryPatchPreview = (cmd) => {
6495
+ const prev = shownPreview;
6496
+ if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
6497
+ return false;
6498
+ }
6499
+ const idx = cmd.guide.startStepIndex ?? 0;
6500
+ if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
6501
+ const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
6502
+ if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
6503
+ return false;
6504
+ const host = document.querySelector("[data-veo-guide]");
6505
+ const card = host ? previewCard(host) : null;
6506
+ if (!host || !card) return false;
6507
+ const prevStep = prev.guide.guideSteps[idx];
6508
+ const nextStep = cmd.guide.guideSteps[idx];
6509
+ if (!prevStep || !nextStep) return false;
6510
+ if (structureOf(prevStep) !== structureOf(nextStep)) return false;
6511
+ const doc = card.ownerDocument;
6512
+ const prevBlocks = prevStep.blocks ?? [];
6513
+ const nextBlocks = nextStep.blocks ?? [];
6514
+ for (let i = 0; i < nextBlocks.length; i++) {
6515
+ const a = prevBlocks[i];
6516
+ const b = nextBlocks[i];
6517
+ if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
6518
+ continue;
6519
+ }
6520
+ if (!b.editorId) return false;
6521
+ const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
6522
+ if (!node) return false;
6523
+ if (b.type === "button") {
6524
+ node.textContent = b.text?.trim() || "OK";
6525
+ node.removeAttribute("style");
6526
+ node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
6527
+ const st = b.style ?? {};
6528
+ if (st.preset === "secondary" || st.preset === "link") {
6529
+ node.classList.add(`veo-guide-cta--${st.preset}`);
6530
+ }
6531
+ if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
6532
+ node.setAttribute("aria-label", st.ariaLabel.trim());
6533
+ } else node.removeAttribute("aria-label");
6534
+ applyBlockStyle(node, b.style, "button");
6535
+ continue;
6536
+ }
6537
+ if (b.type === "code" || b.type === "form") return false;
6538
+ const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
6539
+ const el2 = doc.createElement("div");
6540
+ el2.className = "veo-block-placeholder";
6541
+ el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
6542
+ return el2;
6543
+ })();
6544
+ fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
6545
+ fresh.dataset.veoBlockId = b.editorId;
6546
+ node.replaceWith(fresh);
6547
+ }
6548
+ shownPreview = cmd;
6549
+ previewBlocks = nextBlocks;
6550
+ applyBlockHighlight(host, idx);
6551
+ renderGapZones(host, idx, nextBlocks);
6552
+ return true;
6553
+ };
6554
+ const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
6555
+ const clearBlockHighlight = (host) => {
6556
+ const card = previewCard(host);
6557
+ if (!card) return;
6558
+ for (const el2 of card.querySelectorAll(".veo-block--selected")) {
6559
+ el2.classList.remove("veo-block--selected");
6560
+ }
6561
+ card.querySelector(".veo-block-toolbar")?.remove();
6562
+ card.querySelector(".veo-block-frame")?.remove();
6563
+ };
6564
+ const applyBlockHighlight = (host, stepIndex) => {
6565
+ clearBlockHighlight(host);
6566
+ const card = previewCard(host);
6567
+ if (!card) return;
6568
+ const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
6569
+ if (!el2 || !selectedBlockId) {
6570
+ renderRowChrome(host, stepIndex);
6571
+ return;
6572
+ }
6573
+ el2.classList.add("veo-block--selected");
6574
+ const doc = card.ownerDocument;
6575
+ const cs = getComputedStyle(card);
6576
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
6577
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
6578
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
6579
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
6580
+ const parent = el2.parentElement;
6581
+ const inCol = parent?.classList.contains("veo-col") ? parent : null;
6582
+ const boxL = inCol ? inCol.offsetLeft : padL;
6583
+ const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
6584
+ const boxT = padT;
6585
+ const boxB = card.clientHeight - padB;
6586
+ const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
6587
+ const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
6588
+ const fw = Math.max(0, fr - fx);
6589
+ const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
6590
+ const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
6591
+ const fh = Math.max(FRAME_MIN_H, fb - fy);
6592
+ const frame = doc.createElement("div");
6593
+ frame.className = "veo-block-frame";
6594
+ frame.setAttribute("data-veo-builder-chrome", "");
6595
+ frame.style.left = `${fx}px`;
6596
+ frame.style.top = `${fy}px`;
6597
+ frame.style.width = `${fw}px`;
6598
+ frame.style.height = `${fh}px`;
6599
+ card.appendChild(frame);
6600
+ const bar = doc.createElement("div");
6601
+ bar.className = "veo-block-toolbar";
6602
+ bar.setAttribute("data-veo-builder-chrome", "");
6603
+ const blockId = selectedBlockId;
6604
+ const mk = (action, label, icon, enabled = true) => {
6605
+ const b = doc.createElement("button");
6606
+ b.type = "button";
6607
+ b.title = label;
6608
+ b.setAttribute("aria-label", label);
6609
+ b.className = `veo-block-toolbar-${action}`;
6610
+ b.innerHTML = icon;
6611
+ b.disabled = !enabled;
6612
+ b.addEventListener("click", (e) => {
6613
+ e.preventDefault();
6614
+ e.stopPropagation();
6615
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
6616
+ });
6617
+ bar.appendChild(b);
6618
+ };
6619
+ const can = moveAbility(previewBlocks, blockId);
6620
+ mk("edit", "Editar", ICON.pencil);
6621
+ mk("duplicate", "Duplicar", ICON.copy);
6622
+ mk("remove", "Eliminar", ICON.trash);
6623
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
6624
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
6625
+ bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
6626
+ card.appendChild(bar);
6627
+ const barH = bar.offsetHeight || TOOLBAR_H;
6628
+ bar.dataset.veoTopAbove = String(fy - barH - 6);
6629
+ bar.dataset.veoTopBelow = String(fb + 6);
6630
+ bar.style.top = bar.dataset.veoTopAbove;
6631
+ renderRowChrome(host, stepIndex);
6632
+ };
6633
+ const chromeRect = (el2) => ({
6634
+ left: el2.offsetLeft,
6635
+ top: el2.offsetTop,
6636
+ right: el2.offsetLeft + el2.offsetWidth,
6637
+ bottom: el2.offsetTop + el2.offsetHeight
6638
+ });
6639
+ const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
6640
+ const resolveToolbarOverlaps = (card) => {
6641
+ const cardBar = card.querySelector(".veo-card-toolbar");
6642
+ const blockBar = card.querySelector(".veo-block-toolbar");
6643
+ const rowBar = card.querySelector(".veo-row-toolbar");
6644
+ if (blockBar?.dataset.veoTopAbove) {
6645
+ blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
6646
+ if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
6647
+ blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
6648
+ }
6649
+ }
6650
+ if (rowBar?.dataset.veoRight) {
6651
+ rowBar.style.left = "";
6652
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
6653
+ rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
6654
+ const others = [cardBar, blockBar].filter((b) => !!b);
6655
+ const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
6656
+ if (clashes()) {
6657
+ rowBar.style.right = "";
6658
+ rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
6659
+ }
6660
+ if (clashes() && blockBar) {
6661
+ rowBar.style.left = "";
6662
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
6663
+ rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
6664
+ }
6665
+ }
6666
+ };
6667
+ const rectOf = (el2) => ({
6668
+ left: el2.offsetLeft,
6669
+ top: el2.offsetTop,
6670
+ right: el2.offsetLeft + el2.offsetWidth,
6671
+ bottom: el2.offsetTop + el2.offsetHeight
6672
+ });
6673
+ const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
6674
+ left: Math.min(acc.left, r.left),
6675
+ top: Math.min(acc.top, r.top),
6676
+ right: Math.max(acc.right, r.right),
6677
+ bottom: Math.max(acc.bottom, r.bottom)
6678
+ }));
6679
+ const layoutRows = (card, blocks) => {
6680
+ const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
6681
+ const cs = getComputedStyle(card);
6682
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
6683
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
6684
+ const contentLeft = padL;
6685
+ const contentRight = card.clientWidth - padR;
6686
+ const rows = groupLayout(blocks).map((row) => {
6687
+ const columns = row.columns.map((col) => {
6688
+ const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
6689
+ (x) => !!x.el && !!x.block.editorId
6690
+ );
6691
+ const blocksRect2 = union(items.map((x) => rectOf(x.el)));
6692
+ if (!blocksRect2) return null;
6693
+ const colEl = items[0]?.el.parentElement;
6694
+ const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
6695
+ return { items, rect: rect2 };
6696
+ }).filter((c) => c !== null);
6697
+ const blocksRect = union(columns.map((c) => c.rect));
6698
+ if (!blocksRect) return null;
6699
+ const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
6700
+ const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
6701
+ const id = columns[0]?.items[0]?.block.editorId;
6702
+ return { id, row: row.row, columns, rect };
6703
+ }).filter((r) => r !== null);
6704
+ return { rows, content: { left: contentLeft, right: contentRight } };
6705
+ };
6706
+ const clearRowChrome = (card) => {
6707
+ card.querySelector(".veo-row-frame")?.remove();
6708
+ card.querySelector(".veo-row-toolbar")?.remove();
6709
+ };
6710
+ const renderRowChrome = (host, stepIndex) => {
6711
+ const card = previewCard(host);
6712
+ if (!card) return;
6713
+ clearRowChrome(card);
6714
+ resolveToolbarOverlaps(card);
6715
+ const { rows } = layoutRows(card, previewBlocks);
6716
+ const rowOfSelected = selectedBlockId ? rows.find(
6717
+ (r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
6718
+ ) : void 0;
6719
+ const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
6720
+ if (!target) return;
6721
+ const doc = card.ownerDocument;
6722
+ const cs = getComputedStyle(card);
6723
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
6724
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
6725
+ const PAD = 6;
6726
+ const fx = Math.max(0, target.rect.left - PAD);
6727
+ const fr = Math.min(card.clientWidth, target.rect.right + PAD);
6728
+ const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
6729
+ const fb = Math.min(
6730
+ Math.max(card.clientHeight - padB, target.rect.bottom),
6731
+ target.rect.bottom + PAD
6732
+ );
6733
+ const frame = doc.createElement("div");
6734
+ frame.className = "veo-row-frame";
6735
+ frame.setAttribute("data-veo-builder-chrome", "");
6736
+ frame.style.left = `${fx}px`;
6737
+ frame.style.top = `${fy}px`;
6738
+ frame.style.width = `${Math.max(0, fr - fx)}px`;
6739
+ frame.style.height = `${Math.max(0, fb - fy)}px`;
6740
+ card.appendChild(frame);
6741
+ const bar = doc.createElement("div");
6742
+ bar.className = "veo-row-toolbar";
6743
+ bar.setAttribute("data-veo-builder-chrome", "");
6744
+ const blockId = target.id;
6745
+ const mk = (action, label, icon, text) => {
6746
+ const b = doc.createElement("button");
6747
+ b.type = "button";
6748
+ b.title = label;
6749
+ b.setAttribute("aria-label", label);
6750
+ b.className = `veo-row-toolbar-${action}`;
6751
+ b.innerHTML = icon;
6752
+ if (text) b.append(text);
6753
+ b.addEventListener("click", (e) => {
6754
+ e.preventDefault();
6755
+ e.stopPropagation();
6756
+ post({ type: "row-action", payload: { stepIndex, blockId, action } });
6757
+ });
6758
+ bar.appendChild(b);
6759
+ };
6760
+ mk("edit", "Editar fila", ICON.pencil, "Editar fila");
6761
+ mk("duplicate", "Duplicar fila", ICON.copy);
6762
+ mk("remove", "Eliminar fila", ICON.trash);
6763
+ card.appendChild(bar);
6764
+ bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
6765
+ bar.dataset.veoLeft = String(fx);
6766
+ bar.dataset.veoTopBelow = String(fb + 6);
6767
+ resolveToolbarOverlaps(card);
6768
+ };
6769
+ const renderCardChrome = (host, stepIndex) => {
6770
+ const card = previewCard(host);
6771
+ if (!card) return;
6772
+ card.querySelector(".veo-card-frame")?.remove();
6773
+ card.querySelector(".veo-card-toolbar")?.remove();
6774
+ if (!hoverCard) {
6775
+ resolveToolbarOverlaps(card);
6776
+ return;
6777
+ }
6778
+ const doc = card.ownerDocument;
6779
+ const frame = doc.createElement("div");
6780
+ frame.className = "veo-card-frame";
6781
+ frame.setAttribute("data-veo-builder-chrome", "");
6782
+ card.appendChild(frame);
6783
+ const bar = doc.createElement("div");
6784
+ bar.className = "veo-card-toolbar";
6785
+ bar.setAttribute("data-veo-builder-chrome", "");
6786
+ const mk = (action, label, active2) => {
6787
+ const b = doc.createElement("button");
6788
+ b.type = "button";
6789
+ b.className = `veo-card-toolbar-${action}${active2 ? " is-active" : ""}`;
6790
+ b.setAttribute("aria-pressed", active2 ? "true" : "false");
6791
+ b.textContent = label;
6792
+ b.addEventListener("click", (e) => {
6793
+ e.preventDefault();
6794
+ e.stopPropagation();
6795
+ post({ type: "card-action", payload: { stepIndex, action } });
6796
+ });
6797
+ bar.appendChild(b);
6798
+ };
6799
+ mk("edit-container", "Editar contenedor", panelSection === "step");
6800
+ mk("edit-rows", "Editar filas", panelSection === "structure");
6801
+ card.appendChild(bar);
6802
+ bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
6803
+ resolveToolbarOverlaps(card);
6804
+ };
6805
+ const attachRowHover = (host, stepIndex) => {
6806
+ const onMove = (e) => {
6807
+ const card = previewCard(host);
6808
+ if (!card) return;
6809
+ const overCard = e.composedPath().some(
6810
+ (n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
6811
+ );
6812
+ if (overCard !== hoverCard) {
6813
+ hoverCard = overCard;
6814
+ renderCardChrome(host, stepIndex);
6815
+ }
6816
+ if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
6817
+ return;
6818
+ }
6819
+ const rect = card.getBoundingClientRect();
6820
+ const x = e.clientX - rect.left - card.clientLeft;
6821
+ const y = e.clientY - rect.top - card.clientTop;
6822
+ const bar = card.querySelector(".veo-row-toolbar");
6823
+ if (bar) {
6824
+ const AIR = 10;
6825
+ if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
6826
+ return;
6827
+ }
6828
+ }
6829
+ const TOL = 6;
6830
+ const hit = layoutRows(card, previewBlocks).rows.find(
6831
+ (r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
6832
+ );
6833
+ const next = hit?.id ?? null;
6834
+ if (next === hoverRowId) return;
6835
+ hoverRowId = next;
6836
+ renderRowChrome(host, stepIndex);
6837
+ };
6838
+ const onLeave = () => {
6839
+ if (hoverCard) {
6840
+ hoverCard = false;
6841
+ renderCardChrome(host, stepIndex);
6842
+ }
6843
+ if (hoverRowId === null) return;
6844
+ hoverRowId = null;
6845
+ renderRowChrome(host, stepIndex);
6846
+ };
6847
+ host.addEventListener("mousemove", onMove);
6848
+ host.addEventListener("mouseleave", onLeave);
6849
+ return () => {
6850
+ host.removeEventListener("mousemove", onMove);
6851
+ host.removeEventListener("mouseleave", onLeave);
6852
+ };
6853
+ };
6854
+ const renderGapZones = (host, stepIndex, blocks) => {
6855
+ const card = previewCard(host);
6856
+ if (!card) return;
6857
+ for (const z of card.querySelectorAll(".veo-gap")) z.remove();
6858
+ const doc = card.ownerDocument;
6859
+ const GAP = 18;
6860
+ const EDGE2 = 4;
6861
+ const zone = (kind, x, y, w, h, label, blockId, action) => {
6862
+ const z = doc.createElement("div");
6863
+ z.className = `veo-gap veo-gap--${kind}`;
6864
+ z.setAttribute("data-veo-builder-chrome", "");
6865
+ z.dataset.veoGapAction = action;
6866
+ z.style.left = `${x}px`;
6867
+ z.style.top = `${y}px`;
6868
+ z.style.width = `${Math.max(w, GAP)}px`;
6869
+ z.style.height = `${Math.max(h, GAP)}px`;
6870
+ const line = doc.createElement("i");
6871
+ const btn = doc.createElement("button");
6872
+ btn.type = "button";
6873
+ btn.title = label;
6874
+ btn.setAttribute("aria-label", label);
6875
+ btn.textContent = "+";
6876
+ btn.addEventListener("click", (e) => {
6877
+ e.preventDefault();
6878
+ e.stopPropagation();
6879
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
6880
+ });
6881
+ z.append(line, btn);
6882
+ card.appendChild(z);
6883
+ };
6884
+ const { rows, content } = layoutRows(card, blocks);
6885
+ if (rows.length === 0) return;
6886
+ const cw = content.right - content.left;
6887
+ rows.forEach((row, k) => {
6888
+ const first = row.columns[0]?.items[0]?.block.editorId;
6889
+ if (k === 0) {
6890
+ zone(
6891
+ "h",
6892
+ content.left,
6893
+ row.rect.top - EDGE2 - GAP,
6894
+ cw,
6895
+ GAP,
6896
+ "Agregar fila",
6897
+ first,
6898
+ "row-before"
6899
+ );
6900
+ } else {
6901
+ const prev = rows[k - 1];
6902
+ const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
6903
+ zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
6904
+ }
6905
+ if (k === rows.length - 1) {
6906
+ zone(
6907
+ "h",
6908
+ content.left,
6909
+ row.rect.bottom + EDGE2,
6910
+ cw,
6911
+ GAP,
6912
+ "Agregar fila",
6913
+ first,
6914
+ "row-after"
6915
+ );
6916
+ }
6917
+ });
6918
+ for (const row of rows) {
6919
+ const rh = row.rect.bottom - row.rect.top;
6920
+ row.columns.forEach((col, j) => {
6921
+ const id = col.items[0]?.block.editorId;
6922
+ if (j === 0) {
6923
+ zone(
6924
+ "v",
6925
+ row.rect.left - EDGE2 - GAP,
6926
+ row.rect.top,
6927
+ GAP,
6928
+ rh,
6929
+ "Agregar columna",
6930
+ id,
6931
+ "col-before"
6932
+ );
6933
+ } else {
6934
+ const prev = row.columns[j - 1];
6935
+ const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
6936
+ zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
6937
+ }
6938
+ if (j === row.columns.length - 1) {
6939
+ zone(
6940
+ "v",
6941
+ row.rect.right + EDGE2,
6942
+ row.rect.top,
6943
+ GAP,
6944
+ rh,
6945
+ "Agregar columna",
6946
+ id,
6947
+ "col-after"
6948
+ );
6949
+ }
6950
+ });
6951
+ }
6952
+ for (const row of rows) {
6953
+ for (const col of row.columns) {
6954
+ const w = col.rect.right - col.rect.left;
6955
+ col.items.forEach((it, i) => {
6956
+ const next = col.items[i + 1];
6957
+ if (next) {
6958
+ const a = rectOf(it.el);
6959
+ const b = rectOf(next.el);
6960
+ const y = (a.bottom + b.top) / 2 - GAP / 2;
6961
+ zone(
6962
+ "h",
6963
+ col.rect.left,
6964
+ y,
6965
+ w,
6966
+ GAP,
6967
+ "Agregar bloque",
6968
+ it.block.editorId,
6969
+ "stack-after"
6970
+ );
6971
+ } else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
6972
+ zone(
6973
+ "h",
6974
+ col.rect.left,
6975
+ col.rect.bottom + EDGE2,
6976
+ w,
6977
+ GAP,
6978
+ "Agregar bloque",
6979
+ it.block.editorId,
6980
+ "stack-after"
6981
+ );
6982
+ }
6983
+ });
6984
+ }
6985
+ }
6986
+ };
6987
+ const attachCardClick = (host, stepIndex) => {
6988
+ let downAt = null;
6989
+ const onDown = (e) => {
6990
+ downAt = { x: e.clientX, y: e.clientY };
6991
+ };
6992
+ const onClick = (e) => {
6993
+ const path = e.composedPath();
6994
+ if (path.some(
6995
+ (n) => n instanceof Element && (n.classList.contains("veo-block-toolbar") || n.classList.contains("veo-row-toolbar") || n.classList.contains("veo-card-toolbar") || n.classList.contains("veo-gap"))
6996
+ )) {
6997
+ return;
6998
+ }
6999
+ const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
7000
+ if (!inCard) return;
7001
+ if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
7002
+ const blockEl = path.find(
7003
+ (n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
7004
+ );
7005
+ if (blockEl) {
7006
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
7007
+ e.preventDefault();
7008
+ e.stopPropagation();
7009
+ }
7010
+ selectedBlockId = blockEl.dataset.veoBlockId ?? null;
7011
+ applyBlockHighlight(host, stepIndex);
7012
+ if (selectedBlockId) {
7013
+ post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
7014
+ }
7015
+ return;
7016
+ }
7017
+ const interactive = path.some(
7018
+ (n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
7019
+ );
7020
+ if (interactive) return;
7021
+ selectedBlockId = null;
7022
+ clearBlockHighlight(host);
7023
+ renderRowChrome(host, stepIndex);
7024
+ post({ type: "card-clicked", payload: { stepIndex } });
7025
+ };
7026
+ host.addEventListener("mousedown", onDown, true);
7027
+ host.addEventListener("click", onClick, true);
7028
+ return () => {
7029
+ host.removeEventListener("mousedown", onDown, true);
7030
+ host.removeEventListener("click", onClick, true);
7031
+ };
7032
+ };
7033
+ const attachRawBlock = (stepIndex, blockId) => {
7034
+ const nodes = Array.from(
7035
+ document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
7036
+ );
7037
+ if (nodes.length === 0) return null;
7038
+ const doc = document;
7039
+ const box = doc.createElement("div");
7040
+ box.setAttribute("data-veo-builder-chrome", "");
7041
+ box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
7042
+ box.style.display = "none";
7043
+ const shadow = box.attachShadow({ mode: "open" });
7044
+ const style = doc.createElement("style");
7045
+ style.textContent = GUIDE_STYLES;
7046
+ shadow.appendChild(style);
7047
+ const frame = doc.createElement("div");
7048
+ frame.className = "veo-block-frame";
7049
+ frame.style.position = "fixed";
7050
+ const bar = doc.createElement("div");
7051
+ bar.className = "veo-block-toolbar";
7052
+ bar.style.position = "fixed";
7053
+ const mk = (action, label, icon, enabled = true) => {
7054
+ const b = doc.createElement("button");
7055
+ b.type = "button";
7056
+ b.title = label;
7057
+ b.setAttribute("aria-label", label);
7058
+ b.className = `veo-block-toolbar-${action}`;
7059
+ b.innerHTML = icon;
7060
+ b.disabled = !enabled;
7061
+ b.addEventListener("click", (e) => {
7062
+ e.preventDefault();
7063
+ e.stopPropagation();
7064
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
7065
+ });
7066
+ bar.appendChild(b);
7067
+ };
7068
+ mk("edit", "Editar", ICON.pencil);
7069
+ mk("duplicate", "Duplicar", ICON.copy, false);
7070
+ mk("remove", "Eliminar", ICON.trash);
7071
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
7072
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
7073
+ frame.style.display = "none";
7074
+ bar.style.display = "none";
7075
+ const GAP = 18;
7076
+ const EDGE2 = 4;
7077
+ const gap = (kind, label, action) => {
7078
+ const z = doc.createElement("div");
7079
+ z.className = `veo-gap veo-gap--${kind}`;
7080
+ z.style.position = "fixed";
7081
+ z.dataset.veoGapAction = action;
7082
+ const line = doc.createElement("i");
7083
+ const btn = doc.createElement("button");
7084
+ btn.type = "button";
7085
+ btn.title = label;
7086
+ btn.setAttribute("aria-label", label);
7087
+ btn.textContent = "+";
7088
+ btn.addEventListener("click", (e) => {
7089
+ e.preventDefault();
7090
+ e.stopPropagation();
7091
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
7092
+ });
7093
+ z.append(line, btn);
7094
+ return z;
7095
+ };
7096
+ const gaps = {
7097
+ top: gap("h", "Agregar fila", "row-before"),
7098
+ bottom: gap("h", "Agregar fila", "row-after"),
7099
+ left: gap("v", "Agregar columna", "col-before"),
7100
+ right: gap("v", "Agregar columna", "col-after")
7101
+ };
7102
+ shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
7103
+ doc.body.appendChild(box);
7104
+ box.style.display = "";
7105
+ let visible = false;
7106
+ const place = (el2, x, y, w, h) => {
7107
+ el2.style.left = `${x}px`;
7108
+ el2.style.top = `${y}px`;
7109
+ el2.style.width = `${w}px`;
7110
+ el2.style.height = `${h}px`;
7111
+ };
7112
+ const reposition = () => {
7113
+ const live = nodes.filter((n) => n.isConnected);
7114
+ if (live.length === 0) return;
7115
+ let l = Number.POSITIVE_INFINITY;
7116
+ let t = Number.POSITIVE_INFINITY;
7117
+ let r = Number.NEGATIVE_INFINITY;
7118
+ let b = Number.NEGATIVE_INFINITY;
7119
+ for (const n of live) {
7120
+ const rc = n.getBoundingClientRect();
7121
+ if (rc.width === 0 && rc.height === 0) continue;
7122
+ l = Math.min(l, rc.left);
7123
+ t = Math.min(t, rc.top);
7124
+ r = Math.max(r, rc.right);
7125
+ b = Math.max(b, rc.bottom);
7126
+ }
7127
+ if (!Number.isFinite(l)) return;
7128
+ place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
7129
+ place(gaps.bottom, l, b + EDGE2, r - l, GAP);
7130
+ place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
7131
+ place(gaps.right, r + EDGE2, t, GAP, b - t);
7132
+ if (!visible) return;
7133
+ place(frame, l, t, r - l, b - t);
7134
+ const bh = bar.offsetHeight || TOOLBAR_H;
7135
+ const bw = bar.offsetWidth;
7136
+ bar.style.left = `${Math.max(0, r - bw)}px`;
7137
+ bar.style.top = `${Math.max(0, t - bh - 6)}px`;
7138
+ };
7139
+ const select = (id) => {
7140
+ visible = id === blockId;
7141
+ frame.style.display = visible ? "" : "none";
7142
+ bar.style.display = visible ? "" : "none";
7143
+ reposition();
7144
+ };
7145
+ reposition();
7146
+ const onClick = (e) => {
7147
+ const path = e.composedPath();
7148
+ if (!nodes.some((n) => path.includes(n))) return;
7149
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
7150
+ e.preventDefault();
7151
+ e.stopPropagation();
7152
+ }
7153
+ selectedBlockId = blockId;
7154
+ select(blockId);
7155
+ post({ type: "block-clicked", payload: { stepIndex, blockId } });
7156
+ };
7157
+ doc.addEventListener("click", onClick, true);
7158
+ window.addEventListener("scroll", reposition, true);
7159
+ window.addEventListener("resize", reposition);
7160
+ return {
7161
+ select,
7162
+ detach: () => {
7163
+ doc.removeEventListener("click", onClick, true);
7164
+ window.removeEventListener("scroll", reposition, true);
7165
+ window.removeEventListener("resize", reposition);
7166
+ box.remove();
7167
+ }
7168
+ };
7169
+ };
5293
7170
  const applyPreviewGated = (cmd) => {
5294
- lastPreview = cmd;
7171
+ const showNow = !cmd.honorTrigger;
7172
+ if (showNow) {
7173
+ disarmPreviewTrigger();
7174
+ cancelPreviewDelay();
7175
+ previewFired = true;
7176
+ previewDismissed = false;
7177
+ }
7178
+ lastPreview = showNow ? { ...cmd, honorTrigger: true } : cmd;
5295
7179
  if (!previewMatchesPage(cmd)) {
5296
7180
  cancelPreviewDelay();
5297
7181
  withholdPreview();
@@ -5302,13 +7186,13 @@ function initBuilderMode() {
5302
7186
  return;
5303
7187
  }
5304
7188
  const devices = cmd.guide.activationRules?.devices;
5305
- if (cmd.honorTrigger && !previewMode && devices && !isDeviceAllowed(devices)) {
7189
+ if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
5306
7190
  cancelPreviewDelay();
5307
7191
  withholdPreview();
5308
7192
  postPreviewStatus({ state: "device-mismatch", devices });
5309
7193
  return;
5310
7194
  }
5311
- applyPreview(cmd);
7195
+ applyPreview(lastPreview);
5312
7196
  };
5313
7197
  const disarmPreviewTrigger = () => {
5314
7198
  previewTrigger?.();
@@ -5333,6 +7217,7 @@ function initBuilderMode() {
5333
7217
  }
5334
7218
  disarmPreviewTrigger();
5335
7219
  previewFired = true;
7220
+ previewDismissed = false;
5336
7221
  if (lastPreview) applyPreviewGated(lastPreview);
5337
7222
  };
5338
7223
  document.addEventListener("click", onFire, true);
@@ -5353,22 +7238,24 @@ function initBuilderMode() {
5353
7238
  postPreviewStatus({ state: "waiting-delay", delayMs });
5354
7239
  };
5355
7240
  const applyPreview = (cmd) => {
5356
- manipulator?.detach();
5357
- manipulator = null;
5358
- dropTarget?.detach();
5359
- dropTarget = null;
7241
+ if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
7242
+ previewStepIndex = cmd.guide.startStepIndex ?? 0;
7243
+ return;
7244
+ }
7245
+ detachPreviewTools();
5360
7246
  previewStepIndex = cmd.guide.startStepIndex ?? 0;
5361
7247
  const rules = cmd.guide.activationRules;
5362
7248
  const trig = rules?.trigger;
5363
7249
  const sel = rules?.selector?.trim();
5364
- const gate = cmd.honorTrigger && !previewFired && !previewMode;
5365
- if (gate && (trig === "on_click" || trig === "on_hover") && sel) {
7250
+ const gate = cmd.honorTrigger && !previewFired && previewMode;
7251
+ const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
7252
+ if (gate && waitsForTarget && sel) {
5366
7253
  cancelPreviewDelay();
5367
7254
  closeGuidePreview();
5368
7255
  armPreviewTrigger(sel, trig);
5369
7256
  return;
5370
7257
  }
5371
- const delayMs = trig === "delayed" ? rules?.delayMs ?? 0 : 0;
7258
+ const delayMs = activationDelayMs(rules);
5372
7259
  if (gate && delayMs > 0) {
5373
7260
  disarmPreviewTrigger();
5374
7261
  closeGuidePreview();
@@ -5377,15 +7264,21 @@ function initBuilderMode() {
5377
7264
  }
5378
7265
  disarmPreviewTrigger();
5379
7266
  cancelPreviewDelay();
7267
+ const fetchProgress = previewProgressFetcher();
5380
7268
  const handle = previewGuide({
5381
7269
  ...cmd.guide,
7270
+ ...fetchProgress ? { fetchProgress } : {},
5382
7271
  onClosed: () => {
5383
- manipulator?.detach();
5384
- manipulator = null;
5385
- dropTarget?.detach();
5386
- dropTarget = null;
7272
+ detachPreviewTools();
7273
+ shownPreview = null;
5387
7274
  previewFired = false;
5388
- if (lastPreview) applyPreviewGated(lastPreview);
7275
+ previewDismissed = true;
7276
+ if (previewMode) {
7277
+ const r = lastPreview?.guide.activationRules;
7278
+ const t = r?.trigger;
7279
+ const selector = r?.selector?.trim();
7280
+ if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
7281
+ }
5389
7282
  }
5390
7283
  });
5391
7284
  if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
@@ -5393,7 +7286,23 @@ function initBuilderMode() {
5393
7286
  void handle.ready.then((result) => {
5394
7287
  if (!result.rendered) return;
5395
7288
  const host = handle.host();
5396
- if (!host) return;
7289
+ if (!host) {
7290
+ const idx = cmd.guide.startStepIndex ?? 0;
7291
+ const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
7292
+ if (only?.type === "code" && only.editorId) {
7293
+ rawBlock = attachRawBlock(idx, only.editorId);
7294
+ rawBlock?.select(selectedBlockId);
7295
+ }
7296
+ return;
7297
+ }
7298
+ host.classList.add("veo-no-anim");
7299
+ shownPreview = cmd;
7300
+ cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
7301
+ const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
7302
+ previewBlocks = stepBlocks;
7303
+ applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
7304
+ renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
7305
+ rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
5397
7306
  manipulator = attachDesignManipulator({
5398
7307
  host,
5399
7308
  guideType: cmd.guide.guideType,
@@ -5461,13 +7370,21 @@ function initBuilderMode() {
5461
7370
  break;
5462
7371
  case "editor-state":
5463
7372
  chrome?.setState(cmd.state);
7373
+ if (panelSection !== cmd.state.section) {
7374
+ panelSection = cmd.state.section;
7375
+ const h = document.querySelector("[data-veo-guide]");
7376
+ if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7377
+ }
5464
7378
  panel?.setTitle(
5465
- 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.steps[cmd.state.activeStep]?.label ?? "Paso"
7379
+ 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"
5466
7380
  );
5467
7381
  break;
5468
7382
  case "panel-visibility":
5469
7383
  panel?.setVisible(cmd.visible);
5470
7384
  break;
7385
+ case "panel-size":
7386
+ panel?.setSize(cmd.size);
7387
+ break;
5471
7388
  case "start-picker": {
5472
7389
  picker?.stop();
5473
7390
  const panelWasShown = panel?.isVisible() ?? false;
@@ -5475,10 +7392,7 @@ function initBuilderMode() {
5475
7392
  chrome?.setHidden(true);
5476
7393
  const previewToRestore = lastPreview;
5477
7394
  if (previewToRestore) {
5478
- manipulator?.detach();
5479
- manipulator = null;
5480
- dropTarget?.detach();
5481
- dropTarget = null;
7395
+ detachPreviewTools();
5482
7396
  closeGuidePreview();
5483
7397
  }
5484
7398
  const restorePanel = () => {
@@ -5517,17 +7431,28 @@ function initBuilderMode() {
5517
7431
  case "block-drag":
5518
7432
  handleBlockDrag(cmd);
5519
7433
  break;
7434
+ case "select-block": {
7435
+ selectedBlockId = cmd.blockId;
7436
+ const h = document.querySelector("[data-veo-guide]");
7437
+ if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
7438
+ if (!previewMode) rawBlock?.select(cmd.blockId);
7439
+ break;
7440
+ }
7441
+ case "select-row": {
7442
+ selectedRowId = cmd.blockId;
7443
+ const h = document.querySelector("[data-veo-guide]");
7444
+ if (h && !previewMode) renderRowChrome(h, previewStepIndex);
7445
+ break;
7446
+ }
5520
7447
  case "close-preview":
5521
- manipulator?.detach();
5522
- manipulator = null;
5523
- dropTarget?.detach();
5524
- dropTarget = null;
7448
+ detachPreviewTools();
5525
7449
  hideDragGhost();
5526
7450
  pendingPreview = null;
5527
7451
  lastPreview = null;
5528
7452
  disarmPreviewTrigger();
5529
7453
  cancelPreviewDelay();
5530
7454
  previewFired = false;
7455
+ previewDismissed = false;
5531
7456
  closeGuidePreview();
5532
7457
  break;
5533
7458
  case "teardown":
@@ -5573,10 +7498,7 @@ function initBuilderMode() {
5573
7498
  removeNavWatcher();
5574
7499
  picker?.stop();
5575
7500
  picker = null;
5576
- manipulator?.detach();
5577
- manipulator = null;
5578
- dropTarget?.detach();
5579
- dropTarget = null;
7501
+ detachPreviewTools();
5580
7502
  hideDragGhost();
5581
7503
  pendingPreview = null;
5582
7504
  disarmPreviewTrigger();
@@ -5604,6 +7526,8 @@ function initBuilderMode() {
5604
7526
  case "preview":
5605
7527
  if (previewMode) return;
5606
7528
  previewMode = true;
7529
+ previewFired = false;
7530
+ previewDismissed = false;
5607
7531
  panelWasVisible = panel?.isVisible() ?? true;
5608
7532
  panel?.setVisible(false);
5609
7533
  chrome?.setPreview(true);
@@ -5612,6 +7536,8 @@ function initBuilderMode() {
5612
7536
  case "exit-preview":
5613
7537
  if (!previewMode) return;
5614
7538
  previewMode = false;
7539
+ disarmPreviewTrigger();
7540
+ cancelPreviewDelay();
5615
7541
  chrome?.setPreview(false);
5616
7542
  panel?.setVisible(panelWasVisible);
5617
7543
  if (lastPreview) applyPreviewGated(lastPreview);
@@ -5693,12 +7619,36 @@ function resolveDashboardOrigin(originHint) {
5693
7619
  }
5694
7620
  return null;
5695
7621
  }
7622
+ function previewProgressFetcher() {
7623
+ const cfg = window.__veoBuilder;
7624
+ if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
7625
+ const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
7626
+ const apiKey = cfg.apiKey;
7627
+ return async (guideIds) => {
7628
+ const params = new URLSearchParams({
7629
+ endUserId: "veo_preview",
7630
+ guideIds: guideIds.join(","),
7631
+ includeUnpublished: "1"
7632
+ });
7633
+ const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
7634
+ headers: { "X-Api-Key": apiKey }
7635
+ });
7636
+ if (!res.ok) return [];
7637
+ const data = await res.json();
7638
+ return data.items ?? [];
7639
+ };
7640
+ }
5696
7641
  var init_builder_mode = __esm({
5697
7642
  "src/plugins/builder/builder-mode.ts"() {
5698
7643
  init_builder_token();
5699
7644
  init_safe_env();
5700
7645
  init_activation_devices();
7646
+ init_activation_rules();
7647
+ init_block_builder();
7648
+ init_code_block();
5701
7649
  init_guide_preview();
7650
+ init_layout();
7651
+ init_styles();
5702
7652
  init_url_match();
5703
7653
  init_block_drop_target();
5704
7654
  init_bridge_protocol();
@@ -6699,6 +8649,7 @@ init_safe_env();
6699
8649
 
6700
8650
  // src/plugins/guides/guides-controller.ts
6701
8651
  init_activation_devices();
8652
+ init_activation_rules();
6702
8653
 
6703
8654
  // src/plugins/guides/campaign-weekdays.ts
6704
8655
  function isWeekdayAllowed(campaignWeekdays, now = /* @__PURE__ */ new Date()) {
@@ -7096,6 +9047,7 @@ init_inline_renderer();
7096
9047
  init_modal_renderer();
7097
9048
  init_tooltip_renderer();
7098
9049
  init_walkthrough_renderer();
9050
+ init_snooze();
7099
9051
  init_url_match();
7100
9052
 
7101
9053
  // src/plugins/guides/walkthrough-state.ts
@@ -7288,6 +9240,12 @@ var GuidesController = class {
7288
9240
  * en cada `checkGuides` con {@link pruneComposite}.
7289
9241
  */
7290
9242
  this.activeComposite = /* @__PURE__ */ new Map();
9243
+ /**
9244
+ * Progreso de esta sesión, por guía: pasos vistos y completado, según las
9245
+ * interacciones que ESTE SDK emitió (aunque el lote aún no haya viajado al
9246
+ * backend). Se mezcla con la respuesta del backend en `fetchProgress`.
9247
+ */
9248
+ this.sessionProgress = /* @__PURE__ */ new Map();
7291
9249
  this.debug = config.debug === true;
7292
9250
  this.apiUrl = config.apiUrl;
7293
9251
  this.apiKey = config.apiKey;
@@ -7352,12 +9310,15 @@ var GuidesController = class {
7352
9310
  if (this.debug) console.log(`[veo] guides: ${guides.length} eligible at ${pageUrl}`);
7353
9311
  this.disarmTriggers();
7354
9312
  for (const guide of guides) {
7355
- const trigger = guide.activationRules.trigger;
7356
- if ((trigger === "on_click" || trigger === "on_hover") && guide.activationRules.selector) {
9313
+ const rules = guide.activationRules;
9314
+ const trigger = rules.trigger;
9315
+ if ((trigger === "on_click" || trigger === "on_hover") && rules.selector) {
7357
9316
  this.armInteractionTrigger(guide, endUserId, sessionId, pageUrl);
7358
- continue;
9317
+ if (!isAutomaticActivation(rules)) continue;
7359
9318
  }
7360
- await this.showGuide(guide, endUserId, sessionId, pageUrl);
9319
+ await this.showGuide(guide, endUserId, sessionId, pageUrl, {
9320
+ badgeOpen: trigger !== "manual"
9321
+ });
7361
9322
  }
7362
9323
  }
7363
9324
  /**
@@ -7365,12 +9326,17 @@ var GuidesController = class {
7365
9326
  * tipo). Cuerpo original del loop de `checkGuides`, extraído para que los
7366
9327
  * disparadores por interacción pasen por LOS MISMOS chequeos al disparar.
7367
9328
  */
7368
- async showGuide(guide, endUserId, sessionId, pageUrl) {
9329
+ async showGuide(guide, endUserId, sessionId, pageUrl, opts = {}) {
9330
+ const badgeOpen = opts.badgeOpen !== false;
7369
9331
  if (this.frequencyCache.shouldFilter(guide.guideId, guide.displayFrequency)) {
7370
9332
  if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
7371
9333
  return;
7372
9334
  }
7373
- if (this.isHeldByRepeat(guide)) {
9335
+ if (!opts.byUser && isSnoozed(guide.guideId)) {
9336
+ if (this.debug) console.log(`[veo] guides: snoozed guideId=${guide.guideId}`);
9337
+ return;
9338
+ }
9339
+ if (!opts.byUser && this.isHeldByRepeat(guide)) {
7374
9340
  if (this.debug) console.log(`[veo] guides: held by repeat guideId=${guide.guideId}`);
7375
9341
  return;
7376
9342
  }
@@ -7388,7 +9354,7 @@ var GuidesController = class {
7388
9354
  return;
7389
9355
  }
7390
9356
  if (guide.guideType === "composite") {
7391
- this.startComposite(guide, sessionId, pageUrl);
9357
+ this.startComposite(guide, sessionId, pageUrl, badgeOpen);
7392
9358
  return;
7393
9359
  }
7394
9360
  this.runSingleStepRender(guide, sessionId, pageUrl);
@@ -7417,7 +9383,7 @@ var GuidesController = class {
7417
9383
  return;
7418
9384
  }
7419
9385
  disarm();
7420
- void this.showGuide(guide, endUserId, sessionId, pageUrl);
9386
+ void this.showGuide(guide, endUserId, sessionId, pageUrl, { byUser: true });
7421
9387
  };
7422
9388
  const disarm = () => {
7423
9389
  document.removeEventListener(eventName, onFire, true);
@@ -7497,7 +9463,7 @@ ${pageUrl}`;
7497
9463
  const renderer = this.createSingleStepRenderer(guide.guideType);
7498
9464
  if (!renderer) return;
7499
9465
  this.activeByGuideId.set(guide.guideId, renderer);
7500
- const delayMs = guide.activationRules.delayMs ?? 0;
9466
+ const delayMs = activationDelayMs(guide.activationRules);
7501
9467
  window.setTimeout(
7502
9468
  () => this.mountSingleStepRenderer(guide, renderer, sessionId, pageUrl),
7503
9469
  delayMs
@@ -7508,23 +9474,24 @@ ${pageUrl}`;
7508
9474
  * `pageLocation` matchea la URL actual (targeting por-paso) y lo pinta con el
7509
9475
  * `CompositeRenderer`. Si ningún paso aplica a esta página, no muestra nada.
7510
9476
  */
7511
- startComposite(guide, sessionId, pageUrl) {
9477
+ startComposite(guide, sessionId, pageUrl, badgeOpen = true) {
7512
9478
  if (this.pendingComposite?.guide.guideId === guide.guideId) return;
7513
9479
  const steps = guide.guideSteps;
7514
9480
  const remembered = guide.activationRules.repeat?.onLoad === "resume" ? this.recallStep(guide.guideId) : null;
7515
9481
  const start = remembered !== null && stepMatchesPage(steps[remembered]?.pageLocation, pageUrl) ? remembered : steps.findIndex((s) => stepMatchesPage(s.pageLocation, pageUrl));
7516
9482
  if (start === -1) return;
9483
+ if (guide.activationRules.trigger === "manual" && !steps[start]?.badge?.enabled) return;
7517
9484
  if (this.compositeDelays.has(guide.guideId)) return;
7518
9485
  this.resetDispatched(guide.guideId);
7519
- const delayMs = guide.activationRules.delayMs ?? 0;
9486
+ const delayMs = activationDelayMs(guide.activationRules);
7520
9487
  if (delayMs <= 0) {
7521
- this.renderCompositeStep(guide, start, sessionId, pageUrl);
9488
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
7522
9489
  return;
7523
9490
  }
7524
9491
  const timer = setTimeout(() => {
7525
9492
  this.compositeDelays.delete(guide.guideId);
7526
9493
  if (this.activeByGuideId.has(guide.guideId)) return;
7527
- this.renderCompositeStep(guide, start, sessionId, pageUrl);
9494
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
7528
9495
  }, delayMs);
7529
9496
  this.compositeDelays.set(guide.guideId, { guide, stepIndex: start, timer });
7530
9497
  }
@@ -7626,7 +9593,7 @@ ${pageUrl}`;
7626
9593
  * que los botones next/prev avancen (misma página); en single-step los
7627
9594
  * botones cierran. Cada avance destruye el paso anterior y monta el siguiente.
7628
9595
  */
7629
- renderCompositeStep(guide, idx, sessionId, pageUrl) {
9596
+ renderCompositeStep(guide, idx, sessionId, pageUrl, badgeOpen = true) {
7630
9597
  const steps = guide.guideSteps;
7631
9598
  this.rememberStep(guide.guideId, idx);
7632
9599
  const renderer = new CompositeRenderer();
@@ -7644,9 +9611,20 @@ ${pageUrl}`;
7644
9611
  const goToStep = (target) => {
7645
9612
  cleanup();
7646
9613
  if (target < 0 || target >= steps.length) return;
9614
+ const endUserId = this.client.getEndUserId();
9615
+ if (endUserId) {
9616
+ this.enqueueInteraction(
9617
+ guide.guideId,
9618
+ endUserId,
9619
+ sessionId,
9620
+ pageUrl,
9621
+ target > idx ? "step_advanced" : "step_back",
9622
+ target
9623
+ );
9624
+ }
7647
9625
  const step = steps[target];
7648
9626
  if (step && stepMatchesPage(step.pageLocation, pageUrl)) {
7649
- this.renderCompositeStep(guide, target, sessionId, pageUrl);
9627
+ this.renderCompositeStep(guide, target, sessionId, pageUrl, badgeOpen);
7650
9628
  } else {
7651
9629
  this.pendingComposite = { guide, stepIndex: target };
7652
9630
  }
@@ -7668,10 +9646,19 @@ ${pageUrl}`;
7668
9646
  onInteraction: (e) => this.handleInteraction(guide, sessionId, pageUrl, e),
7669
9647
  onClose: cleanup,
7670
9648
  onTrack: (name, props) => this.client.track(name, props),
7671
- onFormSubmit: (answers) => this.submitFormResponse(guide.guideId, answers),
9649
+ onFormSubmit: (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta),
7672
9650
  onOpenGuide: (targetGuideId) => {
7673
9651
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
7674
9652
  },
9653
+ // Lista de tareas: progreso del usuario y abrir una guía en su paso.
9654
+ fetchProgress: (ids) => this.fetchProgress(ids),
9655
+ onOpenGuideAt: (targetGuideId, stepIndex) => {
9656
+ void this.openGuideById(targetGuideId, sessionId, pageUrl, stepIndex);
9657
+ },
9658
+ // Badge: cuando el MOTOR muestra la guía (automático, elemento objetivo
9659
+ // o encadenada), el contenido se abre solo; el marcador queda para
9660
+ // volver a verla. Solo `manual` deja el tooltip cerrado.
9661
+ badgeOpen,
7675
9662
  ...nav ? { nav } : {}
7676
9663
  });
7677
9664
  } catch (err) {
@@ -7695,7 +9682,7 @@ ${pageUrl}`;
7695
9682
  const onTrack = (eventName, properties) => {
7696
9683
  this.client.track(eventName, properties);
7697
9684
  };
7698
- const onFormSubmit = (answers) => this.submitFormResponse(guide.guideId, answers);
9685
+ const onFormSubmit = (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta);
7699
9686
  const onOpenGuide = (targetGuideId) => {
7700
9687
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
7701
9688
  };
@@ -7715,7 +9702,7 @@ ${pageUrl}`;
7715
9702
  * un botón); respeta el dedupe de guías activas y el flujo normal de
7716
9703
  * tracking. 404/red → no-op (la guía destino pudo borrarse).
7717
9704
  */
7718
- async openGuideById(guideId, sessionId, pageUrl) {
9705
+ async openGuideById(guideId, sessionId, pageUrl, startStep) {
7719
9706
  if (this.activeByGuideId.has(guideId)) return;
7720
9707
  const guide = await this.resolver.fetchById(guideId);
7721
9708
  if (!guide) {
@@ -7723,6 +9710,15 @@ ${pageUrl}`;
7723
9710
  return;
7724
9711
  }
7725
9712
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
9713
+ if (guide.guideType === "composite" && typeof startStep === "number") {
9714
+ const total = guide.guideSteps.length;
9715
+ const idx = Math.max(0, Math.min(total - 1, Math.floor(startStep)));
9716
+ this.enqueueInteractionOnce(guide.guideId, endUserId, sessionId, pageUrl, "shown", 0);
9717
+ if (idx > 0)
9718
+ this.enqueueInteraction(guide.guideId, endUserId, sessionId, pageUrl, "step_advanced", idx);
9719
+ this.renderCompositeStep(guide, idx, sessionId, pageUrl);
9720
+ return;
9721
+ }
7726
9722
  if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
7727
9723
  if (!this.walkthroughState.hasActive()) {
7728
9724
  await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
@@ -7933,6 +9929,7 @@ ${pageUrl}`;
7933
9929
  console.warn("[veo] guides: interaction without endUserId \u2014 skipping enqueue");
7934
9930
  return;
7935
9931
  }
9932
+ this.noteProgress(guide.guideId, event.action, event.stepIndex);
7936
9933
  this.trackerQueue.enqueue({
7937
9934
  guideId: guide.guideId,
7938
9935
  payload: {
@@ -7952,6 +9949,7 @@ ${pageUrl}`;
7952
9949
  * una navegación distinta) y deduplicarlas perdería datos.
7953
9950
  */
7954
9951
  enqueueInteraction(guideId, endUserId, sessionId, pageUrl, action, stepIndex) {
9952
+ this.noteProgress(guideId, action, stepIndex);
7955
9953
  this.trackerQueue.enqueue({
7956
9954
  guideId,
7957
9955
  payload: {
@@ -7964,6 +9962,45 @@ ${pageUrl}`;
7964
9962
  }
7965
9963
  });
7966
9964
  }
9965
+ noteProgress(guideId, action, stepIndex) {
9966
+ if (action !== "shown" && action !== "step_advanced" && action !== "step_back" && action !== "completed") {
9967
+ return;
9968
+ }
9969
+ const cur = this.sessionProgress.get(guideId) ?? { maxStep: -1, completed: false };
9970
+ this.sessionProgress.set(guideId, {
9971
+ maxStep: Math.max(cur.maxStep, stepIndex),
9972
+ completed: cur.completed || action === "completed"
9973
+ });
9974
+ }
9975
+ /**
9976
+ * Progreso del usuario en varias guías (bloque «Lista de tareas»): lo pide
9977
+ * al backend y lo eleva con lo visto en esta sesión (nunca lo baja).
9978
+ */
9979
+ async fetchProgress(guideIds) {
9980
+ const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
9981
+ let rows = [];
9982
+ try {
9983
+ const params = new URLSearchParams({ endUserId, guideIds: guideIds.join(",") });
9984
+ const res = await fetch(`${this.apiUrl}/v1/guides/progress?${params}`, {
9985
+ headers: { "X-Api-Key": this.apiKey }
9986
+ });
9987
+ if (res.ok) rows = (await res.json()).items ?? [];
9988
+ else if (this.debug) console.warn("[veo] guides/progress respondi\xF3", res.status);
9989
+ } catch (err) {
9990
+ if (this.debug) console.warn("[veo] guides/progress fall\xF3:", err);
9991
+ }
9992
+ return rows.map((r) => {
9993
+ const local2 = this.sessionProgress.get(r.guideId);
9994
+ if (!local2) return r;
9995
+ const seen = Math.min(r.totalSteps, local2.maxStep + 1);
9996
+ const completed = r.completed || local2.completed || seen >= r.totalSteps;
9997
+ return {
9998
+ ...r,
9999
+ currentStep: completed ? r.totalSteps : Math.max(r.currentStep, seen),
10000
+ completed
10001
+ };
10002
+ });
10003
+ }
7967
10004
  /**
7968
10005
  * Encola con dedup por sesión del SDK. Para `shown` del primer step:
7969
10006
  * no queremos re-emitirlo si el usuario navega entre dos páginas que
@@ -8008,13 +10045,19 @@ ${pageUrl}`;
8008
10045
  * propiedades del usuario. Usa el endUserId actual (o el anónimo si aún no
8009
10046
  * hubo identify — el backend pre-crea la fila igual que identify).
8010
10047
  */
8011
- async submitFormResponse(guideId, answers) {
10048
+ async submitFormResponse(guideId, answers, meta) {
8012
10049
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
8013
10050
  try {
8014
10051
  const res = await fetch(`${this.apiUrl}/v1/guides/${guideId}/form-response`, {
8015
10052
  method: "POST",
8016
10053
  headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
8017
- body: JSON.stringify({ endUserId, answers })
10054
+ body: JSON.stringify({
10055
+ endUserId,
10056
+ answers,
10057
+ sessionId: this.client.getSessionId(),
10058
+ ...meta ? { stepPosition: meta.stepIndex } : {},
10059
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
10060
+ })
8018
10061
  });
8019
10062
  if (!res.ok && this.debug) {
8020
10063
  console.warn("[veo] form-response respondi\xF3", res.status);
@@ -8044,6 +10087,7 @@ init_guide_preview();
8044
10087
  // src/plugins/guides/preview-mode.ts
8045
10088
  init_preview_loader();
8046
10089
  init_safe_env();
10090
+ init_activation_rules();
8047
10091
  init_guide_preview();
8048
10092
  init_url_match();
8049
10093
  var active = null;
@@ -8066,7 +10110,7 @@ async function runPreviewMode(opts) {
8066
10110
  } catch (err) {
8067
10111
  if (opts.debug) console.warn("[veo] preview-resolve fall\xF3:", err);
8068
10112
  }
8069
- const submitToBackend = async (answers) => {
10113
+ const submitToBackend = async (answers, meta) => {
8070
10114
  if (!guide) return false;
8071
10115
  const veo = window.veo;
8072
10116
  const endUserId = veo?.getEndUserId?.() ?? veo?.getAnonymousId?.() ?? null;
@@ -8075,7 +10119,12 @@ async function runPreviewMode(opts) {
8075
10119
  const res = await fetch(`${opts.apiUrl}/v1/guides/${guide.guideId}/form-response`, {
8076
10120
  method: "POST",
8077
10121
  headers: { "Content-Type": "application/json", "X-Api-Key": opts.apiKey },
8078
- body: JSON.stringify({ endUserId, answers })
10122
+ body: JSON.stringify({
10123
+ endUserId,
10124
+ answers,
10125
+ ...meta ? { stepPosition: meta.stepIndex } : {},
10126
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
10127
+ })
8079
10128
  });
8080
10129
  return res.ok;
8081
10130
  } catch {
@@ -8121,7 +10170,7 @@ async function runPreviewMode(opts) {
8121
10170
  clearWaits();
8122
10171
  const rules = guide.activationRules;
8123
10172
  const selector = rules.selector?.trim();
8124
- if ((rules.trigger === "on_click" || rules.trigger === "on_hover") && selector) {
10173
+ if ((rules.trigger === "on_click" || rules.trigger === "on_hover") && selector && !isAutomaticActivation(rules)) {
8125
10174
  const verb = rules.trigger === "on_hover" ? "pasa el mouse por" : "haz clic en";
8126
10175
  chip.setLabel(`${baseLabel} \xB7 ${verb} el elemento objetivo`);
8127
10176
  const onFire = (e) => {
@@ -8144,7 +10193,7 @@ async function runPreviewMode(opts) {
8144
10193
  };
8145
10194
  return;
8146
10195
  }
8147
- const delayMs = rules.trigger === "delayed" ? rules.delayMs ?? 0 : 0;
10196
+ const delayMs = activationDelayMs(rules);
8148
10197
  if (delayMs > 0) {
8149
10198
  chip.setLabel(`${baseLabel} \xB7 aparece en ${Math.round(delayMs / 1e3)} s`);
8150
10199
  delayTimer = setTimeout(() => {