veo-sdk 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/builder-ENKEIVF2.mjs +5 -0
  2. package/dist/{builder-BKPMANUB.mjs.map → builder-ENKEIVF2.mjs.map} +1 -1
  3. package/dist/builder.cjs +287 -13
  4. package/dist/builder.cjs.map +1 -1
  5. package/dist/builder.d.cts +2 -2
  6. package/dist/builder.d.ts +2 -2
  7. package/dist/builder.mjs +4 -4
  8. package/dist/{chunk-VWF4UHUU.mjs → chunk-4QZDNJJE.mjs} +3 -3
  9. package/dist/{chunk-VWF4UHUU.mjs.map → chunk-4QZDNJJE.mjs.map} +1 -1
  10. package/dist/{chunk-GVPAJFNW.mjs → chunk-L3QQJLDJ.mjs} +3 -3
  11. package/dist/{chunk-GVPAJFNW.mjs.map → chunk-L3QQJLDJ.mjs.map} +1 -1
  12. package/dist/{chunk-NGP7IJVE.mjs → chunk-ZILDR4YE.mjs} +289 -15
  13. package/dist/chunk-ZILDR4YE.mjs.map +1 -0
  14. package/dist/detect-theme-X4K3TLUL.mjs +446 -0
  15. package/dist/detect-theme-X4K3TLUL.mjs.map +1 -0
  16. package/dist/{guide-preview-DylsEhrg.d.cts → guide-preview-DEFhfA4o.d.cts} +115 -0
  17. package/dist/{guide-preview-DylsEhrg.d.ts → guide-preview-DEFhfA4o.d.ts} +115 -0
  18. package/dist/index.cjs +914 -43
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +13 -1
  21. package/dist/index.d.ts +13 -1
  22. package/dist/index.mjs +141 -16
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/veo-builder.js +92 -20
  25. package/dist/veo-builder.js.map +1 -1
  26. package/dist/veo-guides.js +84 -12
  27. package/dist/veo-guides.js.map +1 -1
  28. package/dist/veo-theme.js +2 -0
  29. package/dist/veo-theme.js.map +1 -0
  30. package/dist/veo.js +2 -2
  31. package/dist/veo.js.map +1 -1
  32. package/package.json +2 -2
  33. package/dist/builder-BKPMANUB.mjs +0 -5
  34. package/dist/chunk-NGP7IJVE.mjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -902,6 +902,10 @@ function clampNum(n, min, max) {
902
902
  return Math.min(max, Math.max(min, n));
903
903
  }
