veo-sdk 0.5.34 → 0.6.1

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
@@ -571,6 +571,129 @@ var init_rich_text = __esm({
571
571
  }
572
572
  });
573
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
+
574
697
  // src/plugins/guides/block-builder.ts
575
698
  function buildStepContent(step, doc, callbacks) {
576
699
  const container = doc.createElement("div");
@@ -695,8 +818,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
695
818
  i++;
696
819
  }
697
820
  }
698
- function buildContentBlock(block, doc) {
821
+ function buildContentBlock(block, doc, opts = {}) {
699
822
  switch (block.type) {
823
+ case "video":
824
+ return buildVideoBlock(block, doc, opts);
825
+ case "divider":
826
+ return buildDividerBlock(block, doc);
700
827
  case "title": {
701
828
  const el2 = doc.createElement("h2");
702
829
  el2.className = "veo-guide-title";
@@ -720,7 +847,7 @@ function buildContentBlock(block, doc) {
720
847
  const el2 = doc.createElement("img");
721
848
  el2.className = "veo-guide-image";
722
849
  el2.src = block.url;
723
- el2.alt = "";
850
+ el2.alt = typeof block.alt === "string" ? block.alt : "";
724
851
  applyBlockStyle(el2, block.style, "image");
725
852
  return el2;
726
853
  }
@@ -728,6 +855,31 @@ function buildContentBlock(block, doc) {
728
855
  return null;
729
856
  }
730
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
+ }
731
883
  function buildButtonBlock(block, doc, callbacks) {
732
884
  const btn = doc.createElement("button");
733
885
  btn.type = "button";
@@ -758,14 +910,34 @@ function applyBlockStyle(el2, style, kind) {
758
910
  }
759
911
  const align = typeof s.align === "string" ? s.align : null;
760
912
  if (align && (align === "left" || align === "center" || align === "right")) {
761
- if (kind === "text") el2.style.textAlign = align;
762
- 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";
763
930
  }
764
931
  if (kind !== "image" && typeof s.color === "string") el2.style.color = s.color;
765
932
  if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
766
933
  el2.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
767
934
  }
768
- 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)) {
769
941
  el2.style.width = `${clampNum(s.width, 10, 100)}%`;
770
942
  }
771
943
  if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
@@ -844,16 +1016,24 @@ function readCtaUrl(step) {
844
1016
  if (typeof candidate !== "string") return void 0;
845
1017
  return isSafeUrl(candidate) ? candidate : void 0;
846
1018
  }
847
- var BLOCK_ALIGN_SELF;
1019
+ var DIVIDER_LINE_STYLES, BLOCK_ALIGN_SELF, TEXT_PRESETS;
848
1020
  var init_block_builder = __esm({
849
1021
  "src/plugins/guides/block-builder.ts"() {
850
1022
  init_constants2();
851
1023
  init_rich_text();
1024
+ init_video_block();
1025
+ DIVIDER_LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dotted", "dashed"]);
852
1026
  BLOCK_ALIGN_SELF = {
853
1027
  left: "flex-start",
854
1028
  center: "center",
855
1029
  right: "flex-end"
856
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
+ };
857
1037
  }
858
1038
  });
859
1039
 
@@ -1216,7 +1396,161 @@ var init_styles = __esm({
1216
1396
  }
1217
1397
  /* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
1218
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; }
1219
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;
1220
1554
  pointer-events: auto;
1221
1555
  background: var(--veo-bg, #ffffff);
1222
1556
  color: var(--veo-text, #1f2937);
@@ -1309,6 +1643,13 @@ var init_styles = __esm({
1309
1643
  transition: opacity 120ms ease;
1310
1644
  }
1311
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; }
1312
1653
  .veo-guide-cta:active { transform: translateY(1px); }
1313
1654
  .veo-guide-close {
1314
1655
  position: absolute; top: -6px; right: -6px;
@@ -1339,6 +1680,10 @@ var init_styles = __esm({
1339
1680
 
1340
1681
  .veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
1341
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; }
1342
1687
  .veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
1343
1688
  .veo-form-input {
1344
1689
  font-size: 14px; color: var(--veo-text);
@@ -1347,26 +1692,71 @@ var init_styles = __esm({
1347
1692
  padding: 8px 10px; width: 100%;
1348
1693
  }
1349
1694
  .veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
1350
- .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; }
1351
1729
  .veo-form-option {
1352
- display: flex; align-items: center; gap: 8px;
1353
- font-size: 14px; color: var(--veo-text); cursor: pointer;
1354
- }
1355
- .veo-form-option input { accent-color: var(--veo-primary); margin: 0; cursor: pointer; }
1356
- .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; }
1357
1744
  .veo-nps-btn {
1358
- min-width: 30px; height: 30px; padding: 0 4px;
1359
- font-size: 13px; font-weight: 500; color: var(--veo-text);
1360
- 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;
1361
1748
  cursor: pointer; transition: all 100ms ease;
1362
1749
  }
1363
- .veo-nps-btn:hover { border-color: var(--veo-primary); }
1750
+ .veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
1364
1751
  .veo-rating-btn {
1365
1752
  background: none; border: none; padding: 0 2px;
1366
1753
  font-size: 26px; line-height: 1; color: #d1d5db;
1367
1754
  cursor: pointer; transition: color 100ms ease;
1368
1755
  }
1369
- .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
+ }
1370
1760
  .veo-scale-active.veo-rating-btn { color: #f59e0b; }
1371
1761
  .veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
1372
1762
  .veo-form-thanks {
@@ -2074,6 +2464,217 @@ var init_banner_renderer = __esm({
2074
2464
  }
2075
2465
  });
2076
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
+
2077
2678
  // src/plugins/guides/renderers/custom-frame.ts
2078
2679
  function mountCustomFrame(doc, step, context, opts) {
2079
2680
  const iframe = doc.createElement("iframe");
@@ -2258,6 +2859,11 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
2258
2859
  }
2259
2860
  if (rawInline) {
2260
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
+ }
2261
2867
  insertRawNodes(rawInline.anchor, rawNodes, rawInline.pos);
2262
2868
  injected.push(...rawNodes);
2263
2869
  } else if (host) {
@@ -2267,24 +2873,40 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
2267
2873
  injected.push(holder);
2268
2874
  const slot = doc.createElement("slot");
2269
2875
  slot.setAttribute("name", slotName);
2270
- node = slot;
2876
+ const wrapper = doc.createElement("div");
2877
+ wrapper.className = "veo-code-host";
2878
+ wrapper.appendChild(slot);
2879
+ node = wrapper;
2271
2880
  } else {
2272
2881
  node = holder;
2273
2882
  }
2274
2883
  }
2884
+ let cancelled = false;
2275
2885
  if (typeof block.js === "string" && block.js.trim()) {
2276
- const s = hostDoc.createElement("script");
2277
- s.textContent = block.js;
2278
- hostDoc.body.appendChild(s);
2279
- 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();
2280
2896
  }
2281
2897
  return {
2282
2898
  node,
2283
2899
  cleanup: () => {
2900
+ cancelled = true;
2284
2901
  for (const n of injected) n.parentNode?.removeChild(n);
2285
2902
  }
2286
2903
  };
2287
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
+ }
2288
2910
  function runScript(old, hostDoc) {
2289
2911
  const s = hostDoc.createElement("script");
2290
2912
  for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
@@ -2292,10 +2914,11 @@ function runScript(old, hostDoc) {
2292
2914
  hostDoc.body.appendChild(s);
2293
2915
  return s;
2294
2916
  }
2295
- var codeSlotSeq;
2917
+ var RAW_BLOCK_ATTR, codeSlotSeq;
2296
2918
  var init_code_block = __esm({
2297
2919
  "src/plugins/guides/code-block.ts"() {
2298
2920
  init_custom_frame();
2921
+ RAW_BLOCK_ATTR = "data-veo-raw-block";
2299
2922
  codeSlotSeq = 0;
2300
2923
  }
2301
2924
  });
@@ -2526,8 +3149,14 @@ function buildField(field, doc) {
2526
3149
  wrapper.appendChild(group);
2527
3150
  return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
2528
3151
  }
2529
- case "nps":
2530
- 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
+ }
2531
3160
  case "rating":
2532
3161
  return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
2533
3162
  default: {
@@ -2540,17 +3169,21 @@ function buildField(field, doc) {
2540
3169
  }
2541
3170
  }
2542
3171
  }
2543
- function buildScale(wrapper, doc, min, max, btnClass, symbol) {
3172
+ function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
2544
3173
  const row = doc.createElement("div");
2545
3174
  row.className = "veo-form-scale";
2546
3175
  let selected;
2547
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));
2548
3179
  for (let n = min; n <= max; n++) {
2549
3180
  const btn = doc.createElement("button");
2550
3181
  btn.type = "button";
2551
3182
  btn.className = btnClass;
2552
3183
  btn.textContent = symbol ?? String(n);
2553
- 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;
2554
3187
  btn.addEventListener("click", () => {
2555
3188
  selected = n;
2556
3189
  buttons.forEach((b, i) => {
@@ -2559,7 +3192,17 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
2559
3192
  });
2560
3193
  });
2561
3194
  buttons.push(btn);
2562
- 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);
2563
3206
  }
2564
3207
  wrapper.appendChild(row);
2565
3208
  return { wrapper, read: () => selected };
@@ -2569,6 +3212,126 @@ var init_form_content = __esm({
2569
3212
  init_block_builder();
2570
3213
  }
2571
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
+ });
2572
3335
  function num(v, fallback) {
2573
3336
  return typeof v === "number" && Number.isFinite(v) ? v : fallback;
2574
3337
  }
@@ -2707,13 +3470,68 @@ function markerOffset(anchor, marker, position, offset6) {
2707
3470
  }
2708
3471
  return { x: x + offset6.x, y: y + offset6.y };
2709
3472
  }
3473
+ function canNest(anchor) {
3474
+ const win = anchor.ownerDocument.defaultView;
3475
+ if (!win || !(anchor instanceof win.HTMLElement)) return false;
3476
+ if (NO_CHILDREN.has(anchor.tagName.toLowerCase())) return false;
3477
+ const cs = win.getComputedStyle(anchor);
3478
+ if (cs.display === "contents") return false;
3479
+ const tokens = `${cs.overflow} ${cs.overflowX} ${cs.overflowY}`.split(/\s+/).filter(Boolean);
3480
+ return tokens.every((t) => t === "visible");
3481
+ }
2710
3482
  function attachMarker(anchor, host, marker) {
2711
3483
  host.style.position = "absolute";
2712
3484
  host.style.zIndex = String(num(marker.zIndex, DEFAULT_MARKER_Z));
2713
3485
  host.style.margin = "0";
3486
+ const position = marker.position ?? "top-right";
3487
+ const offset6 = { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) };
3488
+ const markerSize = () => {
3489
+ const m = host.getBoundingClientRect();
3490
+ return { width: m.width, height: m.height };
3491
+ };
3492
+ if (canNest(anchor)) {
3493
+ const win = anchor.ownerDocument.defaultView;
3494
+ const prevPosition = anchor.style.position;
3495
+ const computed = win.getComputedStyle(anchor).position;
3496
+ if (!computed || computed === "static") anchor.style.position = "relative";
3497
+ anchor.appendChild(host);
3498
+ const swallowClick = (e) => {
3499
+ e.preventDefault();
3500
+ e.stopPropagation();
3501
+ };
3502
+ const swallow = (e) => e.stopPropagation();
3503
+ host.addEventListener("click", swallowClick);
3504
+ host.addEventListener("pointerdown", swallow);
3505
+ host.addEventListener("mousedown", swallow);
3506
+ const update2 = () => {
3507
+ const { x, y } = markerOffset(
3508
+ {
3509
+ left: -anchor.clientLeft,
3510
+ top: -anchor.clientTop,
3511
+ width: anchor.offsetWidth,
3512
+ height: anchor.offsetHeight
3513
+ },
3514
+ markerSize(),
3515
+ position,
3516
+ offset6
3517
+ );
3518
+ host.style.left = `${Math.round(x)}px`;
3519
+ host.style.top = `${Math.round(y)}px`;
3520
+ };
3521
+ update2();
3522
+ const stop2 = dom.autoUpdate(anchor, host, update2, { ancestorScroll: false, layoutShift: false });
3523
+ return () => {
3524
+ stop2();
3525
+ host.removeEventListener("click", swallowClick);
3526
+ host.removeEventListener("pointerdown", swallow);
3527
+ host.removeEventListener("mousedown", swallow);
3528
+ host.remove();
3529
+ anchor.style.position = prevPosition;
3530
+ };
3531
+ }
3532
+ anchor.ownerDocument.body.appendChild(host);
2714
3533
  const update = () => {
2715
3534
  const a = anchor.getBoundingClientRect();
2716
- const m = host.getBoundingClientRect();
2717
3535
  const { x, y } = markerOffset(
2718
3536
  {
2719
3537
  left: a.left + window.scrollX,
@@ -2721,17 +3539,21 @@ function attachMarker(anchor, host, marker) {
2721
3539
  width: a.width,
2722
3540
  height: a.height
2723
3541
  },
2724
- { width: m.width, height: m.height },
2725
- marker.position ?? "top-right",
2726
- { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) }
3542
+ markerSize(),
3543
+ position,
3544
+ offset6
2727
3545
  );
2728
3546
  host.style.left = `${Math.round(x)}px`;
2729
3547
  host.style.top = `${Math.round(y)}px`;
2730
3548
  };
2731
3549
  update();
2732
- return dom.autoUpdate(anchor, host, update);
3550
+ const stop = dom.autoUpdate(anchor, host, update);
3551
+ return () => {
3552
+ stop();
3553
+ host.remove();
3554
+ };
2733
3555
  }
2734
- var INFO_GLYPH, QUESTION_GLYPH, STAR_GLYPH, CIRCLE, TARGET_GLYPH, BADGE_ICONS, DEFAULT_MARKER_SIZE, DEFAULT_MARKER_Z, SIDE_GAP;
3556
+ var INFO_GLYPH, QUESTION_GLYPH, STAR_GLYPH, CIRCLE, TARGET_GLYPH, BADGE_ICONS, DEFAULT_MARKER_SIZE, DEFAULT_MARKER_Z, SIDE_GAP, NO_CHILDREN;
2735
3557
  var init_badge_marker = __esm({
2736
3558
  "src/plugins/guides/renderers/badge-marker.ts"() {
2737
3559
  INFO_GLYPH = "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021";
@@ -2754,28 +3576,78 @@ var init_badge_marker = __esm({
2754
3576
  DEFAULT_MARKER_SIZE = 14;
2755
3577
  DEFAULT_MARKER_Z = 19e3;
2756
3578
  SIDE_GAP = 8;
3579
+ NO_CHILDREN = /* @__PURE__ */ new Set([
3580
+ "img",
3581
+ "input",
3582
+ "textarea",
3583
+ "select",
3584
+ "iframe",
3585
+ "video",
3586
+ "audio",
3587
+ "canvas",
3588
+ "br",
3589
+ "hr",
3590
+ "svg",
3591
+ "object",
3592
+ "embed"
3593
+ ]);
2757
3594
  }
2758
3595
  });