904
904
  function applyBlockStyle(el2, style, kind) {
905
+ if (kind === "text") {
906
+ const explicit = style && typeof style === "object" ? style.preset : void 0;
907
+ el2.dataset.veoPreset = typeof explicit === "string" ? explicit : el2.classList.contains("veo-guide-title") ? "title" : "paragraph";
908
+ }
905
909
  if (!style || typeof style !== "object") return;
906
910
  const s = style;
907
911
  if (kind === "image" && s.bleed === true) {
@@ -918,8 +922,9 @@ function applyBlockStyle(el2, style, kind) {
918
922
  if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
919
923
  const preset = TEXT_PRESETS[s.preset];
920
924
  if (preset) {
921
- el2.style.fontSize = `${preset.size}px`;
922
- el2.style.fontWeight = String(preset.weight);
925
+ const token = s.preset === "small" ? null : `--veo-t-${s.preset}`;
926
+ el2.style.fontSize = token ? `var(${token}-size, ${preset.size}px)` : `${preset.size}px`;
927
+ el2.style.fontWeight = token ? `var(${token}-weight, ${preset.weight})` : String(preset.weight);
923
928
  el2.style.lineHeight = String(preset.lineHeight);
924
929
  }
925
930
  }
@@ -1635,20 +1640,92 @@ var init_styles = __esm({
1635
1640
  .veo-guide-actions {
1636
1641
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
1637
1642
  }
1643
+ /* Tema del workspace (design system): cada preset de texto toma sus tokens de
1644
+ las variables que publica \`applyThemeVars\` en la tarjeta. Las reglas solo
1645
+ existen bajo .veo-card--themed; los estilos inline del bloque las pisan. El
1646
+ \`*\` cubre el rich text (la regla global de fuente aplica a cada hijo). */
1647
+ .veo-card--themed [data-veo-preset="title"] {
1648
+ color: var(--veo-t-title-color); font-size: var(--veo-t-title-size);
1649
+ font-weight: var(--veo-t-title-weight); text-align: var(--veo-t-title-align);
1650
+ }
1651
+ .veo-card--themed [data-veo-preset="subtitle"] {
1652
+ color: var(--veo-t-subtitle-color); font-size: var(--veo-t-subtitle-size);
1653
+ font-weight: var(--veo-t-subtitle-weight); text-align: var(--veo-t-subtitle-align);
1654
+ }
1655
+ .veo-card--themed [data-veo-preset="paragraph"] {
1656
+ color: var(--veo-t-paragraph-color); font-size: var(--veo-t-paragraph-size);
1657
+ font-weight: var(--veo-t-paragraph-weight); text-align: var(--veo-t-paragraph-align);
1658
+ }
1659
+ .veo-card--themed [data-veo-preset="small"] { color: var(--veo-t-paragraph-color); }
1660
+ .veo-card--themed [data-veo-preset="title"], .veo-card--themed [data-veo-preset="title"] * {
1661
+ font-family: var(--veo-t-title-font, var(--veo-font));
1662
+ }
1663
+ .veo-card--themed [data-veo-preset="subtitle"], .veo-card--themed [data-veo-preset="subtitle"] * {
1664
+ font-family: var(--veo-t-subtitle-font, var(--veo-font));
1665
+ }
1666
+ .veo-card--themed [data-veo-preset="paragraph"], .veo-card--themed [data-veo-preset="paragraph"] *,
1667
+ .veo-card--themed [data-veo-preset="small"], .veo-card--themed [data-veo-preset="small"] * {
1668
+ font-family: var(--veo-t-paragraph-font, var(--veo-font));
1669
+ }
1670
+ /* Formulario del tema (pesta\xF1a Formulario). Dentro de un .veo-form los tokens
1671
+ de texto \`--veo-t-*\` se REDEFINEN con \xABTexto principal\xBB: la pregunta es un
1672
+ bloque con preset (su tama\xF1o va inline con esas variables), as\xED toma los del
1673
+ formulario y no los del modal. Etiquetas y opciones = \xABTexto secundario\xBB. */
1674
+ .veo-card--themed-form .veo-form {
1675
+ background: var(--veo-f-bg); padding: var(--veo-f-pad);
1676
+ border: var(--veo-f-border, none); border-radius: var(--veo-f-radius, 8px);
1677
+ box-shadow: var(--veo-f-shadow, none);
1678
+ --veo-t-title-color: var(--veo-f-primary-color); --veo-t-title-size: var(--veo-f-primary-size);
1679
+ --veo-t-title-weight: var(--veo-f-primary-weight); --veo-t-title-align: var(--veo-f-primary-align);
1680
+ --veo-t-title-font: var(--veo-f-primary-font);
1681
+ --veo-t-subtitle-color: var(--veo-f-primary-color); --veo-t-subtitle-size: var(--veo-f-primary-size);
1682
+ --veo-t-subtitle-weight: var(--veo-f-primary-weight); --veo-t-subtitle-align: var(--veo-f-primary-align);
1683
+ --veo-t-subtitle-font: var(--veo-f-primary-font);
1684
+ --veo-t-paragraph-color: var(--veo-f-primary-color); --veo-t-paragraph-size: var(--veo-f-primary-size);
1685
+ --veo-t-paragraph-weight: var(--veo-f-primary-weight); --veo-t-paragraph-align: var(--veo-f-primary-align);
1686
+ --veo-t-paragraph-font: var(--veo-f-primary-font);
1687
+ }
1688
+ .veo-card--themed-form .veo-form-label,
1689
+ .veo-card--themed-form .veo-form-option,
1690
+ .veo-card--themed-form .veo-scale-label {
1691
+ color: var(--veo-f-secondary-color); font-weight: var(--veo-f-secondary-weight);
1692
+ font-family: var(--veo-f-secondary-font, var(--veo-font));
1693
+ }
1694
+ .veo-card--themed-form .veo-form-label,
1695
+ .veo-card--themed-form .veo-form-option {
1696
+ font-size: var(--veo-f-secondary-size); text-align: var(--veo-f-secondary-align);
1697
+ }
1698
+ .veo-card--themed-form .veo-form-input:focus { outline-color: var(--veo-f-accent); }
1699
+ .veo-card--themed-form .veo-form-option input { accent-color: var(--veo-f-accent); }
1700
+ .veo-card--themed-form .veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-f-accent)); }
1701
+ .veo-card--themed-form .veo-scale-active.veo-nps-btn {
1702
+ background: var(--veo-scale-color, var(--veo-f-accent)); color: var(--veo-f-on-accent);
1703
+ border-color: var(--veo-scale-color, var(--veo-f-accent));
1704
+ }
1638
1705
  .veo-guide-cta {
1639
1706
  background: var(--veo-cta-bg, var(--veo-primary));
1640
- color: var(--veo-cta-color, #fff); border: none;
1641
- padding: 9px 18px; border-radius: var(--veo-cta-radius, 8px);
1642
- font-size: var(--veo-cta-size, 14px); font-weight: 500; cursor: pointer;
1707
+ color: var(--veo-cta-color, #fff); border: var(--veo-cta-border, none);
1708
+ padding: var(--veo-cta-pad, 9px 18px); border-radius: var(--veo-cta-radius, 8px);
1709
+ font-size: var(--veo-cta-size, 14px); font-weight: var(--veo-cta-weight, 500); cursor: pointer;
1643
1710
  transition: opacity 120ms ease;
1644
1711
  }
1645
1712
  .veo-guide-cta:hover { opacity: 0.9; }
1646
1713
  /* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
1647
1714
  .veo-guide-cta--secondary {
1648
- background: transparent; color: var(--veo-primary);
1649
- border: 1px solid var(--veo-primary);
1715
+ background: var(--veo-cta2-bg, transparent); color: var(--veo-cta2-color, var(--veo-primary));
1716
+ border: var(--veo-cta2-border, 1px solid var(--veo-primary));
1717
+ padding: var(--veo-cta2-pad, var(--veo-cta-pad, 9px 18px));
1718
+ border-radius: var(--veo-cta2-radius, var(--veo-cta-radius, 8px));
1719
+ font-size: var(--veo-cta2-size, var(--veo-cta-size, 14px));
1720
+ font-weight: var(--veo-cta2-weight, var(--veo-cta-weight, 500));
1721
+ }
1722
+ .veo-guide-cta--link {
1723
+ background: transparent; color: var(--veo-cta3-color, var(--veo-primary));
1724
+ border: var(--veo-cta3-border, none); padding: var(--veo-cta3-pad, 0);
1725
+ border-radius: var(--veo-cta3-radius, 0);
1726
+ font-size: var(--veo-cta3-size, var(--veo-cta-size, 14px));
1727
+ font-weight: var(--veo-cta3-weight, var(--veo-cta-weight, 500));
1650
1728
  }
1651
- .veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
1652
1729
  .veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
1653
1730
  .veo-guide-cta:active { transform: translateY(1px); }
1654
1731
  .veo-guide-close {
@@ -2030,8 +2107,18 @@ function positionArrow(arrowEl, placement, data) {
2030
2107
  if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
2031
2108
  if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
2032
2109
  arrowEl.style.setProperty(side, "-6px");
2110
+ const width = arrowEl.dataset.veoBorder;
2111
+ if (width) {
2112
+ for (const edge of ["top", "right", "bottom", "left"]) {
2113
+ arrowEl.style.setProperty(
2114
+ `border-${edge}-width`,
2115
+ OUTER_EDGES[side].includes(edge) ? `${width}px` : "0"
2116
+ );
2117
+ }
2118
+ arrowEl.style.zIndex = "1";
2119
+ }
2033
2120
  }
2034
- var STATIC_SIDE;
2121
+ var STATIC_SIDE, OUTER_EDGES;
2035
2122
  var init_floating_arrow = __esm({
2036
2123
  "src/plugins/guides/renderers/floating-arrow.ts"() {
2037
2124
  STATIC_SIDE = {
@@ -2040,6 +2127,12 @@ var init_floating_arrow = __esm({
2040
2127
  left: "right",
2041
2128
  right: "left"
2042
2129
  };
2130
+ OUTER_EDGES = {
2131
+ top: ["top", "left"],
2132
+ right: ["top", "right"],
2133
+ bottom: ["bottom", "right"],
2134
+ left: ["bottom", "left"]
2135
+ };
2043
2136
  }
2044
2137
  });
2045
2138
  function readBadgeConfig(step) {
@@ -3213,6 +3306,173 @@ var init_form_content = __esm({
3213
3306
  }
3214
3307
  });
3215
3308
 
3309
+ // src/plugins/guides/guide-theme.ts
3310
+ function channels(hex) {
3311
+ const h = hex.replace("#", "");
3312
+ const full = h.length === 3 ? [...h].map((x) => x + x).join("") : h.slice(0, 6);
3313
+ const ch = (i) => Number.parseInt(full.slice(i, i + 2), 16) || 0;
3314
+ return [ch(0), ch(2), ch(4)];
3315
+ }
3316
+ function boxShadow(shadow, hex) {
3317
+ if (shadow === "none") return "none";
3318
+ const spec = SHADOW_SPEC[shadow];
3319
+ const [r, g, b] = channels(hex);
3320
+ return `0 ${spec.y}px ${spec.blur}px rgba(${r}, ${g}, ${b}, ${spec.alpha})`;
3321
+ }
3322
+ function readableOn(hex) {
3323
+ const [r, g, b] = channels(hex);
3324
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.6 ? "#111827" : "#ffffff";
3325
+ }
3326
+ function themeModeFor(theme, design) {
3327
+ if (!theme || design.theme !== WORKSPACE_THEME_ID) return null;
3328
+ return (design.colorMode === "dark" ? theme.dark : theme.light) ?? null;
3329
+ }
3330
+ function themeRowJustify(mode, inline, preset) {
3331
+ const box = inline ? mode.embedded : mode.overlay;
3332
+ if (box.align === "distribute") return null;
3333
+ const button = preset === "secondary" ? mode.buttons?.secondary : preset === "link" ? mode.buttons?.tertiary : preset === void 0 || preset === null || preset === "primary" ? mode.buttons?.primary : void 0;
3334
+ return button ? JUSTIFY[button.align] ?? null : null;
3335
+ }
3336
+ function appFont(ref, doc) {
3337
+ const view = doc.defaultView;
3338
+ if (!view) return null;
3339
+ if (ref === "inherit") return view.getComputedStyle(doc.body).fontFamily || null;
3340
+ if (ref === "app-heading") {
3341
+ const heading = Array.from(doc.querySelectorAll("h1,h2,h3")).find(
3342
+ (el2) => el2.getClientRects().length > 0
3343
+ );
3344
+ return view.getComputedStyle(heading ?? doc.body).fontFamily || null;
3345
+ }
3346
+ return ref;
3347
+ }
3348
+ function themeContainerDesign(mode, inline, tooltip = false) {
3349
+ const box = inline ? mode.embedded : mode.overlay;
3350
+ const tip = tooltip ? mode.tooltip : void 0;
3351
+ const padding = tip?.padding ?? box.padding;
3352
+ const out = {
3353
+ // El embebido va en el flujo de la página: sin fondo propio.
3354
+ background: tip ? tip.background : inline ? "transparent" : box.background ?? mode.colors.background,
3355
+ color: mode.colors.text,
3356
+ paddingTop: padding.top,
3357
+ paddingRight: padding.right,
3358
+ paddingBottom: padding.bottom,
3359
+ paddingLeft: padding.left,
3360
+ borderColor: tip?.borderColor ?? box.borderColor,
3361
+ borderWidth: tip?.borderWidth ?? box.borderWidth,
3362
+ radius: tip?.borderRadius ?? box.borderRadius
3363
+ };
3364
+ const shadow = tip?.shadow ?? box.shadow;
3365
+ const shadowColor = tip?.shadowColor ?? box.shadowColor;
3366
+ if (shadow === "none") {
3367
+ Object.assign(out, { shadowColor: "transparent", shadowBlur: 0 });
3368
+ } else {
3369
+ const spec = SHADOW_SPEC[shadow];
3370
+ const [r, g, b] = channels(shadowColor);
3371
+ Object.assign(out, {
3372
+ shadowX: 0,
3373
+ shadowY: spec.y,
3374
+ shadowBlur: spec.blur,
3375
+ shadowSpread: 0,
3376
+ shadowColor: `rgba(${r}, ${g}, ${b}, ${spec.alpha})`
3377
+ });
3378
+ }
3379
+ return out;
3380
+ }
3381
+ function applyThemeVars(card, mode, inline, tooltip = false) {
3382
+ const doc = card.ownerDocument ?? document;
3383
+ const box = inline ? mode.embedded : mode.overlay;
3384
+ const set = (name, value) => {
3385
+ if (value) card.style.setProperty(name, value);
3386
+ };
3387
+ card.classList.add("veo-card--themed");
3388
+ set("--veo-primary", mode.colors.accent);
3389
+ set("--veo-cta-color", readableOn(mode.colors.accent));
3390
+ const primary = mode.buttons?.primary;
3391
+ const publish = (prefix, b) => {
3392
+ if (!b) return;
3393
+ const p = b.padding;
3394
+ set(`${prefix}-bg`, b.background);
3395
+ set(`${prefix}-color`, b.color);
3396
+ set(`${prefix}-pad`, `${p.top}px ${p.right}px ${p.bottom}px ${p.left}px`);
3397
+ set(`${prefix}-size`, `${b.fontSize}px`);
3398
+ set(`${prefix}-weight`, String(b.fontWeight));
3399
+ set(`${prefix}-radius`, `${b.borderRadius}px`);
3400
+ set(
3401
+ `${prefix}-border`,
3402
+ b.borderWidth > 0 ? `${b.borderWidth}px solid ${b.borderColor}` : "none"
3403
+ );
3404
+ };
3405
+ publish("--veo-cta", primary);
3406
+ publish("--veo-cta2", mode.buttons?.secondary);
3407
+ publish("--veo-cta3", mode.buttons?.tertiary);
3408
+ set("--veo-text", mode.colors.text);
3409
+ set("--veo-text-secondary", mode.colors.textMuted);
3410
+ set("--veo-font", appFont("inherit", doc));
3411
+ set(
3412
+ "--veo-actions-justify",
3413
+ box.align === "distribute" || !primary ? JUSTIFY[box.align] : JUSTIFY[primary.align]
3414
+ );
3415
+ const form = mode.form;
3416
+ if (form) {
3417
+ card.classList.add("veo-card--themed-form");
3418
+ const fp = form.padding;
3419
+ set("--veo-f-bg", form.background);
3420
+ set("--veo-f-accent", form.accent);
3421
+ set("--veo-f-on-accent", form.onAccent);
3422
+ set("--veo-f-pad", `${fp.top}px ${fp.right}px ${fp.bottom}px ${fp.left}px`);
3423
+ if (typeof form.borderWidth === "number" && form.borderColor) {
3424
+ set(
3425
+ "--veo-f-border",
3426
+ form.borderWidth > 0 ? `${form.borderWidth}px solid ${form.borderColor}` : "none"
3427
+ );
3428
+ }
3429
+ if (typeof form.borderRadius === "number") set("--veo-f-radius", `${form.borderRadius}px`);
3430
+ if (form.shadow && form.shadowColor) {
3431
+ set("--veo-f-shadow", boxShadow(form.shadow, form.shadowColor));
3432
+ }
3433
+ for (const [name, t] of [
3434
+ ["primary", form.text.primary],
3435
+ ["secondary", form.text.secondary]
3436
+ ]) {
3437
+ set(`--veo-f-${name}-color`, t.color);
3438
+ set(`--veo-f-${name}-size`, `${t.fontSize}px`);
3439
+ set(`--veo-f-${name}-weight`, String(t.fontWeight));
3440
+ set(`--veo-f-${name}-align`, t.align);
3441
+ set(`--veo-f-${name}-font`, appFont(t.fontFamily, doc));
3442
+ }
3443
+ }
3444
+ const tip = tooltip ? mode.tooltip?.text : void 0;
3445
+ const tokens = [
3446
+ ["title", tip?.primary ?? mode.text.title],
3447
+ ["subtitle", tip?.secondary ?? mode.text.subtitle],
3448
+ ["paragraph", tip?.secondary ?? mode.text.paragraph]
3449
+ ];
3450
+ for (const [name, t] of tokens) {
3451
+ set(`--veo-t-${name}-color`, t.color);
3452
+ set(`--veo-t-${name}-size`, `${t.fontSize}px`);
3453
+ set(`--veo-t-${name}-weight`, String(t.fontWeight));
3454
+ set(`--veo-t-${name}-align`, t.align);
3455
+ set(`--veo-t-${name}-font`, appFont(t.fontFamily, doc));
3456
+ }
3457
+ }
3458
+ var WORKSPACE_THEME_ID, SHADOW_SPEC, JUSTIFY;
3459
+ var init_guide_theme = __esm({
3460
+ "src/plugins/guides/guide-theme.ts"() {
3461
+ WORKSPACE_THEME_ID = "workspace";
3462
+ SHADOW_SPEC = {
3463
+ soft: { y: 4, blur: 14, alpha: 0.12 },
3464
+ medium: { y: 12, blur: 32, alpha: 0.22 },
3465
+ strong: { y: 24, blur: 64, alpha: 0.38 }
3466
+ };
3467
+ JUSTIFY = {
3468
+ left: "flex-start",
3469
+ center: "center",
3470
+ right: "flex-end",
3471
+ distribute: "space-between"
3472
+ };
3473
+ }
3474
+ });
3475
+
3216
3476
  // src/plugins/guides/layout.ts
3217
3477
  function groupLayout(blocks) {
3218
3478
  const rows = /* @__PURE__ */ new Map();
@@ -3673,6 +3933,7 @@ function makeCaret(doc, design) {
3673
3933
  if (typeof design.borderWidth === "number" && design.borderWidth > 0) {
3674
3934
  const bc = typeof design.borderColor === "string" ? design.borderColor : "#bbbbbb";
3675
3935
  s.border = `${design.borderWidth}px solid ${bc}`;
3936
+ caret.dataset.veoBorder = String(design.borderWidth);
3676
3937
  }
3677
3938
  return caret;
3678
3939
  }
@@ -3765,6 +4026,7 @@ var init_composite_renderer = __esm({
3765
4026
  init_checklist_block();
3766
4027
  init_code_block();
3767
4028
  init_form_content();
4029
+ init_guide_theme();
3768
4030
  init_inline_host();
3769
4031
  init_layout();
3770
4032
  init_rich_text();
@@ -3786,6 +4048,11 @@ var init_composite_renderer = __esm({
3786
4048
  bottom: "flex-end"
3787
4049
  };
3788
4050
  CompositeRenderer = class extends BaseRenderer {
4051
+ constructor() {
4052
+ super(...arguments);
4053
+ /** Alineación de filas de botones según el tema del workspace (null = sin tema). */
4054
+ this.themeRowJustify = null;
4055
+ }
3789
4056
  async render(context) {
3790
4057
  const idx = context.nav?.stepIndex ?? 0;
3791
4058
  const step = context.guide.guideSteps[idx];
@@ -3816,7 +4083,13 @@ var init_composite_renderer = __esm({
3816
4083
  if (typeof design.zIndex === "number") host.style.zIndex = String(design.zIndex);
3817
4084
  const card = doc.createElement("div");
3818
4085
  card.className = "veo-card";
3819
- applyCompositeDesign(card, design);
4086
+ const themeMode = themeModeFor(context.guide.theme, design);
4087
+ const inline = step.presentation === "inline";
4088
+ const tooltipLike = !inline && (badgeMode || step.position === "relative-to-element");
4089
+ if (themeMode) applyThemeVars(card, themeMode, inline, tooltipLike);
4090
+ this.themeRowJustify = themeMode ? (preset) => themeRowJustify(themeMode, inline, preset) : null;
4091
+ const cardDesign = themeMode ? { ...themeContainerDesign(themeMode, inline, tooltipLike), ...design } : design;
4092
+ applyCompositeDesign(card, cardDesign);
3820
4093
  if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
3821
4094
  card.setAttribute("role", "dialog");
3822
4095
  card.setAttribute("aria-label", design.ariaLabel.trim());
@@ -3887,7 +4160,7 @@ var init_composite_renderer = __esm({
3887
4160
  });
3888
4161
  }
3889
4162
  card.appendChild(closeBtn);
3890
- reserveCloseSpace(card, blocks, closePos, design);
4163
+ reserveCloseSpace(card, blocks, closePos, cardDesign);
3891
4164
  }
3892
4165
  if (badgeMode && anchor) {
3893
4166
  closeTooltip = this.renderBadgeMode(
@@ -3900,7 +4173,8 @@ var init_composite_renderer = __esm({
3900
4173
  doc,
3901
4174
  context,
3902
4175
  idx,
3903
- design
4176
+ // Con el tema ya fundido: la flecha del tooltip toma su fondo y borde.
4177
+ cardDesign
3904
4178
  );
3905
4179
  return;
3906
4180
  }
@@ -3923,7 +4197,7 @@ var init_composite_renderer = __esm({
3923
4197
  }
3924
4198
  const wrapper = doc.createElement("div");
3925
4199
  wrapper.className = "veo-composite-anchored";
3926
- const caretEl = design.caret === false ? null : makeCaret(doc, design);
4200
+ const caretEl = design.caret === false ? null : makeCaret(doc, cardDesign);
3927
4201
  if (caretEl) wrapper.appendChild(caretEl);
3928
4202
  wrapper.appendChild(card);
3929
4203
  root.appendChild(wrapper);
@@ -3983,6 +4257,7 @@ var init_composite_renderer = __esm({
3983
4257
  const row = doc.createElement("div");
3984
4258
  row.className = "veo-guide-actions";
3985
4259
  let rowAlign;
4260
+ const firstPreset = b.style?.preset;
3986
4261
  while (i < cell.length && cell[i]?.block.type === "button") {
3987
4262
  const { block: bb, index: bi } = cell[i];
3988
4263
  if (!rowAlign) {
@@ -3993,6 +4268,10 @@ var init_composite_renderer = __esm({
3993
4268
  i++;
3994
4269
  }
3995
4270
  if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
4271
+ else {
4272
+ const themed = this.themeRowJustify?.(firstPreset);
4273
+ if (themed) row.style.justifyContent = themed;
4274
+ }
3996
4275
  container.appendChild(row);
3997
4276
  continue;
3998
4277
  }
@@ -4959,7 +5238,8 @@ function normalize(input) {
4959
5238
  guideSteps: input.guideSteps,
4960
5239
  activationRules,
4961
5240
  displayFrequency: "always",
4962
- displayPriority: 0
5241
+ displayPriority: 0,
5242
+ ...input.theme ? { theme: input.theme } : {}
4963
5243
  };
4964
5244
  }
4965
5245
  function createSingleStepRenderer(type) {
@@ -5741,9 +6021,9 @@ function injectBuilderPanel(opts) {
5741
6021
  card.className = "veo-panel";
5742
6022
  let topOffset = opts.topOffset ?? 0;
5743
6023
  let left = 40;
5744
- let top = topOffset + MARGIN;
6024
+ let top2 = topOffset + MARGIN;
5745
6025
  card.style.left = `${left}px`;
5746
- card.style.top = `${top}px`;
6026
+ card.style.top = `${top2}px`;
5747
6027
  card.style.width = `${PANEL_W}px`;
5748
6028
  card.style.height = `${PANEL_H}px`;
5749
6029
  const header = document.createElement("div");
@@ -5787,7 +6067,7 @@ function injectBuilderPanel(opts) {
5787
6067
  if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
5788
6068
  card.style.minHeight = "0";
5789
6069
  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`;
6070
+ card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top2 - MARGIN)}px`;
5791
6071
  } else if (compactRestore) {
5792
6072
  card.style.minHeight = "";
5793
6073
  card.style.width = `${compactRestore.w}px`;
@@ -5798,19 +6078,19 @@ function injectBuilderPanel(opts) {
5798
6078
  maximize.addEventListener("click", () => {
5799
6079
  maximized = !maximized;
5800
6080
  if (maximized) {
5801
- restore = { left, top, w: card.offsetWidth, h: card.offsetHeight };
6081
+ restore = { left, top: top2, w: card.offsetWidth, h: card.offsetHeight };
5802
6082
  left = MARGIN;
5803
- top = topOffset + MARGIN;
6083
+ top2 = topOffset + MARGIN;
5804
6084
  card.style.left = `${left}px`;
5805
- card.style.top = `${top}px`;
6085
+ card.style.top = `${top2}px`;
5806
6086
  card.style.width = `${Math.max(MIN_W, window.innerWidth - MARGIN * 2)}px`;
5807
- card.style.height = `${Math.max(MIN_H, window.innerHeight - top - MARGIN)}px`;
6087
+ card.style.height = `${Math.max(MIN_H, window.innerHeight - top2 - MARGIN)}px`;
5808
6088
  maximize.title = "Restaurar";
5809
6089
  } else if (restore) {
5810
6090
  left = restore.left;
5811
- top = restore.top;
6091
+ top2 = restore.top;
5812
6092
  card.style.left = `${left}px`;
5813
- card.style.top = `${top}px`;
6093
+ card.style.top = `${top2}px`;
5814
6094
  card.style.width = `${restore.w}px`;
5815
6095
  card.style.height = `${restore.h}px`;
5816
6096
  maximize.title = "Maximizar";
@@ -5846,13 +6126,13 @@ function injectBuilderPanel(opts) {
5846
6126
  const sx = e.clientX;
5847
6127
  const sy = e.clientY;
5848
6128
  const ol = left;
5849
- const ot = top;
6129
+ const ot = top2;
5850
6130
  iframe.style.pointerEvents = "none";
5851
6131
  const move = (ev) => {
5852
6132
  left = clamp3(ol + (ev.clientX - sx), 0, Math.max(0, window.innerWidth - card.offsetWidth));
5853
- top = clamp3(ot + (ev.clientY - sy), topOffset, Math.max(topOffset, window.innerHeight - 40));
6133
+ top2 = clamp3(ot + (ev.clientY - sy), topOffset, Math.max(topOffset, window.innerHeight - 40));
5854
6134
  card.style.left = `${left}px`;
5855
- card.style.top = `${top}px`;
6135
+ card.style.top = `${top2}px`;
5856
6136
  };
5857
6137
  const up = () => {
5858
6138
  window.removeEventListener("mousemove", move);
@@ -5898,9 +6178,9 @@ function injectBuilderPanel(opts) {
5898
6178
  isVisible: () => visible,
5899
6179
  setTopOffset: (px) => {
5900
6180
  topOffset = px;
5901
- if (top < topOffset + MARGIN) {
5902
- top = topOffset + MARGIN;
5903
- card.style.top = `${top}px`;
6181
+ if (top2 < topOffset + MARGIN) {
6182
+ top2 = topOffset + MARGIN;
6183
+ card.style.top = `${top2}px`;
5904
6184
  }
5905
6185
  }
5906
6186
  };
@@ -6081,9 +6361,9 @@ function attachModal(opts, shadow, card) {
6081
6361
  const span = window.innerWidth - w;
6082
6362
  return span <= 0 ? 0.5 : round3(clamp4(left / span, 0, 1));
6083
6363
  };
6084
- const posYFor = (top, h) => {
6364
+ const posYFor = (top2, h) => {
6085
6365
  const span = window.innerHeight - h;
6086
- return span <= 0 ? 0.5 : round3(clamp4(top / span, 0, 1));
6366
+ return span <= 0 ? 0.5 : round3(clamp4(top2 / span, 0, 1));
6087
6367
  };
6088
6368
  const onCardDown = (e) => {
6089
6369
  if (isInteractive(e.target)) return;
@@ -7878,6 +8158,471 @@ var init_builder = __esm({
7878
8158
  }
7879
8159
  });
7880
8160
 
8161
+ // src/plugins/theme-detect/color.ts
8162
+ function parseRgb(value) {
8163
+ const m = RGB_RX.exec(value.trim());
8164
+ if (!m) return null;
8165
+ const a = m[4] === void 0 ? 1 : m[4].endsWith("%") ? Number.parseFloat(m[4]) / 100 : +m[4];
8166
+ return [+m[1], +m[2], +m[3], a];
8167
+ }
8168
+ function toCssColor(raw) {
8169
+ const v = raw.trim();
8170
+ const parts = v.split(/[\s,/]+/).filter(Boolean);
8171
+ const bare = parts.length >= 3 && parts.every((p) => /^-?[\d.]+(%|deg)?$/.test(p));
8172
+ if (!bare) return v;
8173
+ return v.includes("%") ? `hsl(${v})` : `rgb(${v})`;
8174
+ }
8175
+ function looksLikeColor(raw) {
8176
+ const v = raw.trim();
8177
+ if (!v || v.length > 64) return false;
8178
+ return /^(#|rgb|hsl|hwb|lab|lch|oklab|oklch|color\(|color-mix|light-dark)/i.test(v) || toCssColor(v) !== v;
8179
+ }
8180
+ function flatten(c, bg) {
8181
+ if (c[3] >= 1) return c;
8182
+ const mix = (i) => Math.round(c[i] * c[3] + bg[i] * (1 - c[3]));
8183
+ return [mix(0), mix(1), mix(2), 1];
8184
+ }
8185
+ function toHex(c) {
8186
+ const h = (n) => Math.round(n).toString(16).padStart(2, "0");
8187
+ return `#${h(c[0])}${h(c[1])}${h(c[2])}`.toUpperCase();
8188
+ }
8189
+ function luminance(c) {
8190
+ return (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255;
8191
+ }
8192
+ function saturation(c) {
8193
+ const mx = Math.max(c[0], c[1], c[2]) / 255;
8194
+ const mn = Math.min(c[0], c[1], c[2]) / 255;
8195
+ const l = (mx + mn) / 2;
8196
+ return mx === mn ? 0 : (mx - mn) / (1 - Math.abs(2 * l - 1));
8197
+ }
8198
+ function createColorResolver(doc) {
8199
+ const probe = doc.createElement("span");
8200
+ probe.style.display = "none";
8201
+ doc.body.appendChild(probe);
8202
+ const view = doc.defaultView;
8203
+ let ctx;
8204
+ const cache = /* @__PURE__ */ new Map();
8205
+ const viaCanvas = (computed) => {
8206
+ if (ctx === void 0) {
8207
+ try {
8208
+ const canvas = doc.createElement("canvas");
8209
+ canvas.width = 1;
8210
+ canvas.height = 1;
8211
+ ctx = canvas.getContext("2d", { willReadFrequently: true });
8212
+ } catch {
8213
+ ctx = null;
8214
+ }
8215
+ }
8216
+ if (!ctx) return null;
8217
+ ctx.clearRect(0, 0, 1, 1);
8218
+ ctx.fillStyle = "#000";
8219
+ ctx.fillStyle = computed;
8220
+ ctx.fillRect(0, 0, 1, 1);
8221
+ const d = ctx.getImageData(0, 0, 1, 1).data;
8222
+ return [d[0] ?? 0, d[1] ?? 0, d[2] ?? 0, (d[3] ?? 255) / 255];
8223
+ };
8224
+ return {
8225
+ resolve(raw) {
8226
+ const key = raw.trim();
8227
+ if (!key || !view) return null;
8228
+ const hit = cache.get(key);
8229
+ if (hit !== void 0) return hit;
8230
+ let out = null;
8231
+ probe.style.color = "";
8232
+ probe.style.color = toCssColor(key);
8233
+ if (probe.style.color) {
8234
+ const computed = view.getComputedStyle(probe).color;
8235
+ out = parseRgb(computed) ?? viaCanvas(computed);
8236
+ }
8237
+ cache.set(key, out);
8238
+ return out;
8239
+ },
8240
+ dispose() {
8241
+ probe.remove();
8242
+ }
8243
+ };
8244
+ }
8245
+ var RGB_RX;
8246
+ var init_color = __esm({
8247
+ "src/plugins/theme-detect/color.ts"() {
8248
+ RGB_RX = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/;
8249
+ }
8250
+ });
8251
+
8252
+ // src/plugins/theme-detect/fonts.ts
8253
+ function cleanFamily(name) {
8254
+ return name.trim().replace(/^["']|["']$/g, "");
8255
+ }
8256
+ function primaryFamily(stack) {
8257
+ const families = stack.split(",").map(cleanFamily).filter(Boolean);
8258
+ return families.find((f) => !GENERIC.test(f) && !/fallback/i.test(f)) ?? families[0] ?? "";
8259
+ }
8260
+ function prettyFamily(family) {
8261
+ return family.replace(/^__/, "").replace(/_Fallback_[0-9a-f]+$/i, " Fallback").replace(/_[0-9a-f]{6,}$/i, "").replace(/_/g, " ").trim();
8262
+ }
8263
+ function describeFont(doc, el2, faces) {
8264
+ const view = doc.defaultView;
8265
+ if (!el2 || !view) return void 0;
8266
+ const family = primaryFamily(view.getComputedStyle(el2).fontFamily);
8267
+ if (!family) return void 0;
8268
+ const font = {
8269
+ name: prettyFamily(family).slice(0, 80),
8270
+ family: family.slice(0, 120)
8271
+ };
8272
+ const weights = /* @__PURE__ */ new Set();
8273
+ try {
8274
+ for (const face of Array.from(doc.fonts ?? [])) {
8275
+ if (cleanFamily(face.family) === family && face.status === "loaded") weights.add(face.weight);
8276
+ }
8277
+ } catch {
8278
+ }
8279
+ if (weights.size > 0) font.weights = [...weights].sort().join(" ").slice(0, 80);
8280
+ const own = faces.filter((f) => f.family === family);
8281
+ const url = own.find((f) => f.url)?.url ?? null;
8282
+ let origin = null;
8283
+ try {
8284
+ origin = url ? new URL(url, doc.baseURI).origin : null;
8285
+ } catch {
8286
+ origin = null;
8287
+ }
8288
+ const links = Array.from(doc.querySelectorAll("link[href]")).map((l) => l.href);
8289
+ if (origin) {
8290
+ font.hosting = origin.includes("fonts.gstatic.com") ? "google" : origin === view.location.origin ? "self" : "external";
8291
+ } else if (own.length > 0) {
8292
+ font.hosting = "self";
8293
+ } else if (links.some((h) => h.includes("fonts.googleapis.com"))) {
8294
+ font.hosting = "google";
8295
+ } else if (links.some((h) => h.includes("use.typekit.net"))) {
8296
+ font.hosting = "adobe";
8297
+ } else {
8298
+ font.hosting = "system";
8299
+ }
8300
+ return font;
8301
+ }
8302
+ var GENERIC;
8303
+ var init_fonts = __esm({
8304
+ "src/plugins/theme-detect/fonts.ts"() {
8305
+ GENERIC = /^(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-sans-serif|ui-serif|ui-monospace|ui-rounded|emoji|math|fangsong|inherit|initial|-apple-system|BlinkMacSystemFont|Segoe UI|Roboto|Helvetica|Helvetica Neue|Arial|Noto Sans|Apple Color Emoji|Segoe UI Emoji|Segoe UI Symbol|Noto Color Emoji)$/i;
8306
+ }
8307
+ });
8308
+
8309
+ // src/plugins/theme-detect/detect-theme.ts
8310
+ var detect_theme_exports = {};
8311
+ __export(detect_theme_exports, {
8312
+ detectTheme: () => detectTheme,
8313
+ snapshotHash: () => snapshotHash
8314
+ });
8315
+ function scanSheets(doc) {
8316
+ const scan = {
8317
+ names: /* @__PURE__ */ new Set(),
8318
+ byMode: { light: /* @__PURE__ */ new Map(), dark: /* @__PURE__ */ new Map() },
8319
+ faces: []
8320
+ };
8321
+ const walk = (rules, inDarkMedia) => {
8322
+ for (const rule of Array.from(rules)) {
8323
+ const style = rule.style;
8324
+ const selector = rule.selectorText;
8325
+ if (rule.constructor.name === "CSSFontFaceRule" && style) {
8326
+ const src = style.getPropertyValue("src");
8327
+ const url = /url\(["']?([^"')]+)["']?\)/.exec(src)?.[1] ?? null;
8328
+ scan.faces.push({ family: cleanFamily(style.getPropertyValue("font-family")), url });
8329
+ } else if (style && typeof selector === "string") {
8330
+ const selectors = selector.split(",").map((s) => s.trim());
8331
+ const mode = selectors.some((s) => DARK_SELECTOR.test(s)) ? "dark" : selectors.some((s) => LIGHT_SELECTOR.test(s)) ? inDarkMedia ? "dark" : "light" : null;
8332
+ for (const prop of Array.from(style)) {
8333
+ if (!prop.startsWith("--")) continue;
8334
+ scan.names.add(prop);
8335
+ if (mode) scan.byMode[mode].set(prop, style.getPropertyValue(prop).trim());
8336
+ }
8337
+ }
8338
+ const nested = rule.cssRules;
8339
+ if (nested) {
8340
+ const media = rule.conditionText ?? "";
8341
+ walk(nested, inDarkMedia || /prefers-color-scheme:\s*dark/.test(media));
8342
+ }
8343
+ }
8344
+ };
8345
+ for (const sheet of Array.from(doc.styleSheets)) {
8346
+ try {
8347
+ walk(sheet.cssRules, false);
8348
+ } catch {
8349
+ }
8350
+ }
8351
+ return scan;
8352
+ }
8353
+ function top(counts) {
8354
+ let best = null;
8355
+ let max = 0;
8356
+ for (const [hex, n] of counts) {
8357
+ if (n > max) {
8358
+ max = n;
8359
+ best = hex;
8360
+ }
8361
+ }
8362
+ return best;
8363
+ }
8364
+ function detectTheme(doc = document) {
8365
+ const view = doc.defaultView;
8366
+ const body = doc.body;
8367
+ if (!view || !body) return null;
8368
+ const html = doc.documentElement;
8369
+ const colors = createColorResolver(doc);
8370
+ const modeProbe = doc.createElement("div");
8371
+ try {
8372
+ const bodyCs = view.getComputedStyle(body);
8373
+ let bg = [255, 255, 255, 1];
8374
+ for (const el2 of [body, html]) {
8375
+ const c = colors.resolve(view.getComputedStyle(el2).backgroundColor);
8376
+ if (c && c[3] > 0) {
8377
+ bg = flatten(c, [255, 255, 255, 1]);
8378
+ break;
8379
+ }
8380
+ }
8381
+ const text = flatten(colors.resolve(bodyCs.color) ?? [0, 0, 0, 1], bg);
8382
+ const textHex = toHex(text);
8383
+ const muted = /* @__PURE__ */ new Map();
8384
+ const border = /* @__PURE__ */ new Map();
8385
+ const accent = /* @__PURE__ */ new Map();
8386
+ const link = /* @__PURE__ */ new Map();
8387
+ const bump = (m, k) => {
8388
+ m.set(k, (m.get(k) ?? 0) + 1);
8389
+ };
8390
+ const contrast = Math.abs(luminance(text) - luminance(bg));
8391
+ const elements = Array.from(body.querySelectorAll("*")).slice(0, MAX_ELEMENTS);
8392
+ for (const el2 of elements) {
8393
+ if (el2.getClientRects().length === 0) continue;
8394
+ const cs = view.getComputedStyle(el2);
8395
+ const hasText = Array.from(el2.childNodes).some(
8396
+ (n) => n.nodeType === 3 && (n.textContent ?? "").trim() !== ""
8397
+ );
8398
+ if (hasText) {
8399
+ const c = colors.resolve(cs.color);
8400
+ if (c && c[3] > 0) {
8401
+ const flat = flatten(c, bg);
8402
+ const hex = toHex(flat);
8403
+ const softer = Math.abs(luminance(flat) - luminance(bg)) < contrast;
8404
+ if (hex !== textHex && saturation(flat) < 0.3 && softer) bump(muted, hex);
8405
+ if (el2.closest("a") && saturation(flat) >= 0.3) bump(link, hex);
8406
+ }
8407
+ }
8408
+ if (Number.parseFloat(cs.borderTopWidth) > 0 && cs.borderTopStyle !== "none") {
8409
+ const c = colors.resolve(cs.borderTopColor);
8410
+ if (c && c[3] > 0) {
8411
+ const flat = flatten(c, bg);
8412
+ if (saturation(flat) < 0.3 && toHex(flat) !== textHex) bump(border, toHex(flat));
8413
+ }
8414
+ }
8415
+ if (el2.matches('button,[role="button"],input[type="submit"],a[class*="btn"],a[class*="button"]')) {
8416
+ const c = colors.resolve(cs.backgroundColor);
8417
+ if (c && c[3] > 0.5 && saturation(c) >= 0.3) bump(accent, toHex(c));
8418
+ }
8419
+ }
8420
+ const meta = doc.querySelector('meta[name="theme-color"]');
8421
+ const metaColor = meta ? colors.resolve(meta.content) : null;
8422
+ const sampled = {
8423
+ background: toHex(bg),
8424
+ text: textHex
8425
+ };
8426
+ const sampledAccent = top(accent) ?? top(link) ?? (metaColor ? toHex(metaColor) : null);
8427
+ if (sampledAccent) sampled.accent = sampledAccent;
8428
+ const sampledMuted = top(muted);
8429
+ if (sampledMuted) sampled.textMuted = sampledMuted;
8430
+ const sampledBorder = top(border);
8431
+ if (sampledBorder) sampled.border = sampledBorder;
8432
+ const hints = [
8433
+ html.className,
8434
+ html.dataset.theme,
8435
+ html.dataset.mode,
8436
+ body.className,
8437
+ body.dataset.theme
8438
+ ].join(" ").toLowerCase();
8439
+ const mode = /\bdark\b/.test(hints) ? "dark" : /\blight\b/.test(hints) ? "light" : view.getComputedStyle(html).colorScheme === "dark" || luminance(bg) < 0.4 ? "dark" : "light";
8440
+ const other = mode === "dark" ? "light" : "dark";
8441
+ modeProbe.className = other;
8442
+ modeProbe.dataset.theme = other;
8443
+ modeProbe.dataset.mode = other;
8444
+ modeProbe.style.cssText = `display:none;color-scheme:${other}`;
8445
+ body.appendChild(modeProbe);
8446
+ const probeCs = view.getComputedStyle(modeProbe);
8447
+ const hexOf = (raw, over) => {
8448
+ if (!looksLikeColor(raw)) return null;
8449
+ const c = colors.resolve(raw);
8450
+ if (!c || c[3] === 0) return null;
8451
+ if (c[3] > 0.98) return toHex(c);
8452
+ return over ? toHex(flatten(c, over)) : null;
8453
+ };
8454
+ const scan = scanSheets(doc);
8455
+ const roleVar = {};
8456
+ const current = {};
8457
+ for (const role of ROLES) {
8458
+ for (const name of KNOWN_VARS[role]) {
8459
+ const hex = hexOf(bodyCs.getPropertyValue(name), bg);
8460
+ if (hex) {
8461
+ roleVar[role] = { name, confidence: 3 };
8462
+ current[role] = { hex, confidence: 3 };
8463
+ break;
8464
+ }
8465
+ }
8466
+ }
8467
+ const missing = ROLES.filter((r) => !roleVar[r] && sampled[r]);
8468
+ if (missing.length > 0) {
8469
+ const candidates = [];
8470
+ for (const name of Array.from(scan.names).slice(0, MAX_VARS)) {
8471
+ const cur = hexOf(bodyCs.getPropertyValue(name));
8472
+ if (cur) candidates.push({ name, cur, oth: hexOf(probeCs.getPropertyValue(name)) });
8473
+ }
8474
+ for (const role of missing) {
8475
+ let best = null;
8476
+ for (const c of candidates) {
8477
+ if (c.cur !== sampled[role]) continue;
8478
+ const score = (ROLE_NAME[role].test(c.name) ? 2 : 0) + (c.oth && c.oth !== c.cur ? 2 : 0) - (PALETTE_NAME.test(c.name) ? 1 : 0);
8479
+ if (!best || score > best.score) best = { name: c.name, score };
8480
+ }
8481
+ if (best && best.score >= 2) roleVar[role] = { name: best.name, confidence: 2 };
8482
+ }
8483
+ }
8484
+ for (const role of ROLES) {
8485
+ const hex = sampled[role];
8486
+ if (!current[role] && hex) current[role] = { hex, confidence: roleVar[role] ? 2 : 1 };
8487
+ }
8488
+ const otherPalette = {};
8489
+ let differs = false;
8490
+ const otherOrder = ["background", "accent", "text", "textMuted", "border"];
8491
+ let otherBg = null;
8492
+ const noteBg = (role, hex) => {
8493
+ if (role === "background") otherBg = colors.resolve(hex);
8494
+ };
8495
+ for (const role of otherOrder) {
8496
+ const ref = roleVar[role];
8497
+ if (!ref) continue;
8498
+ const hex = hexOf(probeCs.getPropertyValue(ref.name), otherBg);
8499
+ if (!hex) continue;
8500
+ noteBg(role, hex);
8501
+ otherPalette[role] = { hex, confidence: ref.confidence };
8502
+ if (hex !== current[role]?.hex) differs = true;
8503
+ }
8504
+ if (!differs) {
8505
+ for (const role of ROLES) delete otherPalette[role];
8506
+ otherBg = null;
8507
+ const sheet = scan.byMode[other];
8508
+ const resolveVar = (value, depth = 0) => depth > 6 ? value : value.replace(
8509
+ /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*))?\)/g,
8510
+ (_m, name, fb) => resolveVar(
8511
+ sheet.get(name) ?? scan.byMode.light.get(name) ?? (bodyCs.getPropertyValue(name).trim() || fb || ""),
8512
+ depth + 1
8513
+ )
8514
+ );
8515
+ for (const role of otherOrder) {
8516
+ const ref = roleVar[role];
8517
+ const raw = ref ? sheet.get(ref.name) : void 0;
8518
+ const hex = raw ? hexOf(resolveVar(raw), otherBg) : null;
8519
+ if (!hex) continue;
8520
+ noteBg(role, hex);
8521
+ otherPalette[role] = { hex, confidence: 2 };
8522
+ if (hex !== current[role]?.hex) differs = true;
8523
+ }
8524
+ }
8525
+ const palettes = { [mode]: current };
8526
+ if (differs) palettes[other] = otherPalette;
8527
+ const firstVisible = (selector) => Array.from(doc.querySelectorAll(selector)).find(
8528
+ (el2) => el2.getClientRects().length > 0 && (el2.textContent ?? "").trim() !== ""
8529
+ ) ?? null;
8530
+ const bodyFont = describeFont(doc, body, scan.faces);
8531
+ const headingFont = describeFont(doc, firstVisible("h1,h2,h3"), scan.faces);
8532
+ const fonts = {};
8533
+ if (bodyFont) fonts.body = bodyFont;
8534
+ if (headingFont) fonts.heading = headingFont;
8535
+ const snapshot = { host: view.location.host, mode, palettes };
8536
+ const appName = doc.title.trim().slice(0, 120);
8537
+ if (appName) snapshot.appName = appName;
8538
+ if (fonts.body || fonts.heading) snapshot.fonts = fonts;
8539
+ return snapshot;
8540
+ } finally {
8541
+ colors.dispose();
8542
+ modeProbe.remove();
8543
+ }
8544
+ }
8545
+ function snapshotHash(snapshot) {
8546
+ const colorsOf = (p) => ROLES.map((r) => {
8547
+ const c = p?.[r];
8548
+ return c ? `${c.hex}${c.confidence}` : "-";
8549
+ }).join("");
8550
+ return [
8551
+ snapshot.host,
8552
+ colorsOf(snapshot.palettes.light),
8553
+ colorsOf(snapshot.palettes.dark),
8554
+ snapshot.fonts?.body?.family ?? "",
8555
+ snapshot.fonts?.heading?.family ?? ""
8556
+ ].join("|");
8557
+ }
8558
+ var ROLES, KNOWN_VARS, ROLE_NAME, PALETTE_NAME, DARK_SELECTOR, LIGHT_SELECTOR, MAX_ELEMENTS, MAX_VARS;
8559
+ var init_detect_theme = __esm({
8560
+ "src/plugins/theme-detect/detect-theme.ts"() {
8561
+ init_color();
8562
+ init_fonts();
8563
+ ROLES = ["accent", "text", "textMuted", "background", "border"];
8564
+ KNOWN_VARS = {
8565
+ accent: [
8566
+ "--primary",
8567
+ "--color-primary",
8568
+ "--brand",
8569
+ "--color-brand",
8570
+ "--brand-primary",
8571
+ "--mui-palette-primary-main",
8572
+ "--bs-primary",
8573
+ "--ant-color-primary",
8574
+ "--chakra-colors-brand-500",
8575
+ "--accent-color",
8576
+ "--accent"
8577
+ ],
8578
+ text: [
8579
+ "--foreground",
8580
+ "--color-foreground",
8581
+ "--text",
8582
+ "--color-text",
8583
+ "--text-primary",
8584
+ "--bs-body-color",
8585
+ "--mui-palette-text-primary"
8586
+ ],
8587
+ textMuted: [
8588
+ "--muted-foreground",
8589
+ "--color-muted-foreground",
8590
+ "--text-muted",
8591
+ "--text-secondary",
8592
+ "--bs-secondary-color",
8593
+ "--mui-palette-text-secondary"
8594
+ ],
8595
+ background: [
8596
+ "--background",
8597
+ "--color-background",
8598
+ "--bg",
8599
+ "--color-bg",
8600
+ "--bs-body-bg",
8601
+ "--mui-palette-background-default"
8602
+ ],
8603
+ border: [
8604
+ "--border",
8605
+ "--color-border",
8606
+ "--border-color",
8607
+ "--bs-border-color",
8608
+ "--mui-palette-divider"
8609
+ ]
8610
+ };
8611
+ ROLE_NAME = {
8612
+ accent: /brand|primary|accent|main/i,
8613
+ text: /text|foreground|\bfg\b|-fg|ink|content|body/i,
8614
+ textMuted: /muted|secondary|subtle|soft|hint|placeholder|text|-fg/i,
8615
+ background: /background|\bbg\b|-bg|surface|canvas|page|base/i,
8616
+ border: /border|line|divider|stroke|outline|separator/i
8617
+ };
8618
+ PALETTE_NAME = /-(50|[1-9]00|950)$|white|black/i;
8619
+ DARK_SELECTOR = /\.dark\b|\[data-(theme|mode|color-scheme)=["']?dark|\.theme-dark|\.dark-theme/i;
8620
+ LIGHT_SELECTOR = /^(:root|html|:host|body)$|\.light\b|\[data-(theme|mode)=["']?light/i;
8621
+ MAX_ELEMENTS = 1500;
8622
+ MAX_VARS = 600;
8623
+ }
8624
+ });
8625
+
7881
8626
  // src/contract/v1.ts
7882
8627
  var SDK_VERSION = "0.0.1";
7883
8628
  var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
@@ -8186,6 +8931,119 @@ function spaRouterPlugin() {
8186
8931
  });
8187
8932
  }
8188
8933
 
8934
+ // src/plugins/theme-detect/index.ts
8935
+ init_safe_env();
8936
+ var STORAGE_PREFIX = "veo_theme_";
8937
+ var RESEND_AFTER_MS = 24 * 60 * 60 * 1e3;
8938
+ var SETTLE_MS = 4e3;
8939
+ var MAX_RUNS_PER_PAGE = 4;
8940
+ function readState(key) {
8941
+ try {
8942
+ const raw = localStorage.getItem(key);
8943
+ const parsed = raw ? JSON.parse(raw) : null;
8944
+ return parsed && typeof parsed === "object" ? parsed : {};
8945
+ } catch {
8946
+ return {};
8947
+ }
8948
+ }
8949
+ function writeState(key, state) {
8950
+ try {
8951
+ localStorage.setItem(key, JSON.stringify(state));
8952
+ } catch {
8953
+ }
8954
+ }
8955
+ function guessMode(doc, prefersDark) {
8956
+ const hints = [
8957
+ doc.documentElement.className,
8958
+ doc.documentElement.dataset.theme,
8959
+ doc.documentElement.dataset.mode,
8960
+ doc.body?.className
8961
+ ].join(" ").toLowerCase();
8962
+ if (/\bdark\b/.test(hints)) return "dark";
8963
+ if (/\blight\b/.test(hints)) return "light";
8964
+ return prefersDark ? "dark" : "light";
8965
+ }
8966
+ function isFresh(state, mode, now) {
8967
+ const last = state[mode];
8968
+ return last !== void 0 && now - last.at < RESEND_AFTER_MS;
8969
+ }
8970
+ function shouldSendSnapshot(state, mode, hash, now) {
8971
+ const last = state[mode];
8972
+ return !last || last.hash !== hash || now - last.at >= RESEND_AFTER_MS;
8973
+ }
8974
+ function themeDetectPlugin(config) {
8975
+ const storageKey = `${STORAGE_PREFIX}${config.apiKey.slice(-16) || "default"}`;
8976
+ let observer = null;
8977
+ let timer = null;
8978
+ let destroyed = false;
8979
+ let runs = 0;
8980
+ const run = async () => {
8981
+ if (destroyed || runs >= MAX_RUNS_PER_PAGE) return;
8982
+ const prefersDark = typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
8983
+ const guess = guessMode(document, prefersDark);
8984
+ if (isFresh(readState(storageKey), guess, Date.now())) return;
8985
+ runs++;
8986
+ try {
8987
+ const detector = await config.loadDetector();
8988
+ if (!detector || destroyed) return;
8989
+ const snapshot = detector.detectTheme();
8990
+ if (!snapshot) return;
8991
+ const hash = detector.snapshotHash(snapshot);
8992
+ const state = readState(storageKey);
8993
+ const now = Date.now();
8994
+ const mark = () => {
8995
+ const record = { hash, at: Date.now() };
8996
+ state[snapshot.mode] = record;
8997
+ state[guess] = state[guess] && guess !== snapshot.mode ? state[guess] : record;
8998
+ writeState(storageKey, state);
8999
+ };
9000
+ if (!shouldSendSnapshot(state, snapshot.mode, hash, now)) {
9001
+ mark();
9002
+ return;
9003
+ }
9004
+ if (config.debug) console.log("[veo] theme snapshot", snapshot);
9005
+ const res = await fetch(`${config.apiUrl}/v1/workspaces/theme-snapshot`, {
9006
+ method: "POST",
9007
+ headers: { "X-Api-Key": config.apiKey, "Content-Type": "application/json" },
9008
+ body: JSON.stringify(snapshot),
9009
+ credentials: "omit"
9010
+ });
9011
+ if (res.ok) mark();
9012
+ } catch (err) {
9013
+ if (config.debug) console.warn("[veo] theme detect failed", err);
9014
+ }
9015
+ };
9016
+ const schedule = (delayMs) => {
9017
+ if (timer) clearTimeout(timer);
9018
+ timer = setTimeout(() => {
9019
+ timer = null;
9020
+ const idle = window.requestIdleCallback;
9021
+ if (typeof idle === "function") idle(() => void run());
9022
+ else void run();
9023
+ }, delayMs);
9024
+ };
9025
+ return {
9026
+ name: "theme-detect",
9027
+ install() {
9028
+ if (!hasWindow() || !hasDocument()) return;
9029
+ if (document.readyState === "complete") schedule(SETTLE_MS);
9030
+ else window.addEventListener("load", () => schedule(SETTLE_MS), { once: true });
9031
+ if (typeof MutationObserver === "function") {
9032
+ observer = new MutationObserver(() => schedule(1500));
9033
+ observer.observe(document.documentElement, {
9034
+ attributes: true,
9035
+ attributeFilter: ["class", "data-theme", "data-mode"]
9036
+ });
9037
+ }
9038
+ },
9039
+ destroy() {
9040
+ destroyed = true;
9041
+ observer?.disconnect();
9042
+ if (timer) clearTimeout(timer);
9043
+ }
9044
+ };
9045
+ }
9046
+
8189
9047
  // src/core/client.ts
8190
9048
  init_safe_env();
8191
9049
  init_uuid();
@@ -8338,26 +9196,26 @@ var Session = class {
8338
9196
 
8339
9197
  // src/core/storage.ts
8340
9198
  init_safe_env();
8341
- var STORAGE_PREFIX = "veo:";
9199
+ var STORAGE_PREFIX2 = "veo:";
8342
9200
  var ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
8343
9201
  var LocalStorageAdapter = class {
8344
9202
  get(key) {
8345
- return localStorage.getItem(STORAGE_PREFIX + key);
9203
+ return localStorage.getItem(STORAGE_PREFIX2 + key);
8346
9204
  }
8347
9205
  set(key, value) {
8348
9206
  try {
8349
- localStorage.setItem(STORAGE_PREFIX + key, value);
9207
+ localStorage.setItem(STORAGE_PREFIX2 + key, value);
8350
9208
  } catch {
8351
9209
  }
8352
9210
  }
8353
9211
  remove(key) {
8354
- localStorage.removeItem(STORAGE_PREFIX + key);
9212
+ localStorage.removeItem(STORAGE_PREFIX2 + key);
8355
9213
  }
8356
9214
  clear() {
8357
9215
  const toRemove = [];
8358
9216
  for (let i = 0; i < localStorage.length; i++) {
8359
9217
  const key = localStorage.key(i);
8360
- if (key?.startsWith(STORAGE_PREFIX)) toRemove.push(key);
9218
+ if (key?.startsWith(STORAGE_PREFIX2)) toRemove.push(key);
8361
9219
  }
8362
9220
  for (const key of toRemove) localStorage.removeItem(key);
8363
9221
  }
@@ -8368,7 +9226,7 @@ function readCookies() {
8368
9226
  }
8369
9227
  var CookieAdapter = class {
8370
9228
  get(key) {
8371
- const name = `${STORAGE_PREFIX}${key}=`;
9229
+ const name = `${STORAGE_PREFIX2}${key}=`;
8372
9230
  for (const cookie of readCookies()) {
8373
9231
  if (cookie.startsWith(name)) {
8374
9232
  return decodeURIComponent(cookie.slice(name.length));
@@ -8379,19 +9237,19 @@ var CookieAdapter = class {
8379
9237
  set(key, value, options) {
8380
9238
  const maxAgeSec = Math.floor((options?.ttlMs ?? ONE_YEAR_MS) / 1e3);
8381
9239
  const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
8382
- document.cookie = `${STORAGE_PREFIX}${key}=${encodeURIComponent(
9240
+ document.cookie = `${STORAGE_PREFIX2}${key}=${encodeURIComponent(
8383
9241
  value
8384
9242
  )}; Path=/; Max-Age=${maxAgeSec}; SameSite=Lax${secure}`;
8385
9243
  }
8386
9244
  remove(key) {
8387
- document.cookie = `${STORAGE_PREFIX}${key}=; Path=/; Max-Age=0; SameSite=Lax`;
9245
+ document.cookie = `${STORAGE_PREFIX2}${key}=; Path=/; Max-Age=0; SameSite=Lax`;
8388
9246
  }
8389
9247
  clear() {
8390
9248
  for (const cookie of readCookies()) {
8391
9249
  const eq = cookie.indexOf("=");
8392
9250
  const name = eq === -1 ? cookie : cookie.slice(0, eq);
8393
- if (name.startsWith(STORAGE_PREFIX)) {
8394
- this.remove(name.slice(STORAGE_PREFIX.length));
9251
+ if (name.startsWith(STORAGE_PREFIX2)) {
9252
+ this.remove(name.slice(STORAGE_PREFIX2.length));
8395
9253
  }
8396
9254
  }
8397
9255
  }
@@ -8681,13 +9539,23 @@ var Client = class {
8681
9539
  };
8682
9540
 
8683
9541
  // src/core/setup.ts
8684
- function setupClient(config, onGuides, loadBuilder) {
9542
+ function setupClient(config, onGuides, loadBuilder, loadThemeDetector) {
8685
9543
  const client = new Client(config);
8686
9544
  client.use(spaRouterPlugin());
8687
9545
  if (config.autocapture !== false) {
8688
9546
  const autocaptureConfig = typeof config.autocapture === "object" ? config.autocapture : {};
8689
9547
  client.use(autocapturePlugin(autocaptureConfig));
8690
9548
  }
9549
+ if (config.detectTheme !== false && loadThemeDetector && !isBuilderMode()) {
9550
+ client.use(
9551
+ themeDetectPlugin({
9552
+ apiUrl: resolveApiUrl(config),
9553
+ apiKey: config.apiKey,
9554
+ loadDetector: loadThemeDetector,
9555
+ ...config.debug !== void 0 ? { debug: config.debug } : {}
9556
+ })
9557
+ );
9558
+ }
8691
9559
  onGuides(client);
8692
9560
  if (typeof window !== "undefined") {
8693
9561
  const stub = window.veo;
@@ -10391,7 +11259,10 @@ function init(config) {
10391
11259
  // Fallback de carga del builder para bundlers: `import()` dinámico → el bundler
10392
11260
  // lo code-splitea (solo se descarga en modo builder). Así el consumidor por
10393
11261
  // bundler NO necesita copiar `veo-builder.js` ni setear `builderUrl`.
10394
- () => Promise.resolve().then(() => (init_builder(), builder_exports)).then((m) => m.initBuilderMode())
11262
+ () => Promise.resolve().then(() => (init_builder(), builder_exports)).then((m) => m.initBuilderMode()),
11263
+ // Detector de tema: también por `import()` dinámico → chunk aparte que el
11264
+ // bundler solo descarga cuando toca detectar (como mucho una vez al día).
11265
+ () => Promise.resolve().then(() => (init_detect_theme(), detect_theme_exports))
10395
11266
  );
10396
11267
  }
10397
11268