3596
+ function applyRowStyle(rowEl, style) {
3597
+ const s = rowEl.style;
3598
+ if (typeof style.background === "string" && style.background) s.background = style.background;
3599
+ for (const side of ["Top", "Right", "Bottom", "Left"]) {
3600
+ const p = style[`padding${side}`];
3601
+ if (typeof p === "number" && Number.isFinite(p))
3602
+ s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
3603
+ const m = style[`margin${side}`];
3604
+ if (typeof m === "number" && Number.isFinite(m))
3605
+ s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
3606
+ }
3607
+ if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
3608
+ s.minHeight = `${style.minHeight}px`;
3609
+ }
3610
+ if (style.align && ROW_JUSTIFY[style.align]) {
3611
+ s.justifyContent = ROW_JUSTIFY[style.align];
3612
+ rowEl.classList.add("veo-row--aligned");
3613
+ }
3614
+ if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
3615
+ }
2759
3616
  function inlinePosOf(p) {
2760
3617
  return p === "before" || p === "prepend" || p === "append" ? p : "after";
2761
3618
  }
2762
3619
  function applyViewportPosition(card, position, design) {
2763
3620
  const s = card.style;
2764
3621
  s.position = "fixed";
2765
- const M = "16px";
3622
+ const BASE = 16;
3623
+ const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
3624
+ s.margin = "0";
2766
3625
  const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
2767
3626
  const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
2768
3627
  const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
2769
- if (v === "top") s.top = M;
2770
- else if (v === "bottom") s.bottom = M;
3628
+ if (v === "top") s.top = `${BASE + m("marginTop")}px`;
3629
+ else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
2771
3630
  else s.top = "50%";
2772
- if (h === "left") s.left = M;
2773
- else if (h === "right") s.right = M;
3631
+ if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
3632
+ else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
2774
3633
  else s.left = "50%";
2775
- const tx = h === "middle" ? "-50%" : "0";
2776
- const ty = v === "middle" ? "-50%" : "0";
3634
+ const dx = m("marginLeft") - m("marginRight");
3635
+ const dy = m("marginTop") - m("marginBottom");
3636
+ const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
3637
+ const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
2777
3638
  s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
2778
3639
  }
3640
+ function reserveCloseSpace(card, blocks, position, design) {
3641
+ if (blocks.length === 0) return;
3642
+ const side = position.includes("bottom") ? "Bottom" : "Top";
3643
+ const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
3644
+ if (edgeBlock?.type === "image") return;
3645
+ const margin = design[`closeMargin${side}`];
3646
+ const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
3647
+ const current = design[`padding${side}`];
3648
+ const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
3649
+ if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
3650
+ }
2779
3651
  function applyClosePosition(btn, position) {
2780
3652
  const s = btn.style;
2781
3653
  s.top = "auto";
@@ -2844,6 +3716,11 @@ function applyCompositeDesign(card, design) {
2844
3716
  const s = card.style;
2845
3717
  const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
2846
3718
  const strv = (v) => typeof v === "string" && v ? v : null;
3719
+ if (design.colorMode === "dark") {
3720
+ s.background = "#1f1d1b";
3721
+ s.color = "#f5f0eb";
3722
+ card.classList.add("veo-card--dark");
3723
+ }
2847
3724
  if (strv(design.background)) s.background = design.background;
2848
3725
  const textColor = strv(design.color) ?? strv(design.textColor);
2849
3726
  if (textColor) s.color = textColor;
@@ -2865,13 +3742,14 @@ function applyCompositeDesign(card, design) {
2865
3742
  const n = (k) => typeof design[k] === "number" ? design[k] : 0;
2866
3743
  s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
2867
3744
  }
3745
+ const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
2868
3746
  if (design.fullWidth === true) {
2869
3747
  s.width = "100%";
2870
3748
  } else if (design.widthType === "fluid") {
2871
- s.width = "auto";
2872
- if (px(design.width)) s.maxWidth = px(design.width);
2873
- } else if (px(design.width)) {
2874
- s.width = px(design.width);
3749
+ s.width = "100%";
3750
+ if (width) s.maxWidth = width;
3751
+ } else if (width) {
3752
+ s.width = width;
2875
3753
  }
2876
3754
  for (const side of ["Top", "Right", "Bottom", "Left"]) {
2877
3755
  const p = px(design[`padding${side}`]);
@@ -2880,13 +3758,18 @@ function applyCompositeDesign(card, design) {
2880
3758
  if (m) s.setProperty(`margin-${side.toLowerCase()}`, m);
2881
3759
  }
2882
3760
  }
2883
- var ROW_ALIGN, CompositeRenderer, CLOSE_INSET;
3761
+ var ROW_ALIGN, ROW_JUSTIFY, ROW_VALIGN, CompositeRenderer, CLOSE_INSET, CLOSE_RESERVE_PX, CARD_DEFAULT_PAD_PX, MIN_CARD_WIDTH, NARROW_CARD_WIDTH;
2884
3762
  var init_composite_renderer = __esm({
2885
3763
  "src/plugins/guides/renderers/composite-renderer.ts"() {
2886
3764
  init_block_builder();
3765
+ init_checklist_block();
2887
3766
  init_code_block();
2888
3767
  init_form_content();
2889
3768
  init_inline_host();
3769
+ init_layout();
3770
+ init_rich_text();
3771
+ init_scroll_lock();
3772
+ init_snooze();
2890
3773
  init_wait_for_element();
2891
3774
  init_badge_marker();
2892
3775
  init_base_renderer();
@@ -2896,6 +3779,12 @@ var init_composite_renderer = __esm({
2896
3779
  center: "center",
2897
3780
  right: "flex-end"
2898
3781
  };
3782
+ ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
3783
+ ROW_VALIGN = {
3784
+ top: "flex-start",
3785
+ center: "center",
3786
+ bottom: "flex-end"
3787
+ };
2899
3788
  CompositeRenderer = class extends BaseRenderer {
2900
3789
  async render(context) {
2901
3790
  const idx = context.nav?.stepIndex ?? 0;
@@ -2914,7 +3803,8 @@ var init_composite_renderer = __esm({
2914
3803
  const rawDoc = anchor.ownerDocument ?? document;
2915
3804
  const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
2916
3805
  anchor,
2917
- pos: inlinePosOf(step.inlinePosition)
3806
+ pos: inlinePosOf(step.inlinePosition),
3807
+ ...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
2918
3808
  });
2919
3809
  this.registerCleanup(cleanup);
2920
3810
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
@@ -2927,6 +3817,10 @@ var init_composite_renderer = __esm({
2927
3817
  const card = doc.createElement("div");
2928
3818
  card.className = "veo-card";
2929
3819
  applyCompositeDesign(card, design);
3820
+ if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
3821
+ card.setAttribute("role", "dialog");
3822
+ card.setAttribute("aria-label", design.ariaLabel.trim());
3823
+ }
2930
3824
  const dismiss = (action, meta) => {
2931
3825
  context.onInteraction({
2932
3826
  guideId: context.guide.guideId,
@@ -2937,6 +3831,28 @@ var init_composite_renderer = __esm({
2937
3831
  };
2938
3832
  const readers = [];
2939
3833
  this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
3834
+ if (blocks.length === 0) {
3835
+ card.classList.add("veo-card--empty");
3836
+ if (context.isPreview) {
3837
+ const zone = doc.createElement("div");
3838
+ zone.className = "veo-card-empty-zone";
3839
+ const plus = doc.createElement("b");
3840
+ plus.textContent = "+";
3841
+ const text = doc.createElement("span");
3842
+ text.textContent = "Clic para agregar contenido";
3843
+ zone.append(plus, text);
3844
+ card.appendChild(zone);
3845
+ const applyNarrow = () => {
3846
+ card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
3847
+ };
3848
+ if (typeof ResizeObserver !== "undefined") {
3849
+ const ro = new ResizeObserver(applyNarrow);
3850
+ ro.observe(card);
3851
+ this.registerCleanup(() => ro.disconnect());
3852
+ }
3853
+ applyNarrow();
3854
+ }
3855
+ }
2940
3856
  const lastBlock = card.lastElementChild;
2941
3857
  if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
2942
3858
  let closeTooltip = null;
@@ -2954,6 +3870,14 @@ var init_composite_renderer = __esm({
2954
3870
  if (closeColor) closeBtn.style.color = closeColor;
2955
3871
  const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
2956
3872
  applyClosePosition(closeBtn, closePos);
3873
+ for (const side of ["Top", "Right", "Bottom", "Left"]) {
3874
+ const p = design[`closePadding${side}`];
3875
+ if (typeof p === "number")
3876
+ closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
3877
+ const m = design[`closeMargin${side}`];
3878
+ if (typeof m === "number")
3879
+ closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
3880
+ }
2957
3881
  if (hoverColor) {
2958
3882
  closeBtn.addEventListener("mouseenter", () => {
2959
3883
  closeBtn.style.color = hoverColor;
@@ -2963,6 +3887,7 @@ var init_composite_renderer = __esm({
2963
3887
  });
2964
3888
  }
2965
3889
  card.appendChild(closeBtn);
3890
+ reserveCloseSpace(card, blocks, closePos, design);
2966
3891
  }
2967
3892
  if (badgeMode && anchor) {
2968
3893
  closeTooltip = this.renderBadgeMode(
@@ -2993,6 +3918,7 @@ var init_composite_renderer = __esm({
2993
3918
  const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
2994
3919
  bg.style.zIndex = String(bz);
2995
3920
  root.appendChild(bg);
3921
+ this.registerCleanup(lockScroll(doc));
2996
3922
  if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
2997
3923
  }
2998
3924
  const wrapper = doc.createElement("div");
@@ -3019,6 +3945,7 @@ var init_composite_renderer = __esm({
3019
3945
  this.closeOrSkip(context);
3020
3946
  }
3021
3947
  });
3948
+ this.registerCleanup(lockScroll(doc));
3022
3949
  }
3023
3950
  applyViewportPosition(card, step.position, design);
3024
3951
  overlay.appendChild(card);
@@ -3036,83 +3963,217 @@ var init_composite_renderer = __esm({
3036
3963
  ...block.id ? { metadata: { buttonId: block.id } } : {}
3037
3964
  });
3038
3965
  };
3039
- let i = 0;
3040
- while (i < blocks.length) {
3041
- const b = blocks[i];
3042
- if (!b) {
3043
- i++;
3044
- continue;
3045
- }
3046
- if (b.type === "button") {
3047
- const row = doc.createElement("div");
3048
- row.className = "veo-guide-actions";
3049
- let rowAlign;
3050
- while (i < blocks.length && blocks[i]?.type === "button") {
3051
- const bb = blocks[i];
3052
- if (!rowAlign) {
3053
- const a = bb.style?.align;
3054
- if (a === "left" || a === "center" || a === "right") rowAlign = a;
3966
+ const tag = (node, block, index) => {
3967
+ node.dataset.veoBlock = String(index);
3968
+ if (block.editorId) node.dataset.veoBlockId = block.editorId;
3969
+ return node;
3970
+ };
3971
+ const placeholder = (block, label) => {
3972
+ if (!context.isPreview || !block.editorId) return null;
3973
+ const el2 = doc.createElement("div");
3974
+ el2.className = "veo-block-placeholder";
3975
+ el2.textContent = label;
3976
+ return el2;
3977
+ };
3978
+ const buildSequence = (container, cell) => {
3979
+ let i = 0;
3980
+ while (i < cell.length) {
3981
+ const { block: b, index } = cell[i];
3982
+ if (b.type === "button") {
3983
+ const row = doc.createElement("div");
3984
+ row.className = "veo-guide-actions";
3985
+ let rowAlign;
3986
+ while (i < cell.length && cell[i]?.block.type === "button") {
3987
+ const { block: bb, index: bi } = cell[i];
3988
+ if (!rowAlign) {
3989
+ const a = bb.style?.align;
3990
+ if (a === "left" || a === "center" || a === "right") rowAlign = a;
3991
+ }
3992
+ row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
3993
+ i++;
3055
3994
  }
3056
- row.appendChild(this.buildButton(bb, doc, context, idx, readers, emit));
3995
+ if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
3996
+ container.appendChild(row);
3997
+ continue;
3998
+ }
3999
+ if (b.type === "code") {
4000
+ const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
4001
+ const el2 = node2 ?? placeholder(b, "Bloque de c\xF3digo");
4002
+ if (el2) container.appendChild(tag(el2, b, index));
4003
+ this.registerCleanup(cleanup);
3057
4004
  i++;
4005
+ continue;
3058
4006
  }
3059
- if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
3060
- card.appendChild(row);
3061
- continue;
3062
- }
3063
- if (b.type === "code") {
3064
- const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
3065
- if (node2) card.appendChild(node2);
3066
- this.registerCleanup(cleanup);
3067
- i++;
3068
- continue;
3069
- }
3070
- if (b.type === "form") {
3071
- const form = doc.createElement("div");
3072
- form.className = "veo-form";
3073
- for (const field of b.fields ?? []) {
3074
- const { wrapper, read } = buildField(field, doc);
3075
- readers.push({ field, read });
3076
- form.appendChild(wrapper);
4007
+ if (b.type === "form") {
4008
+ container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
4009
+ i++;
4010
+ continue;
3077
4011
  }
3078
- card.appendChild(form);
4012
+ if (b.type === "checklist") {
4013
+ const el2 = buildChecklistBlock(b, doc, context);
4014
+ applyBlockStyle(el2, b.style, "text");
4015
+ el2.style.alignSelf = "stretch";
4016
+ el2.style.width = "100%";
4017
+ container.appendChild(tag(el2, b, index));
4018
+ i++;
4019
+ continue;
4020
+ }
4021
+ const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
4022
+ b,
4023
+ b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
4024
+ );
4025
+ if (node) container.appendChild(tag(node, b, index));
3079
4026
  i++;
4027
+ }
4028
+ };
4029
+ const rowStyles = context.guide.guideSteps[idx]?.rows;
4030
+ for (const row of groupLayout(blocks)) {
4031
+ const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
4032
+ if (row.columns.length === 1 && !rowStyle) {
4033
+ buildSequence(card, row.columns[0].blocks);
3080
4034
  continue;
3081
4035
  }
3082
- const node = buildContentBlock(b, doc);
3083
- if (node) card.appendChild(node);
3084
- i++;
4036
+ const rowEl = doc.createElement("div");
4037
+ rowEl.className = "veo-row";
4038
+ if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
4039
+ for (const column of row.columns) {
4040
+ const colEl = doc.createElement("div");
4041
+ colEl.className = "veo-col";
4042
+ buildSequence(colEl, column.blocks);
4043
+ rowEl.appendChild(colEl);
4044
+ }
4045
+ if (rowStyle) applyRowStyle(rowEl, rowStyle);
4046
+ card.appendChild(rowEl);
3085
4047
  }
3086
4048
  }
3087
4049
  /** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
3088
- buildButton(block, doc, context, idx, readers, emit) {
4050
+ /**
4051
+ * Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
4052
+ * de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
4053
+ * la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
4054
+ * bloque viste la pregunta (preset/alineación/color); los márgenes van al
4055
+ * contenedor del bloque entero.
4056
+ */
4057
+ buildForm(b, doc, context, idx, readers, emit) {
4058
+ const form = doc.createElement("div");
4059
+ form.className = "veo-form";
4060
+ const style = b.style ?? {};
4061
+ const { marginTop, marginBottom, ...questionStyle } = style;
4062
+ for (const [side, v] of [
4063
+ ["marginTop", marginTop],
4064
+ ["marginBottom", marginBottom]
4065
+ ]) {
4066
+ if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
4067
+ }
4068
+ let question = null;
4069
+ if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
4070
+ question = doc.createElement("div");
4071
+ question.className = "veo-form-question";
4072
+ if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
4073
+ else question.textContent = b.text;
4074
+ applyBlockStyle(question, questionStyle, "text");
4075
+ form.appendChild(question);
4076
+ }
4077
+ const questionText = question?.textContent?.trim() ?? "";
4078
+ const own = [];
4079
+ for (const field of b.fields ?? []) {
4080
+ const { wrapper, read } = buildField(field, doc);
4081
+ const st = b.style ?? {};
4082
+ if (typeof st.scaleColor === "string" && st.scaleColor) {
4083
+ wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
4084
+ }
4085
+ if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
4086
+ wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
4087
+ }
4088
+ if (question) {
4089
+ wrapper.querySelector(".veo-form-label")?.remove();
4090
+ if (questionText) {
4091
+ for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
4092
+ inp.setAttribute("aria-label", questionText);
4093
+ }
4094
+ }
4095
+ }
4096
+ const reader = { field, read, blockId: b.id ?? null };
4097
+ readers.push(reader);
4098
+ own.push(reader);
4099
+ form.appendChild(wrapper);
4100
+ }
4101
+ if (b.submit) {
4102
+ const actions = doc.createElement("div");
4103
+ actions.className = "veo-guide-actions";
4104
+ const submit = { ...b.submit, type: "button" };
4105
+ const sa = submit.style?.align;
4106
+ if (sa === "left" || sa === "center" || sa === "right") {
4107
+ actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
4108
+ }
4109
+ actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
4110
+ form.appendChild(actions);
4111
+ }
4112
+ return form;
4113
+ }
4114
+ buildButton(block, doc, context, idx, readers, emit, submitFirst) {
3089
4115
  const btn = doc.createElement("button");
3090
4116
  btn.type = "button";
3091
4117
  btn.className = "veo-guide-cta";
3092
4118
  btn.textContent = block.text?.trim() || "OK";
4119
+ const st = block.style ?? {};
4120
+ if (st.preset === "secondary" || st.preset === "link") {
4121
+ btn.classList.add(`veo-guide-cta--${st.preset}`);
4122
+ }
4123
+ if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
4124
+ btn.setAttribute("aria-label", st.ariaLabel.trim());
4125
+ }
3093
4126
  applyBlockStyle(btn, block.style, "button");
3094
4127
  const nav = context.nav?.callbacks;
3095
- btn.addEventListener("click", () => {
3096
- switch (block.action) {
4128
+ const runSimple = (spec) => {
4129
+ switch (spec.action) {
3097
4130
  case "next":
3098
- emit(block);
3099
4131
  if (nav) nav.onNext();
3100
4132
  else context.onClose();
3101
4133
  break;
3102
4134
  case "prev":
3103
- emit(block);
3104
4135
  nav?.onBack();
3105
4136
  break;
4137
+ case "goto":
4138
+ if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
4139
+ else context.onClose();
4140
+ break;
4141
+ case "snooze":
4142
+ snoozeGuide(
4143
+ context.guide.guideId,
4144
+ typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
4145
+ spec.snoozeUnit ?? "day"
4146
+ );
4147
+ context.onClose();
4148
+ break;
3106
4149
  case "url":
3107
- emit(block);
3108
- if (typeof block.url === "string" && isSafeUrl(block.url)) {
3109
- window.open(block.url, "_blank", "noopener,noreferrer");
4150
+ if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
4151
+ if (spec.newTab === false) window.location.assign(spec.url);
4152
+ else window.open(spec.url, "_blank", "noopener,noreferrer");
3110
4153
  }
3111
4154
  break;
3112
4155
  case "guide":
3113
- emit(block);
3114
- if (block.targetGuideId) context.onOpenGuide?.(block.targetGuideId);
4156
+ if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
4157
+ this.closeOrSkip(context);
4158
+ break;
4159
+ default:
3115
4160
  this.closeOrSkip(context);
4161
+ }
4162
+ };
4163
+ const runSecond = () => {
4164
+ if (block.secondAction?.action) runSimple(block.secondAction);
4165
+ };
4166
+ const runActions = () => {
4167
+ switch (block.action) {
4168
+ case "next":
4169
+ case "prev":
4170
+ case "goto":
4171
+ case "snooze":
4172
+ case "url":
4173
+ case "guide":
4174
+ emit(block);
4175
+ runSimple(block);
4176
+ runSecond();
3116
4177
  break;
3117
4178
  case "submit-forms":
3118
4179
  case "submit-forms-advance":
@@ -3131,7 +4192,12 @@ var init_composite_renderer = __esm({
3131
4192
  default:
3132
4193
  emit(block);
3133
4194
  this.closeOrSkip(context);
4195
+ runSecond();
3134
4196
  }
4197
+ };
4198
+ btn.addEventListener("click", () => {
4199
+ if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
4200
+ else runActions();
3135
4201
  });
3136
4202
  return btn;
3137
4203
  }
@@ -3150,17 +4216,27 @@ var init_composite_renderer = __esm({
3150
4216
  }
3151
4217
  answers[field.key] = value;
3152
4218
  }
3153
- if (readers.length > 0 && Object.keys(answers).length === 0) return;
4219
+ if (Object.keys(answers).length === 0) {
4220
+ onDone();
4221
+ return;
4222
+ }
3154
4223
  btn.disabled = true;
3155
4224
  const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
3156
- void submitFn(answers).then((ok) => {
4225
+ const blockIds = new Set(
4226
+ readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
4227
+ );
4228
+ const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
4229
+ void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
3157
4230
  btn.disabled = false;
3158
4231
  if (!ok) return;
3159
- context.onInteraction({
3160
- guideId: context.guide.guideId,
3161
- stepIndex: idx,
3162
- action: "completed"
3163
- });
4232
+ const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
4233
+ if (lastStep) {
4234
+ context.onInteraction({
4235
+ guideId: context.guide.guideId,
4236
+ stepIndex: idx,
4237
+ action: "completed"
4238
+ });
4239
+ }
3164
4240
  onDone();
3165
4241
  });
3166
4242
  }
@@ -3203,9 +4279,7 @@ var init_composite_renderer = __esm({
3203
4279
  root.appendChild(wrapper);
3204
4280
  if (marker) {
3205
4281
  host.style.display = "inline-block";
3206
- (anchor.ownerDocument ?? document).body.appendChild(host);
3207
4282
  this.registerCleanup(attachMarker(anchor, host, marker));
3208
- this.registerCleanup(() => host.remove());
3209
4283
  } else {
3210
4284
  host.style.display = "inline-block";
3211
4285
  const pos = inlinePosOf(step.inlinePosition);
@@ -3269,6 +4343,10 @@ var init_composite_renderer = __esm({
3269
4343
  }
3270
4344
  };
3271
4345
  CLOSE_INSET = "10px";
4346
+ CLOSE_RESERVE_PX = 42;
4347
+ CARD_DEFAULT_PAD_PX = 20;
4348
+ MIN_CARD_WIDTH = 120;
4349
+ NARROW_CARD_WIDTH = 260;
3272
4350
  }
3273
4351
  });
3274
4352
  function applyFloatingPosition(wrapper, placement) {
@@ -3364,6 +4442,7 @@ var FormRenderer;
3364
4442
  var init_form_renderer = __esm({
3365
4443
  "src/plugins/guides/renderers/form-renderer.ts"() {
3366
4444
  init_form_content();
4445
+ init_scroll_lock();
3367
4446
  init_base_renderer();
3368
4447
  FormRenderer = class extends BaseRenderer {
3369
4448
  render(context) {
@@ -3379,6 +4458,7 @@ var init_form_renderer = __esm({
3379
4458
  mountFormContent(card, context, doc);
3380
4459
  overlay.appendChild(card);
3381
4460
  root.appendChild(overlay);
4461
+ this.registerCleanup(lockScroll(doc));
3382
4462
  const dismiss = () => {
3383
4463
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
3384
4464
  context.onClose();
@@ -3535,12 +4615,25 @@ var ModalRenderer;
3535
4615
  var init_modal_renderer = __esm({
3536
4616
  "src/plugins/guides/renderers/modal-renderer.ts"() {
3537
4617
  init_block_builder();
4618
+ init_scroll_lock();
3538
4619
  init_walkthrough_block_builder();
3539
4620
  init_base_renderer();
3540
4621
  ModalRenderer = class extends BaseRenderer {
3541
4622
  constructor() {
3542
4623
  super(...arguments);
3543
4624
  this.overlay = null;
4625
+ /** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
4626
+ this.releaseScroll = null;
4627
+ }
4628
+ /** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
4629
+ syncScrollLock(step) {
4630
+ const wantLock = step.style?.backdrop !== false;
4631
+ if (wantLock && !this.releaseScroll) {
4632
+ this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
4633
+ } else if (!wantLock && this.releaseScroll) {
4634
+ this.releaseScroll();
4635
+ this.releaseScroll = null;
4636
+ }
3544
4637
  }
3545
4638
  render(context) {
3546
4639
  const idx = context.nav?.stepIndex ?? 0;
@@ -3552,6 +4645,7 @@ var init_modal_renderer = __esm({
3552
4645
  const overlay = ownerDocument.createElement("div");
3553
4646
  overlay.className = overlayClassOf(step);
3554
4647
  this.overlay = overlay;
4648
+ this.syncScrollLock(step);
3555
4649
  const card = ownerDocument.createElement("div");
3556
4650
  card.className = "veo-modal-card";
3557
4651
  const dismiss = (action, ctaUrl, meta) => {
@@ -3609,9 +4703,12 @@ var init_modal_renderer = __esm({
3609
4703
  }
3610
4704
  onLiveUpdate(step) {
3611
4705
  if (this.overlay) this.overlay.className = overlayClassOf(step);
4706
+ this.syncScrollLock(step);
3612
4707
  }
3613
4708
  destroy() {
3614
4709
  this.overlay = null;
4710
+ this.releaseScroll?.();
4711
+ this.releaseScroll = null;
3615
4712
  super.destroy();
3616
4713
  }
3617
4714
  };
@@ -3802,9 +4899,35 @@ function matchesUrl(matcher, pageUrl) {
3802
4899
  }
3803
4900
  }
3804
4901
  function stepMatchesPage(loc, pageUrl) {
3805
- if (!loc || loc.scope === "sitewide" || !loc.pattern) return true;
4902
+ if (!loc || loc.scope === "sitewide") return true;
4903
+ if (loc.scope === "pages") {
4904
+ const rules = loc.rules ?? [];
4905
+ return rules.some((r) => pageRuleMatches(r, pageUrl));
4906
+ }
4907
+ if (!loc.pattern) return true;
3806
4908
  return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
3807
4909
  }
4910
+ function pageRuleMatches(rule, pageUrl) {
4911
+ const pattern = rule.pattern.toLowerCase();
4912
+ const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
4913
+ switch (rule.type) {
4914
+ case "exact":
4915
+ return candidates.some((c) => c === pattern);
4916
+ case "prefix":
4917
+ return candidates.some((c) => c.startsWith(pattern));
4918
+ case "contains":
4919
+ return candidates.some((c) => c.includes(pattern));
4920
+ case "regex":
4921
+ try {
4922
+ const re = new RegExp(rule.pattern, "i");
4923
+ return candidates.some((c) => re.test(c));
4924
+ } catch {
4925
+ return false;
4926
+ }
4927
+ default:
4928
+ return false;
4929
+ }
4930
+ }
3808
4931
  var init_url_match = __esm({
3809
4932
  "src/plugins/guides/url-match.ts"() {
3810
4933
  }
@@ -3973,6 +5096,7 @@ var init_guide_preview = __esm({
3973
5096
  // Preview de forms: por defecto simula el envío SIN escribir props;
3974
5097
  // el caller puede pasar `formSubmit` real (link de preview).
3975
5098
  onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
5099
+ ...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
3976
5100
  isPreview: true,
3977
5101
  ...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
3978
5102
  });
@@ -4365,8 +5489,14 @@ function injectBuilderChrome(opts) {
4365
5489
  stepsList.append(activation);
4366
5490
  s.steps.forEach((st, i) => {
4367
5491
  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>';
4368
- const card = stepCard(thumb, st.label, s.section === "step" && s.activeStep === i);
4369
- card.addEventListener("click", () => act({ action: "select-step", index: i }));
5492
+ const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
5493
+ const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
5494
+ // Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
5495
+ canRemove: true,
5496
+ onEdit: () => act({ action: "select-step", index: i }),
5497
+ onDuplicate: () => act({ action: "duplicate-step", index: i }),
5498
+ onRemove: () => act({ action: "remove-step", index: i })
5499
+ });
4370
5500
  stepsList.append(card);
4371
5501
  });
4372
5502
  const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
@@ -4395,16 +5525,40 @@ function injectBuilderChrome(opts) {
4395
5525
  }
4396
5526
  };
4397
5527
  }
4398
- function stepCard(inner, label, active2) {
4399
- const b = el(
4400
- "button",
4401
- `veo-step${active2 ? " is-active" : ""}`,
4402
- `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
5528
+ function stepCard(inner, label, active2, actions) {
5529
+ const cls = `veo-step${active2 ? " is-active" : ""}`;
5530
+ if (!actions) {
5531
+ const b = el(
5532
+ "button",
5533
+ cls,
5534
+ `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
5535
+ );
5536
+ b.type = "button";
5537
+ b.querySelector(".veo-step-label").textContent = label;
5538
+ b.setAttribute("aria-pressed", active2 ? "true" : "false");
5539
+ return b;
5540
+ }
5541
+ const card = el(
5542
+ "div",
5543
+ cls,
5544
+ `<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>`
4403
5545
  );
4404
- b.type = "button";
4405
- b.querySelector(".veo-step-label").textContent = label;
4406
- b.setAttribute("aria-pressed", active2 ? "true" : "false");
4407
- return b;
5546
+ card.querySelector(".veo-step-label").textContent = label;
5547
+ card.setAttribute("aria-current", active2 ? "step" : "false");
5548
+ const remove = card.querySelector(".veo-step-remove");
5549
+ remove.disabled = !actions.canRemove;
5550
+ if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
5551
+ const on = (sel, fn) => {
5552
+ card.querySelector(sel)?.addEventListener("click", (e) => {
5553
+ e.stopPropagation();
5554
+ fn();
5555
+ });
5556
+ };
5557
+ on(".veo-step-edit", actions.onEdit);
5558
+ on(".veo-step-duplicate", actions.onDuplicate);
5559
+ on(".veo-step-remove", actions.onRemove);
5560
+ card.addEventListener("click", actions.onEdit);
5561
+ return card;
4408
5562
  }
4409
5563
  var CHROME_TOPBAR_H, CHROME_STEPS_H, ICONS, CHROME_CSS;
4410
5564
  var init_builder_chrome = __esm({
@@ -4421,6 +5575,9 @@ var init_builder_chrome = __esm({
4421
5575
  chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
4422
5576
  chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
4423
5577
  plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
5578
+ 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"/>',
5579
+ 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"/>',
5580
+ 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"/>',
4424
5581
  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"/>',
4425
5582
  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"/>'
4426
5583
  };
@@ -4507,10 +5664,31 @@ var init_builder_chrome = __esm({
4507
5664
  .veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
4508
5665
  .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; }
4509
5666
  .veo-step-box {
5667
+ position: relative; overflow: hidden;
4510
5668
  width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
4511
5669
  display: flex; align-items: center; justify-content: center; color: #2b2623;
4512
5670
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
4513
5671
  }
5672
+ /* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
5673
+ .veo-step-hover {
5674
+ position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
5675
+ display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
5676
+ opacity: 0; transition: opacity 120ms ease-out;
5677
+ }
5678
+ .veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
5679
+ .veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
5680
+ .veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
5681
+ .veo-step-icon {
5682
+ border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
5683
+ width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
5684
+ }
5685
+ .veo-step-icon:hover { background: rgba(255,255,255,0.14); }
5686
+ .veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
5687
+ .veo-step-edit {
5688
+ width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
5689
+ border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
5690
+ }
5691
+ .veo-step-edit:hover { background: #cbe4f9; }
4514
5692
  .veo-step.is-active .veo-step-box { border-color: #b74d00; }
4515
5693
  .veo-step-label { font-size: 13px; color: #6b635d; }
4516
5694
  .veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
@@ -4548,6 +5726,8 @@ function injectBuilderPanel(opts) {
4548
5726
  },
4549
5727
  isVisible: () => false,
4550
5728
  setTopOffset: () => {
5729
+ },
5730
+ setSize: () => {
4551
5731
  }
4552
5732
  };
4553
5733
  }
@@ -4600,6 +5780,21 @@ function injectBuilderPanel(opts) {
4600
5780
  card.style.display = next && !(popped && !popped.closed) ? "" : "none";
4601
5781
  };
4602
5782
  minimize.addEventListener("click", () => setVisible(false));
5783
+ let compactRestore = null;
5784
+ const setSize = (size) => {
5785
+ if (maximized || popped && !popped.closed) return;
5786
+ if (size) {
5787
+ if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
5788
+ card.style.minHeight = "0";
5789
+ card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
5790
+ card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
5791
+ } else if (compactRestore) {
5792
+ card.style.minHeight = "";
5793
+ card.style.width = `${compactRestore.w}px`;
5794
+ card.style.height = `${compactRestore.h}px`;
5795
+ compactRestore = null;
5796
+ }
5797
+ };
4603
5798
  maximize.addEventListener("click", () => {
4604
5799
  maximized = !maximized;
4605
5800
  if (maximized) {
@@ -4699,6 +5894,7 @@ function injectBuilderPanel(opts) {
4699
5894
  title.textContent = t;
4700
5895
  },
4701
5896
  setVisible,
5897
+ setSize,
4702
5898
  isVisible: () => visible,
4703
5899
  setTopOffset: (px) => {
4704
5900
  topOffset = px;
@@ -5284,6 +6480,9 @@ function initBuilderMode() {
5284
6480
  let staticTarget = null;
5285
6481
  let manipulator = null;
5286
6482
  let dropTarget = null;
6483
+ let cardClick = null;
6484
+ let rawBlock = null;
6485
+ let selectedBlockId = null;
5287
6486
  let dragGhost = null;
5288
6487
  let previewStepIndex = 0;
5289
6488
  let pendingPreview = null;
@@ -5304,13 +6503,742 @@ function initBuilderMode() {
5304
6503
  };
5305
6504
  const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
5306
6505
  const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
5307
- const withholdPreview = () => {
6506
+ const detachPreviewTools = () => {
5308
6507
  manipulator?.detach();
5309
6508
  manipulator = null;
5310
6509
  dropTarget?.detach();
5311
6510
  dropTarget = null;
6511
+ cardClick?.();
6512
+ cardClick = null;
6513
+ rowHover?.();
6514
+ rowHover = null;
6515
+ hoverRowId = null;
6516
+ hoverCard = false;
6517
+ rawBlock?.detach();
6518
+ rawBlock = null;
6519
+ shownPreview = null;
6520
+ };
6521
+ const withholdPreview = () => {
6522
+ detachPreviewTools();
6523
+ shownPreview = null;
5312
6524
  closeGuidePreview();
5313
6525
  };
6526
+ const FRAME_PAD = 4;
6527
+ const FRAME_MIN_H = 0;
6528
+ const TOOLBAR_H = 30;
6529
+ const ICON = {
6530
+ 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>',
6531
+ 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>',
6532
+ 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>'
6533
+ };
6534
+ const ICON_MOVE = {
6535
+ 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>',
6536
+ 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>'
6537
+ };
6538
+ const moveAbility = (blocks, blockId) => {
6539
+ for (const row of groupLayout(blocks)) {
6540
+ for (let c = 0; c < row.columns.length; c++) {
6541
+ const col = row.columns[c];
6542
+ if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
6543
+ return { left: c > 0, right: c < row.columns.length - 1 };
6544
+ }
6545
+ }
6546
+ return { left: false, right: false };
6547
+ };
6548
+ let previewBlocks = [];
6549
+ let hoverRowId = null;
6550
+ let selectedRowId = null;
6551
+ let rowHover = null;
6552
+ let hoverCard = false;
6553
+ let panelSection = null;
6554
+ let shownPreview = null;
6555
+ const structureOf = (step) => {
6556
+ if (!step) return "";
6557
+ const { blocks, ...rest } = step;
6558
+ return JSON.stringify({
6559
+ rest,
6560
+ blocks: (blocks ?? []).map((b) => {
6561
+ const { text: _t, html: _h, style: _s, ...keep } = b;
6562
+ return keep;
6563
+ })
6564
+ });
6565
+ };
6566
+ const tryPatchPreview = (cmd) => {
6567
+ const prev = shownPreview;
6568
+ if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
6569
+ return false;
6570
+ }
6571
+ const idx = cmd.guide.startStepIndex ?? 0;
6572
+ if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
6573
+ const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
6574
+ if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
6575
+ return false;
6576
+ const host = document.querySelector("[data-veo-guide]");
6577
+ const card = host ? previewCard(host) : null;
6578
+ if (!host || !card) return false;
6579
+ const prevStep = prev.guide.guideSteps[idx];
6580
+ const nextStep = cmd.guide.guideSteps[idx];
6581
+ if (!prevStep || !nextStep) return false;
6582
+ if (structureOf(prevStep) !== structureOf(nextStep)) return false;
6583
+ const doc = card.ownerDocument;
6584
+ const prevBlocks = prevStep.blocks ?? [];
6585
+ const nextBlocks = nextStep.blocks ?? [];
6586
+ for (let i = 0; i < nextBlocks.length; i++) {
6587
+ const a = prevBlocks[i];
6588
+ const b = nextBlocks[i];
6589
+ if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
6590
+ continue;
6591
+ }
6592
+ if (!b.editorId) return false;
6593
+ const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
6594
+ if (!node) return false;
6595
+ if (b.type === "button") {
6596
+ node.textContent = b.text?.trim() || "OK";
6597
+ node.removeAttribute("style");
6598
+ node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
6599
+ const st = b.style ?? {};
6600
+ if (st.preset === "secondary" || st.preset === "link") {
6601
+ node.classList.add(`veo-guide-cta--${st.preset}`);
6602
+ }
6603
+ if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
6604
+ node.setAttribute("aria-label", st.ariaLabel.trim());
6605
+ } else node.removeAttribute("aria-label");
6606
+ applyBlockStyle(node, b.style, "button");
6607
+ continue;
6608
+ }
6609
+ if (b.type === "code" || b.type === "form") return false;
6610
+ const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
6611
+ const el2 = doc.createElement("div");
6612
+ el2.className = "veo-block-placeholder";
6613
+ el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
6614
+ return el2;
6615
+ })();
6616
+ fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
6617
+ fresh.dataset.veoBlockId = b.editorId;
6618
+ node.replaceWith(fresh);
6619
+ }
6620
+ shownPreview = cmd;
6621
+ previewBlocks = nextBlocks;
6622
+ applyBlockHighlight(host, idx);
6623
+ renderGapZones(host, idx, nextBlocks);
6624
+ return true;
6625
+ };
6626
+ const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
6627
+ const clearBlockHighlight = (host) => {
6628
+ const card = previewCard(host);
6629
+ if (!card) return;
6630
+ for (const el2 of card.querySelectorAll(".veo-block--selected")) {
6631
+ el2.classList.remove("veo-block--selected");
6632
+ }
6633
+ card.querySelector(".veo-block-toolbar")?.remove();
6634
+ card.querySelector(".veo-block-frame")?.remove();
6635
+ };
6636
+ const applyBlockHighlight = (host, stepIndex) => {
6637
+ clearBlockHighlight(host);
6638
+ const card = previewCard(host);
6639
+ if (!card) return;
6640
+ const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
6641
+ if (!el2 || !selectedBlockId) {
6642
+ renderRowChrome(host, stepIndex);
6643
+ return;
6644
+ }
6645
+ el2.classList.add("veo-block--selected");
6646
+ const doc = card.ownerDocument;
6647
+ const cs = getComputedStyle(card);
6648
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
6649
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
6650
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
6651
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
6652
+ const parent = el2.parentElement;
6653
+ const inCol = parent?.classList.contains("veo-col") ? parent : null;
6654
+ const boxL = inCol ? inCol.offsetLeft : padL;
6655
+ const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
6656
+ const boxT = padT;
6657
+ const boxB = card.clientHeight - padB;
6658
+ const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
6659
+ const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
6660
+ const fw = Math.max(0, fr - fx);
6661
+ const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
6662
+ const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
6663
+ const fh = Math.max(FRAME_MIN_H, fb - fy);
6664
+ const frame = doc.createElement("div");
6665
+ frame.className = "veo-block-frame";
6666
+ frame.setAttribute("data-veo-builder-chrome", "");
6667
+ frame.style.left = `${fx}px`;
6668
+ frame.style.top = `${fy}px`;
6669
+ frame.style.width = `${fw}px`;
6670
+ frame.style.height = `${fh}px`;
6671
+ card.appendChild(frame);
6672
+ const bar = doc.createElement("div");
6673
+ bar.className = "veo-block-toolbar";
6674
+ bar.setAttribute("data-veo-builder-chrome", "");
6675
+ const blockId = selectedBlockId;
6676
+ const mk = (action, label, icon, enabled = true) => {
6677
+ const b = doc.createElement("button");
6678
+ b.type = "button";
6679
+ b.title = label;
6680
+ b.setAttribute("aria-label", label);
6681
+ b.className = `veo-block-toolbar-${action}`;
6682
+ b.innerHTML = icon;
6683
+ b.disabled = !enabled;
6684
+ b.addEventListener("click", (e) => {
6685
+ e.preventDefault();
6686
+ e.stopPropagation();
6687
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
6688
+ });
6689
+ bar.appendChild(b);
6690
+ };
6691
+ const can = moveAbility(previewBlocks, blockId);
6692
+ mk("edit", "Editar", ICON.pencil);
6693
+ mk("duplicate", "Duplicar", ICON.copy);
6694
+ mk("remove", "Eliminar", ICON.trash);
6695
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
6696
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
6697
+ bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
6698
+ card.appendChild(bar);
6699
+ const barH = bar.offsetHeight || TOOLBAR_H;
6700
+ bar.dataset.veoTopAbove = String(fy - barH - 6);
6701
+ bar.dataset.veoTopBelow = String(fb + 6);
6702
+ bar.style.top = bar.dataset.veoTopAbove;
6703
+ renderRowChrome(host, stepIndex);
6704
+ };
6705
+ const chromeRect = (el2) => ({
6706
+ left: el2.offsetLeft,
6707
+ top: el2.offsetTop,
6708
+ right: el2.offsetLeft + el2.offsetWidth,
6709
+ bottom: el2.offsetTop + el2.offsetHeight
6710
+ });
6711
+ const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
6712
+ const resolveToolbarOverlaps = (card) => {
6713
+ const cardBar = card.querySelector(".veo-card-toolbar");
6714
+ const blockBar = card.querySelector(".veo-block-toolbar");
6715
+ const rowBar = card.querySelector(".veo-row-toolbar");
6716
+ if (blockBar?.dataset.veoTopAbove) {
6717
+ blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
6718
+ if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
6719
+ blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
6720
+ }
6721
+ }
6722
+ if (rowBar?.dataset.veoRight) {
6723
+ rowBar.style.left = "";
6724
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
6725
+ rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
6726
+ const others = [cardBar, blockBar].filter((b) => !!b);
6727
+ const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
6728
+ if (clashes()) {
6729
+ rowBar.style.right = "";
6730
+ rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
6731
+ }
6732
+ if (clashes() && blockBar) {
6733
+ rowBar.style.left = "";
6734
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
6735
+ rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
6736
+ }
6737
+ }
6738
+ };
6739
+ const rectOf = (el2) => ({
6740
+ left: el2.offsetLeft,
6741
+ top: el2.offsetTop,
6742
+ right: el2.offsetLeft + el2.offsetWidth,
6743
+ bottom: el2.offsetTop + el2.offsetHeight
6744
+ });
6745
+ const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
6746
+ left: Math.min(acc.left, r.left),
6747
+ top: Math.min(acc.top, r.top),
6748
+ right: Math.max(acc.right, r.right),
6749
+ bottom: Math.max(acc.bottom, r.bottom)
6750
+ }));
6751
+ const layoutRows = (card, blocks) => {
6752
+ const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
6753
+ const cs = getComputedStyle(card);
6754
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
6755
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
6756
+ const contentLeft = padL;
6757
+ const contentRight = card.clientWidth - padR;
6758
+ const rows = groupLayout(blocks).map((row) => {
6759
+ const columns = row.columns.map((col) => {
6760
+ const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
6761
+ (x) => !!x.el && !!x.block.editorId
6762
+ );
6763
+ const blocksRect2 = union(items.map((x) => rectOf(x.el)));
6764
+ if (!blocksRect2) return null;
6765
+ const colEl = items[0]?.el.parentElement;
6766
+ const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
6767
+ return { items, rect: rect2 };
6768
+ }).filter((c) => c !== null);
6769
+ const blocksRect = union(columns.map((c) => c.rect));
6770
+ if (!blocksRect) return null;
6771
+ const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
6772
+ const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
6773
+ const id = columns[0]?.items[0]?.block.editorId;
6774
+ return { id, row: row.row, columns, rect };
6775
+ }).filter((r) => r !== null);
6776
+ return { rows, content: { left: contentLeft, right: contentRight } };
6777
+ };
6778
+ const clearRowChrome = (card) => {
6779
+ card.querySelector(".veo-row-frame")?.remove();
6780
+ card.querySelector(".veo-row-toolbar")?.remove();
6781
+ };
6782
+ const renderRowChrome = (host, stepIndex) => {
6783
+ const card = previewCard(host);
6784
+ if (!card) return;
6785
+ clearRowChrome(card);
6786
+ resolveToolbarOverlaps(card);
6787
+ const { rows } = layoutRows(card, previewBlocks);
6788
+ const rowOfSelected = selectedBlockId ? rows.find(
6789
+ (r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
6790
+ ) : void 0;
6791
+ const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
6792
+ if (!target) return;
6793
+ const doc = card.ownerDocument;
6794
+ const cs = getComputedStyle(card);
6795
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
6796
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
6797
+ const PAD = 6;
6798
+ const fx = Math.max(0, target.rect.left - PAD);
6799
+ const fr = Math.min(card.clientWidth, target.rect.right + PAD);
6800
+ const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
6801
+ const fb = Math.min(
6802
+ Math.max(card.clientHeight - padB, target.rect.bottom),
6803
+ target.rect.bottom + PAD
6804
+ );
6805
+ const frame = doc.createElement("div");
6806
+ frame.className = "veo-row-frame";
6807
+ frame.setAttribute("data-veo-builder-chrome", "");
6808
+ frame.style.left = `${fx}px`;
6809
+ frame.style.top = `${fy}px`;
6810
+ frame.style.width = `${Math.max(0, fr - fx)}px`;
6811
+ frame.style.height = `${Math.max(0, fb - fy)}px`;
6812
+ card.appendChild(frame);
6813
+ const bar = doc.createElement("div");
6814
+ bar.className = "veo-row-toolbar";
6815
+ bar.setAttribute("data-veo-builder-chrome", "");
6816
+ const blockId = target.id;
6817
+ const mk = (action, label, icon, text) => {
6818
+ const b = doc.createElement("button");
6819
+ b.type = "button";
6820
+ b.title = label;
6821
+ b.setAttribute("aria-label", label);
6822
+ b.className = `veo-row-toolbar-${action}`;
6823
+ b.innerHTML = icon;
6824
+ if (text) b.append(text);
6825
+ b.addEventListener("click", (e) => {
6826
+ e.preventDefault();
6827
+ e.stopPropagation();
6828
+ post({ type: "row-action", payload: { stepIndex, blockId, action } });
6829
+ });
6830
+ bar.appendChild(b);
6831
+ };
6832
+ mk("edit", "Editar fila", ICON.pencil, "Editar fila");
6833
+ mk("duplicate", "Duplicar fila", ICON.copy);
6834
+ mk("remove", "Eliminar fila", ICON.trash);
6835
+ card.appendChild(bar);
6836
+ bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
6837
+ bar.dataset.veoLeft = String(fx);
6838
+ bar.dataset.veoTopBelow = String(fb + 6);
6839
+ resolveToolbarOverlaps(card);
6840
+ };
6841
+ const renderCardChrome = (host, stepIndex) => {
6842
+ const card = previewCard(host);
6843
+ if (!card) return;
6844
+ card.querySelector(".veo-card-frame")?.remove();
6845
+ card.querySelector(".veo-card-toolbar")?.remove();
6846
+ if (!hoverCard) {
6847
+ resolveToolbarOverlaps(card);
6848
+ return;
6849
+ }
6850
+ const doc = card.ownerDocument;
6851
+ const frame = doc.createElement("div");
6852
+ frame.className = "veo-card-frame";
6853
+ frame.setAttribute("data-veo-builder-chrome", "");
6854
+ card.appendChild(frame);
6855
+ const bar = doc.createElement("div");
6856
+ bar.className = "veo-card-toolbar";
6857
+ bar.setAttribute("data-veo-builder-chrome", "");
6858
+ const mk = (action, label, active2) => {
6859
+ const b = doc.createElement("button");
6860
+ b.type = "button";
6861
+ b.className = `veo-card-toolbar-${action}${active2 ? " is-active" : ""}`;
6862
+ b.setAttribute("aria-pressed", active2 ? "true" : "false");
6863
+ b.textContent = label;
6864
+ b.addEventListener("click", (e) => {
6865
+ e.preventDefault();
6866
+ e.stopPropagation();
6867
+ post({ type: "card-action", payload: { stepIndex, action } });
6868
+ });
6869
+ bar.appendChild(b);
6870
+ };
6871
+ mk("edit-container", "Editar contenedor", panelSection === "step");
6872
+ mk("edit-rows", "Editar filas", panelSection === "structure");
6873
+ card.appendChild(bar);
6874
+ bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
6875
+ resolveToolbarOverlaps(card);
6876
+ };
6877
+ const attachRowHover = (host, stepIndex) => {
6878
+ const onMove = (e) => {
6879
+ const card = previewCard(host);
6880
+ if (!card) return;
6881
+ const overCard = e.composedPath().some(
6882
+ (n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
6883
+ );
6884
+ if (overCard !== hoverCard) {
6885
+ hoverCard = overCard;
6886
+ renderCardChrome(host, stepIndex);
6887
+ }
6888
+ if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
6889
+ return;
6890
+ }
6891
+ const rect = card.getBoundingClientRect();
6892
+ const x = e.clientX - rect.left - card.clientLeft;
6893
+ const y = e.clientY - rect.top - card.clientTop;
6894
+ const bar = card.querySelector(".veo-row-toolbar");
6895
+ if (bar) {
6896
+ const AIR = 10;
6897
+ if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
6898
+ return;
6899
+ }
6900
+ }
6901
+ const TOL = 6;
6902
+ const hit = layoutRows(card, previewBlocks).rows.find(
6903
+ (r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
6904
+ );
6905
+ const next = hit?.id ?? null;
6906
+ if (next === hoverRowId) return;
6907
+ hoverRowId = next;
6908
+ renderRowChrome(host, stepIndex);
6909
+ };
6910
+ const onLeave = () => {
6911
+ if (hoverCard) {
6912
+ hoverCard = false;
6913
+ renderCardChrome(host, stepIndex);
6914
+ }
6915
+ if (hoverRowId === null) return;
6916
+ hoverRowId = null;
6917
+ renderRowChrome(host, stepIndex);
6918
+ };
6919
+ host.addEventListener("mousemove", onMove);
6920
+ host.addEventListener("mouseleave", onLeave);
6921
+ return () => {
6922
+ host.removeEventListener("mousemove", onMove);
6923
+ host.removeEventListener("mouseleave", onLeave);
6924
+ };
6925
+ };
6926
+ const renderGapZones = (host, stepIndex, blocks) => {
6927
+ const card = previewCard(host);
6928
+ if (!card) return;
6929
+ for (const z of card.querySelectorAll(".veo-gap")) z.remove();
6930
+ const doc = card.ownerDocument;
6931
+ const GAP = 18;
6932
+ const EDGE2 = 4;
6933
+ const zone = (kind, x, y, w, h, label, blockId, action) => {
6934
+ const z = doc.createElement("div");
6935
+ z.className = `veo-gap veo-gap--${kind}`;
6936
+ z.setAttribute("data-veo-builder-chrome", "");
6937
+ z.dataset.veoGapAction = action;
6938
+ z.style.left = `${x}px`;
6939
+ z.style.top = `${y}px`;
6940
+ z.style.width = `${Math.max(w, GAP)}px`;
6941
+ z.style.height = `${Math.max(h, GAP)}px`;
6942
+ const line = doc.createElement("i");
6943
+ const btn = doc.createElement("button");
6944
+ btn.type = "button";
6945
+ btn.title = label;
6946
+ btn.setAttribute("aria-label", label);
6947
+ btn.textContent = "+";
6948
+ btn.addEventListener("click", (e) => {
6949
+ e.preventDefault();
6950
+ e.stopPropagation();
6951
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
6952
+ });
6953
+ z.append(line, btn);
6954
+ card.appendChild(z);
6955
+ };
6956
+ const { rows, content } = layoutRows(card, blocks);
6957
+ if (rows.length === 0) return;
6958
+ const cw = content.right - content.left;
6959
+ rows.forEach((row, k) => {
6960
+ const first = row.columns[0]?.items[0]?.block.editorId;
6961
+ if (k === 0) {
6962
+ zone(
6963
+ "h",
6964
+ content.left,
6965
+ row.rect.top - EDGE2 - GAP,
6966
+ cw,
6967
+ GAP,
6968
+ "Agregar fila",
6969
+ first,
6970
+ "row-before"
6971
+ );
6972
+ } else {
6973
+ const prev = rows[k - 1];
6974
+ const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
6975
+ zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
6976
+ }
6977
+ if (k === rows.length - 1) {
6978
+ zone(
6979
+ "h",
6980
+ content.left,
6981
+ row.rect.bottom + EDGE2,
6982
+ cw,
6983
+ GAP,
6984
+ "Agregar fila",
6985
+ first,
6986
+ "row-after"
6987
+ );
6988
+ }
6989
+ });
6990
+ for (const row of rows) {
6991
+ const rh = row.rect.bottom - row.rect.top;
6992
+ row.columns.forEach((col, j) => {
6993
+ const id = col.items[0]?.block.editorId;
6994
+ if (j === 0) {
6995
+ zone(
6996
+ "v",
6997
+ row.rect.left - EDGE2 - GAP,
6998
+ row.rect.top,
6999
+ GAP,
7000
+ rh,
7001
+ "Agregar columna",
7002
+ id,
7003
+ "col-before"
7004
+ );
7005
+ } else {
7006
+ const prev = row.columns[j - 1];
7007
+ const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
7008
+ zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
7009
+ }
7010
+ if (j === row.columns.length - 1) {
7011
+ zone(
7012
+ "v",
7013
+ row.rect.right + EDGE2,
7014
+ row.rect.top,
7015
+ GAP,
7016
+ rh,
7017
+ "Agregar columna",
7018
+ id,
7019
+ "col-after"
7020
+ );
7021
+ }
7022
+ });
7023
+ }
7024
+ for (const row of rows) {
7025
+ for (const col of row.columns) {
7026
+ const w = col.rect.right - col.rect.left;
7027
+ col.items.forEach((it, i) => {
7028
+ const next = col.items[i + 1];
7029
+ if (next) {
7030
+ const a = rectOf(it.el);
7031
+ const b = rectOf(next.el);
7032
+ const y = (a.bottom + b.top) / 2 - GAP / 2;
7033
+ zone(
7034
+ "h",
7035
+ col.rect.left,
7036
+ y,
7037
+ w,
7038
+ GAP,
7039
+ "Agregar bloque",
7040
+ it.block.editorId,
7041
+ "stack-after"
7042
+ );
7043
+ } else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
7044
+ zone(
7045
+ "h",
7046
+ col.rect.left,
7047
+ col.rect.bottom + EDGE2,
7048
+ w,
7049
+ GAP,
7050
+ "Agregar bloque",
7051
+ it.block.editorId,
7052
+ "stack-after"
7053
+ );
7054
+ }
7055
+ });
7056
+ }
7057
+ }
7058
+ };
7059
+ const attachCardClick = (host, stepIndex) => {
7060
+ let downAt = null;
7061
+ const onDown = (e) => {
7062
+ downAt = { x: e.clientX, y: e.clientY };
7063
+ };
7064
+ const onClick = (e) => {
7065
+ const path = e.composedPath();
7066
+ if (path.some(
7067
+ (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"))
7068
+ )) {
7069
+ return;
7070
+ }
7071
+ const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
7072
+ if (!inCard) return;
7073
+ if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
7074
+ const blockEl = path.find(
7075
+ (n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
7076
+ );
7077
+ if (blockEl) {
7078
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
7079
+ e.preventDefault();
7080
+ e.stopPropagation();
7081
+ }
7082
+ selectedBlockId = blockEl.dataset.veoBlockId ?? null;
7083
+ applyBlockHighlight(host, stepIndex);
7084
+ if (selectedBlockId) {
7085
+ post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
7086
+ }
7087
+ return;
7088
+ }
7089
+ const interactive = path.some(
7090
+ (n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
7091
+ );
7092
+ if (interactive) return;
7093
+ selectedBlockId = null;
7094
+ clearBlockHighlight(host);
7095
+ renderRowChrome(host, stepIndex);
7096
+ post({ type: "card-clicked", payload: { stepIndex } });
7097
+ };
7098
+ host.addEventListener("mousedown", onDown, true);
7099
+ host.addEventListener("click", onClick, true);
7100
+ return () => {
7101
+ host.removeEventListener("mousedown", onDown, true);
7102
+ host.removeEventListener("click", onClick, true);
7103
+ };
7104
+ };
7105
+ const attachRawBlock = (stepIndex, blockId) => {
7106
+ const nodes = Array.from(
7107
+ document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
7108
+ );
7109
+ if (nodes.length === 0) return null;
7110
+ const doc = document;
7111
+ const box = doc.createElement("div");
7112
+ box.setAttribute("data-veo-builder-chrome", "");
7113
+ box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
7114
+ box.style.display = "none";
7115
+ const shadow = box.attachShadow({ mode: "open" });
7116
+ const style = doc.createElement("style");
7117
+ style.textContent = GUIDE_STYLES;
7118
+ shadow.appendChild(style);
7119
+ const frame = doc.createElement("div");
7120
+ frame.className = "veo-block-frame";
7121
+ frame.style.position = "fixed";
7122
+ const bar = doc.createElement("div");
7123
+ bar.className = "veo-block-toolbar";
7124
+ bar.style.position = "fixed";
7125
+ const mk = (action, label, icon, enabled = true) => {
7126
+ const b = doc.createElement("button");
7127
+ b.type = "button";
7128
+ b.title = label;
7129
+ b.setAttribute("aria-label", label);
7130
+ b.className = `veo-block-toolbar-${action}`;
7131
+ b.innerHTML = icon;
7132
+ b.disabled = !enabled;
7133
+ b.addEventListener("click", (e) => {
7134
+ e.preventDefault();
7135
+ e.stopPropagation();
7136
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
7137
+ });
7138
+ bar.appendChild(b);
7139
+ };
7140
+ mk("edit", "Editar", ICON.pencil);
7141
+ mk("duplicate", "Duplicar", ICON.copy, false);
7142
+ mk("remove", "Eliminar", ICON.trash);
7143
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
7144
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
7145
+ frame.style.display = "none";
7146
+ bar.style.display = "none";
7147
+ const GAP = 18;
7148
+ const EDGE2 = 4;
7149
+ const gap = (kind, label, action) => {
7150
+ const z = doc.createElement("div");
7151
+ z.className = `veo-gap veo-gap--${kind}`;
7152
+ z.style.position = "fixed";
7153
+ z.dataset.veoGapAction = action;
7154
+ const line = doc.createElement("i");
7155
+ const btn = doc.createElement("button");
7156
+ btn.type = "button";
7157
+ btn.title = label;
7158
+ btn.setAttribute("aria-label", label);
7159
+ btn.textContent = "+";
7160
+ btn.addEventListener("click", (e) => {
7161
+ e.preventDefault();
7162
+ e.stopPropagation();
7163
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
7164
+ });
7165
+ z.append(line, btn);
7166
+ return z;
7167
+ };
7168
+ const gaps = {
7169
+ top: gap("h", "Agregar fila", "row-before"),
7170
+ bottom: gap("h", "Agregar fila", "row-after"),
7171
+ left: gap("v", "Agregar columna", "col-before"),
7172
+ right: gap("v", "Agregar columna", "col-after")
7173
+ };
7174
+ shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
7175
+ doc.body.appendChild(box);
7176
+ box.style.display = "";
7177
+ let visible = false;
7178
+ const place = (el2, x, y, w, h) => {
7179
+ el2.style.left = `${x}px`;
7180
+ el2.style.top = `${y}px`;
7181
+ el2.style.width = `${w}px`;
7182
+ el2.style.height = `${h}px`;
7183
+ };
7184
+ const reposition = () => {
7185
+ const live = nodes.filter((n) => n.isConnected);
7186
+ if (live.length === 0) return;
7187
+ let l = Number.POSITIVE_INFINITY;
7188
+ let t = Number.POSITIVE_INFINITY;
7189
+ let r = Number.NEGATIVE_INFINITY;
7190
+ let b = Number.NEGATIVE_INFINITY;
7191
+ for (const n of live) {
7192
+ const rc = n.getBoundingClientRect();
7193
+ if (rc.width === 0 && rc.height === 0) continue;
7194
+ l = Math.min(l, rc.left);
7195
+ t = Math.min(t, rc.top);
7196
+ r = Math.max(r, rc.right);
7197
+ b = Math.max(b, rc.bottom);
7198
+ }
7199
+ if (!Number.isFinite(l)) return;
7200
+ place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
7201
+ place(gaps.bottom, l, b + EDGE2, r - l, GAP);
7202
+ place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
7203
+ place(gaps.right, r + EDGE2, t, GAP, b - t);
7204
+ if (!visible) return;
7205
+ place(frame, l, t, r - l, b - t);
7206
+ const bh = bar.offsetHeight || TOOLBAR_H;
7207
+ const bw = bar.offsetWidth;
7208
+ bar.style.left = `${Math.max(0, r - bw)}px`;
7209
+ bar.style.top = `${Math.max(0, t - bh - 6)}px`;
7210
+ };
7211
+ const select = (id) => {
7212
+ visible = id === blockId;
7213
+ frame.style.display = visible ? "" : "none";
7214
+ bar.style.display = visible ? "" : "none";
7215
+ reposition();
7216
+ };
7217
+ reposition();
7218
+ const onClick = (e) => {
7219
+ const path = e.composedPath();
7220
+ if (!nodes.some((n) => path.includes(n))) return;
7221
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
7222
+ e.preventDefault();
7223
+ e.stopPropagation();
7224
+ }
7225
+ selectedBlockId = blockId;
7226
+ select(blockId);
7227
+ post({ type: "block-clicked", payload: { stepIndex, blockId } });
7228
+ };
7229
+ doc.addEventListener("click", onClick, true);
7230
+ window.addEventListener("scroll", reposition, true);
7231
+ window.addEventListener("resize", reposition);
7232
+ return {
7233
+ select,
7234
+ detach: () => {
7235
+ doc.removeEventListener("click", onClick, true);
7236
+ window.removeEventListener("scroll", reposition, true);
7237
+ window.removeEventListener("resize", reposition);
7238
+ box.remove();
7239
+ }
7240
+ };
7241
+ };
5314
7242
  const applyPreviewGated = (cmd) => {
5315
7243
  const showNow = !cmd.honorTrigger;
5316
7244
  if (showNow) {
@@ -5330,7 +7258,7 @@ function initBuilderMode() {
5330
7258
  return;
5331
7259
  }
5332
7260
  const devices = cmd.guide.activationRules?.devices;
5333
- if (!showNow && !previewMode && devices && !isDeviceAllowed(devices)) {
7261
+ if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
5334
7262
  cancelPreviewDelay();
5335
7263
  withholdPreview();
5336
7264
  postPreviewStatus({ state: "device-mismatch", devices });
@@ -5382,15 +7310,16 @@ function initBuilderMode() {
5382
7310
  postPreviewStatus({ state: "waiting-delay", delayMs });
5383
7311
  };
5384
7312
  const applyPreview = (cmd) => {
5385
- manipulator?.detach();
5386
- manipulator = null;
5387
- dropTarget?.detach();
5388
- dropTarget = null;
7313
+ if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
7314
+ previewStepIndex = cmd.guide.startStepIndex ?? 0;
7315
+ return;
7316
+ }
7317
+ detachPreviewTools();
5389
7318
  previewStepIndex = cmd.guide.startStepIndex ?? 0;
5390
7319
  const rules = cmd.guide.activationRules;
5391
7320
  const trig = rules?.trigger;
5392
7321
  const sel = rules?.selector?.trim();
5393
- const gate = cmd.honorTrigger && !previewFired && !previewMode;
7322
+ const gate = cmd.honorTrigger && !previewFired && previewMode;
5394
7323
  const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
5395
7324
  if (gate && waitsForTarget && sel) {
5396
7325
  cancelPreviewDelay();
@@ -5407,13 +7336,13 @@ function initBuilderMode() {
5407
7336
  }
5408
7337
  disarmPreviewTrigger();
5409
7338
  cancelPreviewDelay();
7339
+ const fetchProgress = previewProgressFetcher();
5410
7340
  const handle = previewGuide({
5411
7341
  ...cmd.guide,
7342
+ ...fetchProgress ? { fetchProgress } : {},
5412
7343
  onClosed: () => {
5413
- manipulator?.detach();
5414
- manipulator = null;
5415
- dropTarget?.detach();
5416
- dropTarget = null;
7344
+ detachPreviewTools();
7345
+ shownPreview = null;
5417
7346
  previewFired = false;
5418
7347
  previewDismissed = true;
5419
7348
  if (previewMode) {
@@ -5421,9 +7350,7 @@ function initBuilderMode() {
5421
7350
  const t = r?.trigger;
5422
7351
  const selector = r?.selector?.trim();
5423
7352
  if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
5424
- return;
5425
7353
  }
5426
- if (lastPreview) applyPreviewGated(lastPreview);
5427
7354
  }
5428
7355
  });
5429
7356
  if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
@@ -5431,7 +7358,23 @@ function initBuilderMode() {
5431
7358
  void handle.ready.then((result) => {
5432
7359
  if (!result.rendered) return;
5433
7360
  const host = handle.host();
5434
- if (!host) return;
7361
+ if (!host) {
7362
+ const idx = cmd.guide.startStepIndex ?? 0;
7363
+ const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
7364
+ if (only?.type === "code" && only.editorId) {
7365
+ rawBlock = attachRawBlock(idx, only.editorId);
7366
+ rawBlock?.select(selectedBlockId);
7367
+ }
7368
+ return;
7369
+ }
7370
+ host.classList.add("veo-no-anim");
7371
+ shownPreview = cmd;
7372
+ cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
7373
+ const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
7374
+ previewBlocks = stepBlocks;
7375
+ applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
7376
+ renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
7377
+ rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
5435
7378
  manipulator = attachDesignManipulator({
5436
7379
  host,
5437
7380
  guideType: cmd.guide.guideType,
@@ -5499,13 +7442,21 @@ function initBuilderMode() {
5499
7442
  break;
5500
7443
  case "editor-state":
5501
7444
  chrome?.setState(cmd.state);
7445
+ if (panelSection !== cmd.state.section) {
7446
+ panelSection = cmd.state.section;
7447
+ const h = document.querySelector("[data-veo-guide]");
7448
+ if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7449
+ }
5502
7450
  panel?.setTitle(
5503
- 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"
7451
+ cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso"
5504
7452
  );
5505
7453
  break;
5506
7454
  case "panel-visibility":
5507
7455
  panel?.setVisible(cmd.visible);
5508
7456
  break;
7457
+ case "panel-size":
7458
+ panel?.setSize(cmd.size);
7459
+ break;
5509
7460
  case "start-picker": {
5510
7461
  picker?.stop();
5511
7462
  const panelWasShown = panel?.isVisible() ?? false;
@@ -5513,10 +7464,7 @@ function initBuilderMode() {
5513
7464
  chrome?.setHidden(true);
5514
7465
  const previewToRestore = lastPreview;
5515
7466
  if (previewToRestore) {
5516
- manipulator?.detach();
5517
- manipulator = null;
5518
- dropTarget?.detach();
5519
- dropTarget = null;
7467
+ detachPreviewTools();
5520
7468
  closeGuidePreview();
5521
7469
  }
5522
7470
  const restorePanel = () => {
@@ -5555,11 +7503,21 @@ function initBuilderMode() {
5555
7503
  case "block-drag":
5556
7504
  handleBlockDrag(cmd);
5557
7505
  break;
7506
+ case "select-block": {
7507
+ selectedBlockId = cmd.blockId;
7508
+ const h = document.querySelector("[data-veo-guide]");
7509
+ if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
7510
+ if (!previewMode) rawBlock?.select(cmd.blockId);
7511
+ break;
7512
+ }
7513
+ case "select-row": {
7514
+ selectedRowId = cmd.blockId;
7515
+ const h = document.querySelector("[data-veo-guide]");
7516
+ if (h && !previewMode) renderRowChrome(h, previewStepIndex);
7517
+ break;
7518
+ }
5558
7519
  case "close-preview":
5559
- manipulator?.detach();
5560
- manipulator = null;
5561
- dropTarget?.detach();
5562
- dropTarget = null;
7520
+ detachPreviewTools();
5563
7521
  hideDragGhost();
5564
7522
  pendingPreview = null;
5565
7523
  lastPreview = null;
@@ -5612,10 +7570,7 @@ function initBuilderMode() {
5612
7570
  removeNavWatcher();
5613
7571
  picker?.stop();
5614
7572
  picker = null;
5615
- manipulator?.detach();
5616
- manipulator = null;
5617
- dropTarget?.detach();
5618
- dropTarget = null;
7573
+ detachPreviewTools();
5619
7574
  hideDragGhost();
5620
7575
  pendingPreview = null;
5621
7576
  disarmPreviewTrigger();
@@ -5643,6 +7598,8 @@ function initBuilderMode() {
5643
7598
  case "preview":
5644
7599
  if (previewMode) return;
5645
7600
  previewMode = true;
7601
+ previewFired = false;
7602
+ previewDismissed = false;
5646
7603
  panelWasVisible = panel?.isVisible() ?? true;
5647
7604
  panel?.setVisible(false);
5648
7605
  chrome?.setPreview(true);
@@ -5651,6 +7608,8 @@ function initBuilderMode() {
5651
7608
  case "exit-preview":
5652
7609
  if (!previewMode) return;
5653
7610
  previewMode = false;
7611
+ disarmPreviewTrigger();
7612
+ cancelPreviewDelay();
5654
7613
  chrome?.setPreview(false);
5655
7614
  panel?.setVisible(panelWasVisible);
5656
7615
  if (lastPreview) applyPreviewGated(lastPreview);
@@ -5732,13 +7691,36 @@ function resolveDashboardOrigin(originHint) {
5732
7691
  }
5733
7692
  return null;
5734
7693
  }
7694
+ function previewProgressFetcher() {
7695
+ const cfg = window.__veoBuilder;
7696
+ if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
7697
+ const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
7698
+ const apiKey = cfg.apiKey;
7699
+ return async (guideIds) => {
7700
+ const params = new URLSearchParams({
7701
+ endUserId: "veo_preview",
7702
+ guideIds: guideIds.join(","),
7703
+ includeUnpublished: "1"
7704
+ });
7705
+ const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
7706
+ headers: { "X-Api-Key": apiKey }
7707
+ });
7708
+ if (!res.ok) return [];
7709
+ const data = await res.json();
7710
+ return data.items ?? [];
7711
+ };
7712
+ }
5735
7713
  var init_builder_mode = __esm({
5736
7714
  "src/plugins/builder/builder-mode.ts"() {
5737
7715
  init_builder_token();
5738
7716
  init_safe_env();
5739
7717
  init_activation_devices();
5740
7718
  init_activation_rules();
7719
+ init_block_builder();
7720
+ init_code_block();
5741
7721
  init_guide_preview();
7722
+ init_layout();
7723
+ init_styles();
5742
7724
  init_url_match();
5743
7725
  init_block_drop_target();
5744
7726
  init_bridge_protocol();
@@ -7137,6 +9119,7 @@ init_inline_renderer();
7137
9119
  init_modal_renderer();
7138
9120
  init_tooltip_renderer();
7139
9121
  init_walkthrough_renderer();
9122
+ init_snooze();
7140
9123
  init_url_match();
7141
9124
 
7142
9125
  // src/plugins/guides/walkthrough-state.ts
@@ -7329,6 +9312,12 @@ var GuidesController = class {
7329
9312
  * en cada `checkGuides` con {@link pruneComposite}.
7330
9313
  */
7331
9314
  this.activeComposite = /* @__PURE__ */ new Map();
9315
+ /**
9316
+ * Progreso de esta sesión, por guía: pasos vistos y completado, según las
9317
+ * interacciones que ESTE SDK emitió (aunque el lote aún no haya viajado al
9318
+ * backend). Se mezcla con la respuesta del backend en `fetchProgress`.
9319
+ */
9320
+ this.sessionProgress = /* @__PURE__ */ new Map();
7332
9321
  this.debug = config.debug === true;
7333
9322
  this.apiUrl = config.apiUrl;
7334
9323
  this.apiKey = config.apiKey;
@@ -7415,6 +9404,10 @@ var GuidesController = class {
7415
9404
  if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
7416
9405
  return;
7417
9406
  }
9407
+ if (!opts.byUser && isSnoozed(guide.guideId)) {
9408
+ if (this.debug) console.log(`[veo] guides: snoozed guideId=${guide.guideId}`);
9409
+ return;
9410
+ }
7418
9411
  if (!opts.byUser && this.isHeldByRepeat(guide)) {
7419
9412
  if (this.debug) console.log(`[veo] guides: held by repeat guideId=${guide.guideId}`);
7420
9413
  return;
@@ -7690,6 +9683,17 @@ ${pageUrl}`;
7690
9683
  const goToStep = (target) => {
7691
9684
  cleanup();
7692
9685
  if (target < 0 || target >= steps.length) return;
9686
+ const endUserId = this.client.getEndUserId();
9687
+ if (endUserId) {
9688
+ this.enqueueInteraction(
9689
+ guide.guideId,
9690
+ endUserId,
9691
+ sessionId,
9692
+ pageUrl,
9693
+ target > idx ? "step_advanced" : "step_back",
9694
+ target
9695
+ );
9696
+ }
7693
9697
  const step = steps[target];
7694
9698
  if (step && stepMatchesPage(step.pageLocation, pageUrl)) {
7695
9699
  this.renderCompositeStep(guide, target, sessionId, pageUrl, badgeOpen);
@@ -7714,10 +9718,15 @@ ${pageUrl}`;
7714
9718
  onInteraction: (e) => this.handleInteraction(guide, sessionId, pageUrl, e),
7715
9719
  onClose: cleanup,
7716
9720
  onTrack: (name, props) => this.client.track(name, props),
7717
- onFormSubmit: (answers) => this.submitFormResponse(guide.guideId, answers),
9721
+ onFormSubmit: (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta),
7718
9722
  onOpenGuide: (targetGuideId) => {
7719
9723
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
7720
9724
  },
9725
+ // Lista de tareas: progreso del usuario y abrir una guía en su paso.
9726
+ fetchProgress: (ids) => this.fetchProgress(ids),
9727
+ onOpenGuideAt: (targetGuideId, stepIndex) => {
9728
+ void this.openGuideById(targetGuideId, sessionId, pageUrl, stepIndex);
9729
+ },
7721
9730
  // Badge: cuando el MOTOR muestra la guía (automático, elemento objetivo
7722
9731
  // o encadenada), el contenido se abre solo; el marcador queda para
7723
9732
  // volver a verla. Solo `manual` deja el tooltip cerrado.
@@ -7745,7 +9754,7 @@ ${pageUrl}`;
7745
9754
  const onTrack = (eventName, properties) => {
7746
9755
  this.client.track(eventName, properties);
7747
9756
  };
7748
- const onFormSubmit = (answers) => this.submitFormResponse(guide.guideId, answers);
9757
+ const onFormSubmit = (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta);
7749
9758
  const onOpenGuide = (targetGuideId) => {
7750
9759
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
7751
9760
  };
@@ -7765,7 +9774,7 @@ ${pageUrl}`;
7765
9774
  * un botón); respeta el dedupe de guías activas y el flujo normal de
7766
9775
  * tracking. 404/red → no-op (la guía destino pudo borrarse).
7767
9776
  */
7768
- async openGuideById(guideId, sessionId, pageUrl) {
9777
+ async openGuideById(guideId, sessionId, pageUrl, startStep) {
7769
9778
  if (this.activeByGuideId.has(guideId)) return;
7770
9779
  const guide = await this.resolver.fetchById(guideId);
7771
9780
  if (!guide) {
@@ -7773,6 +9782,15 @@ ${pageUrl}`;
7773
9782
  return;
7774
9783
  }
7775
9784
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
9785
+ if (guide.guideType === "composite" && typeof startStep === "number") {
9786
+ const total = guide.guideSteps.length;
9787
+ const idx = Math.max(0, Math.min(total - 1, Math.floor(startStep)));
9788
+ this.enqueueInteractionOnce(guide.guideId, endUserId, sessionId, pageUrl, "shown", 0);
9789
+ if (idx > 0)
9790
+ this.enqueueInteraction(guide.guideId, endUserId, sessionId, pageUrl, "step_advanced", idx);
9791
+ this.renderCompositeStep(guide, idx, sessionId, pageUrl);
9792
+ return;
9793
+ }
7776
9794
  if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
7777
9795
  if (!this.walkthroughState.hasActive()) {
7778
9796
  await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
@@ -7983,6 +10001,7 @@ ${pageUrl}`;
7983
10001
  console.warn("[veo] guides: interaction without endUserId \u2014 skipping enqueue");
7984
10002
  return;
7985
10003
  }
10004
+ this.noteProgress(guide.guideId, event.action, event.stepIndex);
7986
10005
  this.trackerQueue.enqueue({
7987
10006
  guideId: guide.guideId,
7988
10007
  payload: {
@@ -8002,6 +10021,7 @@ ${pageUrl}`;
8002
10021
  * una navegación distinta) y deduplicarlas perdería datos.
8003
10022
  */
8004
10023
  enqueueInteraction(guideId, endUserId, sessionId, pageUrl, action, stepIndex) {
10024
+ this.noteProgress(guideId, action, stepIndex);
8005
10025
  this.trackerQueue.enqueue({
8006
10026
  guideId,
8007
10027
  payload: {
@@ -8014,6 +10034,45 @@ ${pageUrl}`;
8014
10034
  }
8015
10035
  });
8016
10036
  }
10037
+ noteProgress(guideId, action, stepIndex) {
10038
+ if (action !== "shown" && action !== "step_advanced" && action !== "step_back" && action !== "completed") {
10039
+ return;
10040
+ }
10041
+ const cur = this.sessionProgress.get(guideId) ?? { maxStep: -1, completed: false };
10042
+ this.sessionProgress.set(guideId, {
10043
+ maxStep: Math.max(cur.maxStep, stepIndex),
10044
+ completed: cur.completed || action === "completed"
10045
+ });
10046
+ }
10047
+ /**
10048
+ * Progreso del usuario en varias guías (bloque «Lista de tareas»): lo pide
10049
+ * al backend y lo eleva con lo visto en esta sesión (nunca lo baja).
10050
+ */
10051
+ async fetchProgress(guideIds) {
10052
+ const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
10053
+ let rows = [];
10054
+ try {
10055
+ const params = new URLSearchParams({ endUserId, guideIds: guideIds.join(",") });
10056
+ const res = await fetch(`${this.apiUrl}/v1/guides/progress?${params}`, {
10057
+ headers: { "X-Api-Key": this.apiKey }
10058
+ });
10059
+ if (res.ok) rows = (await res.json()).items ?? [];
10060
+ else if (this.debug) console.warn("[veo] guides/progress respondi\xF3", res.status);
10061
+ } catch (err) {
10062
+ if (this.debug) console.warn("[veo] guides/progress fall\xF3:", err);
10063
+ }
10064
+ return rows.map((r) => {
10065
+ const local2 = this.sessionProgress.get(r.guideId);
10066
+ if (!local2) return r;
10067
+ const seen = Math.min(r.totalSteps, local2.maxStep + 1);
10068
+ const completed = r.completed || local2.completed || seen >= r.totalSteps;
10069
+ return {
10070
+ ...r,
10071
+ currentStep: completed ? r.totalSteps : Math.max(r.currentStep, seen),
10072
+ completed
10073
+ };
10074
+ });
10075
+ }
8017
10076
  /**
8018
10077
  * Encola con dedup por sesión del SDK. Para `shown` del primer step:
8019
10078
  * no queremos re-emitirlo si el usuario navega entre dos páginas que
@@ -8058,13 +10117,19 @@ ${pageUrl}`;
8058
10117
  * propiedades del usuario. Usa el endUserId actual (o el anónimo si aún no
8059
10118
  * hubo identify — el backend pre-crea la fila igual que identify).
8060
10119
  */
8061
- async submitFormResponse(guideId, answers) {
10120
+ async submitFormResponse(guideId, answers, meta) {
8062
10121
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
8063
10122
  try {
8064
10123
  const res = await fetch(`${this.apiUrl}/v1/guides/${guideId}/form-response`, {
8065
10124
  method: "POST",
8066
10125
  headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
8067
- body: JSON.stringify({ endUserId, answers })
10126
+ body: JSON.stringify({
10127
+ endUserId,
10128
+ answers,
10129
+ sessionId: this.client.getSessionId(),
10130
+ ...meta ? { stepPosition: meta.stepIndex } : {},
10131
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
10132
+ })
8068
10133
  });
8069
10134
  if (!res.ok && this.debug) {
8070
10135
  console.warn("[veo] form-response respondi\xF3", res.status);
@@ -8117,7 +10182,7 @@ async function runPreviewMode(opts) {
8117
10182
  } catch (err) {
8118
10183
  if (opts.debug) console.warn("[veo] preview-resolve fall\xF3:", err);
8119
10184
  }
8120
- const submitToBackend = async (answers) => {
10185
+ const submitToBackend = async (answers, meta) => {
8121
10186
  if (!guide) return false;
8122
10187
  const veo = window.veo;
8123
10188
  const endUserId = veo?.getEndUserId?.() ?? veo?.getAnonymousId?.() ?? null;
@@ -8126,7 +10191,12 @@ async function runPreviewMode(opts) {
8126
10191
  const res = await fetch(`${opts.apiUrl}/v1/guides/${guide.guideId}/form-response`, {
8127
10192
  method: "POST",
8128
10193
  headers: { "Content-Type": "application/json", "X-Api-Key": opts.apiKey },
8129
- body: JSON.stringify({ endUserId, answers })
10194
+ body: JSON.stringify({
10195
+ endUserId,
10196
+ answers,
10197
+ ...meta ? { stepPosition: meta.stepIndex } : {},
10198
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
10199
+ })
8130
10200
  });
8131
10201
  return res.ok;
8132
10202
  } catch {